Implement a readable, testable evidence analyzer in one C++20 source file without third-party runtime code or external process execution.
The architecture is a pipeline with an explicit safety ledger. Parsers produce data plus diagnostics/completeness state; the analyzer may emit a subset only after the ledger proves every required stage complete.
External developer workflow
├─ changed-path list
├─ CMake File API replies
├─ compiler dependency files
└─ CTest JSON
↓ files/stdin only
diff2test analyzer
↓
selected names + explanations + status
↓
External workflow runs tests separately
diff2test has no process-control component by design.
Recommended top-to-bottom sections inside diff2test.cpp:
- Standard headers and build metadata constants
d2t::core— identifiers, status/result types, limitsd2t::diag— source positions, diagnostics, renderingd2t::cli— command/options parsingd2t::json— tokenizer, parser, value representation/accessorsd2t::dep— Make-style dependency parserd2t::path— normalization and root checksd2t::cmake— File API/codemodel loaderd2t::ctest— CTest catalogue loaderd2t::model— graph and evidence recordsd2t::impact— mapping/traversal/explanationsd2t::safety— completeness ledger and final outcomed2t::output— human/names formatters- top-level orchestration and
main
The exact namespace names may change, but the dependency direction must remain downward: parsing layers do not call output or orchestration layers.
Conceptual types, not implementation code:
PathIdTargetIdTestIdArtifactIdEvidenceId
Compact numeric ids may reduce repeated strings and make graph traversal predictable. Human strings live in indexed records.
NormalizedPath: display form, comparison key, root classificationTranslationUnit: source path, object/dependency evidenceBuildTarget: CMake id/name/type, sources, artifacts, dependenciesRegisteredTest: name, command, mapped target or mapping failureEvidenceEdge: source node, destination node, evidence category/locationDiagnostic: severity, code, message, optional source positionSafetyIssue: category, subject, consequence
- state: subset / full-known / full-required / usage / internal
- selected test ids
- explanation paths
- sorted diagnostics
Use a recursive-descent parser with:
- byte cursor plus line/column;
- tokenizer/parser combined or separated according to clarity;
- bounded nesting;
- value representation supporting all JSON types;
- strict string escape and number grammar;
- object lookup helpers that distinguish missing member from wrong type;
- unknown fields ignored only after structural validity is established.
Avoid an overly clever zero-copy representation during the hackathon. A straightforward owning value tree is acceptable for bounded metadata sizes.
Use a small state machine:
- join physical lines using valid backslash-newline continuation;
- locate unescaped rule separator;
- tokenize targets/prerequisites with escape awareness;
- normalize tokens;
- return rules plus diagnostics.
Do not reuse shell tokenization assumptions; Make-style escaping is its own format.
- validate reply/index containment;
- select supported codemodel and configuration;
- follow referenced JSON files;
- load target records;
- preserve target ids as opaque strings;
- collect sources/artifacts/dependencies;
- mark generated or unsupported constructs;
- report missing references to safety ledger.
- validate kind/version;
- read all test names and commands;
- reject catalogue ambiguity;
- normalize first command token under build-root context;
- map only exact unique target artifacts;
- retain mapping failures for the safety ledger.
- discover or receive
.dfiles; - parse each supported rule;
- connect rule target to translation unit/object under a tested policy;
- build reverse prerequisite index;
- detect missing/stale/duplicate ambiguity;
- preserve evidence filename.
Maintain separate typed relationships rather than an untyped universal graph where possible:
- prerequisite path → translation units
- translation unit → owning targets
- target → dependent targets
- target artifact → registered tests
This keeps propagation rules explicit. Explanations can reconstruct a typed path from predecessor maps recorded during BFS.
Parse the test catalogue early, then all other inputs. Collect diagnostics and safety issues without emitting test names.
Check catalogue, codemodel, dependency coverage, mapping uniqueness, configuration, roots, and changed-path classification.
If any issue forbids subset selection, skip minimal traversal and construct the safe full outcome.
For each changed path:
- mark directly matching translation unit(s);
- use reverse
.dindex to mark translation units that depend on it; - mark owning targets;
- traverse supported dependent-target direction;
- mark tests mapped to affected target artifacts;
- store predecessor/evidence for at least one path.
Ensure every output test belongs to the parsed catalogue, no duplicate exists, and deterministic sorting is applied. Re-check safety ledger before returning subset.
CMake target dependency direction requires a kickoff spike and fixtures. The implementation must name edges clearly—e.g., target depends on dependency—then construct reverse adjacency when asking “what becomes affected if dependency changes?” Do not traverse the wrong direction because of ambiguous variable names.
Use a single normalization boundary:
- convert separators to the platform-preferred lexical form;
- resolve
./..lexically; - classify under project root, build root, external/system, or invalid;
- store a stable comparison key;
- retain display path relative to project/build root where possible.
Do not call filesystem canonicalization blindly: missing/deleted paths and symlinks complicate it. Any physical canonicalization must be optional and error-aware.
Every loader returns useful partial data plus categorized safety issues. The final evaluator owns the policy that maps issues to outcomes. Parsers do not independently decide to output all tests.
Suggested categories:
- catalogue unavailable/untrusted;
- build graph unavailable/incomplete;
- dependency evidence incomplete/stale;
- test mapping incomplete;
- changed path unknown/unsafe;
- unsupported construct/version;
- resource limit;
- invocation error;
- internal invariant.
This centralization prevents a forgotten warning from accidentally permitting subset selection.
- Use value-or-error results for expected failures.
- Exceptions may handle allocation/filesystem failures internally but must be caught at the orchestration boundary.
- Every caught unexpected exception becomes
INTERNAL_ERRORand discards partial subset output. - Diagnostics use stable codes, e.g.,
JSON_UNTERMINATED_STRING,DEP_TRAILING_ESCAPE,SAFETY_UNKNOWN_CHANGED_PATH.
- Sort filesystem discoveries before parsing.
- Sort normalized records before assigning ids, or ensure output sorting is independent of id creation.
- Use lexical tie-breaks for BFS explanations.
- Never depend on directory iteration order or hash-table iteration order.
- Avoid timestamps in normal output and built-in version strings if attempting reproducible builds.
- Validate containment of referenced reply files.
- Reject traversal outside declared roots.
- Bound input size, nesting, node count, and diagnostic count.
- Do not interpret command strings, environment variables, or shell syntax.
- Do not execute metadata.
- Avoid quadratic concatenation for large strings and graphs.
Options to decide after kickoff:
- black-box executable tests driven by a standard-library test harness/script allowed as dev-only tooling;
- compile
diff2test.cppwith a macro that excludesmainand exposes internal test entry points; - built-in
self-testmode, though this enlarges runtime surface and is not preferred.
Preferred approach: a simple separate test executable or shell harness created during the event, with no runtime dependency in the shipped artifact. The organizer allows separate tests.
compile_commands.jsonenrichment- explicit manual mapping for wrapper tests
- per-test always-run policy
- multi-config support
- additional dependency formats
- JSON result output
- graph visualization/export
- freshness fingerprints
No extension point justifies weakening the current safety contract.