Files
pv/autoconf/scripts/run-test.sh
T

211 lines
8.0 KiB
Bash
Executable File

#!/bin/sh
#
# Parameters: testSubject sourcePath [testScript...]
#
# Run one or more test scripts. The ${testSubject} is the path of the test
# subject program, and the ${sourcePath} is the path to the top level of the
# source directory. If any ${testScript} parameters are given, they are the
# tests to run (each one being either the full path to a script, or the base
# filename of the script in the test directory). If no test scripts are
# listed, then all tests directly under "${sourcePath}/tests/" are run.
#
# Test scripts should be written to exit 0 if the test passed, 2 if the test
# is to be skipped, or any other exit status to indicate failure. They are
# run via "sh -e", so any command exiting non-zero will cause the test
# script to exit early.
#
# The test scripts can rely on the environment variables "testSubject",
# "sourcePath", "workFile1", "workFile2", "workFile3", and "workFile4" being
# populated, and should use those work files - which will be present and
# empty - for scratch space. They should not delete those files.
#
# When the test scripts are called, LANG and LC_ALL are set to "C".
#
# Anything output by a test script on stdout or stderr is captured and shown
# after the "OK" / "FAILED" / "skipped" result description.
#
# The exit status of this script will be 1 if any test failed, 0 otherwise.
# A skipped test is not counted as a failure.
#
# Parameters.
testSubject="$1"
sourcePath="$2"
shift
shift
selectedTests="$*"
# Constants.
formatCodesPass="setaf 2;bold" # terminal capabilities for "pass" formatting
formatCodesFail="setaf 1;bold" # terminal capabilities for "fail" formatting
formatCodesSkip="setaf 3" # terminal capabilities for "skip" formatting
formatCodesZero="setaf 4" # terminal capabilities for zero result
# Temporary working files, for the test scripts to use.
workFile1=$(mktemp 2>/dev/null) || workFile1="./.tmp1"
workFile2=$(mktemp 2>/dev/null) || workFile2="./.tmp2"
workFile3=$(mktemp 2>/dev/null) || workFile3="./.tmp3"
workFile4=$(mktemp 2>/dev/null) || workFile4="./.tmp4"
# Clean up the temporary files on exit, in case we are interrupted.
trap 'rm -f "${workFile1}" "${workFile2}" "${workFile3}" "${workFile4}"' EXIT
# Variables used by the test scripts.
export testSubject sourcePath workFile1 workFile2 workFile3 workFile4
# Set everything to the "C" locale.
LANG=C
LC_ALL=C
export LANG LC_ALL
# If no tests were specified, list all test scripts under the source path.
test -n "${selectedTests}" || selectedTests=$(find "${sourcePath}/tests" -maxdepth 1 -type f | sort -n)
# Initial pass to count the number of tests and find the length of the
# longest test name, to use when formatting the output.
#
numberOfTests=0
maxTestNameLength=0
for testScript in ${selectedTests}; do
# Find the test script, make sure it exists.
test -f "${testScript}" || testScript="${sourcePath}/tests/${testScript}"
test -f "${testScript}" || testScript=$(find "${sourcePath}/tests" -maxdepth 1 -type f -name "${testScript##*/}*" | sed -n '1p')
test -f "${testScript}" || continue
numberOfTests=$((1+numberOfTests))
testScriptLeaf="${testScript##*/}"
testName="$(echo "${testScriptLeaf%.sh}" | tr '_' ' ' | sed 's/ - /: /')"
testNameLength=${#testName}
test "${testNameLength}" -gt "${maxTestNameLength}" && maxTestNameLength="${testNameLength}"
done
# Set a minimum and maximum test name length.
test "${maxTestNameLength}" -lt 10 && maxTestNameLength=10
test "${maxTestNameLength}" -gt 60 && maxTestNameLength=60
# Count the number of digits in the number of tests so we know how wide the
# column for the test count should be.
testCountWidth=${#numberOfTests}
# Get the terminal width, for later display formatting calculations.
#
# We have to fiddle with `stty' because $COLUMNS is only in `bash', `resize`
# is not available everywhere, and `stty' itself varies in its output format
# between "columns 158" and "158 cols".
#
terminalWidth=80
test -t 1 && terminalWidth=$(stty -a 2>/dev/null | tr ';' '\n' | grep -e cols -e columns | tr ' ' '\n' | grep '[0-9]' | sed -n 1p)
test -n "${terminalWidth}" || terminalWidth=80
# Remaining space left on each line after the result description.
colsUsed=$((testCountWidth + 1 + testCountWidth + 2 + maxTestNameLength + 2 + 8 + 2))
colsRemaining=0
test "${colsUsed}" -lt "${terminalWidth}" && colsRemaining=$((terminalWidth - colsUsed))
# Run all of the selected test scripts, formatting the output.
#
testPassCount=0
testFailCount=0
testSkipCount=0
testNumber=0
for testScript in ${selectedTests}; do
# Find the test script, make sure it exists.
test -f "${testScript}" || testScript="${sourcePath}/tests/${testScript}"
test -f "${testScript}" || testScript=$(find "${sourcePath}/tests" -maxdepth 1 -type f -name "${testScript##*/}*" | sed -n '1p')
test -f "${testScript}" || continue
testNumber=$((1+testNumber))
testScriptLeaf="${testScript##*/}"
testName="$(echo "${testScriptLeaf%.sh}" | tr '_' ' ' | sed 's/ - /: /')"
printf "%${testCountWidth}d/%d: %-${maxTestNameLength}.${maxTestNameLength}s " "${testNumber}" "${numberOfTests}" "${testName}"
# Run the test script, capturing the output and the exit status.
testExitStatus=0
testOutput=""
if test -s "${testScript}"; then
true > "${workFile1}"
true > "${workFile2}"
true > "${workFile3}"
true > "${workFile4}"
testOutput=$(sh -e "${testScript}" 2>&1)
testExitStatus=$?
else
testOutput="test script has not yet been written"
testExitStatus=2
fi
# Work out what we're going to say about this test result.
testResultDescription=""
resultFormatCodes=""
if test ${testExitStatus} -eq 0; then
resultFormatCodes="${formatCodesPass}"
testResultDescription="OK"
testPassCount=$((1+testPassCount))
elif test ${testExitStatus} -eq 2; then
resultFormatCodes="${formatCodesSkip}"
testResultDescription="skipped"
testSkipCount=$((1+testSkipCount))
else
resultFormatCodes="${formatCodesFail}"
testResultDescription="FAILED"
testFailCount=$((1+testFailCount))
fi
# If stdout is not a terminal, don't use terminal format codes.
test -t 1 || resultFormatCodes=""
# Show the description of the test result, and start a new line.
test -n "${resultFormatCodes}" && command -v tput >/dev/null 2>&1 && echo "${resultFormatCodes}" | tr ';' '\n' | tput -S 2>/dev/null
printf "%s" "${testResultDescription}"
# If there was any output from the test, display it - either on the
# current line, if there's space, or the next line, with each line
# prefixed with the test number.
if test -n "${testOutput}"; then
if test "${#testOutput}" -lt "${colsRemaining}"; then
printf " - %s" "${testOutput}"
else
printf "\n%s" "$(echo "${testOutput}" | sed "s,^,$(printf "%${testCountWidth}d/%d: - " "${testNumber}" "${numberOfTests}"),")"
fi
fi
printf "\n"
test -n "${resultFormatCodes}" && command -v tput >/dev/null 2>&1 && tput sgr0 2>/dev/null
done
# Clean up.
rm -f "${workFile1}" "${workFile2}" "${workFile3}" "${workFile4}"
trap '' EXIT
# Report the totals.
printf "\n"
printf "%s: " "Tests passed"
numberFormatting="${formatCodesPass}"
test "${testPassCount}" -gt 0 || numberFormatting="${formatCodesZero}"
test -t 1 && command -v tput >/dev/null 2>&1 && echo "${numberFormatting}" | tr ';' '\n' | tput -S 2>/dev/null
printf "%d" "${testPassCount}"
test -t 1 && command -v tput >/dev/null 2>&1 && tput sgr0 2>/dev/null
printf " %s: " "Tests failed"
numberFormatting="${formatCodesFail}"
test "${testFailCount}" -gt 0 || numberFormatting="${formatCodesZero}"
test -t 1 && command -v tput >/dev/null 2>&1 && echo "${numberFormatting}" | tr ';' '\n' | tput -S 2>/dev/null
printf "%d" "${testFailCount}"
test -t 1 && command -v tput >/dev/null 2>&1 && tput sgr0 2>/dev/null
printf " %s: " "Tests skipped"
numberFormatting="${formatCodesSkip}"
test "${testSkipCount}" -gt 0 || numberFormatting="${formatCodesZero}"
test -t 1 && command -v tput >/dev/null 2>&1 && echo "${numberFormatting}" | tr ';' '\n' | tput -S 2>/dev/null
printf "%d" "${testSkipCount}"
test -t 1 && command -v tput >/dev/null 2>&1 && tput sgr0 2>/dev/null
printf "\n"
# Exit with status 1 if any test failed outright, 0 otherwise.
test "${testFailCount}" -eq 0 || exit 1
exit 0
# EOF