From db92664e5513fc56183f15621953ad0539ad16a3 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 19 Apr 2026 14:08:12 +0200 Subject: [PATCH 1/6] fix(runner): fail test file with syntax error instead of silent halt Capture stderr and exit code of `source "$test_file"`. On syntax error or non-zero exit, record a source hook failure and skip the file so the suite surfaces the problem instead of continuing silently. Closes #220 --- CHANGELOG.md | 1 + src/runner.sh | 20 +++++++++++++- .../acceptance/bashunit_syntax_error_test.sh | 26 +++++++++++++++++++ .../test_bashunit_when_syntax_error.sh | 15 +++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/bashunit_syntax_error_test.sh create mode 100644 tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh 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/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..446fbffc --- /dev/null +++ b/tests/acceptance/bashunit_syntax_error_test.sh @@ -0,0 +1,26 @@ +#!/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.sh + + local actual_raw + set +e + actual_raw="$(./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 "syntax error" "$actual" + assert_contains "failed" "$actual" + assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1)" +} diff --git a/tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh b/tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh new file mode 100644 index 00000000..84c6cd03 --- /dev/null +++ b/tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh @@ -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 +} From e45105922af06b39a3d1e06f86e326b5da8805d8 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 19 Apr 2026 14:17:46 +0200 Subject: [PATCH 2/6] fix(ci): make syntax-error test locale-independent and skip fixture in shellcheck - Force LC_ALL=C/LANG=C in the acceptance test so bash's "syntax error" message stays English; assert on "Error" + "failed" which the runner renders regardless of locale. - Exclude fixtures/test_bashunit_when_syntax_error.sh from the ShellCheck workflow; the fixture intentionally contains a parse error. --- .github/workflows/static_analysis.yml | 2 ++ tests/acceptance/bashunit_syntax_error_test.sh | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 169229ab..7ba1576f 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_paths: tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh diff --git a/tests/acceptance/bashunit_syntax_error_test.sh b/tests/acceptance/bashunit_syntax_error_test.sh index 446fbffc..12ea3fcb 100644 --- a/tests/acceptance/bashunit_syntax_error_test.sh +++ b/tests/acceptance/bashunit_syntax_error_test.sh @@ -14,13 +14,15 @@ function test_bashunit_when_test_file_has_syntax_error() { local actual_raw set +e - actual_raw="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file" 2>&1)" + 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 "syntax error" "$actual" assert_contains "failed" "$actual" - assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1)" + assert_contains "Error" "$actual" + assert_general_error "$(LC_ALL=C LANG=C ./bashunit \ + --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1)" } From bb58b7b56a2b2b0bad435fee0b7dfb68f1384c17 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 19 Apr 2026 14:20:57 +0200 Subject: [PATCH 3/6] fix(ci): rename syntax-error fixture to .bash so shellcheck skips it ShellCheck's find-based scanner matched the fixture even with ignore_paths, so rename to .bash to keep it out of both the CI scanner and `make sa`. --- .github/workflows/static_analysis.yml | 2 -- tests/acceptance/bashunit_syntax_error_test.sh | 2 +- ...hen_syntax_error.sh => test_bashunit_when_syntax_error.bash} | 0 3 files changed, 1 insertion(+), 3 deletions(-) rename tests/acceptance/fixtures/{test_bashunit_when_syntax_error.sh => test_bashunit_when_syntax_error.bash} (100%) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 7ba1576f..169229ab 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -25,6 +25,4 @@ jobs: uses: ludeeus/action-shellcheck@master env: SHELLCHECK_OPTS: -e SC1091 -e SC2155 -e SC2016 - with: - ignore_paths: tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh diff --git a/tests/acceptance/bashunit_syntax_error_test.sh b/tests/acceptance/bashunit_syntax_error_test.sh index 12ea3fcb..2785ed22 100644 --- a/tests/acceptance/bashunit_syntax_error_test.sh +++ b/tests/acceptance/bashunit_syntax_error_test.sh @@ -10,7 +10,7 @@ function strip_ansi() { } function test_bashunit_when_test_file_has_syntax_error() { - local test_file=./tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh + local test_file=./tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash local actual_raw set +e diff --git a/tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh b/tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash similarity index 100% rename from tests/acceptance/fixtures/test_bashunit_when_syntax_error.sh rename to tests/acceptance/fixtures/test_bashunit_when_syntax_error.bash From ebbaef94c3d95dd30df2767b8a50cabe9db40146 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 19 Apr 2026 14:41:44 +0200 Subject: [PATCH 4/6] fix(ci): use shellcheck ignore_names for syntax-error fixture ignore_paths only matches directory prefixes; ignore_names skips by basename, which is what we need for a single fixture file. --- .github/workflows/static_analysis.yml | 2 ++ 1 file changed, 2 insertions(+) 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 From 78cb2c3f3ecf6bbced70d4d86f11d66febf1b64a Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 19 Apr 2026 14:56:14 +0200 Subject: [PATCH 5/6] docs(test-files): document syntax-error handling --- docs/test-files.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/test-files.md b/docs/test-files.md index 9e9e066f..36a23b0b 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 — it never passes +by absence. From 94b0b175687415a70f18765be60c7a126bafc530 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 19 Apr 2026 15:06:21 +0200 Subject: [PATCH 6/6] docs(test-files): remove em-dash --- docs/test-files.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/test-files.md b/docs/test-files.md index 36a23b0b..9a7339c4 100644 --- a/docs/test-files.md +++ b/docs/test-files.md @@ -147,5 +147,5 @@ Tests: 1 failed, 1 total Some tests failed ``` -This guarantees a broken test file always fails the suite — it never passes -by absence. +This guarantees a broken test file always fails the suite, so it never +passes by absence.