🎯 Goal
Surface per-line execution hit counts (gcov-style: how many times each line ran) in coverage reports, instead of only boolean covered/not-covered. The raw data to do this largely exists already — this is mostly a reporting change.
📍 Where
- Raw hit records are produced by
bashunit::coverage::record_line into _BASHUNIT_COVERAGE_DATA_FILE / _BASHUNIT_COVERAGE_TEST_HITS_FILE (src/coverage.sh:294-301, flush at :336-339). - Report readers collapse to a covered set today:
src/coverage.sh:575 (grep … | sort -u), :688, :743. - Report writers:
src/reports.sh (HTML/JUnit/etc.) and the console coverage summary.
⚠️ Interaction with the dedup issue (read first)
The per-line dedup issue proposes collapsing repeated hits at source (one record per file:line). If that lands, hit counts are lost at the source. Two options — the implementer must pick and document:
- (A) Count in memory: keep dedup, but maintain a small in-memory
file:line → count tally in record_line and flush counts (not repeated records). Preserves counts and the I/O win. Preferred. - (B) Order the work: land hit-counts first (parse repeated raw records → counts at report time), then make dedup opt-out when counts are requested.
Coordinate so the two issues don't contradict. (A) is the clean end state.
🔧 Approach (assuming option A)
- In
record_line, when a (file,line) is first seen, record it; on repeat, increment an in-memory counter keyed file:line rather than appending a duplicate record. - On flush, write
file:line:count (extend the record format; keep backward-compatible parsing or bump the data-file format guarded by a version marker). - Report layer: aggregate counts across the run (and across
--parallel per-$$ files at merge), expose per-line counts in HTML (heat/number column) and optionally a --coverage-mode flag to show counts in the console. - Keep the existing boolean summary (coverage %) unchanged — counts are additive detail.
✅ Acceptance criteria
- Reports can show, per line: run count (0 = uncovered).
--parallel merge sums counts across worker files correctly.- Coverage percentage and the covered-line set are unchanged vs. current output.
- Data-file format change (if any) is versioned and parsed safely.
🧪 TDD / measurement
- RED: fixture with a line executed a known number of times (e.g. a 5-iteration loop) → assert the report attributes count
5 to that line; a once-run line → 1; an unrun line → 0. --parallel: two workers each hitting the same shared-source line → summed count.- Gate:
./bashunit tests/, ./bashunit --parallel --simple --strict tests/, make sa, make lint. Regenerate coverage HTML/report snapshots if any.
⛓️ Constraints
- Bash 3.0+ — in-memory count tally via the existing string-cache pattern (bounded by distinct lines); no
${var//} over big strings (perf-fork-budget.md). - Don't regress the dedup I/O win — option (A) keeps both.
- If it materially adds
record_line overhead, gate counts behind a flag (e.g. BASHUNIT_COVERAGE_COUNTS=true) so default coverage stays lean.
📈 Impact
Turns coverage from "covered?" into "how hot?" — useful for spotting under-exercised branches and dead-ish code. Depends on / must be sequenced with the per-line dedup issue.
🎯 Goal
Surface per-line execution hit counts (gcov-style: how many times each line ran) in coverage reports, instead of only boolean covered/not-covered. The raw data to do this largely exists already — this is mostly a reporting change.
📍 Where
bashunit::coverage::record_lineinto_BASHUNIT_COVERAGE_DATA_FILE/_BASHUNIT_COVERAGE_TEST_HITS_FILE(src/coverage.sh:294-301, flush at:336-339).src/coverage.sh:575(grep … | sort -u),:688,:743.src/reports.sh(HTML/JUnit/etc.) and the console coverage summary.The per-line dedup issue proposes collapsing repeated hits at source (one record per file:line). If that lands, hit counts are lost at the source. Two options — the implementer must pick and document:
file:line → counttally inrecord_lineand flush counts (not repeated records). Preserves counts and the I/O win. Preferred.Coordinate so the two issues don't contradict. (A) is the clean end state.
🔧 Approach (assuming option A)
record_line, when a (file,line) is first seen, record it; on repeat, increment an in-memory counter keyedfile:linerather than appending a duplicate record.file:line:count(extend the record format; keep backward-compatible parsing or bump the data-file format guarded by a version marker).--parallelper-$$files at merge), expose per-line counts in HTML (heat/number column) and optionally a--coverage-mode flag to show counts in the console.✅ Acceptance criteria
--parallelmerge sums counts across worker files correctly.🧪 TDD / measurement
5to that line; a once-run line →1; an unrun line →0.--parallel: two workers each hitting the same shared-source line → summed count../bashunit tests/,./bashunit --parallel --simple --strict tests/,make sa,make lint. Regenerate coverage HTML/report snapshots if any.⛓️ Constraints
${var//}over big strings (perf-fork-budget.md).record_lineoverhead, gate counts behind a flag (e.g.BASHUNIT_COVERAGE_COUNTS=true) so default coverage stays lean.📈 Impact
Turns coverage from "covered?" into "how hot?" — useful for spotting under-exercised branches and dead-ish code. Depends on / must be sequenced with the per-line dedup issue.