From 839a8546775736cee5ae6ce7736f387300ef233f Mon Sep 17 00:00:00 2001 From: bfs2021 <101915083+bfs2021@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:21:21 +0800 Subject: [PATCH 1/2] fix(runner): count parallel results under a hidden relative directory --- src/runner/result.sh | 4 +++ .../bashunit_parallel_consistency_test.sh | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/runner/result.sh b/src/runner/result.sh index f3ca5004..9af7f657 100644 --- a/src/runner/result.sh +++ b/src/runner/result.sh @@ -54,6 +54,10 @@ _BASHUNIT_RUNNER_FILE_SLOT="" function bashunit::runner::file_path_to_slot() { local key="${1#./}" key="${key%.sh}" + # A leading dot (a hidden relative directory, or a hidden file) would make the + # per-file scratch directory hidden, and the aggregation's `*` would never see + # its `.result` files (#1393). + while [ "${key#.}" != "$key" ]; do key="${key#\.}"; done _BASHUNIT_RUNNER_FILE_SLOT="${key//\//_}" } diff --git a/tests/acceptance/bashunit_parallel_consistency_test.sh b/tests/acceptance/bashunit_parallel_consistency_test.sh index 9a03b3ac..2cfa81ca 100644 --- a/tests/acceptance/bashunit_parallel_consistency_test.sh +++ b/tests/acceptance/bashunit_parallel_consistency_test.sh @@ -84,6 +84,32 @@ function test_parallel_does_not_lose_same_named_files_in_different_dirs() { assert_contains "2 total" "$sequential_counts" } +# A hidden relative directory folds into a slot whose name starts with a dot, +# and the aggregation's `*` never matched hidden entries: the workers' `.result` +# files existed but the run printed `No tests found` and zero totals (#1393). +function test_parallel_counts_tests_under_a_hidden_relative_dir() { + local passing=".hidden-tests-1393/hidden_pass_test.sh" + local failing=".hidden-tests-1393/hidden_fail_test.sh" + mkdir -p .hidden-tests-1393 + printf 'function test_hidden_pass() { assert_same 1 1; }\n' >"$passing" + printf 'function test_hidden_fail() { assert_same 1 2; }\n' >"$failing" + + local sequential_output + sequential_output=$(NO_COLOR=1 ./bashunit --no-parallel --env "$TEST_ENV_FILE" "$passing" "$failing" 2>&1) || true + local parallel_output + parallel_output=$(NO_COLOR=1 ./bashunit --parallel --env "$TEST_ENV_FILE" "$passing" "$failing" 2>&1) || true + + rm -rf .hidden-tests-1393 + + local sequential_counts parallel_counts + sequential_counts=$(summary_counts "$sequential_output") + parallel_counts=$(summary_counts "$parallel_output") + + assert_same "$sequential_counts" "$parallel_counts" + # Guard against both sides collapsing to nothing and matching vacuously. + assert_contains "2 total" "$sequential_counts" +} + # The shape #1048 reported: a zero-test summary in front of the real one. The # source of that stray summary was never reproduced -- 40+ local attempts, # including under concurrent load, never produced one -- so this pins the From d0597b3a08f30c9bb40ef2f5672612a902299aa9 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 24 Oct 2026 17:01:50 +0200 Subject: [PATCH 2/2] fix(runner): aggregate hidden results safely --- CHANGELOG.md | 4 ++ src/runner/result.sh | 4 -- src/state/parallel.sh | 11 ++-- .../bashunit_parallel_consistency_test.sh | 57 +++++++++++++++---- 4 files changed, 53 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c55cd31..630489a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Blog posts for releases 0.43.0 through 0.52.0, including the 0.50.1 cleanup patch +### Fixed + +- Parallel runs count tests under relative hidden paths and keep their results separate from matching visible paths (#1393) + ## [0.52.0](https://github.com/TypedDevs/bashunit/compare/0.51.0...0.52.0) - 2026-10-11 ### Changed diff --git a/src/runner/result.sh b/src/runner/result.sh index 9af7f657..f3ca5004 100644 --- a/src/runner/result.sh +++ b/src/runner/result.sh @@ -54,10 +54,6 @@ _BASHUNIT_RUNNER_FILE_SLOT="" function bashunit::runner::file_path_to_slot() { local key="${1#./}" key="${key%.sh}" - # A leading dot (a hidden relative directory, or a hidden file) would make the - # per-file scratch directory hidden, and the aggregation's `*` would never see - # its `.result` files (#1393). - while [ "${key#.}" != "$key" ]; do key="${key#\.}"; done _BASHUNIT_RUNNER_FILE_SLOT="${key//\//_}" } diff --git a/src/state/parallel.sh b/src/state/parallel.sh index 70634f87..5eecfee5 100644 --- a/src/state/parallel.sh +++ b/src/state/parallel.sh @@ -26,13 +26,10 @@ function bashunit::state::aggregate_parallel_results() { local total_snapshot=0 local script_dir="" - for script_dir in "$temp_dir_parallel_test_suite"/*; do - # nullglob is enabled below, for the inner glob, and so does not cover this - # one: when no worker ran, this loop iterates once over the pattern itself. - # That phantom directory has no `.result` files, so the branch below printed - # `No tests found` onto stdout on top of the notice the summary renders -- - # twice on the console, and ahead of the document under `--output`, which - # made the JSON and the XML of an empty run unparseable. + # Include hidden keys without matching . or .., regardless of dotglob state. + for script_dir in "$temp_dir_parallel_test_suite"/[!.]* \ + "$temp_dir_parallel_test_suite"/.[!.]* "$temp_dir_parallel_test_suite"/..?*; do + # Skip unmatched patterns so an empty run keeps machine-readable output valid. [ -d "$script_dir" ] || continue shopt -s nullglob # Bash 3.0 compatible: separate declaration and assignment for arrays diff --git a/tests/acceptance/bashunit_parallel_consistency_test.sh b/tests/acceptance/bashunit_parallel_consistency_test.sh index 2cfa81ca..5b40e8b5 100644 --- a/tests/acceptance/bashunit_parallel_consistency_test.sh +++ b/tests/acceptance/bashunit_parallel_consistency_test.sh @@ -84,22 +84,20 @@ function test_parallel_does_not_lose_same_named_files_in_different_dirs() { assert_contains "2 total" "$sequential_counts" } -# A hidden relative directory folds into a slot whose name starts with a dot, -# and the aggregation's `*` never matched hidden entries: the workers' `.result` -# files existed but the run printed `No tests found` and zero totals (#1393). function test_parallel_counts_tests_under_a_hidden_relative_dir() { - local passing=".hidden-tests-1393/hidden_pass_test.sh" - local failing=".hidden-tests-1393/hidden_fail_test.sh" - mkdir -p .hidden-tests-1393 - printf 'function test_hidden_pass() { assert_same 1 1; }\n' >"$passing" - printf 'function test_hidden_fail() { assert_same 1 2; }\n' >"$failing" + local dir runner="$PWD/bashunit" env_file="$PWD/$TEST_ENV_FILE" + dir="$(bashunit::temp_dir hidden_parallel)" + local passing=".hidden-tests/hidden_pass_test.sh" + local failing=".hidden-tests/hidden_fail_test.sh" + mkdir -p "$dir/.hidden-tests" + printf 'function test_hidden_pass() { assert_same 1 1; }\n' >"$dir/$passing" + printf 'function test_hidden_fail() { assert_same 1 2; }\n' >"$dir/$failing" local sequential_output - sequential_output=$(NO_COLOR=1 ./bashunit --no-parallel --env "$TEST_ENV_FILE" "$passing" "$failing" 2>&1) || true + sequential_output=$(cd "$dir" && NO_COLOR=1 "$runner" --no-parallel --env "$env_file" \ + "$passing" "$failing" 2>&1) || true local parallel_output - parallel_output=$(NO_COLOR=1 ./bashunit --parallel --env "$TEST_ENV_FILE" "$passing" "$failing" 2>&1) || true - - rm -rf .hidden-tests-1393 + parallel_output=$(cd "$dir" && NO_COLOR=1 "$runner" --parallel --env "$env_file" "$passing" "$failing" 2>&1) || true local sequential_counts parallel_counts sequential_counts=$(summary_counts "$sequential_output") @@ -110,6 +108,41 @@ function test_parallel_counts_tests_under_a_hidden_relative_dir() { assert_contains "2 total" "$sequential_counts" } +function test_parallel_keeps_hidden_and_visible_paths_distinct() { + local dir runner="$PWD/bashunit" env_file="$PWD/$TEST_ENV_FILE" + dir="$(bashunit::temp_dir hidden_visible_parallel)" + mkdir -p "$dir/tests" "$dir/.tests" + printf 'function test_visible_path_fails() { assert_same 1 2; }\n' >"$dir/tests/example_test.sh" + printf 'function test_hidden_path_passes() { assert_same 1 1; }\n' >"$dir/.tests/example_test.sh" + + local sequential_output parallel_output sequential_status=0 parallel_status=0 + sequential_output=$(cd "$dir" && NO_COLOR=1 "$runner" --no-parallel --env "$env_file" \ + tests/example_test.sh .tests/example_test.sh 2>&1) || sequential_status=$? + # One job makes the hidden file overwrite the visible file deterministically if their keys collide. + parallel_output=$(cd "$dir" && NO_COLOR=1 "$runner" --parallel --jobs 1 --env "$env_file" \ + tests/example_test.sh .tests/example_test.sh 2>&1) || parallel_status=$? + + assert_same "1" "$sequential_status" + assert_same "$sequential_status" "$parallel_status" + assert_same "$(summary_counts "$sequential_output")" "$(summary_counts "$parallel_output")" + assert_contains "2 total" "$(summary_counts "$parallel_output")" +} + +function test_parallel_preserves_result_keys_at_the_filename_limit() { + local dir runner="$PWD/bashunit" env_file="$PWD/$TEST_ENV_FILE" filename + dir="$(bashunit::temp_dir parallel_long_path)" + filename="$(printf '%0247d_test.sh' 0)" + mkdir -p "$dir/ab" + printf 'function test_long_path_passes() { assert_same 1 1; }\n' >"$dir/ab/$filename" + + local output status=0 + output=$(cd "$dir" && NO_COLOR=1 "$runner" --parallel --jobs 1 --env "$env_file" \ + "ab/$filename" 2>&1) || status=$? + + assert_same "0" "$status" + assert_contains "1 passed 1 total" "$(summary_counts "$output")" +} + # The shape #1048 reported: a zero-test summary in front of the real one. The # source of that stray summary was never reproduced -- 40+ local attempts, # including under concurrent load, never produced one -- so this pins the