/** * @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 * * 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 . */ #include #include #include #include #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(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; }