Problem
The DEBUG trap calls a shell function for every executed line, including every line of bashunit's own framework code running inside the test body and the lifecycle hooks:
trap'bashunit::coverage::record_line "${BASH_SOURCE[0]:-}" "${LINENO:-}"' DEBUGrecord_line (src/coverage/engine.sh:215) then decides whether the file is tracked at all. Most events are framework lines that lose that decision and return early, after paying a function call, two local assignments and a cache scan.
Measured on Bash 3.2 arm64, macOS, running tests/unit/assert/basic_test.sh:
| run | wall |
|---|
| no coverage | 0.39s |
--coverage --coverage-paths /nonexistent (trap fires, nothing tracked) | 1.63s |
--coverage --coverage-paths src (11 files tracked) | 6.42s |
Splitting the 6.42s: 0.39s test work, roughly 1.24s raw trap dispatch, roughly 1.27s report phase (measured separately at 11 files), leaving roughly 3.5s of capture bookkeeping. That bookkeeping is the target here.
Proposal
Reject untracked files inside the trap command, before any function call.
- Build a glob from
BASHUNIT_COVERAGE_PATHS once in bashunit::coverage::init and export it. - Make the trap dispatch on it:
trap 'case "${BASH_SOURCE[0]:-}" in $glob) bashunit::coverage::record_line "${BASH_SOURCE[0]}" "${LINENO}";; esac' DEBUG. - The exclude patterns stay in
should_track, which still runs for the files the glob admits. The glob is a cheap pre-filter, not a replacement for the decision.
Be honest about the ceiling: 1.24s of the run is trap dispatch that no filter can remove. This issue targets the per-line work layered on top of it. The two hot-path caches are the other half of that work and have their own issue.
Where to change
src/coverage/engine.sh:34-55enable_trapsrc/coverage/config.sh:61-112init, build and export the globsrc/coverage/paths.sh:29-125should_track stays authoritative for exclusions
Acceptance criteria
Repo checklist (agent)
- TDD: RED then GREEN then REFACTOR. Write the failing test first.
- Bash 3.0+ only: no
printf -v, no += append, no declare -A, no [[ ]], no ${var,,}, no &>>, no ${arr[-1]}. - Gates:
make sa, make lint, ./bashunit tests/, ./bashunit --parallel tests/. Never run shfmt -w. - CHANGELOG.md: one line under
## Unreleased (performance entry with the measured factor). - One issue = one PR.
Problem
The DEBUG trap calls a shell function for every executed line, including every line of bashunit's own framework code running inside the test body and the lifecycle hooks:
record_line(src/coverage/engine.sh:215) then decides whether the file is tracked at all. Most events are framework lines that lose that decision and return early, after paying a function call, two local assignments and a cache scan.Measured on Bash 3.2 arm64, macOS, running
tests/unit/assert/basic_test.sh:--coverage --coverage-paths /nonexistent(trap fires, nothing tracked)--coverage --coverage-paths src(11 files tracked)Splitting the 6.42s: 0.39s test work, roughly 1.24s raw trap dispatch, roughly 1.27s report phase (measured separately at 11 files), leaving roughly 3.5s of capture bookkeeping. That bookkeeping is the target here.
Proposal
Reject untracked files inside the trap command, before any function call.
BASHUNIT_COVERAGE_PATHSonce inbashunit::coverage::initand export it.trap 'case "${BASH_SOURCE[0]:-}" in $glob) bashunit::coverage::record_line "${BASH_SOURCE[0]}" "${LINENO}";; esac' DEBUG.should_track, which still runs for the files the glob admits. The glob is a cheap pre-filter, not a replacement for the decision.Be honest about the ceiling: 1.24s of the run is trap dispatch that no filter can remove. This issue targets the per-line work layered on top of it. The two hot-path caches are the other half of that work and have their own issue.
Where to change
src/coverage/engine.sh:34-55enable_trapsrc/coverage/config.sh:61-112init, build and export the globsrc/coverage/paths.sh:29-125should_trackstays authoritative for exclusionsAcceptance criteria
src), an absolute one, and several comma-separated paths all still track their filesBASHUNIT_COVERAGE_EXCLUDEstill excludes a file that the glob admitsset -uin subshells, which is why the current one uses${VAR:-}xtraceengine unaffected, since it never entersrecord_lineRepo checklist (agent)
printf -v, no+=append, nodeclare -A, no[[ ]], no${var,,}, no&>>, no${arr[-1]}.make sa,make lint,./bashunit tests/,./bashunit --parallel tests/. Never runshfmt -w.## Unreleased(performance entry with the measured factor).