diff --git a/CHANGELOG.md b/CHANGELOG.md index d7b990d6..dc902a86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +### Added +- Display test output (stdout/stderr) on failure for runtime errors + - Shows captured output in an "Output:" section when tests fail with runtime errors + - Helps debug test failures without manually capturing output + - Enabled by default; use `--no-output-on-failure` or `BASHUNIT_SHOW_OUTPUT_ON_FAILURE=false` to disable + - New CLI options: `--show-output`, `--no-output-on-failure` + ## [0.32.0](https://github.com/TypedDevs/bashunit/compare/0.31.0...0.32.0) - 2026-01-12 ### Changed diff --git a/docs/command-line.md b/docs/command-line.md index 6e16cbcc..99c866e5 100644 --- a/docs/command-line.md +++ b/docs/command-line.md @@ -69,6 +69,8 @@ bashunit test tests/ --parallel --simple | `--debug [file]` | Enable shell debug mode | | `--no-output` | Suppress all output | | `--failures-only` | Only show failures | +| `--show-output` | Show test output on failure (default) | +| `--no-output-on-failure` | Hide test output on failure | | `--strict` | Enable strict shell mode | | `--skip-env-file` | Skip `.env` loading, use shell environment only | | `-l, --login` | Run tests in login shell context | @@ -231,6 +233,33 @@ bashunit test tests/ --report-html report.html ``` ::: +### Show Output on Failure + +> `bashunit test --show-output` +> `bashunit test --no-output-on-failure` + +Control whether test output (stdout/stderr) is displayed when tests fail with runtime errors. + +By default (`--show-output`), when a test fails due to a runtime error (command not found, +unbound variable, permission denied, etc.), bashunit displays the captured output in an +"Output:" section to help debug the failure. + +Use `--no-output-on-failure` to suppress this output. + +::: code-group +```bash [Example] +bashunit test tests/ --no-output-on-failure +``` +```[Output with --show-output (default)] +✗ Error: My test function + command not found + Output: + Debug: Setting up test + Running command: my_command + /path/to/test.sh: line 5: my_command: command not found +``` +::: + ### Strict Mode > `bashunit test --strict` diff --git a/docs/configuration.md b/docs/configuration.md index fdae2b48..19b686ab 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -347,6 +347,30 @@ BASHUNIT_FAILURES_ONLY=true ``` ::: +## Show output on failure + +> `BASHUNIT_SHOW_OUTPUT_ON_FAILURE=true|false` + +Display captured stdout/stderr output when tests fail with runtime errors. `true` by default. + +When a test fails due to a runtime error (command not found, unbound variable, etc.), +bashunit displays the test's output in an "Output:" section to help debug the failure. + +Similar as using `--show-output` or `--no-output-on-failure` options on the [command line](/command-line#show-output-on-failure). + +::: code-group +```[Output example] +✗ Error: My test function + command not found + Output: + Debug: Setting up test + Running command: my_command +``` +```bash [.env to disable] +BASHUNIT_SHOW_OUTPUT_ON_FAILURE=false +``` +::: + ## Color output > `NO_COLOR=1` diff --git a/src/console_header.sh b/src/console_header.sh index 75b0302d..bc5467a3 100644 --- a/src/console_header.sh +++ b/src/console_header.sh @@ -117,6 +117,8 @@ Options: --debug [file] Enable shell debug mode --no-output Suppress all output --failures-only Only show failures (suppress passed/skipped/incomplete) + --show-output Show test output on failure (default: enabled) + --no-output-on-failure Hide test output on failure --strict Enable strict shell mode (set -euo pipefail) --skip-env-file Skip .env loading, use shell environment only -l, --login Run tests in login shell context diff --git a/src/console_results.sh b/src/console_results.sh index fadf1baa..c768d477 100644 --- a/src/console_results.sh +++ b/src/console_results.sh @@ -335,6 +335,7 @@ function bashunit::console_results::print_snapshot_test() { function bashunit::console_results::print_error_test() { local function_name=$1 local error="$2" + local raw_output="${3:-}" local test_name test_name=$(bashunit::helper::normalize_test_function_name "$function_name") @@ -343,6 +344,13 @@ function bashunit::console_results::print_error_test() { line="$(printf "${_BASHUNIT_COLOR_FAILED}✗ Error${_BASHUNIT_COLOR_DEFAULT}: %s ${_BASHUNIT_COLOR_FAINT}%s${_BASHUNIT_COLOR_DEFAULT}\n" "${test_name}" "${error}")" + if [[ -n "$raw_output" ]] && bashunit::env::is_show_output_on_failure_enabled; then + line+="$(printf " %sOutput:%s\n" "${_BASHUNIT_COLOR_FAINT}" "${_BASHUNIT_COLOR_DEFAULT}")" + while IFS= read -r output_line; do + line+="$(printf " %s\n" "$output_line")" + done <<< "$raw_output" + fi + bashunit::state::print_line "error" "$line" } diff --git a/src/env.sh b/src/env.sh index 682486b7..08e77864 100644 --- a/src/env.sh +++ b/src/env.sh @@ -62,6 +62,7 @@ _BASHUNIT_DEFAULT_SKIP_ENV_FILE="false" _BASHUNIT_DEFAULT_LOGIN_SHELL="false" _BASHUNIT_DEFAULT_FAILURES_ONLY="false" _BASHUNIT_DEFAULT_NO_COLOR="false" +_BASHUNIT_DEFAULT_SHOW_OUTPUT_ON_FAILURE="true" : "${BASHUNIT_PARALLEL_RUN:=${PARALLEL_RUN:=$_BASHUNIT_DEFAULT_PARALLEL_RUN}}" : "${BASHUNIT_SHOW_HEADER:=${SHOW_HEADER:=$_BASHUNIT_DEFAULT_SHOW_HEADER}}" @@ -80,6 +81,7 @@ _BASHUNIT_DEFAULT_NO_COLOR="false" : "${BASHUNIT_SKIP_ENV_FILE:=${SKIP_ENV_FILE:=$_BASHUNIT_DEFAULT_SKIP_ENV_FILE}}" : "${BASHUNIT_LOGIN_SHELL:=${LOGIN_SHELL:=$_BASHUNIT_DEFAULT_LOGIN_SHELL}}" : "${BASHUNIT_FAILURES_ONLY:=${FAILURES_ONLY:=$_BASHUNIT_DEFAULT_FAILURES_ONLY}}" +: "${BASHUNIT_SHOW_OUTPUT_ON_FAILURE:=${SHOW_OUTPUT_ON_FAILURE:=$_BASHUNIT_DEFAULT_SHOW_OUTPUT_ON_FAILURE}}" # Support NO_COLOR standard (https://no-color.org) if [[ -n "${NO_COLOR:-}" ]]; then BASHUNIT_NO_COLOR="true" @@ -159,6 +161,10 @@ function bashunit::env::is_failures_only_enabled() { [[ "$BASHUNIT_FAILURES_ONLY" == "true" ]] } +function bashunit::env::is_show_output_on_failure_enabled() { + [[ "$BASHUNIT_SHOW_OUTPUT_ON_FAILURE" == "true" ]] +} + function bashunit::env::is_no_color_enabled() { [[ "$BASHUNIT_NO_COLOR" == "true" ]] } diff --git a/src/main.sh b/src/main.sh index 7b43f3b7..83f6e2d4 100644 --- a/src/main.sh +++ b/src/main.sh @@ -82,6 +82,12 @@ function bashunit::main::cmd_test() { --failures-only) export BASHUNIT_FAILURES_ONLY=true ;; + --show-output) + export BASHUNIT_SHOW_OUTPUT_ON_FAILURE=true + ;; + --no-output-on-failure) + export BASHUNIT_SHOW_OUTPUT_ON_FAILURE=false + ;; --strict) export BASHUNIT_STRICT_MODE=true ;; diff --git a/src/runner.sh b/src/runner.sh index 66472a3a..9012724e 100755 --- a/src/runner.sh +++ b/src/runner.sh @@ -567,9 +567,9 @@ function bashunit::runner::run_test() { elif [[ -z "$error_message" && -n "$hook_message" ]]; then error_message="$hook_message" fi - bashunit::console_results::print_error_test "$failure_function" "$error_message" + bashunit::console_results::print_error_test "$failure_function" "$error_message" "$runtime_output" bashunit::reports::add_test_failed "$test_file" "$failure_label" "$duration" "$total_assertions" - bashunit::runner::write_failure_result_output "$test_file" "$failure_function" "$error_message" + bashunit::runner::write_failure_result_output "$test_file" "$failure_function" "$error_message" "$runtime_output" bashunit::internal_log "Test error" "$failure_label" "$error_message" return fi @@ -760,6 +760,7 @@ function bashunit::runner::write_failure_result_output() { local test_file=$1 local fn_name=$2 local error_msg=$3 + local raw_output="${4:-}" local line_number line_number=$(bashunit::helper::get_function_line_number "$fn_name") @@ -769,7 +770,12 @@ function bashunit::runner::write_failure_result_output() { test_nr=$(bashunit::state::get_tests_failed) fi - echo -e "$test_nr) $test_file:$line_number\n$error_msg" >> "$FAILURES_OUTPUT_PATH" + local output_section="" + if [[ -n "$raw_output" ]] && bashunit::env::is_show_output_on_failure_enabled; then + output_section="\n Output:\n$raw_output" + fi + + echo -e "$test_nr) $test_file:$line_number\n$error_msg$output_section" >> "$FAILURES_OUTPUT_PATH" } function bashunit::runner::write_skipped_result_output() { diff --git a/tests/acceptance/bashunit_show_output_on_failure_test.sh b/tests/acceptance/bashunit_show_output_on_failure_test.sh new file mode 100644 index 00000000..0d99d8ce --- /dev/null +++ b/tests/acceptance/bashunit_show_output_on_failure_test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" +} + +function test_show_output_on_failure_enabled_by_default() { + local test_file=./tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh + + local actual + actual="$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1 || true)" + + assert_contains "Output:" "$actual" + assert_contains "Debug: Starting test" "$actual" + assert_contains "Info: About to run command" "$actual" +} + +function test_show_output_on_failure_disabled_via_flag() { + local test_file=./tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh + + local actual + actual="$(./bashunit --no-parallel --env "$TEST_ENV_FILE" --no-output-on-failure "$test_file" 2>&1 || true)" + + assert_not_contains "Output:" "$actual" + assert_not_contains "Debug: Starting test" "$actual" +} + +function test_show_output_on_failure_disabled_via_env() { + local test_file=./tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh + + local actual + actual="$( + BASHUNIT_SHOW_OUTPUT_ON_FAILURE=false \ + ./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1 || true + )" + + assert_not_contains "Output:" "$actual" + assert_not_contains "Debug: Starting test" "$actual" +} + +function test_show_output_flag_overrides_env() { + local test_file=./tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh + + local actual + actual="$( + BASHUNIT_SHOW_OUTPUT_ON_FAILURE=false \ + ./bashunit --no-parallel --env "$TEST_ENV_FILE" --show-output "$test_file" 2>&1 || true + )" + + assert_contains "Output:" "$actual" + assert_contains "Debug: Starting test" "$actual" +} diff --git a/tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh b/tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh new file mode 100644 index 00000000..5808649b --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_show_output_on_failure.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +function test_with_output_before_error() { + echo "Debug: Starting test" + echo "Info: About to run command" + nonexistent_command_xyz +}