Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- `--jobs auto` / `-j auto` caps parallel concurrency at the CPU core count (portable across Linux/macOS/BSD); the default stays unlimited (#766)

### Changed
- Faster test runs: the runner detects a failed source (syntax error / unexpected EOF) and discovery detects the `.bash` pattern variant with shell `case` matches instead of `grep` forks (2 -> 0 `grep` forks per sourced test file). No behaviour change
- Faster cold start: the base64 `-w` capability is detected with a shell `case` on `base64 --help` instead of piping into a `grep` fork (1 -> 0 `grep` forks per cold start). Same detection, no behaviour change
- Faster cold start: selecting the `perl` clock reads the time once instead of an empty `Time::HiRes` probe followed by a separate first read (2 -> 1 `perl` fork per cold start on bash 3.2 / macOS). No behaviour change
- Faster cold start: the deferred-output scratch files (failures/skipped/incomplete/risky/profile/rerun) now share one run-unique directory instead of forking `mktemp` once each (6 -> 0 `mktemp` forks per cold start; ~62ms -> ~50ms per nested run on bash 3.2). No behaviour change (#798)
Expand Down
12 changes: 6 additions & 6 deletions src/helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,13 @@ function bashunit::helper::find_files_recursive() {
local path="${1%%/}"
local pattern="${2:-*[tT]est.sh}"

# When the pattern targets *.sh test files, also match the .bash variant. Both
# the plain `*test.sh` and the default glob `*[tT]est.sh` end in `.sh`; a case
# match on either (no grep fork) is enough to decide.
local alt_pattern=""
local _re='\[tT\]est\.sh$'
local _pattern_match=false
case "$pattern" in *test.sh) _pattern_match=true ;; esac
if [ "$_pattern_match" = true ] || [ "$(echo "$pattern" | "$GREP" -cE "$_re" || true)" -gt 0 ]; then
alt_pattern="${pattern%.sh}.bash"
fi
case "$pattern" in
*test.sh | *'[tT]est.sh') alt_pattern="${pattern%.sh}.bash" ;;
esac

local _has_glob=false
case "$path" in *"*"*) _has_glob=true ;; esac
Expand Down
13 changes: 11 additions & 2 deletions src/runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,17 @@ function bashunit::runner::load_test_files() {
source_err="$(cat "$source_err_file")"
fi
rm -f "$source_err_file"
if [ "$source_status" -ne 0 ] || [ "$(printf '%s' "$source_err" |
"$GREP" -cE 'syntax error|unexpected EOF' || true)" -gt 0 ]; then
# A non-zero source status, or a syntax-error line on stderr, means the file
# failed to load. Match the captured stderr with `case` (no grep fork).
local source_failed=false
if [ "$source_status" -ne 0 ]; then
source_failed=true
else
case "$source_err" in
*"syntax error"* | *"unexpected EOF"*) source_failed=true ;;
esac
fi
if [ "$source_failed" = true ]; then
local message="$source_err"
[ -z "$message" ] && message="Failed to source '$test_file' (exit $source_status)"
bashunit::runner::record_file_hook_failure \
Expand Down
28 changes: 28 additions & 0 deletions tests/acceptance/bashunit_run_forks_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail

# Regression guard for the per-file run path. Running a test file used to fork
# `grep` twice: once in the runner to scan sourcing stderr for "syntax error"/
# "unexpected EOF", and once in discovery to decide whether to also match the
# `.bash` variant of the test pattern. Both are shell `case` matches now, so a
# plain run forks `grep` zero times — this matters across the acceptance suite's
# ~258 nested runs, each of which sources at least one test file.
function test_running_a_test_file_does_not_fork_grep() {
if bashunit::check_os::is_windows; then
bashunit::skip "process tracing is unreliable under Git Bash" && return
fi

local dir
dir="$(bashunit::temp_dir)"
local fixture="$dir/grep_forks_test.sh"
printf 'function test_ok() { assert_true true; }\n' >"$fixture"

local trace
trace="$(PS4='+ ' bash -x ./bashunit --no-parallel "$fixture" 2>&1 >/dev/null)"

# Count real `grep` process executions (resolved absolute path with args).
local grep_forks
grep_forks="$(printf '%s\n' "$trace" | grep -cE '^\++ +/[^ ]*grep ' || true)"

assert_equals 0 "$grep_forks"
}
Loading