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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
## Unreleased

### Changed
- A `--tag` that matches nothing names the tags the run saw, or says no test carries one. Tags appear only in `# @tag` comments and nothing lists them, so a typo had no way back (#1265)
- A `--tag` matching nothing now names the tags the run saw, or says no test carries one β€” tags live only in `# @tag` comments and nothing lists them, so a typo had no way back (#1265)

### Fixed
- A test that both fails an assertion and hits a shell error no longer reports the failure text with the diagnostic glued onto the end; the error message is the diagnostic alone (#1267)

## [0.48.0](https://github.com/TypedDevs/bashunit/compare/0.47.0...0.48.0) - 2026-08-14

Expand Down
12 changes: 7 additions & 5 deletions src/runner/diagnostics.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ function bashunit::runner::_scan_diagnostic_lines() {
*"ambiguous redirect"* | *"integer expression expected"* | \
*"too many arguments"* | *"value too great"* | \
*"not a valid identifier"* | *"unexpected EOF"*)
# Extract from the whole capture, not the matched line: the message shape
# (leading source stripped, newlines removed) is pinned by
# tests/unit/runner/diagnostics_test.sh.
local runtime_error="${runtime_output#*: }"
_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT="${runtime_error//$'\n'/}"
# Extract from the matched line, not the whole capture. A test can both
# fail an assertion and hit a runtime error, and bashunit renders the
# failure inside the capture subshell -- so its own "βœ— Failed: <name>"
# text sits ahead of the shell's diagnostic. Stripping to the first ": "
# across the whole blob landed inside that rendering and reported the
# failure text with the diagnostic glued onto the end.
_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT="${line#*: }"
return
;;
esac
Expand Down
17 changes: 17 additions & 0 deletions tests/acceptance/bashunit_runtime_error_classification_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,20 @@ function test_a_real_shell_error_is_still_reported() {
assert_contains "βœ— Error: Hits a real shell error" "$output"
assert_contains "command not found" "$output"
}

# A test can fail an assertion *and* hit a shell error. The reported error must
# be the diagnostic alone: extracting it from the whole capture stripped to the
# first ": " anywhere, which landed inside bashunit's own "βœ— Failed: <name>"
# rendering, so the error read as the failure text with the diagnostic glued on
# ("...but got 'b' at file:2file: line 8: nope: command not found").
function test_an_assertion_failure_does_not_leak_into_the_error_message() {
local fixture=tests/acceptance/fixtures/runtime_error/fails_and_errors.sh
local output exit_code=0

output=$(LC_ALL=C NO_COLOR=1 ./bashunit --no-parallel --skip-env-file "$fixture" 2>&1) || exit_code=$?

assert_same 1 "$exit_code"
assert_contains "line 8: no_such_command_from_a_fixture: command not found" "$output"
# The failure text must not appear inside the error message line.
assert_not_contains "but got 'b' at" "$output"
}
9 changes: 9 additions & 0 deletions tests/acceptance/fixtures/runtime_error/fails_and_errors.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

# Both at once: the assertion fails, then the shell cannot find the command.
# bashunit renders the failure inside the capture subshell, so its own text
# precedes the diagnostic in the captured output.
function test_fails_an_assertion_and_then_hits_a_shell_error() {
assert_same "a" "b"
no_such_command_from_a_fixture
}
27 changes: 25 additions & 2 deletions tests/unit/runner/diagnostics_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ function test_detect_runtime_error_matches_killed() {
assert_same "killed" "$_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT"
}

function test_detect_runtime_error_strips_newlines_from_extracted_message() {
# The message is one line because it is *one line of the capture* -- the
# diagnostic itself, with its source prefix stripped. It used to be the whole
# capture with the newlines deleted, which glued unrelated output onto the end
# ("...command not foundextra"). Anything else the test printed is display
# output, and stays there.
function test_detect_runtime_error_reports_the_diagnostic_line_not_the_whole_capture() {
local input=$'bash: line 1: foo: command not found\nextra'
bashunit::runner::detect_runtime_error "$input"

assert_same "line 1: foo: command not foundextra" "$_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT"
assert_same "line 1: foo: command not found" "$_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT"
assert_same "$input" "$_BASHUNIT_RUNNER_RUNTIME_OUTPUT_OUT"
}

function test_detect_runtime_error_matches_unexpected_eof() {
Expand Down Expand Up @@ -122,3 +128,20 @@ function test_detect_runtime_error_prefers_the_matched_text_over_the_exit_code()

assert_contains "command not found" "$_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT"
}

# A test can both fail an assertion and hit a runtime error. bashunit prints the
# failure inside the capture subshell, so its own rendering sits in the capture
# ahead of the shell's diagnostic. Extracting from the whole capture then strips
# to the first ": " anywhere -- which lands inside "βœ— Failed: <name>" -- and the
# reported error becomes the failure text with the diagnostic glued on the end.
function test_detect_runtime_error_reports_only_the_diagnostic_not_the_captured_failure() {
local input="βœ— Failed: Both
Expected 'a'
but got 'b'
at f_test.sh:2
f_test.sh: line 4: nope: command not found"

bashunit::runner::detect_runtime_error "$input"

assert_same "line 4: nope: command not found" "$_BASHUNIT_RUNNER_RUNTIME_ERROR_OUT"
}
Loading