dpm_module_info_of becomes dpm_get_module_info, joining the other two readers on the context in stating that a call fetches a value. Recording an error had two entry points doing the same write: the exported dpm_set_last_error and an internal dpm_core::set_last_error taking a std::string. The internal one is gone and the loader calls the exported function, so a reason reaches the context by one path whether a module or the library records it.
170 lines
6.4 KiB
C++
170 lines
6.4 KiB
C++
/**
|
|
* @file test_core.cpp
|
|
* @brief Test binary: drives libdpm-core through its public C API
|
|
*
|
|
* Exercises context lifecycle, configuration, the full load-time
|
|
* validation matrix against the fixture modules, require, dispatch,
|
|
* and enumeration. Exits nonzero on any failure.
|
|
*
|
|
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
|
* @author Chris Punches <chris.punches@silogroup.org>
|
|
*
|
|
* Part of the Dark Horse Linux Package Manager (DPM)
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <dpm/core.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#ifndef TEST_FIXTURE_MODULES
|
|
#error "TEST_FIXTURE_MODULES must be defined"
|
|
#endif
|
|
#ifndef TEST_FIXTURE_CONF
|
|
#error "TEST_FIXTURE_CONF must be defined"
|
|
#endif
|
|
|
|
static int g_failures = 0;
|
|
static int g_checks = 0;
|
|
|
|
#define CHECK(cond) \
|
|
do { \
|
|
g_checks++; \
|
|
if (!(cond)) { \
|
|
g_failures++; \
|
|
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, \
|
|
#cond); \
|
|
} \
|
|
} while (0)
|
|
|
|
static bool error_contains(dpm_ctx* ctx, const char* needle) {
|
|
const char* err = dpm_get_last_error(ctx);
|
|
return err != nullptr && std::strstr(err, needle) != nullptr;
|
|
}
|
|
|
|
int main(void) {
|
|
/* ---- dpm_open: invalid explicit override refuses ---- */
|
|
{
|
|
dpm_open_overrides bad = {"/does/not/exist/conf", nullptr, nullptr, -1};
|
|
CHECK(dpm_open(&bad) == nullptr);
|
|
|
|
dpm_open_overrides bad_level = {nullptr, nullptr, nullptr, 99};
|
|
CHECK(dpm_open(&bad_level) == nullptr);
|
|
}
|
|
|
|
/* ---- context with fixture config and fixture module path ---- */
|
|
dpm_open_overrides overrides = {TEST_FIXTURE_CONF, TEST_FIXTURE_MODULES,
|
|
nullptr, -1};
|
|
dpm_ctx* ctx = dpm_open(&overrides);
|
|
CHECK(ctx != nullptr);
|
|
if (!ctx) {
|
|
std::fprintf(stderr, "cannot continue without a context\n");
|
|
return 1;
|
|
}
|
|
|
|
/* ---- services ---- */
|
|
{
|
|
CHECK(std::strcmp(dpm_core_version(), DPM_CORE_VERSION_EXPECTED) == 0);
|
|
|
|
/* config: values resolve per-module, per-section */
|
|
const char* v = dpm_config_get(ctx, "testmod", "main", "key");
|
|
CHECK(v != nullptr && std::strcmp(v, "value") == 0);
|
|
v = dpm_config_get(ctx, "testmod", "section2", "other");
|
|
CHECK(v != nullptr && std::strcmp(v, "42") == 0);
|
|
CHECK(dpm_config_get(ctx, "testmod", "main", "absent") == nullptr);
|
|
CHECK(dpm_config_get(ctx, "nomodule", "main", "key") == nullptr);
|
|
|
|
/* module-path override wins over the config value */
|
|
const char* path = dpm_get_resolved_module_path(ctx);
|
|
CHECK(path != nullptr &&
|
|
std::strncmp(path, TEST_FIXTURE_MODULES,
|
|
std::strlen(TEST_FIXTURE_MODULES)) == 0);
|
|
}
|
|
|
|
/* ---- validation matrix: every broken fixture refused, precisely ---- */
|
|
{
|
|
CHECK(dpm_require(ctx, "missing_symbols") == nullptr);
|
|
CHECK(error_contains(ctx, "missing required contract symbols"));
|
|
CHECK(error_contains(ctx, "dpm_module_execute"));
|
|
|
|
CHECK(dpm_require(ctx, "bad_version") == nullptr);
|
|
CHECK(error_contains(ctx, "malformed version"));
|
|
|
|
CHECK(dpm_require(ctx, "nonexistent") == nullptr);
|
|
CHECK(error_contains(ctx, "not found"));
|
|
}
|
|
|
|
/* ---- known-good module: require, versions, api, execute ---- */
|
|
{
|
|
dpm_module* good = dpm_require(ctx, "good");
|
|
CHECK(good != nullptr);
|
|
|
|
/* loaded at most once per context */
|
|
CHECK(dpm_require(ctx, "good") == good);
|
|
|
|
/* libdpm-core reports what it saw; the caller judges compatibility */
|
|
dpm_module_info seen;
|
|
CHECK(dpm_get_module_info(ctx, good, &seen) == 0);
|
|
CHECK(std::strcmp(seen.name, "good") == 0);
|
|
CHECK(std::strcmp(seen.version, "1.2.3") == 0);
|
|
CHECK(seen.description != nullptr && *seen.description);
|
|
|
|
CHECK(dpm_get_module_info(ctx, nullptr, &seen) != 0);
|
|
CHECK(dpm_get_module_info(ctx, good, nullptr) != 0);
|
|
|
|
/* dispatch: the module's return value comes back verbatim, so a
|
|
command round trip is observable without any compile-time
|
|
knowledge of the module */
|
|
CHECK(dpm_execute(ctx, good, "ping", 0, nullptr) == 42);
|
|
CHECK(dpm_execute(ctx, good, "anything_else", 0, nullptr) == 0);
|
|
CHECK(dpm_execute(ctx, good, nullptr, 0, nullptr) == 0);
|
|
}
|
|
|
|
/* ---- enumeration: only the valid module surfaces ---- */
|
|
{
|
|
dpm_cursor* cur = dpm_list_modules(ctx);
|
|
CHECK(cur != nullptr);
|
|
if (cur) {
|
|
int count = 0;
|
|
dpm_module_info info;
|
|
while (dpm_cursor_next(cur, &info) == 0) {
|
|
count++;
|
|
CHECK(std::strcmp(info.name, "good") == 0);
|
|
CHECK(std::strcmp(info.version, "1.2.3") == 0);
|
|
CHECK(info.description != nullptr && *info.description);
|
|
}
|
|
CHECK(count == 1);
|
|
dpm_cursor_free(cur);
|
|
}
|
|
|
|
/* unreadable module path refuses with a reason */
|
|
dpm_open_overrides bad_path = {TEST_FIXTURE_CONF, "/does/not/exist",
|
|
nullptr, -1};
|
|
dpm_ctx* ctx2 = dpm_open(&bad_path);
|
|
CHECK(ctx2 != nullptr);
|
|
if (ctx2) {
|
|
CHECK(dpm_list_modules(ctx2) == nullptr);
|
|
CHECK(error_contains(ctx2, "module path"));
|
|
dpm_close(ctx2);
|
|
}
|
|
}
|
|
|
|
dpm_close(ctx);
|
|
|
|
std::printf("%d checks, %d failures\n", g_checks, g_failures);
|
|
return g_failures == 0 ? 0 : 1;
|
|
}
|