🎯 Goal
Expand branch coverage beyond the current MVP (only if/elif/else chains and case patterns) to the other common Bash decision points, so branch-coverage numbers reflect real control flow.
📍 Where
bashunit::coverage::extract_branches and the _branch_* helpers — src/coverage.sh:877-990+. Scope is documented at src/coverage.sh:880 and adrs/adr-007-branch-coverage-mvp.md: if/elif/else + case only.- The
_branch_* helpers deliberately mutate caller locals via dynamic scope (documented src/coverage.sh:818-821, :954) — extend within that established pattern, with inline justification.
💡 Targets to add (pick the high-value, low-ambiguity ones first)
&& / || short-circuit — cmd_a && cmd_b, cmd_a || cmd_b: each right-hand side is a branch that may or may not execute.- Guard idiom —
[ cond ] && action and [ cond ] || action (extremely common in this codebase); the action arm is a branch. - Ternary-style —
[ cond ] && x || y: two arms. - Loop taken / not-taken —
while/until/for bodies that may execute zero times; a zero-iteration loop is an uncovered branch.
Explicitly out of scope (note in ADR): [[ ]] (not used — Bash 3.0 floor), arithmetic ((.
🔧 Approach
- Extend the single-pass tokenizer in
extract_branches to recognise the new constructs and emit branch points with the same record shape the report already consumes (verify against src/coverage.sh:575,688 report readers). - Reuse the DEBUG-trap hit data — a branch arm is "covered" when its line appears in the covered-line set. No new runtime cost; this is a static extraction change plus report accounting.
- Keep each construct additive and independently testable.
✅ Acceptance criteria
- For a fixture exercising each construct, branch coverage reports the correct total branch count and covered/uncovered arms.
- Existing
if/case branch results are unchanged (pure superset). - No runtime (
record_line) changes; extraction stays a static pass.
🧪 TDD / measurement
- RED per construct: add a fixture + a
tests/unit/coverage*_test.sh (or the branch-extraction test file) asserting extract_branches emits the expected branch points for &&, ||, guard, ternary, zero-iteration loop — one failing test per construct, implement one at a time. - Regression: an
if/elif/else/case fixture must produce byte-identical branch output to main. - Gate:
./bashunit tests/, ./bashunit --parallel --simple --strict tests/, make sa, make lint.
⛓️ Constraints
- Bash 3.0+ parsing of Bash 3.0 source — no
[[, no ${var,,}. - The
_branch_* dynamic-scope mutation pattern is intentional and documented; extend it, don't refactor it away, and keep the inline justification comments. - Update
adrs/adr-007-branch-coverage-mvp.md (or supersede with adr-009+) to record the expanded scope and the accepted exclusions.
📈 Impact
More faithful branch/condition coverage — the guard idiom ([ ] && …) alone is pervasive in src/, so current branch numbers under-report real decisions.
🎯 Goal
Expand branch coverage beyond the current MVP (only
if/elif/elsechains andcasepatterns) to the other common Bash decision points, so branch-coverage numbers reflect real control flow.📍 Where
bashunit::coverage::extract_branchesand the_branch_*helpers —src/coverage.sh:877-990+. Scope is documented atsrc/coverage.sh:880andadrs/adr-007-branch-coverage-mvp.md: if/elif/else + case only._branch_*helpers deliberately mutate caller locals via dynamic scope (documentedsrc/coverage.sh:818-821,:954) — extend within that established pattern, with inline justification.💡 Targets to add (pick the high-value, low-ambiguity ones first)
&&/||short-circuit —cmd_a && cmd_b,cmd_a || cmd_b: each right-hand side is a branch that may or may not execute.[ cond ] && actionand[ cond ] || action(extremely common in this codebase); the action arm is a branch.[ cond ] && x || y: two arms.while/until/forbodies that may execute zero times; a zero-iteration loop is an uncovered branch.Explicitly out of scope (note in ADR):
[[ ]](not used — Bash 3.0 floor), arithmetic((.🔧 Approach
extract_branchesto recognise the new constructs and emit branch points with the same record shape the report already consumes (verify againstsrc/coverage.sh:575,688report readers).✅ Acceptance criteria
if/casebranch results are unchanged (pure superset).record_line) changes; extraction stays a static pass.🧪 TDD / measurement
tests/unit/coverage*_test.sh(or the branch-extraction test file) assertingextract_branchesemits the expected branch points for&&,||, guard, ternary, zero-iteration loop — one failing test per construct, implement one at a time.if/elif/else/casefixture must produce byte-identical branch output tomain../bashunit tests/,./bashunit --parallel --simple --strict tests/,make sa,make lint.⛓️ Constraints
[[, no${var,,}._branch_*dynamic-scope mutation pattern is intentional and documented; extend it, don't refactor it away, and keep the inline justification comments.adrs/adr-007-branch-coverage-mvp.md(or supersede with adr-009+) to record the expanded scope and the accepted exclusions.📈 Impact
More faithful branch/condition coverage — the guard idiom (
[ ] && …) alone is pervasive insrc/, so current branch numbers under-report real decisions.