diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fffd7e..c501dee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ - The `--parallel` unsupported-OS warning no longer claims Alpine is excluded ### Fixed +- A configured external git differ (`diff.external` / `GIT_EXTERNAL_DIFF`, e.g. difftastic) no longer blanks the multiline and snapshot failure diffs, which now render with `--no-ext-diff` (#912) +- Time reads and the JUnit report no longer set the locale with a temporary-environment prefix (`LC_ALL=C cmd`), which segfaults inside a command substitution on Bash 5.3.9 macOS (#912) - Call assertions (`assert_not_called`, `assert_have_been_called*`) fail with `was never registered as a spy` instead of reporting zero calls when the name was never spied — a typo used to pass silently (#895) - The per-argument form a spy records was written with a literal `$'\x1f'` separator instead of the byte, so it could not be compared against (#894) - `--parallel` no longer discards worker stderr written outside a test body; it renders as a `Stderr from ` block (#864) diff --git a/src/clock.sh b/src/clock.sh index 035cb66e..124c31a1 100644 --- a/src/clock.sh +++ b/src/clock.sh @@ -182,7 +182,10 @@ function bashunit::clock::now() { function bashunit::clock::shell_time() { # Get time directly from the shell variable EPOCHREALTIME (Bash 5+) - [ -n "${EPOCHREALTIME+x}" ] && [ -n "$EPOCHREALTIME" ] && LC_ALL=C echo "$EPOCHREALTIME" + # No `LC_ALL=C` prefix: the value is expanded before the temporary environment + # applies, so it cannot normalize the decimal separator (callers accept both), + # and that form segfaults inside `$()` on Bash 5.3 macOS (#912). + [ -n "${EPOCHREALTIME+x}" ] && [ -n "$EPOCHREALTIME" ] && echo "$EPOCHREALTIME" } function bashunit::clock::total_runtime_in_milliseconds() { diff --git a/src/console_results.sh b/src/console_results.sh index 882baf09..a17a6bf3 100644 --- a/src/console_results.sh +++ b/src/console_results.sh @@ -312,8 +312,9 @@ function bashunit::console_results::render_diff() { # `git diff` exits non-zero when the files differ; the `|| true` keeps that # from tripping `set -e`/`pipefail` under --strict. `tail -n +6` drops git's - # header lines; `sed` indents the body. - git diff --no-index --word-diff "$color_flag" \ + # header lines; `sed` indents the body. `--no-ext-diff` ignores a user's + # `diff.external`/`GIT_EXTERNAL_DIFF`, which would replace this word-diff. + git diff --no-index --no-ext-diff --word-diff "$color_flag" \ "$expected_file" "$actual_file" 2>/dev/null | tail -n +6 | sed "s/^/ /" || true } diff --git a/src/reports.sh b/src/reports.sh index dcbddc40..b3e1df45 100755 --- a/src/reports.sh +++ b/src/reports.sh @@ -102,7 +102,9 @@ function bashunit::reports::generate_junit_xml() { local tests_failed=$(bashunit::state::get_tests_failed) local time_ms=$(bashunit::clock::total_runtime_in_milliseconds) local time - time=$(LC_ALL=C awk -v ms="$time_ms" 'BEGIN {printf "%.3f", ms/1000}') + # `env` rather than a bare `LC_ALL=C` prefix: C keeps awk's radix a dot for the + # XML, and that prefix form segfaults inside `$()` on Bash 5.3 macOS (#912). + time=$(env LC_ALL=C awk -v ms="$time_ms" 'BEGIN {printf "%.3f", ms/1000}') { echo "" @@ -120,7 +122,7 @@ function bashunit::reports::generate_junit_xml() { local test_time_ms="${_BASHUNIT_REPORTS_TEST_DURATIONS[$i]:-}" local failure_message="${_BASHUNIT_REPORTS_TEST_FAILURES[$i]:-}" local test_time - test_time=$(LC_ALL=C awk -v ms="$test_time_ms" 'BEGIN {printf "%.3f", ms/1000}') + test_time=$(env LC_ALL=C awk -v ms="$test_time_ms" 'BEGIN {printf "%.3f", ms/1000}') echo " /dev/null - "$BASHUNIT_PATH" init >/tmp/init.log + "$BASHUNIT_PATH" init >"$TMP_DIR/init.log" assert_file_exists "tests/example_test.sh" assert_file_exists "tests/bootstrap.sh" popd >/dev/null @@ -23,7 +23,7 @@ function test_bashunit_init_creates_structure() { function test_bashunit_init_custom_directory() { pushd "$TMP_DIR" >/dev/null - "$BASHUNIT_PATH" init custom >/tmp/init.log + "$BASHUNIT_PATH" init custom >"$TMP_DIR/init.log" assert_file_exists "custom/example_test.sh" assert_file_exists "custom/bootstrap.sh" popd >/dev/null @@ -31,7 +31,7 @@ function test_bashunit_init_custom_directory() { function test_bashunit_init_creates_github_workflow() { pushd "$TMP_DIR" >/dev/null - "$BASHUNIT_PATH" init >/tmp/init.log + "$BASHUNIT_PATH" init >"$TMP_DIR/init.log" assert_file_exists ".github/workflows/tests.yml" assert_file_contains ".github/workflows/tests.yml" "TypedDevs/bashunit@" popd >/dev/null @@ -41,7 +41,7 @@ function test_bashunit_init_does_not_overwrite_existing_workflow() { pushd "$TMP_DIR" >/dev/null mkdir -p ".github/workflows" echo "custom-workflow" >".github/workflows/tests.yml" - "$BASHUNIT_PATH" init >/tmp/init.log + "$BASHUNIT_PATH" init >"$TMP_DIR/init.log" assert_file_contains ".github/workflows/tests.yml" "custom-workflow" popd >/dev/null } @@ -51,7 +51,7 @@ function test_bashunit_init_updates_env() { pushd "$TMP_DIR" >/dev/null echo "BASHUNIT_BOOTSTRAP=old/bootstrap.sh" >.env - "$BASHUNIT_PATH" init custom >/tmp/init.log + "$BASHUNIT_PATH" init custom >"$TMP_DIR/init.log" assert_file_exists "custom/example_test.sh" assert_file_exists "custom/bootstrap.sh" assert_file_contains .env "#BASHUNIT_BOOTSTRAP=old/bootstrap.sh" diff --git a/tests/acceptance/bashunit_syntax_error_test.sh b/tests/acceptance/bashunit_syntax_error_test.sh index 2f0a1f1e..7cc5476a 100644 --- a/tests/acceptance/bashunit_syntax_error_test.sh +++ b/tests/acceptance/bashunit_syntax_error_test.sh @@ -10,7 +10,7 @@ function test_bashunit_when_test_file_has_syntax_error() { local actual_raw set +e - actual_raw="$(LC_ALL=C LANG=C ./bashunit \ + actual_raw="$(./bashunit \ --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file" 2>&1)" set -e @@ -19,6 +19,6 @@ function test_bashunit_when_test_file_has_syntax_error() { assert_contains "failed" "$actual" assert_contains "Error" "$actual" - assert_general_error "$(LC_ALL=C LANG=C ./bashunit \ + assert_general_error "$(./bashunit \ --no-parallel --env "$TEST_ENV_FILE" "$test_file" 2>&1)" } diff --git a/tests/unit/bash_compatibility_test.sh b/tests/unit/bash_compatibility_test.sh index 1faeee99..454cf4c1 100644 --- a/tests/unit/bash_compatibility_test.sh +++ b/tests/unit/bash_compatibility_test.sh @@ -102,3 +102,14 @@ function test_src_has_no_coproc() { function test_src_has_no_parameter_transformations() { assert_empty "$(bashunit::compat::offenders '\$\{[A-Za-z_][A-Za-z0-9_]*@[QEPAKa]\}')" } + +# A temporary-environment locale prefix (`LC_ALL=C cmd`) makes bash change its +# own locale for that command. Bash 5.3.9 on macOS segfaults on that form inside +# a command substitution -- `x=$(LC_ALL=C echo hi)` exits 139 (#912) -- and no CI +# job runs that build. Use `env LC_ALL=C cmd` instead, which passes the locale +# straight to the child and never touches bash's own. +function test_src_has_no_temporary_locale_assignment_prefix() { + local pattern='(^|[;&|(])[[:space:]]*((LC_[A-Z_]+|LANG)=[^[:space:]]*[[:space:]]+)+[^[:space:]=]' + + assert_empty "$(bashunit::compat::offenders "$pattern")" +} diff --git a/tests/unit/console_results_diff_test.sh b/tests/unit/console_results_diff_test.sh index b8500cd4..c4110bca 100644 --- a/tests/unit/console_results_diff_test.sh +++ b/tests/unit/console_results_diff_test.sh @@ -62,3 +62,26 @@ function test_render_diff_shows_changed_tokens_without_color() { rm -f "$a" "$b" unset BASHUNIT_NO_COLOR } + +function test_render_diff_ignores_a_configured_external_diff() { + if ! bashunit::dependencies::has_git; then + bashunit::skip "git not available" && return + fi + export BASHUNIT_NO_COLOR=true + # An external differ (difftastic in #912) replaces git's own output, so without + # --no-ext-diff the rendered diff is whatever it prints -- here nothing at all. + export GIT_EXTERNAL_DIFF=true + local a b + a=$(bashunit::temp_file diff_a) + b=$(bashunit::temp_file diff_b) + printf 'alpha\nbeta\ngamma\n' >"$a" + printf 'alpha\nDELTA\ngamma\n' >"$b" + + local output + output=$(bashunit::console_results::render_diff "$a" "$b") + + assert_contains "beta" "$output" + assert_contains "DELTA" "$output" + rm -f "$a" "$b" + unset BASHUNIT_NO_COLOR GIT_EXTERNAL_DIFF +} diff --git a/tests/unit/console_results_test.sh b/tests/unit/console_results_test.sh index 4bc7e7d1..5e69e24c 100644 --- a/tests/unit/console_results_test.sh +++ b/tests/unit/console_results_test.sh @@ -363,6 +363,7 @@ function test_render_execution_time_on_osx_with_perl() { local render_result mock_macos + _BASHUNIT_CLOCK_NOW_IMPL="perl" bashunit::mock bashunit::dependencies::has_perl mock_true _BASHUNIT_START_TIME="1726393394574382186" bashunit::mock perl <<<"1726393394574372186"