🎯 Goal (SPIKE + ADR — not a straight merge)
Prototype and benchmark replacing the per-line DEBUG-trap coverage mechanism with set -x / BASH_XTRACEFD tracing to a dedicated file descriptor + offline parse. Decide, with numbers, whether it can make --coverage fast enough to run on every CI job (gating) instead of nightly-only.
📍 Current mechanism
DEBUG trap → bashunit::coverage::record_line "$BASH_SOURCE" "$LINENO" on every executed line (src/coverage.sh:126, enable at :115-127). This invokes a full shell function per line — that dispatch is the dominant cost and the reason coverage is nightly/non-gating (.github/workflows/coverage.yml).
💡 Hypothesis
set -x with a controlled PS4 writes one trace line per executed command at C level to BASH_XTRACEFD, with no per-line shell-function call. Parsing that trace once after the run could be an order of magnitude cheaper than the DEBUG-trap callback. This is the mechanism bashcov/kcov-style tooling leans on.
Sketch:
exec {cov_fd}>coverage.trace # NB: {var}>… is Bash 4.1+; on 3.x use a fixed high FDexport PS4='@@COV@@${BASH_SOURCE}:${LINENO}@@ '
BASH_XTRACEFD=$cov_fdset -x
# … run test body …set +x
# offline: grep '^@@COV@@' coverage.trace, strip, sort -u → covered file:line set🔬 What the spike must answer
- Speed: wall-time of the unit suite under
--coverage via xtrace vs. the current DEBUG trap, on Bash 3.2 (macOS) and Bash 5 (Linux). Include parse time. This is the whole point — quantify it. - Bash 3.0 floor:
BASH_XTRACEFD exists (Bash 4.1+). On Bash 3.x it does not — xtrace goes to stderr, which collides with test output. Determine the fallback: (a) fixed high FD redirect that works on 3.x, (b) DEBUG-trap retained as the 3.x path with xtrace as a 4.1+ fast path, or (c) xtrace-only with a documented Bash-4.1 floor for --coverage (core stays 3.0). Recommend one. - Correctness: can
PS4+trace reconstruct the same covered-line set as the DEBUG trap? Handle: multi-line statements (the DEBUG trap's attribution quirk, src/coverage.sh:668), subshells/pipes, functions, sourced files, and the framework's own lines (must be excluded — the trace will include bashunit internals). - Per-test attribution: DEBUG-trap fills the hits buffer with
file:line|testfile:testfn (src/coverage.sh:299). Reproduce by emitting a marker into the trace at each start_test and partitioning the trace by marker. - Interference: tests that run
set -x / set their own PS4 / read $BASH_XTRACEFD; spies/mocks; --parallel workers (each needs its own trace FD/file). Enumerate the collisions and whether they're containable. - Noise/size: trace files can be huge and expansion-noisy (quoting, command args). Measure size and parse robustness.
📦 Deliverables
- A branch with a working prototype (behind an env flag, e.g.
BASHUNIT_COVERAGE_ENGINE=xtrace|trap) — enough to benchmark, not necessarily production-complete. - Benchmark numbers (table: engine × bash version × suite → wall + parse time + trace size).
adrs/adr-009-coverage-tracing-engine.md recording the decision (adopt / adopt-as-fast-path / reject) with the evidence. Follow adrs/TEMPLATE.md; reference ADR-007 (branch MVP) and ADR-008.- If ADR = adopt: a follow-up implementation issue with the migration + fallback plan.
✅ Definition of done (for the spike)
- ADR merged with a clear recommendation backed by measured numbers on both bash versions.
- No change to the shipped default engine unless the ADR explicitly approves it and the full gate (
./bashunit tests/, --parallel --simple --strict, make sa, make lint) is green with per-test attribution + --parallel merge preserved.
⛓️ Constraints
- Do not break the Bash 3.0 floor for the framework core.
--coverage may adopt a higher floor only if the ADR justifies it and the trap path remains as the 3.x fallback, or the raised floor is explicitly accepted. - Depends on / composes with the per-line dedup issue (that reduces trap-path cost; the spike compares against the improved trap baseline).
🎯 Goal (SPIKE + ADR — not a straight merge)
Prototype and benchmark replacing the per-line
DEBUG-trap coverage mechanism withset -x/BASH_XTRACEFDtracing to a dedicated file descriptor + offline parse. Decide, with numbers, whether it can make--coveragefast enough to run on every CI job (gating) instead of nightly-only.📍 Current mechanism
DEBUGtrap →bashunit::coverage::record_line "$BASH_SOURCE" "$LINENO"on every executed line (src/coverage.sh:126, enable at:115-127). This invokes a full shell function per line — that dispatch is the dominant cost and the reason coverage is nightly/non-gating (.github/workflows/coverage.yml).💡 Hypothesis
set -xwith a controlledPS4writes one trace line per executed command at C level toBASH_XTRACEFD, with no per-line shell-function call. Parsing that trace once after the run could be an order of magnitude cheaper than the DEBUG-trap callback. This is the mechanism bashcov/kcov-style tooling leans on.Sketch:
🔬 What the spike must answer
--coveragevia xtrace vs. the current DEBUG trap, on Bash 3.2 (macOS) and Bash 5 (Linux). Include parse time. This is the whole point — quantify it.BASH_XTRACEFDexists (Bash 4.1+). On Bash 3.x it does not — xtrace goes to stderr, which collides with test output. Determine the fallback: (a) fixed high FD redirect that works on 3.x, (b) DEBUG-trap retained as the 3.x path with xtrace as a 4.1+ fast path, or (c) xtrace-only with a documented Bash-4.1 floor for--coverage(core stays 3.0). Recommend one.PS4+trace reconstruct the same covered-line set as the DEBUG trap? Handle: multi-line statements (the DEBUG trap's attribution quirk,src/coverage.sh:668), subshells/pipes, functions,sourced files, and the framework's own lines (must be excluded — the trace will include bashunit internals).file:line|testfile:testfn(src/coverage.sh:299). Reproduce by emitting a marker into the trace at eachstart_testand partitioning the trace by marker.set -x/ set their ownPS4/ read$BASH_XTRACEFD; spies/mocks;--parallelworkers (each needs its own trace FD/file). Enumerate the collisions and whether they're containable.📦 Deliverables
BASHUNIT_COVERAGE_ENGINE=xtrace|trap) — enough to benchmark, not necessarily production-complete.adrs/adr-009-coverage-tracing-engine.mdrecording the decision (adopt / adopt-as-fast-path / reject) with the evidence. Followadrs/TEMPLATE.md; reference ADR-007 (branch MVP) and ADR-008.✅ Definition of done (for the spike)
./bashunit tests/,--parallel --simple --strict,make sa,make lint) is green with per-test attribution +--parallelmerge preserved.⛓️ Constraints
--coveragemay adopt a higher floor only if the ADR justifies it and the trap path remains as the 3.x fallback, or the raised floor is explicitly accepted.