diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 169229ab..538e1602 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -25,4 +25,6 @@ jobs: uses: ludeeus/action-shellcheck@master env: SHELLCHECK_OPTS: -e SC1091 -e SC2155 -e SC2016 + with: + ignore_names: test_bashunit_when_syntax_error.bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb99422..221122a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Fix `clock::now` shell-time parsing when `EPOCHREALTIME` uses a comma decimal separator - Fix LCOV and HTML coverage reports generating incomplete/empty output due to post-increment operator causing silent exit under `set -e` (#618) - Enable parallel test execution on Alpine Linux; previously gated off due to race conditions, now resolved (#370) +- Fix syntax error in test file silently passing; now reported as a failing test (#220) ## [0.34.1](https://github.com/TypedDevs/bashunit/compare/0.34.0...0.34.1) - 2026-03-20 diff --git a/docs/test-files.md b/docs/test-files.md index 9e9e066f..9a7339c4 100644 --- a/docs/test-files.md +++ b/docs/test-files.md @@ -129,3 +129,23 @@ function tear_down_after_script() { } ``` ::: + +## Syntax errors in test files + +If a test file contains a Bash syntax error, **bashunit** records a failing +test for that file instead of silently skipping the remaining tests. The exact +error message from Bash (including file path and line number) is shown in the +summary, and the suite exits with a non-zero status. + +``` +Running tests/example_test.sh +✗ Error: Source + tests/example_test.sh: line 10: syntax error near unexpected token `fi' + tests/example_test.sh: line 10: ` fi' + +Tests: 1 failed, 1 total +Some tests failed +``` + +This guarantees a broken test file always fails the suite, so it never +passes by absence. diff --git a/src/runner.sh b/src/runner.sh index 1feab35a..76e0c655 100755 --- a/src/runner.sh +++ b/src/runner.sh @@ -72,8 +72,26 @@ function bashunit::runner::load_test_files() { scripts_ids[scripts_ids_count]="${BASHUNIT_CURRENT_SCRIPT_ID}" scripts_ids_count=$((scripts_ids_count + 1)) bashunit::internal_log "Loading file" "$test_file" + local source_err_file source_err source_status + source_err_file="$(bashunit::temp_file "source_err")" # shellcheck source=/dev/null - source "$test_file" + source "$test_file" 2>"$source_err_file" + source_status=$? + source_err="" + if [ -s "$source_err_file" ]; then + 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 + local message="$source_err" + [ -z "$message" ] && message="Failed to source '$test_file' (exit $source_status)" + bashunit::runner::record_file_hook_failure \ + "source" "$test_file" "$message" 1 true + bashunit::runner::clean_set_up_and_tear_down_after_script + bashunit::runner::restore_workdir + continue + fi # Update function cache after sourcing new test file _BASHUNIT_CACHED_ALL_FUNCTIONS=$(declare -F | awk '{print $3}') # Check if any tests match the filter before rendering header or running hooks diff --git a/tests/acceptance/bashunit_syntax_error_test.sh b/tests/acceptance/bashunit_syntax_error_test.sh new file mode 100644 index 00000000..2785ed22 --- /dev/null +++ b/tests/acceptance/bashunit_syntax_error_test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +function set_up_before_script() { + TEST_ENV_FILE="tests/acceptance/fixtures/.env.default" +} + +function strip_ansi() { + sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' +} + +function test_bashunit_when_test_file_has_syntax_error() { + local test_file=./tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash + + local actual_raw + set +e + actual_raw="$(LC_ALL=C LANG=C ./bashunit \ + --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file" 2>&1)" + set -e + + local actual + actual="$(printf "%s" "$actual_raw" | strip_ansi)" + + assert_contains "failed" "$actual" + assert_contains "Error" "$actual" + assert_general_error "$(LC_ALL=C LANG=C ./bashunit \ + --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1)" +} diff --git a/tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash b/tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash new file mode 100644 index 00000000..84c6cd03 --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +function test_good() { + assert_equals 1 1 +} + +function test_with_syntax_error() { + if [ 1 -eq 1 ] + echo "missing then keyword" + fi +} + +function test_another() { + assert_equals 2 2 +}