Consolidates the fragmented chain/stress/streamhub/datasources/recorder E2E suites, unit-test collection, and coverage into one entry point and one report, adds new DebugService/TCPLogger E2E coverage, and fixes the Test/Applications/StreamHub build break blocking `make test`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
Unified test/E2E/reporting/coverage pipeline — design
Date: 2026-07-01
Problem
Test/coverage/reporting infrastructure is fragmented across at least seven independent entry points with no shared report:
Test/E2E/chain/run_chain_e2e.sh— the mature suite: 51-scenario matrix,collect.py(C++ GTest, C++ Integration, 3x Go, Python), lcov coverage,report_build.py→report_data.json/history.jsonl, Typst PDF.Test/E2E/chain/run_stress.sh(+stress.py/stress_run.py) — a 27-case scaling matrix, fully standalone, own JSON/PNG output, no report/history integration. A design+plan+branch (feature/stress-report-integration) already exists for wiring it in but never landed onmainand has since diverged (predates theUDPStreamerClienttest dir andDebugServiceGTestadditions).Test/E2E/streamhub/(Go smoke test) + rootrun_e2e_test.sh— a strict subset of whatTest/E2E/chain/clientalready checks.Test/E2E/datasources/(+run_e2e_report.sh, its ownE2E_Report.typ) — narrower UDPStreamer↔UDPStreamerClient direct round-trip predecessor to the chain suite; has a stale synthetic (np.random) latency histogram in its report generation.Test/E2E/recorder/(+run_recorder_e2e.sh) — BinaryRecorder disk-output check, depends ondatasources/'s generator/validator by relative path.run_combined_test.sh— manual, no-assertion demo launcher for the DebugService + TCPLogger + UDPStreamer combined config.- TCPLogger has zero automated tests of any kind today.
Additionally, Test/Applications/StreamHub's build is currently broken:
two untracked GTest files (BoundsCheckTest.cpp, WSServerBufferTest.cpp —
regression tests for BUG_FIX_PLAN.md items HI-1/HI-4/CR-1) #include <cstdint>, which fails under the project's -std=c++98 build even during a
bare -MM dependency scan (MARTe2's generic dependsRaw rule globs all
.cpp files in a test dir regardless of OBJSX membership). This currently
breaks make test repo-wide.
Coverage is also incomplete: it's rebuilt and collected after E2E scenarios already ran on normal (non-instrumented) binaries, so none of the code paths scenarios actually exercise count toward the C++ coverage percentage.
Goal
One command, one report. Running Test/E2E/suite/run_e2e.sh (renamed from
run_chain_e2e.sh) executes every test suite in the repo — unit tests
(GTest, Integration), the chain/stress/datasources/recorder/debug/tcplogger
E2E scenario families, and Go/Python framework tests — and produces one
results.json, one unit_tests.json/coverage.json, and one
report_data.json/history.jsonl/Typst PDF covering all of it, with
accurate coverage (including E2E-exercised code) and uncontaminated
performance metrics.
Non-goals
- No changes to the actual product code paths being tested (this is
test-infrastructure-only), except the minimal
<cstdint>→ MARTe2-types fix needed to unblock the build. - No new CI wiring (out of scope; this only needs to produce artifacts a CI job could consume later).
- No decision here about merging/resolving
BUG_FIX_PLAN.md's broader remediation phases — only unblocking the two orphaned test files enough to compile and run as part of the unified suite.
Design
1. Layout and entry point
Test/E2E/chain/→Test/E2E/suite/(gitmv),run_chain_e2e.sh→run_e2e.sh.CLAUDE.md/AGENTS.mdreferences updated.- New flags on
run_e2e.sh:--skip-coverage,--skip-stress,--skip-datasources,--skip-recorder,--skip-debug,--skip-tcplogger(all six suites run by default; existing--skip-build/--only/--pdf-only/--cpp-coverageretained,--cpp-coveragenow governs the single instrumented phase below instead of a second rebuild). Test/E2E/datasources/andTest/E2E/recorder/directories stay in place (cfgs,gen_test_data.py,validate_binary.py);run_e2e.shimports them as Python modules (same pattern it already uses forscenarios/gen_data/gen_cfg/validate_waveform).- Deleted:
Test/E2E/streamhub/(dir + Go module/binary), rootrun_e2e_test.sh,Test/E2E/datasources/run_e2e_report.sh+ its standaloneE2E_Report.typ,Test/E2E/recorder/run_recorder_e2e.sh, rootrun_combined_test.sh.
2. Build & coverage flow
Flow when coverage is enabled (default):
- Build normal binaries (
make core apps+ test dirs, no--coverage), unless--skip-build. - Authoritative pass: run chain + datasources + recorder + debug +
tcplogger scenarios (any not individually skipped) on normal binaries.
This is the sole source of
results.json(pass/fail/oracle) andperf_*.json(cpu/RSS/throughput — unchanged from today, feedstrend_perf.png/regression). - Run the stress matrix (unless
--skip-stress) on the same normal binaries. Stress is never instrumented, regardless of--skip-coverage, because gcov overhead skews its scaling/perf measurements. - Rebuild instrumented binaries (
--coverage):core,apps, and test dirs (Test/GTest,Test/Integration,Test/Components/DataSources/ UDPStreamer{,Client},Test/Applications/StreamHub). - Re-run chain + datasources + recorder + debug + tcplogger scenarios on instrumented binaries for coverage accumulation only. Their pass/fail is compared against step 2's results as a sanity check (a mismatch is logged as a warning, not a report-blocking failure — it flags nondeterminism without duplicating the correctness gate); their perf metrics are discarded.
- Run C++ GTest + Integration (instrumented) and the Go/Python suites (unaffected by instrumentation, run once).
- Collect coverage once (
lcov --capture+--extract Source/* Test/*, same restricted scope as today), aggregating gcov data from steps 5 and 6. - Restore a clean, non-instrumented
core appsbuild so the repo ends in its normal runnable state.
When --skip-coverage: steps 4/5/7 are skipped; step 6 runs GTest/
Integration once on normal binaries; coverage.json/the report's coverage
section is marked "skipped": true.
3. Unified scenario model
scenarios.py entries gain a kind field:
"chain"— the existing 51 scenarios, unchanged."direct"— new; the 2 existing datasources cfgs (E2ETest.cfg,E2EMulticastTest.cfg), UDPStreamer→UDPStreamerClient only, no StreamHub/WS hop, validated viavalidate_binary.py(byte-identical round-trip)."recorder"— new;RecorderStreamer.cfg/StreamHubRec.cfg, chain pathBinaryRecorder, validated viavalidate_binary.pyagainst the recorded disk file.
"debug"— new; see §4."tcplogger"— new; see §4.
run_e2e.sh's per-scenario loop dispatches on kind: chain keeps today's
launch/record/validate sequence; direct skips the StreamHub/chain-client
hop (launch MARTeApp.ex with UDPStreamerClient receiving directly, then
byte-compare); recorder runs the chain sequence plus a post-run disk-file
check; debug/tcplogger launch MARTeApp.ex with a DebugService/TCPLogger
config and drive it with debugclient (§4). All five write into the same
results.json (with kind as a discriminator field), so known_issue/
XFAIL/XPASS handling and PASS/FAIL aggregation need no branching beyond
dispatch.
Stress stays structurally separate (stress_results.json, produced by
stress_run.py as today, read by report_build.py as a distinct input —
the same pattern already used for perf_*.json), because it's a scaling
matrix, not a per-signal pass/fail scenario.
4. DebugService & TCPLogger E2E scenarios
Client/debugger/martecontrol.go's MarteController already implements the
needed protocol clients as library code: Connect/runTCP/SendCommand
(TCP 8080 command protocol), runDebugUDP (UDP 8081 trace telemetry), and
runLog (TCP 8082 TCPLogger stream) — currently wired to a WebSocket hub for
the browser UI. Refactor the hub-broadcast calls behind a small callback
interface so this logic is reusable headless, and add a new Go binary
Test/E2E/suite/debugclient/ that imports it, scripted instead of
browser-driven.
kind="debug": launchMARTeApp.exwith a DebugService config derived fromTest/Configurations/combined_test.cfg(trimmed to what's needed).debugclientconnects to TCP 8080 + UDP 8081 and scripts a sequence (force a signal, arm a breakpoint, trace a signal) mirroring whatTest/Integration'sTraceTest.cpp/ValidationTest.cppalready exercise in-process — but validated end-to-end as a separate OS process pair over real sockets, catching wire-format/serialization bugs the in-process suite cannot.kind="tcplogger": same launch,debugclientconnects to TCP 8082, triggers known log-worthy events (a forced value, a config error), asserts expected log lines arrive with correct formatting/ordering.- Both are functional (pass/fail), not perf-sensitive: they follow the
chain/datasources/recorder double-run pattern from §2 for coverage
consistency, but skip
proc_perf.pycapture. run_combined_test.shbecomes redundant once these scenarios exist and is retired, folding its config into the new scenario cfgs.
5. Reporting
E2E_Report.typ/report_data.json gain sections alongside the existing
Progression/Unit tests/Coverage/Performance/Scenarios: Stress Tests
(per-axis scaling tables/plots + regression, per the existing
2026-06-26-stress-suite-report-integration-design.md), Direct
Round-Trip (datasources pass/fail table), Recorder (pass/fail table),
Debug Service E2E and TCPLogger E2E (pass/fail per scenario).
history.jsonl/headline()/regression() extended with fields from each
new section so trend plots and regression-vs-previous-run cover everything
in one place.
6. Test/Applications/StreamHub build fix
BoundsCheckTest.cpp/WSServerBufferTest.cpp replace #include <cstdint>
with MARTe2 types (uint8/uint32/etc. from GeneralDefinitions.h),
matching the sibling GTest files' style and the repo's MARTe2-style
convention. Both are registered in Makefile.inc's OBJSX (as
BoundsCheckTest.x/WSServerBufferTest.x) alongside the existing four
GTest .x entries, so they compile deliberately rather than only being
incidentally scanned by the -MM dependency glob.
7. Stale branch reconciliation
git diff main...feature/stress-report-integration shows a concrete,
mostly-still-relevant 8-commit diff (report_build.py +124/-lines,
run_chain_e2e.sh +27, stress.py +69 for the multi-fragment size-axis
extension, plus a new test_report_stress.py, a CLAUDE.md doc line, and a
stray TODO.md). Port the relevant logic (stress table/plots/regression
wiring, the multi-fragment size-axis fix) onto current main as part of
implementing §5, adapting for the scenario-model/build-flow changes in this
spec (the branch predates both). Once superseded, delete the branch (local +
origin) — confirm before deleting the remote ref.
Testing
- All 76 existing GTests plus the two newly-registered ones must pass.
- Full
run_e2e.sh(default flags) must complete withresults.jsonreporting pass for all scenario kinds,stress_results.jsonproduced,coverage.jsonshowing a higher C++ % than today's GTest/Integration-only baseline, and a singleE2E_Report.pdfcontaining all six sections. run_e2e.sh --skip-coverage --skip-stress --only <id>must still work for fast single-scenario iteration (unchanged developer workflow).Test/E2E/chain/tests_py.py(framework self-tests) extended to cover the newkinddispatch logic and thedirect/recorder/debug/tcploggerscenario definitions the same way it already coverschainscenarios.
Risks
- Double-running chain/datasources/recorder/debug/tcplogger scenarios for coverage roughly doubles that portion of total E2E wall-clock time (stress is unaffected, running once).
- Refactoring
MarteControllerto be headless-usable touchesClient/debugger's production code, not just test infra — needs care not to regress the browser UI. - The
feature/stress-report-integrationbranch'sstress.pymulti-fragment extension needs re-validation against the currentUDPSClient1 MiB reassembly cap mentioned in its own design doc, since main has moved on since that branch diverged.