diff --git a/CHANGELOG.md b/CHANGELOG.md index e91de2a1..e6f1075a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/runner/diagnostics.sh b/src/runner/diagnostics.sh index 1c3c09a8..b7d9e5f1 100644 --- a/src/runner/diagnostics.sh +++ b/src/runner/diagnostics.sh @@ -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: " + # 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 diff --git a/tests/acceptance/bashunit_runtime_error_classification_test.sh b/tests/acceptance/bashunit_runtime_error_classification_test.sh index a14529c6..f0ecb8ed 100644 --- a/tests/acceptance/bashunit_runtime_error_classification_test.sh +++ b/tests/acceptance/bashunit_runtime_error_classification_test.sh @@ -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: " +# 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" +} diff --git a/tests/acceptance/fixtures/runtime_error/fails_and_errors.sh b/tests/acceptance/fixtures/runtime_error/fails_and_errors.sh new file mode 100644 index 00000000..4f0f7dcf --- /dev/null +++ b/tests/acceptance/fixtures/runtime_error/fails_and_errors.sh @@ -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 +} diff --git a/tests/unit/runner/diagnostics_test.sh b/tests/unit/runner/diagnostics_test.sh index ff5626a1..745de9f3 100644 --- a/tests/unit/runner/diagnostics_test.sh +++ b/tests/unit/runner/diagnostics_test.sh @@ -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() { @@ -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: " -- 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" +}