Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ jobs:
run: ./bashunit --parallel --jobs 4 ${{ matrix.test_path }}

macos-acceptance:
name: "macOS - acceptance (${{ matrix.name }})"
name: "macOS - acceptance"
if: github.event_name == 'push'
runs-on: macos-latest
timeout-minutes: 10
Expand All @@ -169,7 +169,7 @@ jobs:
- name: Setup Config
run: cp .env.example .env

- name: Run Tests
- name: "Run acceptance shard ${{ matrix.name }}"
run: ./bashunit --parallel --jobs 4 ${{ matrix.test_path }}

windows:
Expand Down Expand Up @@ -207,7 +207,7 @@ jobs:
run: ./bashunit --parallel --jobs 4 ${{ matrix.test_path }}

windows-acceptance:
name: "windows-acceptance (${{ matrix.name }})"
name: "Windows - acceptance"
if: github.event_name == 'push'
timeout-minutes: 10
runs-on: windows-latest
Expand All @@ -231,7 +231,7 @@ jobs:
shell: bash
run: cp .env.example .env

- name: Run tests
- name: "Run acceptance shard ${{ matrix.name }}"
shell: bash
run: ./bashunit --parallel --jobs 4 ${{ matrix.test_path }}

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
- JSON reports write ordinary filenames, test names and empty messages without per-field processes, preserving existing escaping (#1369)

### Fixed
- Skipped macOS and Windows acceptance CI jobs have readable names, with shard labels in their test steps
- Cancellation regression checks distinguish exited bodies awaiting reaping from running cleanup (#1385)
- Synchronous provider rows retain separate results in parallel runs, so later passing rows cannot hide failures (#1377)
- Linux test jobs install the standalone build optimizer required by beta installer acceptance tests (#1383)
- A late timeout signal no longer interrupts `tear_down`; hanging cleanup remains bounded by the watchdog (#1376)
- Piped parallel output no longer waits for an inactive spinner's sleep process (#1368)
Expand Down
10 changes: 5 additions & 5 deletions src/runner/exec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function bashunit::runner::call_test_functions() {
local -a parsed_data=()
local parsed_data_count=0
local provider_arg_file=""
# Monotonic within this file; names each parallel worker's .result file.
# Monotonic within this file, including tests executed synchronously.
local _test_ordinal=0

# Scan the file once; per-test provider lookups below are pure-bash (#763).
Expand Down Expand Up @@ -178,10 +178,10 @@ function bashunit::runner::call_test_functions() {
# No data provider found: run once without forking to capture provider output.
bashunit::helper::provider_for_function "$fn_name"
if [ -z "$_BASHUNIT_PROVIDER_FN_OUT" ]; then
_test_ordinal=$((_test_ordinal + 1))
_BASHUNIT_RUNNER_RESULT_ORDINAL=$_test_ordinal
if bashunit::parallel::is_enabled && [ "$allow_test_parallel" = true ]; then
bashunit::runner::wait_for_job_slot
_test_ordinal=$((_test_ordinal + 1))
_BASHUNIT_RUNNER_RESULT_ORDINAL=$_test_ordinal
bashunit::runner::run_test_parallel "$script" "$fn_name" &
_BASHUNIT_WORKER_TEST_PIDS="$_BASHUNIT_WORKER_TEST_PIDS $!"
else
Expand Down Expand Up @@ -256,10 +256,10 @@ function bashunit::runner::call_test_functions() {
"$script" "$fn_name" "data provider '$_BASHUNIT_PROVIDER_FN_OUT' $transport_error"
break
fi
_test_ordinal=$((_test_ordinal + 1))
_BASHUNIT_RUNNER_RESULT_ORDINAL=$_test_ordinal
if bashunit::parallel::is_enabled && [ "$allow_test_parallel" = true ]; then
bashunit::runner::wait_for_job_slot
_test_ordinal=$((_test_ordinal + 1))
_BASHUNIT_RUNNER_RESULT_ORDINAL=$_test_ordinal
bashunit::runner::run_test_parallel "$script" "$fn_name" ${parsed_data+"${parsed_data[@]}"} &
_BASHUNIT_WORKER_TEST_PIDS="$_BASHUNIT_WORKER_TEST_PIDS $!"
else
Expand Down
110 changes: 110 additions & 0 deletions tests/acceptance/bashunit_provider_transport_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,113 @@ function test_row_after_scratch_loss() {
assert_empty "$(<"$dir/public_files")"
assert_not_contains "No such file or directory" "$output"
}

function provide_synchronous_modes() {
bashunit::data_set --no-parallel false
bashunit::data_set --parallel false
bashunit::data_set --no-parallel true
bashunit::data_set --parallel true
}

# @data_provider provide_synchronous_modes
function test_synchronous_provider_failures_survive_a_later_passing_row() {
local mode="$1" strict="$2"
local dir="$PROVIDER_TRANSPORT_FIXTURES/ordinal_${mode#--}_$strict"
mkdir -p "$dir"
printf '%s\n' '# bashunit: no-parallel-tests' >"$dir/ordinal_test.sh"
cat >>"$dir/ordinal_test.sh" <<'TEST'
function provide_ordinal_rows() { bashunit::data_set bad; bashunit::data_set good; }
# @data_provider provide_ordinal_rows
function test_ordinal_row() { assert_same good "$1"; }
TEST

local -a options=("$mode")
if [ "$strict" = true ]; then
options[1]=--strict
fi
local output code=0
output="$(./bashunit "${options[@]}" --skip-env-file --no-color \
--report-json "$dir/report.json" "$dir/ordinal_test.sh" 2>&1)" || code=$?

assert_same 1 "$code"
assert_contains "1 passed" "$output"
assert_contains "1 failed" "$output"
assert_contains "2 total" "$output"
assert_file_contains "$dir/report.json" '"total": 2, "passed": 1, "failed": 1'
}

# @data_provider provide_synchronous_modes
function test_synchronous_provider_dispatch_preserves_retries_repeats_reports_and_cleanup() {
local mode="$1" strict="$2"
local dir="$PROVIDER_TRANSPORT_FIXTURES/lifecycle_${mode#--}_$strict"
mkdir -p "$dir/tmp"
printf '%s\n' '# bashunit: no-parallel-tests' >"$dir/lifecycle_test.sh"
cat >>"$dir/lifecycle_test.sh" <<'TEST'
function set_up_before_script() {
SYNC_SCRIPT_TEMP=$(bashunit::temp_file script)
printf '%s\n' "$SYNC_SCRIPT_TEMP" >>"$SYNC_TEMP_PATHS"
}
function set_up() {
if [ -f "$SYNC_ACTIVE" ]; then
printf 'overlap\n' >>"$SYNC_EVENTS"
fi
: >"$SYNC_ACTIVE"
printf 'setup\n' >>"$SYNC_HOOKS"
}
function tear_down() {
rm -f "$SYNC_ACTIVE"
printf 'teardown\n' >>"$SYNC_HOOKS"
}
function tear_down_after_script() {
assert_file_exists "$SYNC_SCRIPT_TEMP"
printf 'script teardown\n' >>"$SYNC_EVENTS"
}
function test_before_rows() { assert_true true; printf 'before\n' >>"$SYNC_EVENTS"; }
function provide_sync_rows() { bashunit::data_set bad; bashunit::data_set good; }
# @data_provider provide_sync_rows
function test_sync_row() {
local temp
temp=$(bashunit::temp_file row)
printf '%s\n' "$temp" >>"$SYNC_TEMP_PATHS"
printf '%s\n' "$1" >>"$SYNC_EVENTS"
assert_same good "$1"
}
function test_after_rows() { assert_true true; printf 'after\n' >>"$SYNC_EVENTS"; }
TEST

local -a options=("$mode")
if [ "$strict" = true ]; then
options[1]=--strict
fi
local output code=0
output="$(TMPDIR="$dir/tmp/" SYNC_EVENTS="$dir/events" SYNC_HOOKS="$dir/hooks" \
SYNC_ACTIVE="$dir/active" SYNC_TEMP_PATHS="$dir/paths" \
./bashunit "${options[@]}" --skip-env-file --no-color --repeat 3 --retry 1 \
--report-json "$dir/report.json" --report-junit "$dir/report.xml" --report-tap "$dir/report.tap" \
"$dir/lifecycle_test.sh" 2>&1)" || code=$?

assert_same 1 "$code"
assert_contains "3 passed" "$output"
assert_contains "1 failed" "$output"
assert_contains "4 total" "$output"
assert_file_contains "$dir/report.json" '"total": 4, "passed": 3, "failed": 1'
assert_same 4 "$("$GREP" -c '<testcase ' "$dir/report.xml")"
assert_file_contains "$dir/report.xml" 'tests="4" failures="1"'
assert_file_contains "$dir/report.tap" '1..4'
assert_same $'before\nbefore\nbefore\nbad\nbad\ngood\ngood\ngood\nafter\nafter\nafter\nscript teardown' \
"$(<"$dir/events")"
assert_same 11 "$("$GREP" -c '^setup$' "$dir/hooks")"
assert_same 11 "$("$GREP" -c '^teardown$' "$dir/hooks")"
assert_file_not_exists "$dir/active"
local path
while IFS= read -r path; do
assert_file_not_exists "$path"
done <"$dir/paths"
local leftover=0 entry
for entry in "$dir/tmp/bashunit/run"/*/* "$dir/tmp/bashunit/parallel"/*/*; do
if [ -e "$entry" ]; then
leftover=$((leftover + 1))
fi
done
assert_same 0 "$leftover"
}
30 changes: 28 additions & 2 deletions tests/acceptance/bashunit_timeout_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,24 @@ TEST
assert_file_not_exists "$marker.completed"
}

function _timeout_body_is_running() {
local body_pid="$1"
kill -0 "$body_pid" 2>/dev/null || return 1
local state
if [ -r "/proc/$body_pid/stat" ]; then
IFS= read -r state 2>/dev/null <"/proc/$body_pid/stat" || return 1
state=${state##*) }
state=${state%% *}
else
state=$(ps -p "$body_pid" -o stat= 2>/dev/null) || return 1
fi
# A cancelled runner can leave an exited body awaiting reaping by its new parent.
case "$state" in
*Z* | "") return 1 ;;
esac
return 0
}

function _assert_cancellation_stops_timeout_teardown() {
if bashunit::check_os::is_windows; then
bashunit::skip "Unix process-group signals"
Expand Down Expand Up @@ -298,17 +316,25 @@ TEST
if [ -f "$marker.body" ]; then
IFS= read -r body_pid <"$marker.body"
ticks=0
while kill -0 "$body_pid" 2>/dev/null && [ "$ticks" -lt 60 ]; do
while _timeout_body_is_running "$body_pid" && [ "$ticks" -lt 60 ]; do
sleep 0.05
ticks=$((ticks + 1))
done
if kill -0 "$body_pid" 2>/dev/null; then
if _timeout_body_is_running "$body_pid"; then
if [ -r "/proc/$body_pid/stat" ]; then
cat "/proc/$body_pid/stat" 2>/dev/null || true
else
ps -p "$body_pid" -o pid=,ppid=,pgid=,stat= 2>/dev/null || true
fi
: >"$marker.forced"
kill -KILL -"$body_pid" 2>/dev/null || true
fi
fi
) || true

if [ ! -f "$marker.started" ] || [ -f "$marker.forced" ]; then
printf '%s\n' "$output"
fi
assert_file_exists "$marker.started"
assert_contains "Caught Ctrl-C, killing all child processes" "$output"
assert_file_not_exists "$marker.forced"
Expand Down
Loading