cxpm docs

Testing

cxpm is tested by two projects of its own workspace, built and run by cxpm test:

Project What it is Cases
tests/unit The library tested in process, with every external process replaced by a recording 172
tests/cli The installed cxpm and cxpm-descriptor-sandbox run as a user would run them, with a real compiler 17

Running the suite

cxpm test                       # with an installed cxpm
./cxpm-build.sh --test          # on a machine that has none: bootstrap first
172/172 tests passed
cxpm-unit-tests passed
17/17 tests passed
cxpm-cli-tests passed
2 of 2 tests projects passed

cxpm test builds the workspace, then each tests project, and runs its executable from the tests project’s directory. Either executable runs one case alone with --run 'group::case', for example tests/unit/.cxpm/build/cxpm-unit-tests/cxpm-unit-tests --run 'Fetcher::...'.

The CMake build runs the same two executables through CTest: ctest --test-dir build, or ctest --test-dir build -L unit for the unit suite alone. The command line suite tests an installed cxpm, so a CTest fixture first installs the build into build/tests-sysroot.

CI (.github/workflows/ci.yaml) runs ./cxpm-build.sh --test --install in a container that has a compiler and no other build system.

Where the tests live

tests/unit/package.cpp           the cxpm-unit-tests project
tests/unit/src/main.cpp          cx::core::testing::run_all
tests/unit/src/all_tests.cpp     includes every *Test.hpp
tests/unit/src/Support.hpp       temporary directories, recorded processes, scoped environment
tests/unit/src/*Test.hpp         one header per component

tests/cli/package.cpp            the cxpm-cli-tests project
tests/cli/src/main.cpp           every command line case
tests/cli/fixtures/              projects the cases build

The command line suite runs what was built

tests/cli depends on cxpm, cxpm-descriptor-sandbox and a toolchain module, so cxpm test installs them into the workspace sysroot first. The suite then starts $CXPM_TEST_SYSROOT/usr/bin/cxpm (cxpm test sets that variable, and puts the sysroot’s bin on PATH) and asserts on exit codes, output and the files left behind. It copies each fixture into a temporary directory, so nothing is ever built inside the repository.

It covers: usage and every error path; generate; building the example projects from package.cpp and package.json; install and uninstall under --root; a target whose name holds spaces and quotes; the sandbox helper on a real plugin; and confinement, with a manifest that reads a file outside its project and one that opens a socket. The confinement cases are skipped, and say so, where bubblewrap cannot create a namespace.

Self-registering groups

The unit suite runs on cx-core-testing. Each header declares one cx::core::testing::test_group per class it covers, in namespace CXPM::Tests:

// tests/unit/src/ManifestApiTest.hpp
namespace CXPM::Tests {

inline static cx::core::testing::test_group status_tests{
    "Status",
    {
        {"Success formats as Status::Success",
         [](cx::core::testing::test_context &ctx) {
           ctx.check_equal(std::to_string(CXPM::Status::Success),
                           std::string("Status::Success"));
         }},
    }};

} // namespace CXPM::Tests

Constructing the group at static-initialization time registers it; run_all runs every registered case, prints one line per case and returns non-zero if any check failed or any case threw. tests/unit/src/all_tests.cpp includes every *Test.hpp, so every group coexists in one translation unit, which surfaces helper-name collisions and ODR problems when the suite compiles. A new *Test.hpp is added to that file.

Nothing test-related is installed: the tests projects are not members of the workspace’s packages, only of its tests.

How the hard parts are tested without a compiler

Every external process cxpm starts goes through CXPM::Controllers::ProcessRunner, and a test can replace what it does (ProcessRunner::handler_set). Support.hpp’s RecordedProcesses records each argument vector and answers with a canned result, so the orchestration that used to be reachable only through a real build is unit-tested:

  • ToolchainDriver: the exact compile, link and archive argument vectors for each target type, including names and paths with spaces and quotes; parallel compilation; stopping at the first failure.
  • ToolchainManager: discovery order, toolchain.cpp winning over toolchain.json, plugin names that cannot collide, selection by name and by language, unusable toolchains skipped.
  • ProjectManager: manifest loading in both formats, every stage of the lifecycle and what makes each one stale, workspaces and dependency order, cxpm test, compile_commands.json.
  • Fetcher, Staging and the package managers: the lock file, the staged layout of each target type, and the archive, pacman, rpm and deb packages byte by byte where the format allows.
  • ProcessRunner: what a confined process is allowed to see.
  • DescriptorPlugin/Installation: the plugin compile command, reuse of an up-to-date plugin, the failure message, and every environment override.
  • PkgConfigManager: the pkg-config queries and .pc rendering.
  • ApplicationView: every command’s exit code and output, run in process with captured streams.

What needs real tools is covered by the command line suite: a real compiler building and loading a real plugin, the cxpm-descriptor-sandbox helper loading it, and bubblewrap confining both.

Sanitizers

tests/unit/package.cpp builds the unit suite with AddressSanitizer and UndefinedBehaviorSanitizer. The full suite passes clean under both.

Known gaps

  • examples/archive-object, examples/shared-object and the gstreamer example are not built by the command line suite; those target types are covered by unit tests only.
  • The sandbox helper’s resource limits (CPU, memory, wall clock) are not exercised by a test that actually exceeds them.
  • There is no coverage report: the CMake build no longer has a coverage target. Building with CompilerOptions::Coverage and running gcovr by hand still works.
  • Installing native packages (pacman -U, rpm -i, dpkg -i) is not part of the suite: it needs root and a matching system. The packages themselves are checked by unit tests.