You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Running with --coverage multiplies wall-clock time by 4.7x to 7.8x depending on Bash version and engine. Measured on tests/unit/util (49 tests), same machine:
Bash 5.3.15
Bash 3.2.57 (macOS system bash)
no coverage
691 ms
707 ms
--coverage with xtrace
3255 ms (4.7x)
not available
--coverage with trap
4622 ms (6.7x)
5496 ms (7.8x)
--coverage with auto
3458 ms (5.0x) — picks xtrace
5496 ms — falls back to trap
auto resolves correctly on both. The problem is what it resolves to.
The part worth acting on first
bashunit::coverage::xtrace_is_supported requires Bash 4.1+. macOS ships 3.2 as /bin/bash, so on a stock Mac every coverage run gets the trap engine — the slowest path — and setting BASHUNIT_COVERAGE_ENGINE=xtrace changes nothing, silently.
That is correct behaviour (the ADR-009 engine needs BASH_XTRACEFD), but it means the audience most likely to run coverage locally is the audience that gets the worst of it, with no signal explaining why. A developer comparing notes with a Linux colleague sees 7.8x against their 4.7x and has nothing to tell them why.
DX observations
The cost discourages local use. At 5x, --coverage stops being something you run while working and becomes a CI-only step. That is a real loss: coverage is most useful while writing the test, not after merging it.
Silent engine downgrade.BASHUNIT_COVERAGE_ENGINE=xtrace on Bash 3.2 is accepted and ignored. --verbose does not say which engine ran. Worth surfacing, even just once per run.
No diff coverage. The report is whole-file. For a PR the useful question is "are the lines I touched covered", and answering it today means reading a full report and doing the diff by eye. This is the single biggest DX win available, and it is independent of the perf work.
Measure where the trap engine actually spends its time before optimising it. .claude/rules/perf-fork-budget.md documents the shim-census method used for the runner; the same approach applied to a --coverage run would say whether the cost is the DEBUG trap firing, the per-line bookkeeping, or the report pass. No optimisation should be chosen before that is known.
Report which engine ran, at least under --verbose, and warn when an explicit BASHUNIT_COVERAGE_ENGINE request was downgraded.
Diff coverage — restrict the report to lines changed against a base ref. Large DX win, no engine work needed.
Reconsider the 4.1 floor for xtrace. Bash 3.2 has no BASH_XTRACEFD, so this is likely a hard no; worth confirming and writing down so it is not re-asked.
Constraints
Bash 3.0+ floor. The trap engine has to keep working there regardless of what happens to xtrace.
Per-test paths stay fork-free — see .claude/rules/perf-fork-budget.md. Note the existing budget already allows ~3 forks per newly-seen file under --coverage.
Summary
Running with
--coveragemultiplies wall-clock time by 4.7x to 7.8x depending on Bash version and engine. Measured ontests/unit/util(49 tests), same machine:--coveragewithxtrace--coveragewithtrap--coveragewithautoxtracetrapautoresolves correctly on both. The problem is what it resolves to.The part worth acting on first
bashunit::coverage::xtrace_is_supportedrequires Bash 4.1+. macOS ships 3.2 as/bin/bash, so on a stock Mac every coverage run gets thetrapengine — the slowest path — and settingBASHUNIT_COVERAGE_ENGINE=xtracechanges nothing, silently.That is correct behaviour (the ADR-009 engine needs
BASH_XTRACEFD), but it means the audience most likely to run coverage locally is the audience that gets the worst of it, with no signal explaining why. A developer comparing notes with a Linux colleague sees 7.8x against their 4.7x and has nothing to tell them why.DX observations
--coveragestops being something you run while working and becomes a CI-only step. That is a real loss: coverage is most useful while writing the test, not after merging it.BASHUNIT_COVERAGE_ENGINE=xtraceon Bash 3.2 is accepted and ignored.--verbosedoes not say which engine ran. Worth surfacing, even just once per run.--paralleland sequential (129/286 both ways), so the aggregation is sound. Worth stating because the report writers are not — see fix(reports): every report format is empty under --parallel #1004.Directions worth investigating, roughly in order
.claude/rules/perf-fork-budget.mddocuments the shim-census method used for the runner; the same approach applied to a--coveragerun would say whether the cost is the DEBUG trap firing, the per-line bookkeeping, or the report pass. No optimisation should be chosen before that is known.--verbose, and warn when an explicitBASHUNIT_COVERAGE_ENGINErequest was downgraded.BASH_XTRACEFD, so this is likely a hard no; worth confirming and writing down so it is not re-asked.Constraints
.claude/rules/perf-fork-budget.md. Note the existing budget already allows ~3 forks per newly-seen file under--coverage.tests/unit/coverage/and gated by a nightly workflow, not by PR CI, so a regression here is not caught on the PR — see fix(ci): coverage workflow globbed a flat tests/unit that no longer exists #980.Acceptance criteria
--coveragetime goes, per engine, before any optimisation landsmake sa·make lint·./bashunit --parallel --simple --strict tests/·bash build.sh bin -v