From 4d0d84f935159c395ccf3b95d5727a3553003b68 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 12:11:41 -0500 Subject: tests: Implement jobserver inheritance --- tests/getopts.sh | 22 +++++++--- tests/run.sh | 119 +++++++++++++++++++++++++++++++++++++++++++------------ tests/tests.mk | 7 ++++ tests/util.sh | 12 ++++-- 4 files changed, 125 insertions(+), 35 deletions(-) create mode 100644 tests/tests.mk (limited to 'tests') diff --git a/tests/getopts.sh b/tests/getopts.sh index ac75140..5214e9f 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -10,6 +10,7 @@ if command -v nproc &>/dev/null; then else JOBS=1 fi +MAKE= PATTERNS=() SUDO=() STOP=0 @@ -24,15 +25,19 @@ VERBOSE_TESTS=0 usage() { local pad=$(printf "%*s" ${#0} "") color cat </dev/null)" || : - debug "$file" $line "${RED}error $ret${RST}" "$cmd" >&$DUPERR + debug "$file" $line "${RED}error $ret${RST}" >&$DUPERR break fi done } -# Run a single test -run_test() ( +# Source a test +source_test() ( set -eE trap debug_err ERR + + if ((${#MAKE[@]})); then + # Close the jobserver pipes + exec {READY_PIPE}<&- {DONE_PIPE}>&- + fi + cd "$TMP" source "$@" ) -# Run a test in the background -bg_test() { +# Run a test +run_test() { if ((VERBOSE_ERRORS)); then - run_test "$1" + source_test "$1" else - run_test "$1" 2>"$TMP/$TEST.err" + source_test "$1" 2>"$TMP/$TEST.err" fi ret=$? + if ((${#MAKE[@]})); then + # Write one byte to the done pipe + printf . >&$DONE_PIPE + fi + case $ret in 0) if ((VERBOSE_TESTS)); then @@ -73,28 +83,87 @@ bg_test() { return $ret } -# Wait for a background test to finish -wait_test() { - wait -n - ret=$? +# Count the tests running in the background +BG=0 + +# Run a test in the background +bg_test() { + run_test "$1" & + ((++BG)) +} + +# Reap a finished background test +reap_test() { ((BG--)) - case $ret in + case "$1" in 0) ((++passed)) - return 0 ;; $EX_SKIP) ((++skipped)) - return 0 ;; *) ((++failed)) - return $ret ;; esac } +# Wait for a background test to finish +wait_test() { + local pid + wait -n -ppid + ret=$? + if [ -z "${pid:-}" ]; then + debug "${BASH_SOURCE[0]}" $((LINENO - 3)) "${RED}error $ret${RST}" >&$DUPERR + exit 1 + fi + + reap_test $ret +} + +# Wait until we're ready to run another test +wait_ready() { + if ((${#MAKE[@]})); then + # We'd like to parse the output of jobs -n, but we can't run it in a + # subshell or we won't get the right output + jobs -n >"$TMP/jobs" + while read -r job status ret foo; do + case "$status" in + Done) + reap_test 0 + ;; + Exit) + reap_test $ret + ;; + esac + done <"$TMP/jobs" + + # Read one byte from the ready pipe + read -r -N1 -u$READY_PIPE + elif ((BG >= JOBS)); then + wait_test + fi +} + +# Run make as a co-process to use its job control +comake() { + coproc { + # We can't just use std{in,out}, due to + # https://www.gnu.org/software/make/manual/html_node/Parallel-Input.html + exec {DONE_PIPE}<&0 {READY_PIPE}>&1 + exec "${MAKE[@]}" -s \ + -f "$TESTS/tests.mk" \ + DONE=$DONE_PIPE \ + READY=$READY_PIPE \ + "${TEST_CASES[@]/#/tests/}" \ + /dev/null + } + + # coproc pipes aren't inherited by subshells, so dup them + exec {READY_PIPE}<&${COPROC[0]} {DONE_PIPE}>&${COPROC[1]} +} + # Run all the tests run_tests() { if ((VERBOSE_TESTS)); then @@ -125,17 +194,17 @@ run_tests() { TEST_FMT="." fi - BG=0 + if ((${#MAKE[@]})); then + comake + fi # Turn off set -e (but turn it back on in run_test) set +e for TEST in "${TEST_CASES[@]}"; do - if ((BG >= JOBS)); then - wait_test - if (($? && STOP)); then - break - fi + wait_ready + if (($? && STOP)); then + break fi percent=$((100 * ran / total)) @@ -144,8 +213,8 @@ run_tests() { mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" - bg_test "$TESTS/$TEST.sh" & - ((++BG, ++ran)) + bg_test "$TESTS/$TEST.sh" + ((++ran)) done while ((BG > 0)); do @@ -185,7 +254,7 @@ skip() { caller | { read -r line file printf "${BOL}" - debug "$file" $line "" "$(awk "NR == $line" "$file")" >&$DUPOUT + debug "$file" $line "" >&$DUPOUT } fi diff --git a/tests/tests.mk b/tests/tests.mk new file mode 100644 index 0000000..5bf4f6c --- /dev/null +++ b/tests/tests.mk @@ -0,0 +1,7 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# GNU makefile that exposes make's job control to tests.sh + +tests/%: + bash -c 'printf . >&$(READY) && read -r -N1 -u$(DONE)' diff --git a/tests/util.sh b/tests/util.sh index ec24958..7dba9fb 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -113,9 +113,13 @@ callers() { # Print a message including path, line number, and command debug() { - local file="${1/#*\/tests\//tests/}" - set -- "$file" "${@:2}" - color printf "${BLD}%s:%d:${RST} %s\n %s\n" "$@" + local file="$1" + local line="$2" + local msg="$3" + local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : + file="${file/#*\/tests\//tests/}" + + color printf "${BLD}%s:%d:${RST} %s\n %s\n" "$file" "$line" "$msg" "$cmd" } ## Deferred cleanup @@ -163,7 +167,7 @@ pop_defer() { eval "$cmd" || ret=$? if ((ret != 0)); then - debug "$file" $line "${RED}error $ret${RST}" "defer $cmd" >&$DUPERR + debug "$file" $line "${RED}error $ret${RST}" >&$DUPERR fi return $ret -- cgit v1.2.3