Skip to content

build: add unit-test harness (Catch2) and coverage tooling#10

Merged
pbosetti merged 14 commits into
v2from
code_coverage
Jul 12, 2026
Merged

build: add unit-test harness (Catch2) and coverage tooling#10
pbosetti merged 14 commits into
v2from
code_coverage

Conversation

@pbosetti

Copy link
Copy Markdown
Owner
  • New CMake options MADS_BUILD_TESTS and MADS_COVERAGE (both OFF by default)
  • tests/: Catch2 v3.7.1 via FetchContent, one executable per test_*.cpp,
    shared helpers (loopback endpoints, RunningGuard, wait_for) and README
  • Coverage: per-target instrumentation of MadsCore, gcovr.cfg with curated
    denominator, coverage CMake target, codecov.yml (informational for now)
  • CI: .github/workflows/coverage.yml builds with clang, runs ctest and
    uploads to codecov.io (tolerates missing CODECOV_TOKEN)
  • Fix: exclude mongo_fetch.cpp from the build when MADS_ENABLE_MONGOCXX=OFF
  • Remove orphaned legacy test/ directory (referenced dead targets)

pbosetti added 14 commits July 11, 2026 12:32
- New CMake options MADS_BUILD_TESTS and MADS_COVERAGE (both OFF by default)
- tests/: Catch2 v3.7.1 via FetchContent, one executable per test_*.cpp,
  shared helpers (loopback endpoints, RunningGuard, wait_for) and README
- Coverage: per-target instrumentation of MadsCore, gcovr.cfg with curated
  denominator, coverage CMake target, codecov.yml (informational for now)
- CI: .github/workflows/coverage.yml builds with clang, runs ctest and
  uploads to codecov.io (tolerates missing CODECOV_TOKEN)
- Fix: exclude mongo_fetch.cpp from the build when MADS_ENABLE_MONGOCXX=OFF
- Remove orphaned legacy test/ directory (referenced dead targets)
…c_path, watcher

Coverage (line %): mads.hpp 100% (46/46), execution_time_window_stats.hpp 100% (64/64), goback.hpp 100% (22/22), exec_path.hpp 84.6% (11/13), watcher.hpp 91.3% (21/23).
…ol, and LazyPayload

Covers src/agent.hpp's LazyPayload text/doc lazy caching (test_lazy_payload.cpp);
Agent pub/sub messaging, blob publishing, topic filtering, accessors, error
paths, disconnect/shutdown safety, and Delivery::LastKnownValue over an
in-process ZeroMQ loopback (test_agent_pubsub.cpp); the WireFormat x
Compression codec matrix, the Compression::Auto threshold, a large
Compression::Snappy round trip, and dropped_messages() on malformed frames
injected via a raw zmqpp socket (test_agent_wire.cpp); and Agent::loop()
pacing/exception handling plus remote_control()'s restart/shutdown/info/no-op
commands (test_agent_loop.cpp). All ports are in the assigned 42100-42199
range.

Coverage (ctest --test-dir build-cov, full 82-test suite, gcovr):
  src/agent.cpp  58.5% lines (531/908)
  src/agent.hpp  96.4% lines (27/28)
With the default infinite linger, a settings request queued toward an
unreachable broker keeps the ZMQ context alive and Agent::shutdown()
blocks forever in zmq_ctx_term. Found by the new broker-timeout unit test.
Local TOML settings loading (fixtures for valid/malformed/missing cases,
sub_topic string vs array, save_settings guard) and an in-process fake
broker covering the REQ/REP settings protocol: happy path, attachments,
timecode offset, version mismatch, refusals, timeouts, start_agent.
Coverage: src/agent.cpp 71% (from 58.5%).
… helper

tests/test_agent_c.cpp (23 cases, 137 assertions): free functions
(mads_version/mads_default_settings_uri/mads_free), full create/init/destroy
lifecycle and accessor sweep (id, pub/sub topics incl. malloc-on-demand
convention, receive timeout, high watermark, wire format, compression,
settings getters against tests/fixtures/settings/valid.toml), agent_init
error paths (missing file, malformed TOML, missing section, double-init),
agent_connect/agent_disconnect guarded states, agent_register_event
before/after connect, discover_broker_settings parameter guards (no network
touched), CURVE setup via the C ABI (string-based and file-based key paths),
a full C-ABI pub/sub round trip over tcp://127.0.0.1:42303 (endpoint/cross
wiring done via the underlying Mads::Agent*, since agent_c.h has no
equivalent), a mixed C++-publisher/C-API-subscriber blob-receive check, and
message_type_t/event_type_t <-> Mads::message_type/event_type enum parity.

