From 59b1c525c4c7c254dce702fca0fa472616a976d2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 22 May 2024 16:12:37 -0400 Subject: tests: Print progress outside the scroll region --- tests/color.sh | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/run.sh | 66 +++++++++++++------------------------ tests/util.sh | 9 +++++ 3 files changed, 132 insertions(+), 44 deletions(-) diff --git a/tests/color.sh b/tests/color.sh index 805d2b8..ca85d28 100644 --- a/tests/color.sh +++ b/tests/color.sh @@ -35,3 +35,104 @@ color() { "$@" | "$SED" $'s/\e\\[[^m]*m//g' fi } + +## Status bar + +# Show the terminal status bar +show_bar() { + if [ -z "$TTY" ]; then + return 1 + fi + + # Name the pipe deterministically based on the ttyname, so that concurrent + # tests.sh runs on the same terminal (e.g. make -jN check) cooperate + local pipe + pipe=$(printf '%s' "$TTY" | tr '/' '-') + pipe="${TMPDIR:-/tmp}/bfs$pipe.bar" + + if mkfifo "$pipe" 2>/dev/null; then + # We won the race, create the background process to manage the bar + bar_proc "$pipe" & + exec {BAR}>"$pipe" + elif [ -p "$pipe" ]; then + # We lost the race, connect to the existing process. + # There is a small TOCTTOU race here but I don't see how to avoid it. + exec {BAR}>"$pipe" + else + return 1 + fi +} + +# Print to the terminal status bar +print_bar() { + printf 'PRINT:%d:%s\0' $$ "$1" >&$BAR +} + +# Hide the terminal status bar +hide_bar() { + exec {BAR}>&- + unset BAR +} + +# The background process that muxes multiple status bars for one TTY +bar_proc() { + # Read from the pipe, write to the TTY + exec <"$1" >"$TTY" + + # Delete the pipe when done + defer rm "$1" + # Reset the scroll region when done + defer printf '\e7\e[r\e8\e[J' + + # Workaround for bash 4: checkwinsize is off by default. We can turn it + # on, but we also have to explicitly trigger a foreground job to finish + # so that it will update the window size before we use $LINES + shopt -s checkwinsize + (:) + + BAR_HEIGHT=0 + resize_bar + # Adjust the bar when the TTY size changes + trap resize_bar WINCH + + # Map from PID to status bar + local -A pid2bar + + # Read commands of the form "OP:PID:STRING\0" + while IFS=':' read -r -d '' op pid str; do + # Map the pid to a bar, creating a new one if necessary + if [ -z "${pid2bar[$pid]:-}" ]; then + pid2bar["$pid"]=$((BAR_HEIGHT++)) + resize_bar + fi + bar="${pid2bar[$pid]}" + + case "$op" in + PRINT) + printf '\e7\e[%d;0f\e[K%s\e8' $((TTY_HEIGHT - bar)) "$str" + ;; + esac + done +} + +# Resize the status bar +resize_bar() { + # Bash gets $LINES from stderr, so if it's redirected use tput instead + TTY_HEIGHT="${LINES:-$(tput lines 2>"$TTY")}" + + if ((BAR_HEIGHT == 0)); then + return + fi + + # Hide the bars temporarily + local seq='\e7\e[r\e8\e[J' + # Print \eD (IND) N times to ensure N blank lines at the bottom + for ((i = 0; i < BAR_HEIGHT; ++i)); do + seq="${seq}\\eD" + done + # Go back up N lines + seq="${seq}\\e[${BAR_HEIGHT}A" + # Create the new scroll region + seq="${seq}\\e7\\e[;$((TTY_HEIGHT - BAR_HEIGHT))r\\e8\\e[J" + printf "$seq" +} diff --git a/tests/run.sh b/tests/run.sh index 629f756..87f94b4 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -5,23 +5,6 @@ ## Running test cases -# Beginning/end of line escape sequences -BOL=$'\n' -EOL=$'\n' - -# Update $EOL for the terminal size -update_eol() { - # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead - local cols="${COLUMNS-}" - if [ -z "$cols" ]; then - cols=$(tput cols 2>/dev/tty) - fi - - # Put the cursor at the last column, then write a space so the next - # character will wrap - EOL=$'\e['"${cols}G " -} - # ERR trap for tests debug_err() { local ret=$? line func file @@ -64,19 +47,19 @@ run_test() { case $ret in 0) if ((VERBOSE_TESTS)); then - color printf "${BOL}${GRN}[PASS]${RST} ${BLD}%s${RST}\n" "$TEST" + color printf "${GRN}[PASS]${RST} ${BLD}%s${RST}\n" "$TEST" fi ;; $EX_SKIP) if ((VERBOSE_SKIPPED || VERBOSE_TESTS)); then - color printf "${BOL}${CYN}[SKIP]${RST} ${BLD}%s${RST}\n" "$TEST" + color printf "${CYN}[SKIP]${RST} ${BLD}%s${RST}\n" "$TEST" fi ;; *) if ((!VERBOSE_ERRORS)); then cat "$TMP/$TEST.err" >&2 fi - color printf "${BOL}${RED}[FAIL]${RST} ${BLD}%s${RST}\n" "$TEST" + color printf "${RED}[FAIL]${RST} ${BLD}%s${RST}\n" "$TEST" ;; esac @@ -173,35 +156,24 @@ comake() { exec {READY_PIPE}<&${COPROC[0]} {DONE_PIPE}>&${COPROC[1]} } -# Run all the tests -run_tests() { - if ((VERBOSE_TESTS)); then - BOL='' - elif ((COLOR_STDOUT)); then - # Carriage return + clear line - BOL=$'\r\e[K' - - # Workaround for bash 4: checkwinsize is off by default. We can turn it - # on, but we also have to explicitly trigger a foreground job to finish - # so that it will update the window size before we use $COLUMNS - shopt -s checkwinsize - (:) - - update_eol - trap update_eol WINCH +# Print the current test progess +progress() { + if [ "${BAR:-}" ]; then + print_bar "$(printf "$@")" + elif ((VERBOSE_TESTS)); then + color printf "$@" fi +} +# Run all the tests +run_tests() { passed=0 failed=0 skipped=0 ran=0 total=${#TEST_CASES[@]} - if ((COLOR_STDOUT || VERBOSE_TESTS)); then - TEST_FMT="${BOL}${YLW}[%3d%%]${RST} ${BLD}%s${RST}${EOL}" - else - TEST_FMT="." - fi + TEST_FMT="${YLW}[%3d%%]${RST} ${BLD}%s${RST}\\n" if ((${#MAKE[@]})); then comake @@ -210,6 +182,10 @@ run_tests() { # Turn off set -e (but turn it back on in run_test) set +e + if ((COLOR_STDOUT && !VERBOSE_TESTS)); then + show_bar + fi + for TEST in "${TEST_CASES[@]}"; do wait_ready if ((STOP && failed > 0)); then @@ -217,7 +193,7 @@ run_tests() { fi percent=$((100 * ran / total)) - color printf "$TEST_FMT" $percent "$TEST" + progress "${YLW}[%3d%%]${RST} ${BLD}%s${RST}\\n" $percent "$TEST" mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" @@ -230,7 +206,10 @@ run_tests() { wait_test done - printf "${BOL}" + if [ "${BAR:-}" ]; then + progress "${YLW}[100%%]${RST} ${BLD}%3d${RST} / ${BLD}%d${RST}\n" $ran $total + hide_bar + fi if ((passed > 0)); then color printf "${GRN}[PASS]${RST} ${BLD}%3d${RST} / ${BLD}%d${RST}\n" $passed $total @@ -262,7 +241,6 @@ skip() { if ((VERBOSE_SKIPPED)); then caller | { read -r line file - printf "${BOL}" debug "$file" $line "" >&$DUPOUT } fi diff --git a/tests/util.sh b/tests/util.sh index 3969db5..76b72b9 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -59,6 +59,15 @@ stdenv() { # Close stdin so bfs doesn't think we're interactive # dup() the standard fds for logging even when redirected exec &1 {DUPERR}>&2 + + # Get the ttyname + if [ -t $DUPOUT ]; then + TTY=$(tty <&$DUPOUT) + elif [ -t $DUPERR ]; then + TTY=$(tty <&$DUPERR) + else + TTY= + fi } # Drop root priviliges or bail -- cgit v1.2.3