diff --git a/mk/tests.mk b/mk/tests.mk index 029d7561..12c2251d 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -22,7 +22,7 @@ ELFUSE_HOST_NOFILE_MIN ?= $(shell bash "$(CURDIR)/tests/test-config.sh" --host-n test-sysroot-create-paths test-fork-ipc-protocol-host \ test-vcpu-run-hooks-host test-identity-override-host \ test-dynamic-array-host test-string-builder-host test-gdbstub-host \ - test-config \ + test-config test-runner \ test-mremap-tail-emfile \ test-proctitle-host test-proctitle-low-stack \ test-sysroot-procfs-exec test-sysroot-fd-magiclink \ @@ -66,6 +66,10 @@ test-hello: $(ELFUSE_BIN) $(TEST_HELLO_DEP) test-config: @bash tests/test-config-cli.sh +## Verify output matching and exit status checks in the shared shell runner +test-runner: + @bash tests/test-runner.sh + ## Run the libc-based file-backed region removal EMFILE regression probe test-mremap-tail-emfile: $(ELFUSE_BIN) $(BUILD_DIR)/test-mremap-tail-emfile @printf "$(BLUE)▸ Running$(RESET) test-mremap-tail-emfile\n" @@ -283,7 +287,7 @@ check-sanitizer: $(ELFUSE_BIN) $(TEST_DEPS) $(CHECK_HOST_UNIT_BINS) $(CHECK_SHARED_LANES) ## Run the unit test suite plus busybox applet validation -check: $(ELFUSE_BIN) $(TEST_DEPS) check-syscall-coverage check-eintr-contract check-lock-order check-atomics check-ascii check-svc-tails check-skill-refs test-config \ +check: $(ELFUSE_BIN) $(TEST_DEPS) check-syscall-coverage check-eintr-contract check-lock-order check-atomics check-ascii check-svc-tails check-skill-refs test-config test-runner \ $(CHECK_HOST_UNIT_BINS) @bash tests/driver.sh -e $(ELFUSE_BIN) -d $(TEST_DIR) -v $(CHECK_SHARED_LANES) diff --git a/tests/lib/test-runner.sh b/tests/lib/test-runner.sh index 502eb016..4568f1d2 100644 --- a/tests/lib/test-runner.sh +++ b/tests/lib/test-runner.sh @@ -264,7 +264,7 @@ run_check() test_report fail "$tool" " (exit rc=$rc)" test_excerpt "$output" fail=$((fail + 1)) - elif printf "%s\n" "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$tool" pass=$((pass + 1)) else @@ -324,7 +324,7 @@ run_pipe() test_report fail "$tool" " (exit rc=$rc)" test_excerpt "$output" fail=$((fail + 1)) - elif printf "%s\n" "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$tool" pass=$((pass + 1)) else diff --git a/tests/test-matrix.sh b/tests/test-matrix.sh index 2bc99566..4e62777d 100755 --- a/tests/test-matrix.sh +++ b/tests/test-matrix.sh @@ -562,7 +562,7 @@ test_check() test_report fail "$label" " (exit $rc)" test_excerpt "$output" fail=$((fail + 1)) - elif echo "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$label" pass=$((pass + 1)) else @@ -630,7 +630,7 @@ test_pipe() test_report fail "$label" " (exit $rc)" test_excerpt "$output" fail=$((fail + 1)) - elif echo "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$label" pass=$((pass + 1)) else diff --git a/tests/test-runner.sh b/tests/test-runner.sh new file mode 100755 index 00000000..ca608471 --- /dev/null +++ b/tests/test-runner.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +# Copyright 2026 elfuse contributors +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +if [ "${1:-}" = "--emit" ]; then + awk 'BEGIN { + print "match-begin" + # Keep output larger than pipe capacity to expose early-match SIGPIPE. + for (i = 0; i < 30000; i++) + print "abcdefghijklmnopqrstuvwxyz" + print "match-end" + }' + exit "$2" +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +export TEST_SAMPLE_TIMEOUTS=0 +TEST_RUNNER=("$BASH") +BIN="$SCRIPT_DIR" + +# shellcheck source=tests/lib/test-runner.sh +. "$SCRIPT_DIR/lib/test-runner.sh" + +checks=0 +failures=0 + +expect_result() +{ + local runner="$1" label="$2" pattern="$3" rc="$4" want="$5" + local output + checks=$((checks + 1)) + # Each subshell inherits zero pass and fail counts and discards updates. + if output=$( + if [ "$runner" = run_pipe ]; then + run_pipe test-runner.sh "$pattern" input --emit "$rc" + else + run_check test-runner.sh "$pattern" --emit "$rc" + fi + [ "$pass" -eq "$want" ] && [ "$fail" -eq "$((1 - want))" ] + ); then + printf 'PASS: %s %s\n' "$runner" "$label" + else + printf 'FAIL: %s %s\n%s\n' "$runner" "$label" "$output" >&2 + failures=$((failures + 1)) + fi +} + +for runner in run_check run_pipe; do + expect_result "$runner" early-match '^match-begin$' 0 1 + expect_result "$runner" late-match '^match-end$' 0 1 + expect_result "$runner" missing-pattern '^absent$' 0 0 + expect_result "$runner" failed-command '^match-begin$' 7 0 +done + +printf 'test-runner: %s checks, %s failures\n' "$checks" "$failures" +[ "$failures" -eq 0 ] diff --git a/tests/test-static-bins.sh b/tests/test-static-bins.sh index eae786ff..443baaa1 100755 --- a/tests/test-static-bins.sh +++ b/tests/test-static-bins.sh @@ -90,7 +90,7 @@ srun_check() if [ "$rc" -eq 124 ]; then test_report fail "$label" " (timeout)" fail=$((fail + 1)) - elif echo "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$label" pass=$((pass + 1)) else @@ -124,7 +124,7 @@ srun_pipe() if [ "$rc" -eq 124 ]; then test_report fail "$label" " (timeout)" fail=$((fail + 1)) - elif echo "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$label" pass=$((pass + 1)) else @@ -158,7 +158,7 @@ srun_script() if [ "$rc" -eq 124 ]; then test_report fail "$label" " (timeout)" fail=$((fail + 1)) - elif echo "$output" | grep -qE "$pattern"; then + elif grep -qE "$pattern" <<< "$output"; then test_report ok "$label" pass=$((pass + 1)) else @@ -355,7 +355,7 @@ if [ -n "$DIFF_BIN" ]; then if [ "$rc" -eq 124 ]; then test_report fail "$label" " (timeout)" fail=$((fail + 1)) - elif [ "$rc" = "1" ] && echo "$output" | grep -qE "^[<>]"; then + elif [ "$rc" = "1" ] && grep -qE "^[<>]" <<< "$output"; then test_report ok "$label" pass=$((pass + 1)) else