tests/test_curve.cpp (13 cases, 35 assertions): CurveAuth::fetch_public_keys,
set_key_dir, key getters/setters, setup_curve_server and both
setup_curve_client overloads (missing-file and happy paths, all keys
generated at runtime via zmqpp::curve::generate_keypair() into a temp dir),
setup_auth, and a full CURVE-encrypted REQ/REP round trip with ZAP
authentication over tcp://127.0.0.1:42350.

Full suite: 138/138 ctest passing (102 pre-existing + 36 new).
Coverage: src/agent_c.cpp 78% lines (0% before), src/curve.hpp 96% lines.

Source bug found (not fixed, per work-package scope): in src/agent_c.cpp,
agent_set_client_public_key/secret_key/server_public_key guard against
"CurveAuth not initialized" with `if (!ag->curve_auth())`, but
Agent::curve_auth() returns `&_curve_auth` (address of a member), which is
never null. Calling any of these three setters before agent_setup_crypto()
dereferences a null CurveAuth* and segfaults instead of returning -1; the
"before setup" precondition is therefore untestable without triggering a
crash and is not exercised. Remaining uncovered lines in agent_c.cpp are:
discover_broker_settings's real-discovery body (network access via UDP
broadcast, out of scope per the no-network-in-tests rule), the six lines of
that dead branch above, several catch(...) "Unexpected" fallbacks that
require throwing a non-std::exception type, and a malloc-failure branch --
all judged not worth a hostile setup per the work-package guidance.
agent_set_client_public_key/secret_key/server_public_key guarded with
!ag->curve_auth(), but curve_auth() returns the address of the member
unique_ptr, which is never null. Calling any of these setters before
agent_setup_crypto() dereferenced a null CurveAuth and crashed instead
of returning -1 as documented. Check the pointed-to unique_ptr instead;
re-enable the unit-test assertions for the precondition.
The repo-wide *.toml gitignore rule silently kept tests/fixtures out of
the WP-C commit (tests passed locally against untracked files, failed on
CI with 'File could not be opened for reading'); negate the rule for
tests/fixtures/**. Also derive the same-minor version string in the
check_version test from Mads::version() instead of hardcoding v0.0.999,
which only matched on tag-less clones.
47 Catch2 test cases / 189 assertions across helpers (line_of_offset,
is_ident_char, find_matching_paren, read_file/write_file, run_command),
detect_protocol/bump_git_tag, apply_literal/apply_regex, load_migrations
against the real share/plugin_migrations chain, SpanRewriter/MethodTransform
on tricky snippets (multi-line signatures, parens in comments/strings, call
vs. declaration disambiguation), and run() end to end against copies of a
new fixture plugin project (tests/fixtures/plugin_p6/): dry-run, full P6->P8
migration with backups, already-at-target, missing CMakeLists.txt,
from/to overrides, missing chain step, undeterminable target, no-op run,
and undetectable protocol.

Full suite: 185/185 ctest passed (138 pre-existing + 47 new).
src/main/plugin_migrate.hpp coverage: 87% lines (445/507), 88.8% functions.
Uncovered: the Tier B compile-check block (invokes cmake/network, out of
scope for a no-network unit suite), the Windows _popen branch and a popen()
failure branch, and a few lexer edge branches (escaped chars, char literals)
already exercised indirectly via find_matching_paren's own test cases.
…ning flag

Agent run state is now owned by a Mads::Runtime object. Each agent gets
its own Runtime by default, so shutting down or destroying one agent no
longer stops the loops, LKV drain threads, or watchdogs of other agents
in the same process, and a disconnected agent can reconnect and loop
again. Agents that should stop together can share a Runtime via
Agent::set_runtime(); signal handlers and the remote shutdown/restart
commands request a process-wide stop (Runtime::stop_process()) that
overrides every Runtime.

Mads::running remains as a [[deprecated]] alias of the process-wide flag,
so existing code keeps compiling and behaving as before, with a
compile-time warning pointing at the replacement API.

New tests: Runtime semantics, agent/runtime wiring, regression tests for
the historical cross-agent stop and reconnect hazards, and the deprecated
shim. Full suite: 193/193; coverage 82.4% lines.
New RUNTIME.md topical page (wired into doc/Doxyfile INPUT, alongside
CRYPTO.md) documenting the Mads::Runtime model: the three stop levels
(agent, Runtime group, process), usage examples, and a migration table
from the deprecated Mads::running flag. The Runtime class doc and the
deprecated flag doc now link to it via \ref.
v2.4.0 hasn't shipped yet, so the test suite/coverage infrastructure
and the Mads::Runtime refactor belong in its CHANGES.md entry rather
than a hypothetical v2.5. Also fixes a stray 'v2.5' reference in
RUNTIME.md.
@pbosetti pbosetti merged commit 8a10d71 into v2 Jul 12, 2026
4 of 5 checks passed
@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant