Files
dpm-core-ng/tests/test_core.cpp
Christopher M. Punches 97b39cac6c Route all module interaction through dispatch
A module is addressed by name and command string, and nothing else.
Typed API access handed a caller a pointer into the callee's function
table, which meant compiling against that module's struct layout — a
build-time dependency between modules that the design does not permit.
Removing it also removes the manifest, the table magic constant, and
the table size field, which existed only to describe and validate
those tables.

Load validation is now three steps: reserved contract symbols resolve,
the minimum-version handshake passes, and the version and description
probes return well-formed values. The contract is four reserved
symbols, and a module's interface is the command vocabulary it
documents.

Documentation is brought in line, and artifacts are named exactly:
libdpm-core.so for the library, <dpm/core.h> for the header, the dpm
binary for the command-line tool.
2026-08-15 02:15:18 -04:00

177 lines
6.7 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_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_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, "core_too_new") == nullptr);
CHECK(error_contains(ctx, "update libdpm-core"));
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_module_info_of(ctx, good, &seen) == 0);
CHECK(std::strcmp(seen.name, "good") == 0);
CHECK(std::strcmp(seen.version, "1.2.3") == 0);
CHECK(std::strcmp(seen.core_min, "0.1.0") == 0);
CHECK(seen.description != nullptr && *seen.description);
CHECK(dpm_module_info_of(ctx, nullptr, &seen) != 0);
CHECK(dpm_module_info_of(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(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;
}