almnck/bin/tapview
2024-01-31 23:35:05 -08:00

287 lines
6.6 KiB
Bash
Executable file

#! /bin/sh
# tapview - a TAP (Test Anything Protocol) viewer in pure POSIX shell
#
# This code is intended to be embedded in your project. The author
# grants permission for it to be distributed under the prevailing
# license of your project if you choose, provided that license is
# OSD-compliant; otherwise the following SPDX tag incorporates the
# MIT No Attribution license by reference.
#
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: MIT-0
#
# This version shipped on 2024-01-31. A newer version may be available
# at https://gitlab.com/esr/tapview; check the ship date oagainst the
# commit list there to see if it might be a good idea to update.
OK="."
FAIL="F"
SKIP="s"
TODO_NOT_OK="x"
TODO_OK="u"
LF='
'
ship_char() {
# shellcheck disable=SC2039
printf '%s' "$1" # https://www.etalabs.net/sh_tricks.html
}
ship_line() {
report="${report}${1}$LF"
}
ship_error() {
# Terminate dot display and bail out with error
if [ "${testcount}" -gt 0 ]
then
echo ""
fi
report="${report}${1}$LF"
echo "${report}"
exit 1
}
testcount=0
failcount=0
skipcount=0
todocount=0
status=0
report=""
ln=0
state=plaintext
# shellcheck disable=SC2086
context_get () { printenv "ctx_${1}${depth}"; }
context_set () { export "ctx_${1}${depth}=${2}"; }
context_push () {
context_set plan ""
context_set count 0
context_set test_before_plan no
context_set test_after_plan no
context_set expect ""
context_set bail no
context_set strict no
}
context_pop () {
if [ "$(context_get count)" -gt 0 ] && [ -z "$(context_get plan)" ]
then
ship_line "Missing a plan at line ${ln}."
status=1
elif [ "$(context_get test_before_plan)" = "yes" ] && [ "$(context_get test_after_plan)" = "yes" ]
then
ship_line "A plan line may only be placed before or after all tests."
status=1
elif [ "$(context_get plan)" != "" ] && [ "$(context_get expect)" -gt "$(context_get count)" ]
then
ship_line "Expected $(context_get expect) tests but only ${testcount} ran."
status=1
elif [ "$(context_get plan)" != "" ] && [ "$(context_get expect)" -lt "$(context_get count)" ]
then
ship_line "${testcount} ran but $(context_get expect) expected."
status=1
fi
}
directive () {
case "$1" in
*[[:space:]]#[[:space:]]*$2*) return 0;;
*) return 1;;
esac
}
depth=0
context_push
while read -r line
do
ln=$((ln + 1))
IFS=" "
# shellcheck disable=SC2086
set -- $line
tok1="$1"
tok2="$2"
tok3="$3"
IFS=""
# Ignore blank lines and comments
if [ -z "$tok1" ] || [ "$tok1" = '#' ]
then
continue
fi
# Process bailout
if [ "$tok1" = "Bail" ] && [ "$tok2" = "out!" ]
then
ship_line "$line"
status=2
break
fi
# Use the current indent to choose a scope level
leading_spaces="${line%%[! ]*}"
indent=${#leading_spaces}
if [ "${indent}" -lt "${depth}" ]
then
context_pop
depth="${indent}"
elif [ "${indent}" -gt "${depth}" ]
then
depth="${indent}"
context_push
fi
# Process a plan line (outer case is because expr is expensive)
case "$tok1" in
[0123456789]*)
if expr "$line" : '[ ]*1\.\.[0-9][0-9]*' >/dev/null
then
if [ "$(context_get plan)" != "" ]
then
ship_error "tapview: cannot have more than one plan line."
fi
if directive "$line" [Ss][Kk][Ii][Pp]
then
ship_line "$line"
echo "${report}"
exit 1 # Not specified in the standard whether this should exit 1 or 0
fi
context_set plan "${line}"
context_set expect "$(expr "$line" : '[ ]*1\.\.\([0-9][0-9]*\)')"
continue
elif expr "$line" : '[ ]*[0-9][0-9]*\.\.[0-9][0-9]*' >/dev/null
then
echo "Ill-formed plan line at ${ln}"
exit 1
fi
;; esac
# Check for test point numbers out-of-order with the sequence (TAP 14)
testpoint=""
case "$tok1" in
ok) testpoint="$tok2";;
not) testpoint="$tok3";;
esac
case "$testpoint" in
*[0123456789]*)
if [ "${testpoint}" != "" ] && [ "$(context_get expect)" != "" ] && [ "${testpoint}" -gt "$(context_get expect)" ]
then
ship_error "tapview: testpoint number ${testpoint} is out of range for plan $(context_get plan)."
fi
;; esac
# Process an ok line
if [ "$tok1" = "ok" ]
then
context_set count $(($(context_get count) + 1))
testcount=$((testcount + 1))
if [ "$(context_get plan)" = "" ]
then
context_set test_before_plan yes
else
context_set test_after_plan yes
fi
if directive "$line" [Tt][Oo][Dd][Oo]
then
ship_char ${TODO_OK}
ship_line "$line"
todocount=$((todocount + 1))
elif directive "$line" [Ss][Kk][Ii][Pp]
then
ship_char ${SKIP}
ship_line "$line"
skipcount=$((skipcount + 1))
else
ship_char ${OK}
fi
state=plaintext
continue
fi
# Process a not-ok line
if [ "$tok1" = "not" ] && [ "$tok2" = "ok" ]
then
context_set count $(($(context_get count) + 1))
testcount=$((testcount + 1))
if [ "$(context_get plan)" = "" ]
then
context_set test_before_plan yes
else
context_set test_after_plan yes
fi
if directive "$line" [Ss][Kk][Ii][Pp]
then
ship_char "${SKIP}"
state=plaintext
skipcount=$((skipcount + 1))
continue
fi
if directive "$line" [Tt][Oo][Dd][Oo]
then
ship_char ${TODO_NOT_OK}
state=plaintext
todocount=$((todocount + 1))
continue
fi
ship_char "${FAIL}"
ship_line "$line"
state=plaintext
failcount=$((failcount + 1))
status=1
if [ "$(context_get bail)" = yes ]
then
ship_line "Bailing out on line ${ln} due to +bail pragma."
break
fi
continue
fi
# Process a TAP 14 pragma
case "$line" in
pragma*)
case "$line" in
*+bail*) context_set bail yes;;
*-bail*) context_set bail yes;;
*+strict*) context_set strict yes;;
*-strict*) context_set strict yes;;
*) ship_line "Pragma '$line' ignored";;
esac
continue
;;
esac
# shellcheck disable=SC2166
if [ "${state}" = "yaml" ]
then
ship_line "$line"
if [ "$tok1" = "..." ]
then
state=plaintext
else
continue
fi
elif [ "$tok1" = "---" ]
then
ship_line "$line"
state=yaml
continue
fi
# Any line that is not a valid plan, test result, pragma,
# or comment lands here.
if [ "$(context_get strict)" = yes ]
then
ship_line "Bailing out on line ${ln} due to +strict pragma"
status=1
break
fi
done
/bin/echo ""
depth=0
context_pop
report="${report}${testcount} tests, ${failcount} failures"
if [ "$todocount" != 0 ]
then
report="${report}, ${todocount} TODOs"
fi
if [ "$skipcount" != 0 ]
then
report="${report}, ${skipcount} SKIPs"
fi
echo "${report}."
exit "${status}"
# end