Skip to content

Commit 894e4e0

Browse files
authored
Merge pull request #643 from TypedDevs/feat/636-speed-up-coverage-report-generation
perf(coverage): collapse is_executable_line grep calls
2 parents 753fa61 + 80d97c1 commit 894e4e0

2 files changed

Lines changed: 24 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
### Added
66
- Display captured test output on assertion failures when `--show-output` is enabled (#637)
77

8+
### Changed
9+
- Speed up coverage report generation by collapsing the per-line non-executable pattern checks in `bashunit::coverage::is_executable_line` into a single combined `grep` invocation (#636)
10+
811
## [0.35.0](https://github.com/TypedDevs/bashunit/compare/0.34.1...0.35.0) - 2026-04-26
912

1013
### Added

src/coverage.sh

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -418,11 +418,24 @@ function bashunit::coverage::aggregate_parallel() {
418418
fi
419419
}
420420

421-
# Pre-compiled regex pattern for function declarations (performance optimization)
422-
# Matches: function foo() { OR foo() { OR function foo() OR foo()
423-
# Does NOT match single-line functions with body: function foo() { echo "hi"; }
424-
_BASHUNIT_COVERAGE_FUNC_PATTERN='^[[:space:]]*(function[[:space:]]+)?'
425-
_BASHUNIT_COVERAGE_FUNC_PATTERN="${_BASHUNIT_COVERAGE_FUNC_PATTERN}"'[a-zA-Z_][a-zA-Z0-9_:]*[[:space:]]*\(\)[[:space:]]*\{?[[:space:]]*$'
421+
# Pre-compiled combined regex of all non-executable line patterns.
422+
# Collapses multiple grep subshells into a single invocation per line for performance.
423+
# Each alternation is fully self-anchored so semantics match the original per-pattern checks.
424+
# Patterns covered (in order):
425+
# - comment-only lines (including shebang)
426+
# - function declarations (but not single-line functions with a body)
427+
# - brace-only lines
428+
# - control flow keywords (then, else, fi, do, done, esac, in, ;;, ;;&, ;&)
429+
# - loop terminators with redirection/pipe/fd (e.g. "done < file", "done | sort")
430+
# - case patterns like "--option)" or "*) # comment"
431+
# - standalone ) for arrays/subshells
432+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN='^[[:space:]]*#'
433+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN="${_BASHUNIT_COVERAGE_NONEXEC_PATTERN}"'|^[[:space:]]*(function[[:space:]]+)?[a-zA-Z_][a-zA-Z0-9_:]*[[:space:]]*\(\)[[:space:]]*\{?[[:space:]]*$'
434+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN="${_BASHUNIT_COVERAGE_NONEXEC_PATTERN}"'|^[[:space:]]*[\{\}][[:space:]]*$'
435+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN="${_BASHUNIT_COVERAGE_NONEXEC_PATTERN}"'|^[[:space:]]*(then|else|fi|do|done|esac|in|;;|;;&|;&)[[:space:]]*(#.*)?$'
436+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN="${_BASHUNIT_COVERAGE_NONEXEC_PATTERN}"'|^[[:space:]]*done[[:space:]]+[^[:space:]#].*$'
437+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN="${_BASHUNIT_COVERAGE_NONEXEC_PATTERN}"'|^[[:space:]]*[^\)]+\)[[:space:]]*(#.*)?$'
438+
_BASHUNIT_COVERAGE_NONEXEC_PATTERN="${_BASHUNIT_COVERAGE_NONEXEC_PATTERN}"'|^[[:space:]]*\)[[:space:]]*(#.*)?$'
426439

427440
# Check if a line is executable (used by get_executable_lines and report_lcov)
428441
# Arguments: line content, line number
@@ -434,29 +447,11 @@ function bashunit::coverage::is_executable_line() {
434447
# Unused but kept for API compatibility
435448
: "$lineno"
436449

437-
# Skip empty lines (line with only whitespace)
450+
# Skip empty lines (line with only whitespace) — built-in, no subshell
438451
[ -z "${line// /}" ] && return 1
439452

440-
# Skip comment-only lines (including shebang)
441-
[ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*#' || true)" -gt 0 ] && return 1
442-
443-
# Skip function declaration lines (but not single-line functions with body)
444-
[ "$(echo "$line" | "$GREP" -cE "$_BASHUNIT_COVERAGE_FUNC_PATTERN" || true)" -gt 0 ] && return 1
445-
446-
# Skip lines with only braces
447-
[ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*[\{\}][[:space:]]*$' || true)" -gt 0 ] && return 1
448-
449-
# Skip control flow keywords (then, else, fi, do, done, esac, in, ;;, ;&, ;;&)
450-
[ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*(then|else|fi|do|done|esac|in|;;|;;&|;&)[[:space:]]*(#.*)?$' || true)" -gt 0 ] && return 1
451-
452-
# Skip loop terminator with trailing redirection/pipe/fd (e.g. "done < file", "done | sort", "done 2>&1", "done &")
453-
[ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*done[[:space:]]+[^[:space:]#].*$' || true)" -gt 0 ] && return 1
454-
455-
# Skip case patterns like "--option)" or "*) # comment"
456-
[ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*[^\)]+\)[[:space:]]*(#.*)?$' || true)" -gt 0 ] && return 1
457-
458-
# Skip standalone ) for arrays/subshells
459-
[ "$(echo "$line" | "$GREP" -cE '^[[:space:]]*\)[[:space:]]*(#.*)?$' || true)" -gt 0 ] && return 1
453+
# Single combined grep covers every non-executable pattern
454+
[ "$(echo "$line" | "$GREP" -cE "$_BASHUNIT_COVERAGE_NONEXEC_PATTERN" || true)" -gt 0 ] && return 1
460455

461456
return 0
462457
}

0 commit comments

Comments
 (0)