Module records and aliases
Installation records what a module reports about itself. dpm_install_module opens a module once, writes its version and description to a .meta file in /var/lib/dpm/metadata/, and records the alternate names it declared in modules.aliases beside it. dpm_uninstall_module removes both, leaving the module file in place. dpm_list_modules reads those records and opens no module. A module with no record lists with a version of <uninstalled> and still loads when a caller names it. An unreadable or absent metadata directory costs the listing its detail and costs nothing else. Aliases give a module alternate names, declared through the new dpm_module_aliases contract symbol or added with dpm_add_module_alias. A name is recorded once: one already serving as an alias, or belonging to an installed module, is refused rather than repointed. dpm_require matches a name against the installed modules, then the alias table, then the module path. The metadata directory is a fifth override field and the -M flag, and [modules] metadata in core.conf. The test suite is four binaries covering context, modules, records, and aliases, each a ctest case of its own, alongside the CLI cases.
This commit is contained in:
@@ -15,40 +15,62 @@ foreach(fixture good missing_symbols bad_version)
|
||||
endforeach()
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# API test binary — drives libdpm-core.so through its public C API
|
||||
# API test binaries — one per area of the library, each its own ctest
|
||||
# case, so a failure names the area it happened in
|
||||
# ---------------------------------------------------------------------
|
||||
add_executable(test_core test_core.cpp)
|
||||
foreach(suite context modules metadata aliases)
|
||||
add_executable(test_${suite} test_${suite}.cpp)
|
||||
|
||||
target_link_libraries(test_core PRIVATE dpm-core)
|
||||
target_link_libraries(test_${suite} PRIVATE dpm-core)
|
||||
|
||||
target_compile_definitions(test_core PRIVATE
|
||||
TEST_FIXTURE_MODULES="${DPM_TEST_FIXTURE_DIR}"
|
||||
TEST_FIXTURE_CONF="${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf"
|
||||
DPM_CORE_VERSION_EXPECTED="${PROJECT_VERSION}"
|
||||
)
|
||||
target_include_directories(test_${suite} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set_target_properties(test_core PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${DPM_TEST_DIR}
|
||||
BUILD_RPATH "$ORIGIN/../lib"
|
||||
)
|
||||
target_compile_definitions(test_${suite} PRIVATE
|
||||
TEST_FIXTURE_MODULES="${DPM_TEST_FIXTURE_DIR}"
|
||||
TEST_FIXTURE_CONF="${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf"
|
||||
TEST_FIXTURE_METADATA="${CMAKE_CURRENT_SOURCE_DIR}/fixtures/metadata"
|
||||
TEST_SCRATCH_ROOT="${DPM_TEST_DIR}/scratch"
|
||||
DPM_CORE_VERSION_EXPECTED="${PROJECT_VERSION}"
|
||||
)
|
||||
|
||||
foreach(fixture good missing_symbols bad_version)
|
||||
add_dependencies(test_core fixture_${fixture})
|
||||
set_target_properties(test_${suite} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${DPM_TEST_DIR}
|
||||
BUILD_RPATH "$ORIGIN/../lib"
|
||||
)
|
||||
|
||||
foreach(fixture good missing_symbols bad_version)
|
||||
add_dependencies(test_${suite} fixture_${fixture})
|
||||
endforeach()
|
||||
|
||||
add_test(NAME api_${suite} COMMAND test_${suite})
|
||||
endforeach()
|
||||
|
||||
add_test(NAME core_api COMMAND test_core)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# CLI end-to-end tests (the dpm binary is the cheapest full-stack client)
|
||||
# ---------------------------------------------------------------------
|
||||
set(CLI_FIXTURE_ARGS
|
||||
-c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules
|
||||
-M ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/metadata
|
||||
)
|
||||
|
||||
add_test(NAME cli_help COMMAND dpm --help)
|
||||
set_tests_properties(cli_help PROPERTIES PASS_REGULAR_EXPRESSION "Usage: dpm")
|
||||
|
||||
add_test(NAME cli_list
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules --list-modules)
|
||||
add_test(NAME cli_help_lists_record_flags COMMAND dpm --help)
|
||||
set_tests_properties(cli_help_lists_record_flags PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "--install-module")
|
||||
|
||||
add_test(NAME cli_list COMMAND dpm ${CLI_FIXTURE_ARGS} --list-modules)
|
||||
set_tests_properties(cli_list PROPERTIES PASS_REGULAR_EXPRESSION "info")
|
||||
|
||||
# The bundled module has no record in the fixture directory, so the
|
||||
# listing reports it as uninstalled rather than opening it.
|
||||
add_test(NAME cli_list_marks_uninstalled
|
||||
COMMAND dpm ${CLI_FIXTURE_ARGS} --list-modules)
|
||||
set_tests_properties(cli_list_marks_uninstalled PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "<uninstalled>")
|
||||
|
||||
add_test(NAME cli_info_version
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules -L INFO info version)
|
||||
@@ -56,11 +78,59 @@ set_tests_properties(cli_info_version PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "libdpm-core\\.so Version: ${PROJECT_VERSION}")
|
||||
|
||||
add_test(NAME cli_module_not_found
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules nonexistent)
|
||||
COMMAND dpm ${CLI_FIXTURE_ARGS} nonexistent)
|
||||
set_tests_properties(cli_module_not_found PROPERTIES WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME cli_rejects_invalid_module
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${DPM_TEST_FIXTURE_DIR} missing_symbols)
|
||||
-m ${DPM_TEST_FIXTURE_DIR}
|
||||
-M ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/metadata
|
||||
missing_symbols)
|
||||
set_tests_properties(cli_rejects_invalid_module PROPERTIES WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME cli_lists_aliases COMMAND dpm ${CLI_FIXTURE_ARGS} --list-aliases)
|
||||
set_tests_properties(cli_lists_aliases PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "goodie")
|
||||
|
||||
add_test(NAME cli_lists_aliases_for_module
|
||||
COMMAND dpm ${CLI_FIXTURE_ARGS} --list-aliases good)
|
||||
set_tests_properties(cli_lists_aliases_for_module PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "gd")
|
||||
|
||||
add_test(NAME cli_refuses_duplicate_alias
|
||||
COMMAND dpm ${CLI_FIXTURE_ARGS} --add-alias good goodie)
|
||||
set_tests_properties(cli_refuses_duplicate_alias PROPERTIES WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME cli_add_alias_requires_two_arguments
|
||||
COMMAND dpm ${CLI_FIXTURE_ARGS} --add-alias good)
|
||||
set_tests_properties(cli_add_alias_requires_two_arguments PROPERTIES
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME cli_uninstall_unrecorded_module
|
||||
COMMAND dpm ${CLI_FIXTURE_ARGS} --uninstall-module nonexistent)
|
||||
set_tests_properties(cli_uninstall_unrecorded_module PROPERTIES WILL_FAIL TRUE)
|
||||
|
||||
# The install and uninstall round trip writes, so it runs against a
|
||||
# scratch directory of its own and the two cases run in order.
|
||||
add_test(NAME cli_install_module
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules
|
||||
-M ${DPM_TEST_DIR}/scratch/cli
|
||||
--install-module info)
|
||||
|
||||
add_test(NAME cli_lists_installed_module
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules
|
||||
-M ${DPM_TEST_DIR}/scratch/cli
|
||||
--list-modules)
|
||||
set_tests_properties(cli_lists_installed_module PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION "Reports and tests libdpm-core"
|
||||
DEPENDS cli_install_module)
|
||||
|
||||
add_test(NAME cli_uninstall_module
|
||||
COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/conf
|
||||
-m ${CMAKE_BINARY_DIR}/modules
|
||||
-M ${DPM_TEST_DIR}/scratch/cli
|
||||
--uninstall-module info)
|
||||
set_tests_properties(cli_uninstall_module PROPERTIES
|
||||
DEPENDS cli_lists_installed_module)
|
||||
|
||||
2
tests/fixtures/metadata/good.meta
vendored
Normal file
2
tests/fixtures/metadata/good.meta
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
version = 1.2.3
|
||||
description = Known-good stub module.
|
||||
2
tests/fixtures/metadata/modules.aliases
vendored
Normal file
2
tests/fixtures/metadata/modules.aliases
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
goodie = good
|
||||
gd = good
|
||||
4
tests/fixtures/src/bad_version.cpp
vendored
4
tests/fixtures/src/bad_version.cpp
vendored
@@ -28,6 +28,10 @@ extern "C" const char* dpm_module_description(void) {
|
||||
return "Fixture with a malformed version.";
|
||||
}
|
||||
|
||||
extern "C" const char* dpm_module_aliases(void) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||
int argc, char** argv) {
|
||||
(void)ctx;
|
||||
|
||||
6
tests/fixtures/src/good.cpp
vendored
6
tests/fixtures/src/good.cpp
vendored
@@ -32,6 +32,12 @@ extern "C" const char* dpm_module_description(void) {
|
||||
return "Known-good stub module.";
|
||||
}
|
||||
|
||||
/* Declares two alternate names, so alias recording is exercised against
|
||||
a module that reports more than one. */
|
||||
extern "C" const char* dpm_module_aliases(void) {
|
||||
return "goodie, gd";
|
||||
}
|
||||
|
||||
/* Answers "ping" with 42 so a dispatch round trip is observable, and
|
||||
0 for anything else. */
|
||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||
|
||||
5
tests/fixtures/src/missing_symbols.cpp
vendored
5
tests/fixtures/src/missing_symbols.cpp
vendored
@@ -2,8 +2,9 @@
|
||||
* @file missing_symbols.cpp
|
||||
* @brief Broken fixture: exports only part of the module contract
|
||||
*
|
||||
* Exports the version and description probes and nothing else.
|
||||
* libdpm-core.so refuses it at step 1 and names dpm_module_execute.
|
||||
* Exports the version and description probes and nothing else, so two
|
||||
* of the four reserved contract symbols are absent. libdpm-core.so
|
||||
* refuses it at step 1 and names both.
|
||||
*
|
||||
* Part of the Dark Horse Linux Package Manager (DPM)
|
||||
*
|
||||
|
||||
87
tests/harness.hpp
Normal file
87
tests/harness.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file harness.hpp
|
||||
* @brief What every test binary shares: counting, reporting, fixture paths
|
||||
*
|
||||
* Each test binary covers one area of the library and is registered as
|
||||
* its own ctest case, so a failure names the area it happened in. This
|
||||
* header carries what all of them need and nothing specific to any one.
|
||||
*
|
||||
* @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/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <dpm/core.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef TEST_FIXTURE_MODULES
|
||||
#error "TEST_FIXTURE_MODULES must be defined"
|
||||
#endif
|
||||
#ifndef TEST_FIXTURE_CONF
|
||||
#error "TEST_FIXTURE_CONF must be defined"
|
||||
#endif
|
||||
#ifndef TEST_FIXTURE_METADATA
|
||||
#error "TEST_FIXTURE_METADATA must be defined"
|
||||
#endif
|
||||
#ifndef TEST_SCRATCH_ROOT
|
||||
#error "TEST_SCRATCH_ROOT must be defined"
|
||||
#endif
|
||||
|
||||
/** Assertions run and assertions failed, reported by harness_report(). */
|
||||
static int g_checks = 0;
|
||||
static int g_failures = 0;
|
||||
|
||||
/**
|
||||
* @brief Records one assertion and reports it when it fails
|
||||
*
|
||||
* Every failure names the file and line, so a ctest case that fails
|
||||
* points at the assertion rather than at the binary.
|
||||
*/
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
g_checks++; \
|
||||
if (!(cond)) { \
|
||||
g_failures++; \
|
||||
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, \
|
||||
#cond); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief Reports whether the context's last error carries a substring
|
||||
*
|
||||
* @param ctx The context to read
|
||||
* @param needle The text the reason is expected to contain
|
||||
* @return true when a reason is recorded and contains needle
|
||||
*/
|
||||
static inline 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints the tally and yields the process exit status
|
||||
*
|
||||
* @return 0 when every assertion passed, 1 otherwise
|
||||
*/
|
||||
static inline int harness_report(const char* suite) {
|
||||
std::printf("%s: %d checks, %d failures\n", suite, g_checks, g_failures);
|
||||
return g_failures == 0 ? 0 : 1;
|
||||
}
|
||||
197
tests/test_aliases.cpp
Normal file
197
tests/test_aliases.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* @file test_aliases.cpp
|
||||
* @brief Alternate module names: resolution, recording, and enumeration
|
||||
*
|
||||
* @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 "harness.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int main(void) {
|
||||
const std::string scratch = std::string(TEST_SCRATCH_ROOT) + "/aliases";
|
||||
|
||||
/* ---- a recorded alias reaches the module it names ---- */
|
||||
{
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF,
|
||||
TEST_FIXTURE_MODULES, nullptr, -1,
|
||||
TEST_FIXTURE_METADATA};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
std::fprintf(stderr, "cannot continue without a context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
dpm_module* by_alias = dpm_require(ctx, "goodie");
|
||||
CHECK(by_alias != nullptr);
|
||||
|
||||
dpm_module_info seen;
|
||||
if (by_alias) {
|
||||
CHECK(dpm_get_module_info(ctx, by_alias, &seen) == 0);
|
||||
CHECK(std::strcmp(seen.name, "good") == 0);
|
||||
}
|
||||
|
||||
/* the alias and the real name reach the same load */
|
||||
CHECK(dpm_require(ctx, "good") == by_alias);
|
||||
CHECK(dpm_require(ctx, "gd") == by_alias);
|
||||
|
||||
/* a name that is neither a module nor an alias stays unresolved */
|
||||
CHECK(dpm_require(ctx, "nosuchname") == nullptr);
|
||||
|
||||
dpm_close(ctx);
|
||||
}
|
||||
|
||||
/* ---- enumeration reports every alias, or one module's ---- */
|
||||
{
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF,
|
||||
TEST_FIXTURE_MODULES, nullptr, -1,
|
||||
TEST_FIXTURE_METADATA};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
dpm_alias_cursor* cur = dpm_list_module_aliases(ctx, nullptr);
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
int count = 0;
|
||||
dpm_alias_info info;
|
||||
while (dpm_alias_cursor_next(cur, &info) == 0) {
|
||||
count++;
|
||||
CHECK(info.alias != nullptr && *info.alias);
|
||||
CHECK(std::strcmp(info.module, "good") == 0);
|
||||
}
|
||||
CHECK(count == 2);
|
||||
CHECK(dpm_alias_cursor_next(cur, &info) != 0);
|
||||
dpm_alias_cursor_free(cur);
|
||||
}
|
||||
|
||||
cur = dpm_list_module_aliases(ctx, "good");
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
int count = 0;
|
||||
dpm_alias_info info;
|
||||
while (dpm_alias_cursor_next(cur, &info) == 0) {
|
||||
count++;
|
||||
}
|
||||
CHECK(count == 2);
|
||||
dpm_alias_cursor_free(cur);
|
||||
}
|
||||
|
||||
cur = dpm_list_module_aliases(ctx, "nosuchmodule");
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
dpm_alias_info info;
|
||||
CHECK(dpm_alias_cursor_next(cur, &info) != 0);
|
||||
dpm_alias_cursor_free(cur);
|
||||
}
|
||||
|
||||
CHECK(dpm_list_module_aliases(nullptr, nullptr) == nullptr);
|
||||
CHECK(dpm_alias_cursor_next(nullptr, nullptr) != 0);
|
||||
dpm_alias_cursor_free(nullptr);
|
||||
|
||||
dpm_close(ctx);
|
||||
}
|
||||
|
||||
/* ---- recording and removing names against a writable directory ---- */
|
||||
{
|
||||
fs::remove_all(scratch);
|
||||
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF,
|
||||
TEST_FIXTURE_MODULES, nullptr, -1,
|
||||
scratch.c_str()};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* installing records what the module declared */
|
||||
CHECK(dpm_install_module(ctx, "good") == 0);
|
||||
CHECK(fs::exists(scratch + "/modules.aliases"));
|
||||
|
||||
dpm_alias_cursor* cur = dpm_list_module_aliases(ctx, "good");
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
int count = 0;
|
||||
dpm_alias_info info;
|
||||
while (dpm_alias_cursor_next(cur, &info) == 0) {
|
||||
count++;
|
||||
}
|
||||
CHECK(count == 2);
|
||||
dpm_alias_cursor_free(cur);
|
||||
}
|
||||
|
||||
/* a name already recorded is refused rather than repointed */
|
||||
CHECK(dpm_add_module_alias(ctx, "good", "goodie") != 0);
|
||||
CHECK(error_contains(ctx, "already resolves"));
|
||||
|
||||
/* an installed module's own name is not available */
|
||||
CHECK(dpm_add_module_alias(ctx, "good", "good") != 0);
|
||||
CHECK(error_contains(ctx, "installed module"));
|
||||
|
||||
/* bad arguments are refused */
|
||||
CHECK(dpm_add_module_alias(nullptr, "good", "x") != 0);
|
||||
CHECK(dpm_add_module_alias(ctx, nullptr, "x") != 0);
|
||||
CHECK(dpm_add_module_alias(ctx, "good", nullptr) != 0);
|
||||
CHECK(dpm_add_module_alias(ctx, "good", "") != 0);
|
||||
|
||||
/* a free name is recorded and resolves in a later context */
|
||||
CHECK(dpm_add_module_alias(ctx, "good", "gg") == 0);
|
||||
|
||||
dpm_ctx* reader = dpm_open(&overrides);
|
||||
CHECK(reader != nullptr);
|
||||
if (reader) {
|
||||
CHECK(dpm_require(reader, "gg") != nullptr);
|
||||
dpm_close(reader);
|
||||
}
|
||||
|
||||
/* removal takes the name back out */
|
||||
CHECK(dpm_remove_module_alias(ctx, "gg") == 0);
|
||||
CHECK(dpm_remove_module_alias(ctx, "gg") != 0);
|
||||
CHECK(error_contains(ctx, "not recorded"));
|
||||
CHECK(dpm_remove_module_alias(ctx, nullptr) != 0);
|
||||
CHECK(dpm_remove_module_alias(nullptr, "gg") != 0);
|
||||
|
||||
/* uninstalling a module takes its aliases with it */
|
||||
CHECK(dpm_uninstall_module(ctx, "good") == 0);
|
||||
|
||||
dpm_ctx* after = dpm_open(&overrides);
|
||||
CHECK(after != nullptr);
|
||||
if (after) {
|
||||
dpm_alias_cursor* empty = dpm_list_module_aliases(after, nullptr);
|
||||
CHECK(empty != nullptr);
|
||||
if (empty) {
|
||||
dpm_alias_info info;
|
||||
CHECK(dpm_alias_cursor_next(empty, &info) != 0);
|
||||
dpm_alias_cursor_free(empty);
|
||||
}
|
||||
dpm_close(after);
|
||||
}
|
||||
|
||||
dpm_close(ctx);
|
||||
}
|
||||
|
||||
return harness_report("aliases");
|
||||
}
|
||||
122
tests/test_context.cpp
Normal file
122
tests/test_context.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @file test_context.cpp
|
||||
* @brief Context lifecycle, override validation, and configuration reading
|
||||
*
|
||||
* @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 "harness.hpp"
|
||||
|
||||
int main(void) {
|
||||
/* ---- an explicit override that cannot work refuses at open ---- */
|
||||
{
|
||||
dpm_open_overrides bad_conf = {"/does/not/exist/conf", nullptr,
|
||||
nullptr, -1, nullptr};
|
||||
CHECK(dpm_open(&bad_conf) == nullptr);
|
||||
|
||||
dpm_open_overrides bad_level = {nullptr, nullptr, nullptr, 99, nullptr};
|
||||
CHECK(dpm_open(&bad_level) == nullptr);
|
||||
|
||||
dpm_open_overrides low_level = {nullptr, nullptr, nullptr, -2, nullptr};
|
||||
CHECK(dpm_open(&low_level) == nullptr);
|
||||
}
|
||||
|
||||
/* ---- a NULL overrides pointer opens against system defaults ---- */
|
||||
{
|
||||
dpm_ctx* system_ctx = dpm_open(nullptr);
|
||||
CHECK(system_ctx != nullptr);
|
||||
if (system_ctx) {
|
||||
const char* path = dpm_get_resolved_module_path(system_ctx);
|
||||
CHECK(path != nullptr && *path);
|
||||
dpm_close(system_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- NULL is a no-op everywhere a context is expected ---- */
|
||||
{
|
||||
dpm_close(nullptr);
|
||||
dpm_log(nullptr, DPM_LOG_INFO, "dropped");
|
||||
CHECK(dpm_get_resolved_module_path(nullptr) == nullptr);
|
||||
CHECK(dpm_get_last_error(nullptr) == nullptr);
|
||||
CHECK(dpm_config_get(nullptr, "core", "modules", "path") == nullptr);
|
||||
}
|
||||
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF, TEST_FIXTURE_MODULES,
|
||||
nullptr, -1, TEST_FIXTURE_METADATA};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
std::fprintf(stderr, "cannot continue without a context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- the version the library reports is the one it was built as ---- */
|
||||
CHECK(std::strcmp(dpm_core_version(), DPM_CORE_VERSION_EXPECTED) == 0);
|
||||
|
||||
/* ---- configuration resolves per namespace, 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, "testmod", "nosection", "key") == nullptr);
|
||||
CHECK(dpm_config_get(ctx, "nomodule", "main", "key") == nullptr);
|
||||
CHECK(dpm_config_get(ctx, nullptr, "main", "key") == nullptr);
|
||||
}
|
||||
|
||||
/* ---- an override beats the value configuration supplied ---- */
|
||||
{
|
||||
const char* configured =
|
||||
dpm_config_get(ctx, "core", "modules", "path");
|
||||
CHECK(configured != nullptr);
|
||||
|
||||
const char* path = dpm_get_resolved_module_path(ctx);
|
||||
CHECK(path != nullptr);
|
||||
CHECK(path != nullptr &&
|
||||
std::strncmp(path, TEST_FIXTURE_MODULES,
|
||||
std::strlen(TEST_FIXTURE_MODULES)) == 0);
|
||||
}
|
||||
|
||||
/* ---- a resolved directory carries a trailing separator ---- */
|
||||
{
|
||||
const char* path = dpm_get_resolved_module_path(ctx);
|
||||
CHECK(path != nullptr && *path && path[std::strlen(path) - 1] == '/');
|
||||
}
|
||||
|
||||
/* ---- the error slot starts empty and holds what was last written --- */
|
||||
{
|
||||
CHECK(dpm_get_last_error(ctx) == nullptr);
|
||||
|
||||
dpm_set_last_error(ctx, "first reason");
|
||||
CHECK(error_contains(ctx, "first reason"));
|
||||
|
||||
dpm_set_last_error(ctx, "second reason");
|
||||
CHECK(error_contains(ctx, "second reason"));
|
||||
CHECK(!error_contains(ctx, "first reason"));
|
||||
|
||||
dpm_set_last_error(ctx, nullptr);
|
||||
CHECK(error_contains(ctx, "second reason"));
|
||||
dpm_set_last_error(nullptr, "ignored");
|
||||
}
|
||||
|
||||
dpm_close(ctx);
|
||||
return harness_report("context");
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
194
tests/test_metadata.cpp
Normal file
194
tests/test_metadata.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* @file test_metadata.cpp
|
||||
* @brief Module records: installation, removal, and listing without loading
|
||||
*
|
||||
* @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 "harness.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int main(void) {
|
||||
const std::string scratch = std::string(TEST_SCRATCH_ROOT) + "/metadata";
|
||||
|
||||
/* ---- listing reads records and marks what has none ---- */
|
||||
{
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF,
|
||||
TEST_FIXTURE_MODULES, nullptr, -1,
|
||||
TEST_FIXTURE_METADATA};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
std::fprintf(stderr, "cannot continue without a context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
dpm_cursor* cur = dpm_list_modules(ctx);
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
int count = 0;
|
||||
int uninstalled = 0;
|
||||
bool saw_good = false;
|
||||
|
||||
dpm_module_info info;
|
||||
while (dpm_cursor_next(cur, &info) == 0) {
|
||||
count++;
|
||||
CHECK(info.name != nullptr);
|
||||
CHECK(info.version != nullptr);
|
||||
CHECK(info.description != nullptr);
|
||||
|
||||
if (std::strcmp(info.name, "good") == 0) {
|
||||
saw_good = true;
|
||||
CHECK(std::strcmp(info.version, "1.2.3") == 0);
|
||||
CHECK(*info.description);
|
||||
}
|
||||
if (std::strcmp(info.version, "<uninstalled>") == 0) {
|
||||
uninstalled++;
|
||||
}
|
||||
}
|
||||
|
||||
/* every .so present is reported, recorded or not */
|
||||
CHECK(count == 3);
|
||||
CHECK(saw_good);
|
||||
CHECK(uninstalled == 2);
|
||||
dpm_cursor_free(cur);
|
||||
}
|
||||
|
||||
/* the cursor reports nothing further once exhausted */
|
||||
cur = dpm_list_modules(ctx);
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
dpm_module_info info;
|
||||
while (dpm_cursor_next(cur, &info) == 0) {
|
||||
}
|
||||
CHECK(dpm_cursor_next(cur, &info) != 0);
|
||||
dpm_cursor_free(cur);
|
||||
}
|
||||
|
||||
CHECK(dpm_cursor_next(nullptr, nullptr) != 0);
|
||||
dpm_cursor_free(nullptr);
|
||||
|
||||
/* an unreadable module path refuses with a reason */
|
||||
dpm_open_overrides bad_path = {TEST_FIXTURE_CONF, "/does/not/exist",
|
||||
nullptr, -1, TEST_FIXTURE_METADATA};
|
||||
dpm_ctx* bad = dpm_open(&bad_path);
|
||||
CHECK(bad != nullptr);
|
||||
if (bad) {
|
||||
CHECK(dpm_list_modules(bad) == nullptr);
|
||||
CHECK(error_contains(bad, "module path"));
|
||||
dpm_close(bad);
|
||||
}
|
||||
|
||||
CHECK(dpm_list_modules(nullptr) == nullptr);
|
||||
dpm_close(ctx);
|
||||
}
|
||||
|
||||
/* ---- installation writes a record a later context reads back ---- */
|
||||
{
|
||||
fs::remove_all(scratch);
|
||||
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF,
|
||||
TEST_FIXTURE_MODULES, nullptr, -1,
|
||||
scratch.c_str()};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
std::fprintf(stderr, "cannot continue without a context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* a module that cannot load cannot be installed */
|
||||
CHECK(dpm_install_module(ctx, "bad_version") != 0);
|
||||
CHECK(error_contains(ctx, "malformed version"));
|
||||
CHECK(dpm_install_module(ctx, "nonexistent") != 0);
|
||||
CHECK(dpm_install_module(ctx, nullptr) != 0);
|
||||
CHECK(dpm_install_module(nullptr, "good") != 0);
|
||||
|
||||
CHECK(dpm_install_module(ctx, "good") == 0);
|
||||
CHECK(fs::exists(scratch + "/good.meta"));
|
||||
|
||||
/* the record answers a fresh context without opening the module */
|
||||
dpm_ctx* reader = dpm_open(&overrides);
|
||||
CHECK(reader != nullptr);
|
||||
if (reader) {
|
||||
dpm_cursor* cur = dpm_list_modules(reader);
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
bool found = false;
|
||||
dpm_module_info info;
|
||||
while (dpm_cursor_next(cur, &info) == 0) {
|
||||
if (std::strcmp(info.name, "good") == 0) {
|
||||
found = true;
|
||||
CHECK(std::strcmp(info.version, "1.2.3") == 0);
|
||||
CHECK(std::strcmp(info.description,
|
||||
"Known-good stub module.") == 0);
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
dpm_cursor_free(cur);
|
||||
}
|
||||
dpm_close(reader);
|
||||
}
|
||||
|
||||
/* installing again over an existing record succeeds */
|
||||
CHECK(dpm_install_module(ctx, "good") == 0);
|
||||
|
||||
/* removal takes the record and leaves the module file alone */
|
||||
CHECK(dpm_uninstall_module(ctx, "good") == 0);
|
||||
CHECK(!fs::exists(scratch + "/good.meta"));
|
||||
CHECK(dpm_uninstall_module(ctx, "good") != 0);
|
||||
CHECK(dpm_uninstall_module(ctx, nullptr) != 0);
|
||||
CHECK(dpm_uninstall_module(nullptr, "good") != 0);
|
||||
|
||||
/* the module still loads by its own name once uninstalled */
|
||||
dpm_ctx* after = dpm_open(&overrides);
|
||||
CHECK(after != nullptr);
|
||||
if (after) {
|
||||
CHECK(dpm_require(after, "good") != nullptr);
|
||||
dpm_close(after);
|
||||
}
|
||||
|
||||
dpm_close(ctx);
|
||||
}
|
||||
|
||||
/* ---- an unwritable metadata directory fails without throwing ---- */
|
||||
{
|
||||
dpm_open_overrides readonly = {TEST_FIXTURE_CONF, TEST_FIXTURE_MODULES,
|
||||
nullptr, -1, "/proc/dpm-metadata"};
|
||||
dpm_ctx* ctx = dpm_open(&readonly);
|
||||
CHECK(ctx != nullptr);
|
||||
if (ctx) {
|
||||
CHECK(dpm_install_module(ctx, "good") != 0);
|
||||
|
||||
/* listing and loading carry on with no records available */
|
||||
dpm_cursor* cur = dpm_list_modules(ctx);
|
||||
CHECK(cur != nullptr);
|
||||
if (cur) {
|
||||
dpm_cursor_free(cur);
|
||||
}
|
||||
CHECK(dpm_require(ctx, "good") != nullptr);
|
||||
dpm_close(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return harness_report("metadata");
|
||||
}
|
||||
90
tests/test_modules.cpp
Normal file
90
tests/test_modules.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @file test_modules.cpp
|
||||
* @brief Load-time validation, module acquisition, and dispatch
|
||||
*
|
||||
* @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 "harness.hpp"
|
||||
|
||||
int main(void) {
|
||||
dpm_open_overrides overrides = {TEST_FIXTURE_CONF, TEST_FIXTURE_MODULES,
|
||||
nullptr, -1, TEST_FIXTURE_METADATA};
|
||||
dpm_ctx* ctx = dpm_open(&overrides);
|
||||
CHECK(ctx != nullptr);
|
||||
if (!ctx) {
|
||||
std::fprintf(stderr, "cannot continue without a context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- every broken fixture is refused, and the reason says why ---- */
|
||||
{
|
||||
CHECK(dpm_require(ctx, "missing_symbols") == nullptr);
|
||||
CHECK(error_contains(ctx, "missing required contract symbols"));
|
||||
CHECK(error_contains(ctx, "dpm_module_execute"));
|
||||
CHECK(error_contains(ctx, "dpm_module_aliases"));
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
/* ---- a bad argument is refused without touching the module path ---- */
|
||||
{
|
||||
CHECK(dpm_require(nullptr, "good") == nullptr);
|
||||
CHECK(dpm_require(ctx, nullptr) == nullptr);
|
||||
CHECK(dpm_require(ctx, "") == nullptr);
|
||||
}
|
||||
|
||||
/* ---- the known-good fixture loads and reports what it declares ---- */
|
||||
{
|
||||
dpm_module* good = dpm_require(ctx, "good");
|
||||
CHECK(good != nullptr);
|
||||
if (!good) {
|
||||
dpm_close(ctx);
|
||||
return harness_report("modules");
|
||||
}
|
||||
|
||||
/* a module is opened at most once per context */
|
||||
CHECK(dpm_require(ctx, "good") == good);
|
||||
|
||||
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);
|
||||
CHECK(dpm_get_module_info(nullptr, good, &seen) != 0);
|
||||
|
||||
/* dispatch returns the module's own value, so a round trip is
|
||||
observable with no 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);
|
||||
|
||||
CHECK(dpm_execute(nullptr, good, "ping", 0, nullptr) == 1);
|
||||
CHECK(dpm_execute(ctx, nullptr, "ping", 0, nullptr) == 1);
|
||||
}
|
||||
|
||||
dpm_close(ctx);
|
||||
return harness_report("modules");
|
||||
}
|
||||
Reference in New Issue
Block a user