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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Performance
- Faster runtime-error detection: single `case` glob instead of 23-iteration loop in `detect_runtime_error` (#668)

## [0.36.0](https://github.com/TypedDevs/bashunit/compare/0.35.0...0.36.0) - 2026-05-07

### Fixed
Expand Down
39 changes: 18 additions & 21 deletions src/runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function bashunit::runner::compute_total_assertions() {
incomplete="${incomplete%%##*}"
snapshot="${test_execution_result##*##ASSERTIONS_SNAPSHOT=}"
snapshot="${snapshot%%##*}"
printf '%d' "$(( ${failed:-0} + ${passed:-0} + ${skipped:-0} + ${incomplete:-0} + ${snapshot:-0} ))"
printf '%d' "$((${failed:-0} + ${passed:-0} + ${skipped:-0} + ${incomplete:-0} + ${snapshot:-0}))"
}

function bashunit::runner::extract_subshell_type() {
Expand All @@ -92,24 +92,21 @@ function bashunit::runner::format_subshell_output() {

function bashunit::runner::detect_runtime_error() {
local runtime_output=$1
local error
for error in "command not found" "unbound variable" "permission denied" \
"no such file or directory" "syntax error" "bad substitution" \
"division by 0" "cannot allocate memory" "bad file descriptor" \
"segmentation fault" "illegal option" "argument list too long" \
"readonly variable" "missing keyword" "killed" \
"cannot execute binary file" "invalid arithmetic operator" \
"ambiguous redirect" "integer expression expected" \
"too many arguments" "value too great" \
"not a valid identifier" "unexpected EOF"; do
case "$runtime_output" in
*"$error"*)
local runtime_error="${runtime_output#*: }"
printf '%s' "${runtime_error//$'\n'/}"
return 0
;;
esac
done
case "$runtime_output" in
*"command not found"* | *"unbound variable"* | *"permission denied"* | \
*"no such file or directory"* | *"syntax error"* | *"bad substitution"* | \
*"division by 0"* | *"cannot allocate memory"* | *"bad file descriptor"* | \
*"segmentation fault"* | *"illegal option"* | *"argument list too long"* | \
*"readonly variable"* | *"missing keyword"* | *"killed"* | \
*"cannot execute binary file"* | *"invalid arithmetic operator"* | \
*"ambiguous redirect"* | *"integer expression expected"* | \
*"too many arguments"* | *"value too great"* | \
*"not a valid identifier"* | *"unexpected EOF"*)
local runtime_error="${runtime_output#*: }"
printf '%s' "${runtime_error//$'\n'/}"
return 0
;;
esac
printf ''
}

Expand Down Expand Up @@ -203,8 +200,8 @@ 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
if [ "$source_status" -ne 0 ] || [ "$(printf '%s' "$source_err" |
"$GREP" -cE 'syntax error|unexpected EOF' || true)" -gt 0 ]; then
local message="$source_err"
[ -z "$message" ] && message="Failed to source '$test_file' (exit $source_status)"
bashunit::runner::record_file_hook_failure \
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/runner_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,56 @@ function test_extract_assertion_runtime_output_keeps_user_output_that_looks_like

assert_same "✗ Failed: emitted by the code under test" "$actual"
}

function test_detect_runtime_error_returns_empty_when_input_is_empty() {
local actual
actual="$(bashunit::runner::detect_runtime_error "")"

assert_empty "$actual"
}

function test_detect_runtime_error_returns_empty_when_no_known_error() {
local actual
actual="$(bashunit::runner::detect_runtime_error "all good here")"

assert_empty "$actual"
}

function test_detect_runtime_error_matches_command_not_found() {
local actual
actual="$(bashunit::runner::detect_runtime_error \
"script.sh: line 3: foo: command not found")"

assert_same "line 3: foo: command not found" "$actual"
}

function test_detect_runtime_error_matches_syntax_error() {
local actual
actual="$(bashunit::runner::detect_runtime_error \
"bash: -c: line 1: syntax error near unexpected token")"

assert_same "-c: line 1: syntax error near unexpected token" "$actual"
}

function test_detect_runtime_error_matches_killed() {
local actual
actual="$(bashunit::runner::detect_runtime_error "process: killed")"

assert_same "killed" "$actual"
}

function test_detect_runtime_error_strips_newlines_from_extracted_message() {
local input=$'bash: line 1: foo: command not found\nextra'
local actual
actual="$(bashunit::runner::detect_runtime_error "$input")"

assert_same "line 1: foo: command not foundextra" "$actual"
}

function test_detect_runtime_error_matches_unexpected_eof() {
local actual
actual="$(bashunit::runner::detect_runtime_error \
"bash: line 5: unexpected EOF while looking for matching")"

assert_same "line 5: unexpected EOF while looking for matching" "$actual"
}
Loading