Files
dpm-core-ng/tests/test_core.cpp

197 lines
7.3 KiB
C++

/**
* @file test_core.cpp
* @brief Core test binary: drives libdpm-core through its public C API
*
* Exercises context lifecycle, configuration, the full load-time
* validation matrix against the fixture modules, versioned require,
* typed API access, generic 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_last_error(ctx);
return err != nullptr && std::strstr(err, needle) != nullptr;
}
/* Mirror of the good fixture's table layout (consumer-side spec decl). */
struct good_api_v1_s {
dpm_api_table_header hdr;
int (*ping)(void);
};
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_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) == nullptr);
CHECK(error_contains(ctx, "missing required contract symbols"));
CHECK(error_contains(ctx, "dpm_module_execute"));
CHECK(dpm_require(ctx, "core_too_new", nullptr) == nullptr);
CHECK(error_contains(ctx, "update core"));
CHECK(dpm_require(ctx, "bad_version", nullptr) == nullptr);
CHECK(error_contains(ctx, "malformed version"));
CHECK(dpm_require(ctx, "lying_manifest", nullptr) == nullptr);
CHECK(error_contains(ctx, "does not resolve"));
CHECK(dpm_require(ctx, "bad_magic", nullptr) == nullptr);
CHECK(error_contains(ctx, "magic"));
CHECK(dpm_require(ctx, "nonexistent", nullptr) == nullptr);
CHECK(error_contains(ctx, "not found"));
}
/* ---- known-good module: require, versions, api, execute ---- */
{
dpm_module* good = dpm_require(ctx, "good", nullptr);
CHECK(good != nullptr);
/* loaded at most once per context */
CHECK(dpm_require(ctx, "good", nullptr) == good);
/* minimum-version negotiation */
CHECK(dpm_require(ctx, "good", "1.0.0") == good);
CHECK(dpm_require(ctx, "good", "1.2.3") == good);
CHECK(dpm_require(ctx, "good", "2.0.0") == nullptr);
CHECK(error_contains(ctx, "below required minimum"));
CHECK(dpm_require(ctx, "good", "not.a.version") == nullptr);
CHECK(error_contains(ctx, "malformed minimum version"));
/* typed access */
const void* table = dpm_get_api(ctx, good, "good", 1);
CHECK(table != nullptr);
if (table) {
const auto* api = static_cast<const good_api_v1_s*>(table);
CHECK(api->hdr.magic == DPM_API_TABLE_MAGIC);
CHECK(api->hdr.size == sizeof(good_api_v1_s));
CHECK(api->ping() == 42);
}
/* absent api/version pairs are known from the manifest */
CHECK(dpm_get_api(ctx, good, "good", 2) == nullptr);
CHECK(dpm_get_api(ctx, good, "ghost", 1) == nullptr);
/* generic dispatch */
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(std::strcmp(info.core_min, "0.1.0") == 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;
}