From f0e021e67548003e3d2d05eb2b802b40342b1ca6 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 5 May 2026 07:31:41 +0800 Subject: [PATCH] Bound static-bin smoke tests with per-test timeout Wrap every guest-binary invocation in timeout TEST_TIMEOUT (default 10s, override via env) so a regression that hangs a guest binary (futex deadlock, EPOLLET busy-loop, blocking syscall miswire) cannot stall "make check" indefinitely. Surface the GNU-timeout rc=124 as a distinct " (timeout)" failure instead of letting it fall through to the generic "pattern not found, rc=124" path; the two have different root causes (livelock vs. semantic regression) and deserve different signal in CI logs. Fail upfront with a clear message when 'timeout' is missing from PATH, so an absent Homebrew coreutils install produces one error rather than every test reporting "command not found, rc=127". Use integer compare on the rc check, and apply the wrapper uniformly across srun_check, srun_pipe, srun_script, and the two inlined diff cases. --- tests/test-static-bins.sh | 49 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/tests/test-static-bins.sh b/tests/test-static-bins.sh index dbad6b8..3d764de 100755 --- a/tests/test-static-bins.sh +++ b/tests/test-static-bins.sh @@ -29,11 +29,20 @@ BIN="" TEST_RUNNER=("$ELFUSE") # shellcheck disable=SC2034 # Consumed by tests/lib/test-runner.sh. TEST_LABEL_WIDTH=24 +# Per-test wall-clock cap (seconds). Override from CI: TEST_TIMEOUT=30 ... # shellcheck disable=SC2034 # Consumed by tests/lib/test-runner.sh. -TEST_TIMEOUT=10 +TEST_TIMEOUT="${TEST_TIMEOUT:-10}" # shellcheck source=tests/lib/test-runner.sh source "$SCRIPT_DIR/lib/test-runner.sh" +# GNU `timeout` is required to bound each guest invocation. On macOS it +# ships via Homebrew coreutils. Fail loudly upfront instead of letting +# every test report a cryptic "command not found, rc=127". +if ! command -v timeout > /dev/null 2>&1; then + printf "error: 'timeout' not on PATH (install Homebrew coreutils)\n" >&2 + exit 2 +fi + RUNNER=("$ELFUSE") if [ -n "$SYSROOT" ]; then RUNNER+=(--sysroot "$SYSROOT") @@ -75,9 +84,12 @@ srun_check() fi local output rc - if output=$("${RUNNER[@]}" "$bin" "$@" 2>&1); then rc=0; else rc=$?; fi + if output=$(timeout "$TEST_TIMEOUT" "${RUNNER[@]}" "$bin" "$@" 2>&1); then rc=0; else rc=$?; fi - if echo "$output" | grep -qE "$pattern"; then + if [ "$rc" -eq 124 ]; then + test_report fail "$label" " (timeout)" + fail=$((fail + 1)) + elif echo "$output" | grep -qE "$pattern"; then test_report ok "$label" pass=$((pass + 1)) else @@ -106,9 +118,12 @@ srun_pipe() fi local output rc - if output=$(printf '%s' "$input" | "${RUNNER[@]}" "$bin" "$@" 2>&1); then rc=0; else rc=$?; fi + if output=$(printf '%s' "$input" | timeout "$TEST_TIMEOUT" "${RUNNER[@]}" "$bin" "$@" 2>&1); then rc=0; else rc=$?; fi - if echo "$output" | grep -qE "$pattern"; then + if [ "$rc" -eq 124 ]; then + test_report fail "$label" " (timeout)" + fail=$((fail + 1)) + elif echo "$output" | grep -qE "$pattern"; then test_report ok "$label" pass=$((pass + 1)) else @@ -137,9 +152,12 @@ srun_script() fi local output rc - if output=$("${RUNNER[@]}" "$bin" "$@" "$script_file" 2>&1); then rc=0; else rc=$?; fi + if output=$(timeout "$TEST_TIMEOUT" "${RUNNER[@]}" "$bin" "$@" "$script_file" 2>&1); then rc=0; else rc=$?; fi - if echo "$output" | grep -qE "$pattern"; then + if [ "$rc" -eq 124 ]; then + test_report fail "$label" " (timeout)" + fail=$((fail + 1)) + elif echo "$output" | grep -qE "$pattern"; then test_report ok "$label" pass=$((pass + 1)) else @@ -180,7 +198,7 @@ srun_check "dash: arithmetic" "$DASH_BIN" "2\\+3=5" -c 'echo "2+3=$((2+3))"' srun_check "dash: loop" "$DASH_BIN" "count=10" -c 'i=0; while [ $i -lt 10 ]; do i=$((i+1)); done; echo "count=$i"' srun_check "dash: conditionals" "$DASH_BIN" "5>3 OK" -c 'if [ 5 -gt 3 ]; then echo "5>3 OK"; fi' srun_check "dash: case" "$DASH_BIN" "glob match" -c 'case "hello" in hel*) echo "glob match" ;; esac' -srun_check "dash: functions" "$DASH_BIN" "result=120" -c 'fact() { [ "$1" -le 1 ] && echo 1 && return; echo $(( $1 * $(fact $(($1 - 1))) )); }; echo "result=$(fact 5)"' +srun_check "dash: functions" "$DASH_BIN" "result=15" -c 'sum() { total=0; for n in 1 2 3 4 5; do total=$((total+n)); done; echo "$total"; }; echo "result=$(sum)"' # Bash shell printf "\n${BLUE}── Bash shell ──${RESET}\n" @@ -328,12 +346,15 @@ printf 'different content\n' > "$TMPDIR/other.txt" # diff returns 1 when files differ but the comparison itself succeeded. label="diff: different files" if [ -n "$DIFF_BIN" ]; then - if output=$("$ELFUSE" "$DIFF_BIN" "$TMPDIR/hello.txt" "$TMPDIR/other.txt" 2>&1); then + if output=$(timeout "$TEST_TIMEOUT" "${RUNNER[@]}" "$DIFF_BIN" "$TMPDIR/hello.txt" "$TMPDIR/other.txt" 2>&1); then rc=0 else rc=$? fi - if [ "$rc" = "1" ] && echo "$output" | grep -qE "^[<>]"; then + if [ "$rc" -eq 124 ]; then + test_report fail "$label" " (timeout)" + fail=$((fail + 1)) + elif [ "$rc" = "1" ] && echo "$output" | grep -qE "^[<>]"; then test_report ok "$label" pass=$((pass + 1)) else @@ -349,12 +370,16 @@ fi # diff: identical files (exit code 0, no content diff output) label="diff: identical" if [ -n "$DIFF_BIN" ]; then - if output=$("$ELFUSE" "$DIFF_BIN" "$TMPDIR/hello.txt" "$TMPDIR/hello.txt" 2>&1); then + if output=$(timeout "$TEST_TIMEOUT" "${RUNNER[@]}" "$DIFF_BIN" "$TMPDIR/hello.txt" "$TMPDIR/hello.txt" 2>&1); then test_report ok "$label" pass=$((pass + 1)) else rc=$? - test_report fail "$label" " (rc=$rc, expected 0)" + if [ "$rc" -eq 124 ]; then + test_report fail "$label" " (timeout)" + else + test_report fail "$label" " (rc=$rc, expected 0)" + fi fail=$((fail + 1)) fi else