: regress - run regression tests in command.tst
command=regress
case $(getopts '[-][123:xyz]' opt --xyz 2>/dev/null; echo 0$opt) in
0123) USAGE=$'
[-?
@(]
'$USAGE_LICENSE$'
[+NAME?regress - run regression tests]
[+DESCRIPTION?\bregress\b runs the tests in \aunit\a, or
\aunit\a\b.tst\b if \aunit\a does not exist. If \acommand\a is omitted
then it is assumed to be the base name of \aunit\a. All testing is done
in the temporary directory \aunit\a\b.tmp\b.]
[+?Default test output lists the \anumber\a and \adescription\a for
each active \bTEST\b group and the \anumber\a:\aline\a for each
individual \bEXEC\b test. Each test that fails results in a diagnostic
that contains the word \bFAILED\b; no other diagnostics contain this
word.]
[b:ignore-space?Ignore space differences when comparing expected
output.]
[i:pipe-input?Repeat each test with the standard input redirected through a
pipe.]
[k:keep?Enable \bcore\b dumps, exit after the first test that fails,
and do not remove the temporary directory \aunit\a\b.tmp\b.]
[l:local-fs?Force \aunit\a\b.tmp\b to be in a local filesystem.]
[o:pipe-output?Repeat each test with the standard output redirected through
a pipe.]
[p:pipe-io?Repeat each test with the standard input and standard output
redirected through pipes.]
[q:quiet?Output information on \bFAILED\b tests only.]
[r!:regular?Run each test with the standard input and standard output
redirected through regular files.]
[t:test?Run only tests matching \apattern\a. Tests are numbered and
consist of at least two digits (0 filled if necessary.) Tests matching
\b+(0)\b are always run.]:[pattern]
[x:trace?Enable debug tracing.]
[v:verbose?List differences between actual (<) and expected (>) output,
errors and exit codes. Also disable long output line truncation.]
unit [ command [ arg ... ] ]
[+INPUT FILES?The regression test file \aunit\a\b.tst\b is a \bksh\b(1)
script that is executed in an environment with the following functions
defined:]
{
[+BODY { ... }?Defines the test body; used for complex tests.]
[+CD \adirectory\a?Create and change to working directory for
one test.]
[+CLEANUP \astatus\a?Called at exit time to remove the
temporary directory \aunit\a\b.tmp\b, list the tests totals via
\bTALLY\b, and exit with status \astatus\a.]
[+COMMAND \aarg\a ...?Runs the current command under test with
\aarg\a ... appended to the default args.]
[+CONTINUE?The background job must be running.]
[+COPY \afrom to\a?Copy file \afrom\a to \ato\a. \afrom\a may
be a regular file or \bINPUT\b, \bOUTPUT\b or \bERROR\b. Post
test comparisons are still done for \afrom\a.]
[+DIAGNOSTICS [ \b1\b | \b0\b | \apattern\a ]]?No argument or an
argument of \b1\b declares that diagnostics are to expected for
the remainder of the current \bTEST\b; \b0\b reverts to the default
state that diagnostics are not expected; otherwise the argument
is a \bksh\b(1) pattern that must match the non-empty contents
of the standard error.]
[+DO \astatement\a?Defines additional statements to be executed
for the current test. \astatement\a may be a { ... } group.]
[+EMPTY \bINPUT|OUTPUT|ERROR|SAME?The corresponding file is
expected to be empty.]
[+ERROR [ \b-n\b ]] \afile\a | - \adata\a ...?The standard
error is expected to match either the contents of \afile\a or
the line \adata\a. \bERROR -n\b does not append a newline to
\adata\a.]
[+EXEC [ \aarg\a ... ]]?Runs the command under test with
optional arguments. \bINPUT\b, \bOUTPUT\b, \bERROR\b, \bEXIT\b
and \bSAME\b calls following this \bEXEC\b up until the next
\bEXEC\b or the end of the script provide details for the
expected results. If no arguments are specified then the
arguments from the previious \bEXEC\b in the current \bTEST\b
group are used, or no arguments if this is the first \bEXEC\b
in the group.]
[+EXIT \astatus\a?The command exit status is expected to match
the pattern \astatus\a.]
[+EXITED?The background job must have exited.]
[+EXPORT [-]] \aname\a=\avalue\a ...?Export environment
variables for one test.]
[+FATAL \amessage\a ...?\amessage\a is printed on the standard
error and \bregress\b exits with status \b1\b.]
[+FIFO \bINPUT|OUTPUT|ERROR\b [ \b-n\b ]] \afile\a | - \adata\a ...?
The \bIO\B file is a fifo.]
[+IF \acommand\a [\anote\a]]?If the \bsh\b(1) \acommand\a exits
0 then tests until the next \bELIF\b, \bELSE\b or \bFI\b are
enabled. Otherwise those tests are skipped. \bIF\b ... \bFI\b
may be nested, but must not cross \bTEST\b boundaries. \anote\a
is listed on the standard error if the correspoding test block
is enabled; \bIF\b, \bELIF\b, \bELSE\b may nave a \anote\a
operand.]
[+IGNORE \afile\a ...?\afile\a is ignored for subsequent result
comparisons. \afile\a may be \bOUTPUT\b or \bERROR\b.]
[+IGNORESPACE?Ignore space differences when comparing expected
output.]
[+INCLUDE \afile\a ...?One or more \afile\a operands are read
via the \bksh\b(1) \b.\b(1) command. \bVIEW\b is used to locate
the files.]
[+INFO \adescription\a?\adescription\a is printed on the
standard error.]
[+INITIALIZE?Called by \bregress\b to initialize a each
\bTEST\b group.]
[+INPUT [ \b-n\b ]] \afile\a | - \adata\a ...?The standard
input is set to either the contents of \afile\a or the line
\adata\a. \bINPUT -n\b does not append a newline to \adata\a.]
[+INTRO?Called by \bregress\b to introduce all \bTEST\b
groups.]
[+IO [ \bFIFO\b | \bPIPE\b ]] \bINPUT|OUTPUT|ERROR\b [ \b-n\b ]] \afile\a | - \adata\a ...?
Internal support for the \bINPUT\b, \bOUTPUT\b and \bERROR\b
functions.]
[+JOB \aop\a [ ... ]]?Like \bEXEC\b except the command is run
as a background job for the duration of the group or until it
is killed via \bKILL\b.]
[+KEEP \apattern\a ...?The temporary directory is cleared for
each test. Files matching \apattern\a are retained between
tests.]
[+KILL [ \asignal\a ]]?Kill the background job with \asignal\a
[ \bSIGKILL\b ]].]
[+MOVE \afrom to\a?Rename file \afrom\a to \ato\a. \afrom\a may
be a regular file or \bINPUT\b, \bOUTPUT\b or \bERROR\b. Post
test comparisons are ignored for \afrom\a.]
[+NOTE \acomment\a?\acomment\a is added to the current test
trace output.]
[+OUTPUT [ \b-n\b ]] \afile\a | - \adata\a ...?The standard
output is expected to match either the contents of \afile\a or
the line \adata\a. \bOUTPUT -n\b does not append a newline to
\adata\a.]
[+PIPE \bINPUT|OUTPUT|ERROR\b [ \b-n\b ]] \afile\a | - \adata\a ...?
The \bIO\B file is a pipe.]
[+PROG \acommand\a [ \aarg\a ... ]]?\acommand\a is run with
optional arguments.]
[+REMOVE \afile\a ...?\afile\a ... are removed after the
current test is done.]
[+RUN?Called by \bregress\b to run the current test.]
[+SAME \anew old\a?\anew\a is expected to be the same as
\aold\a after the current test completes.]
[+SET [\bno\b]]\aname\a[=\avalue\a]]?Set the command line
option --\aname\a. The setting is in effect for all tests until
the next explicit \bSET\b.]
[+TALLY?Called by \bregress\b display the \bTEST\b results.]
[+TEST \anumber\a [ \adescription\a ... ]]?Define a new test
group labelled \anumber\a with optional \adescripion\a.]
[+TITLE [+]] \atext\a?Set the \bTEST\b output title to
\atext\a. If \b+\b is specified then \atext\a is appended to
the default title. The default title is the test file base
name, and, if different from the test file base name, the test
unit base name.]
[+TWD [ \adir\a ... ]]?Set the temporary test dir to \adir\a.
The default is \aunit\a\b.tmp\b, where \aunit\a is the test
input file sans directory and suffix. If \adir\a matches \b/*\b
then it is the directory name; if \adir\a is non-null then the
prefix \b${TMPDIR:-/tmp}\b is added; otherwise if \adir\a is
omitted then
\b${TMPDIR:-/tmp}/tst-\b\aunit\a-$$-$RANDOM.\b\aunit\a is
used.]
[+UMASK [ \amask\a ]]?Run subsequent tests with \bumask\b(1)
\amask\a. If \amask\a is omitted then the original \bumask\b is
used.]
[+UNIT \acommand\a [ \aarg\a ... ]]?Define the command and
optional default arguments to be tested. \bUNIT\b explicitly
overrides the default command name derived from the test script
file name. A \acommand\a operand with optional arguments
overrides the \bUNIT\b \acommand\a and arguments, with the
exception that if the \bUNIT\b \acommand\a is \b-\b or \b+\b
the \bUNIT\b arguments are appended to the operand or default
unit command and arguments.]
[+VIEW \avar\a [ \afile\a ]]?\avar\a is set to the full
pathname of \avar\a [ \afile\a ]] in the current \b$VPATH\b
view if defined.]
}
[+SEE ALSO?\bnmake\b(1), \bksh\b(1)]
'
;;
*) USAGE='ko:[[no]name[=value]]t:[test]v unit [path [arg ...]]'
;;
esac
function FATAL {
print -r -u2 "$command: $*"
GROUP=FINI
exit 1
}
function EMPTY
{
typeset i
typeset -n ARRAY=$1
for i in ${!ARRAY[@]}
do unset ARRAY[$i]
done
}
function INITIALIZE {
typeset i j
cd "$TWD"
case $KEEP in
"") RM *
;;
*) for i in *
do case $i in
!($KEEP)) j="$j $i" ;;
esac
done
case $j in
?*) RM $j ;;
esac
;;
esac
: >INPUT >OUTPUT.ex >ERROR.ex
BODY=""
COPY=""
DIAGNOSTICS=""
DONE=""
ERROR=""
EXIT=0
IGNORE=""
INIT=""
INPUT=""
MOVE=""
OUTPUT=""
EMPTY FILE
EMPTY SAME
EMPTY TYPE
}
function INTRO
{
typeset base command
if [[ ! $TEST_quiet ]]
then base=${REGRESS base=${base%.tst}
command=${COMMAND command=${command%' '*}
set -- $TITLE
TITLE=
case $1 in
''|+) if [[ $command == $base ]]
then TITLE=$COMMAND
else TITLE="$COMMAND, $base"
fi
if (( $ then shift
fi
;;
esac
while (( $ do if [[ $TITLE ]]
then TITLE="$TITLE, $1"
else TITLE="$1"
fi
shift
done
print -u2 "TEST $TITLE"
fi
}
function TALLY {
typeset msg
case $GROUP in
INIT) ;;
*) msg="TEST $TITLE, $TESTS test"
case $TESTS in
1) ;;
*) msg=${msg}s ;;
esac
msg="$msg, $ERRORS error"
case $ERRORS in
1) ;;
*) msg=${msg}s ;;
esac
if (( $ then msg="$msg, $*"
fi
print -u2 "$msg"
GROUP=INIT
TESTS=0
ERRORS=0
;;
esac
}
function TITLE {
TITLE=$@
}
function UNWIND
{
while (( COND > 1 ))
do print -r -u2 "$command: line $LINE: no matching FI for IF on line ${COND_LINE[COND]}"
(( COND-- ))
done
if (( COND > 0 ))
then (( COND = 0 ))
FATAL "line $LINE: no matching FI for IF on line ${COND_LINE[COND+1]}"
fi
if [[ $JOBPID ]]
then if [[ $JOBPID != 0 ]]
then kill -KILL $JOBPID 2>/dev/null
wait
fi
JOBPID=
fi
JOBSTATUS=
JOBOP=
wait
}
function CLEANUP {
typeset note
if [[ $GROUP != INIT ]]
then if [[ ! $TEST_keep ]]
then cd $SOURCE
if [[ $TEST_local ]]
then RM ${TEST_local}
fi
RM "$TWD"
fi
if (( $1 )) && [[ $GROUP != FINI ]]
then note=terminated
fi
fi
TALLY $note
[[ $TEST_keep ]] || UNWIND
exit $1
}
function RUN {
typeset i r=1
[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
case $GROUP in
INIT) RM "$TWD"
if [[ $TEST_local ]]
then TEST_local=${TMPDIR:-/tmp}/rt-$$/${TWD mkdir -p "$TEST_local" && ln -s "$TEST_local" "$TWD" || FATAL "$TWD": cannot create directory
TEST_local=${TEST_local%/*}
else mkdir "$TWD" || FATAL "$TWD": cannot create directory
fi
cd "$TWD"
TWD=$PWD
: > rmu
if rm -u rmu >/dev/null 2>&1
then TEST_rmu=-u
else rm rmu
fi
if [[ $UNIT ]]
then set -- "${ARGV[@]}"
case $1 in
""|[-+]*)
UNIT $UNIT "${ARGV[@]}"
;;
*) UNIT "${ARGV[@]}"
;;
esac
fi
INTRO
;;
FINI) ;;
$TEST_select)
if [[ $ITEM == $FLUSHED ]]
then return 0
fi
FLUSHED=$ITEM
if (( COND_SKIP[COND] ))
then return 1
fi
((COUNT++))
if (( $ITEM <= $LASTITEM ))
then LABEL=$TEST else LASTITEM=$ITEM
LABEL=$TEST:$ITEM
fi
TEST_file=""
exec >/dev/null
for i in $INPUT
do case " $OUTPUT " in
*" $i "*)
if [[ -f $i.sav ]]
then cp $i.sav $i
COMPARE="$COMPARE $i"
elif [[ -f $i ]]
then cp $i $i.sav
COMPARE="$COMPARE $i"
fi
;;
esac
done
for i in $OUTPUT
do case " $COMPARE " in
*" $i "*)
;;
*) COMPARE="$COMPARE $i"
;;
esac
done
for i in $INIT
do $i $TEST INIT
done
if [[ $JOBPID != 0 && ( $JOBPID || $JOBSTATUS ) ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
RESULTS
elif [[ $BODY ]]
then SHOW=$NOTE
if [[ ! $TEST_quiet ]]
then print -r -u2 " $SHOW"
fi
for i in $BODY
do $i $TEST BODY
done
else SHOW=
if [[ ${TYPE[INPUT]} == PIPE ]]
then if [[ ${TYPE[OUTPUT]} == PIPE ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
cat <$TWD/INPUT | COMMAND "${ARGS[@]}" | cat >$TWD/OUTPUT
RESULTS 'pipe input'
else if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
cat <$TWD/INPUT | COMMAND "${ARGS[@]}" >$TWD/OUTPUT
RESULTS 'pipe io'
fi
elif [[ ${TYPE[OUTPUT]} == PIPE ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
COMMAND "${ARGS[@]}" <$TWD/INPUT | cat >$TWD/OUTPUT
RESULTS 'pipe output'
else if [[ $TEST_regular ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
if [[ ${TYPE[INPUT]} == FIFO ]]
then COMMAND "${ARGS[@]}" >$TWD/OUTPUT
else COMMAND "${ARGS[@]}" <$TWD/INPUT >$TWD/OUTPUT
fi
RESULTS
fi
if [[ $TEST_pipe_input ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
(trap '' PIPE; cat <$TWD/INPUT 2>/dev/null; exit 0) | COMMAND "${ARGS[@]}" >$TWD/OUTPUT
STATUS=$?
RESULTS 'pipe input'
fi
if [[ $TEST_pipe_output ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
COMMAND "${ARGS[@]}" <$TWD/INPUT | cat >$TWD/OUTPUT
STATUS=$?
RESULTS 'pipe output'
fi
if [[ $TEST_pipe_io ]]
then if [[ ! $TEST_quiet ]]
then print -nu2 "$LABEL"
fi
(trap '' PIPE; cat <$TWD/INPUT 2>/dev/null; exit 0) | COMMAND "${ARGS[@]}" | cat >$TWD/OUTPUT
STATUS=$?
RESULTS 'pipe io'
fi
fi
set -- $COPY
COPY=""
while :
do case $ 0|1) break ;;
*) cp $1 $2 ;;
esac
shift 2
done
set -- $MOVE
MOVE=""
while (( $ do mv $1 $2
shift 2
done
fi
for i in $DONE
do $i $TEST DONE $STATUS
done
COMPARE=""
r=0
;;
esac
if [[ $COMMAND_ORIG ]]
then COMMAND=$COMMAND_ORIG
COMMAND_ORIG=
ARGS=(${ARGS_ORIG[@]})
fi
return $r
}
function DO {
[[ $GROUP == $TEST_select ]] || return 1
(( COND_SKIP[COND] )) && return 1
[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK
return 0
}
function UNIT {
typeset cmd=$1
case $cmd in
[-+]) shift
if (( UNIT_READONLY ))
then COMMAND="$COMMAND $*"
else set -- "${ARGV[@]}" "$@"
ARGV=("$@")
fi
return
;;
esac
(( UNIT_READONLY )) && return
if [[ $UNIT ]] && (( $ then set -- "${ARGV[@]}"
case $1 in
"") set -- "$cmd" ;;
[-+]*) set -- "$cmd" "${ARGV[@]}" ;;
*) cmd=$1 ;;
esac
fi
UNIT=
COMMAND=$cmd
shift
typeset cmd=$(whence $COMMAND)
if [[ ! $cmd ]]
then FATAL $COMMAND: not found
elif [[ ! $cmd ]]
then FATAL $cmd: not found
fi
case $ 0) ;;
*) COMMAND="$COMMAND $*" ;;
esac
}
function TWD {
case $1 in
'') TWD=${TWD /*) TWD=$1 ;;
*) TWD=${TMPDIR:-/tmp}/$1 ;;
esac
}
function TEST {
RUN
LINE=$TESTLINE
UNWIND
COUNT=0
LASTITEM=0
case $1 in
-) ((LAST++)); TEST=$LAST ;;
+([0123456789])) LAST=$1 TEST=$1 ;;
*) LAST=0${1/[!0123456789]/} TEST=$1 ;;
esac
NOTE=
if [[ ! $TEST_quiet && $TEST == $TEST_select ]] && (( ! COND_SKIP[COND] ))
then print -r -u2 "$TEST $2"
fi
unset ARGS
unset EXPORT
EXPORTS=0
TEST_file=""
if [[ $TEST != ${GROUP}* ]]
then GROUP=${TEST%%+([abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ])}
if [[ $GROUP == $TEST_select ]] && (( ! COND_SKIP[COND] ))
then INITIALIZE
fi
fi
((SUBTESTS=0))
[[ $TEST == $TEST_select ]] && (( ! COND_SKIP[COND] ))
}
function EXEC {
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
if ((SUBTESTS++))
then RUN
fi
case $ 0) set -- "${ARGS[@]}" ;;
esac
ITEM=$LINE
NOTE="$(print -r -f '%q ' -- $COMMAND_ORIG "$@")${JOBPID:+&}"
ARGS=("$@")
}
function JOB {
JOBPID=0
EXEC "$@"
}
function CONTINUE
{
RUN || return
JOBOP=CONTINUE
ITEM=$LINE
NOTE="$(print -r -f '%q ' -- $JOBOP)"
}
function EXITED
{
RUN || return
JOBOP=EXITED
ITEM=$LINE
NOTE="$(print -r -f '%q ' -- $JOBOP)"
}
function KILL {
RUN || return
JOBOP=$2
[[ $JOBOP ]] || JOBOP=KILL
ITEM=$LINE
NOTE="$(print -r -f '%q ' -- $JOBOP)"
}
function CD
{
RUN
if [[ $GROUP == $TEST_select ]] && (( ! COND_SKIP[COND] ))
then mkdir -p "$@" && cd "$@" || FATAL cannot initialize working directory "$@"
fi
}
function EXPORT
{
typeset x n v
if [[ $GROUP == INIT ]]
then for x
do n=${x%%=*}
v=${x ENVIRON[ENVIRONS++]=$n="'$v'"
done
else RUN
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
for x
do n=${x%%=*}
v=${x EXPORT[EXPORTS++]=$n="'$v'"
done
fi
}
function FLUSH
{
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
if ((SUBTESTS++))
then RUN
fi
}
function PROG {
typeset command args
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
ITEM=$LINE
NOTE="$(print -r -f '%q ' -- "$@")"
COMMAND_ORIG=$COMMAND
COMMAND=$1
shift
ARGS_ORIG=(${ARGS[@]})
ARGS=("$@")
}
function NOTE {
NOTE=$*
}
function IO {
typeset op i v f file type x
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
while :
do case $1 in
FIFO|PIPE) type=$1; shift ;;
*) break ;;
esac
done
op=$1
shift
[[ $type ]] && TYPE[$op]=$type
file=$TWD/$op
while :
do case $1 in
-x) x=1; shift ;;
-f*|-n) f=$1; shift ;;
*) break ;;
esac
done
case $ 0) ;;
*) case $1 in
-) ;;
*) file=$1
eval i='$'$op
case " $i " in
*" $file "*)
;;
*) eval $op='"$'$op' $file"'
;;
esac
;;
esac
shift
;;
esac
case " $IGNORE " in
*" $file "*)
for i in $IGNORE
do case $i in
$file) ;;
*) v="$v $i" ;;
esac
done
IGNORE=$v
;;
esac
FILE[$op]=$file
case $op in
OUTPUT|ERROR)
file=$file.ex
if [[ $file != /* ]]
then file=$TWD/$file
fi
;;
esac
SAME[$op]=
if [[ $file == /* ]]
then RM $file.sav
else RM $TWD/$file.sav
fi
if [[ $file == */* ]]
then mkdir -p ${file%/*}
fi
if [[ $file != */ ]]
then if [[ $type == FIFO ]]
then rm -f $file
mkfifo $file
fi
if [[ ${TYPE[$op]} != FIFO ]]
then if [[ $JOBOP ]]
then case $ 0:) ;;
*:-f) printf -- "$@" ;;
*:-f*) printf -- "${f#-f}""$@" ;;
*) print $f -r -- "$@" ;;
esac >> $file
else case $ 0:) ;;
*:-f) printf -- "$@" ;;
*:-f*) printf -- "${f#-f}""$@" ;;
*) print $f -r -- "$@" ;;
esac > $file
fi
elif [[ $ then case $ *:-f) printf -- "$@" ;;
*:-f*) printf -- "${f#-f}""$@" ;;
*) print $f -r -- "$@" ;;
esac >> $file &
fi
if [[ $x ]]
then chmod +x $file
fi
fi
}
function INPUT {
IO $0 "$@"
}
function COPY {
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
COPY="$COPY $@"
}
function MOVE {
typeset f
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
for f
do case $f in
INPUT|OUTPUT|ERROR)
f=$TWD/$f
;;
/*) ;;
*) f=$PWD/$f
;;
esac
MOVE="$MOVE $f"
done
}
function SAME {
typeset i file v
if [[ $GROUP != $TEST_select ]] || (( COND_SKIP[COND] ))
then return
fi
case $ 2) case $1 in
INPUT) cat $2 > $1; return ;;
esac
SAME[$1]=$2
file=$1
COMPARE="$COMPARE $1"
;;
3) SAME[$2]=$3
file=$2
eval i='$'$1
case " $i " in
*" $2 "*)
;;
*) eval $1='"$'$1' $2"'
;;
esac
COMPARE="$COMPARE $2"
;;
esac
case " $IGNORE " in
*" $file "*)
for i in $IGNORE
do case $i in
$file) ;;
*) v="$v $i" ;;
esac
done
IGNORE=$v
;;
esac
}
function OUTPUT {
IO $0 "$@"
}
function ERROR {
IO $0 "$@"
}
function RM {
if [[ ! $TEST_rmu ]]
then chmod -R u+rwx "$@" >/dev/null 2>&1
fi
rm $TEST_rmu $TEST_rmflags "$@"
}
function REMOVE {
typeset i
for i
do RM $i $i.sav
done
}
function IGNORE {
typeset i
for i
do case $i in
INPUT|OUTPUT|ERROR)
i=$TWD/$i
;;
esac
case " $IGNORE " in
*" $i "*)
;;
*) IGNORE="$IGNORE $i"
;;
esac
done
}
function KEEP {
typeset i
for i
do case $KEEP in
"") KEEP="$i" ;;
*) KEEP="$KEEP|$i" ;;
esac
done
}
function DIAGNOSTICS {
case $ 0:|1:1) DIAGNOSTICS=1
EXIT='*'
;;
1:|1:0) DIAGNOSTICS=""
EXIT=0
;;
*) DIAGNOSTICS=$1
EXIT='*'
;;
esac
}
function IGNORESPACE
{
: ${IGNORESPACE=-b}
}
function EXIT {
EXIT=$1
}
function INFO {
typeset -R15 info=$1
if [[ ! $1 ]]
then info=no
fi
shift
if [[ ! $TEST_quiet ]]
then print -r -u2 "$info " "$@"
fi
}
function COMMAND {
typeset input
((TESTS++))
case " ${ENVIRON[*]} ${EXPORT[*]}" in
*' 'LC_ALL=*)
;;
*' 'LC_+([A-Z])=*)
EXPORT[EXPORTS++]="LC_ALL="
;;
esac
if [[ $TEST_keep ]]
then (
PS4=''
set -x
print -r -- "${ENVIRON[@]}" "${EXPORT[@]}" "PATH=$PATH" $COMMAND "$@"
) 2>&1 >/dev/null |
sed -e 's,^print -r -- ,,' -e 's,$, "$@",' >$TWD/COMMAND
chmod +x $TWD/COMMAND
fi
if [[ $UMASK != $UMASK_ORIG ]]
then : >$TWD/ERROR
umask $UMASK
fi
if [[ ${TYPE[INPUT]} == FIFO && ${FILE[INPUT]} == */INPUT ]]
then input="< ${FILE[INPUT]}"
fi
if [[ $TEST_trace ]]
then set +x
eval print -u2 "$PS4" "${ENVIRON[@]}" "${EXPORT[@]}" PATH='$PATH' '$'COMMAND '"$@"' '$input' '"2>$TWD/ERROR"' '"${JOBPID:+&}"'
fi
eval "${ENVIRON[@]}" "${EXPORT[@]}" PATH='$PATH' '$'COMMAND '"$@"' $input "2>$TWD/ERROR" "${JOBPID:+&}"
STATUS=$?
[[ $TEST_trace ]] && set -x
if [[ $JOBPID ]]
then JOBPID=$!
fi
[[ $UMASK != $UMASK_ORIG ]] && umask $UMASK_ORIG
return $STATUS
}
function RESULTS {
typeset i j k s failed ignore io
if [[ $1 ]]
then io="$1 "
fi
[[ $JOBOP || $JOBPID || $JOBSTATUS ]] && sleep 1
for i in $COMPARE $TWD/OUTPUT $TWD/ERROR
do case " $IGNORE $ignore $MOVE " in
*" $i "*) continue ;;
esac
ignore="$ignore $i"
j=${SAME[${i if [[ ! $j ]]
then if [[ $i == /* ]]
then k=$i
else k=$TWD/$i
fi
for s in ex sav err
do [[ -f $k.$s ]] && break
done
j=$k.$s
fi
if [[ "$DIAGNOSTICS" && $i == */ERROR ]]
then if [[ $STATUS == 0 && ! -s $TWD/ERROR || $DIAGNOSTICS != 1 && $(<$i) != $DIAGNOSTICS ]]
then failed=$failed${failed:+,}DIAGNOSTICS
if [[ $TEST_verbose && $DIAGNOSTICS != 1 ]]
then print -u2 " ===" "diagnostic pattern '$DIAGNOSTICS' did not match" ${i cat $i >&2
fi
fi
continue
fi
diff $IGNORESPACE $i $j >$i.diff 2>&1
if [[ -s $i.diff ]]
then failed=$failed${failed:+,}${i if [[ $TEST_verbose ]]
then print -u2 " ===" diff $IGNORESPACE ${i cat $i.diff >&2
fi
fi
done
if [[ $JOBOP ]]
then if [[ $JOBPID ]] && ! kill -0 $JOBPID 2>/dev/null
then wait $JOBPID
JOBSTATUS=$?
JOBPID=
fi
case $JOBOP in
CONTINUE)
if [[ ! $JOBPID ]]
then failed=$failed${failed:+,}EXITED
fi
;;
EXITED) if [[ $JOBPID ]]
then failed=$failed${failed:+,}RUNNING
fi
;;
*) if [[ ! $JOBPID ]]
then failed=$failed${failed:+,}EXITED
fi
if ! kill -$JOBOP $JOBPID 2>/dev/null
then failed=$failed${failed:+,}KILL-$JOBOP
fi
;;
esac
JOBOP=
fi
if [[ ! $failed && $STATUS != $EXIT ]]
then failed="exit code $EXIT expected -- got $STATUS"
fi
if [[ $failed ]]
then ((ERRORS++))
if [[ ! $TEST_quiet ]]
then SHOW="FAILED ${io}[ $failed ] $NOTE"
print -r -u2 " $SHOW"
fi
if [[ $TEST_keep ]]
then GROUP=FINI
exit
fi
elif [[ ! $TEST_quiet ]]
then SHOW=$NOTE
print -r -u2 " $SHOW"
fi
}
function SET {
typeset i r
if [[ $TEST ]]
then RUN
fi
for i
do if [[ $i == - ]]
then r=1
elif [[ $i == + ]]
then r=
else if [[ $i == no?* ]]
then i=${i v=
elif [[ $i == *=* ]]
then v=${i if [[ $v == 0 ]]
then v=
fi
i=${i%%=*}
else v=1
fi
i=${i//-/_}
if [[ $r ]]
then READONLY[$i]=1
elif [[ ${READONLY[$i]} ]]
then continue
fi
eval TEST_$i=$v
fi
done
}
function VIEW {
nameref var=$1
typeset i bwd file pwd view root offset
if [[ $var ]]
then return 0
fi
case $ 1) file=$1 ;;
*) file=$2 ;;
esac
pwd=${TWD%/*}
bwd=${PMP%/*}
if [[ -r $file ]]
then if [[ ! -d $file ]]
then var=$PWD/$file
return 0
fi
for i in $file/*
do if [[ -r $i ]]
then var=$PWD/$file
return 0
fi
break
done
fi
for view in ${VIEWS[@]}
do case $view in
/*) ;;
*) view=$pwd/$view ;;
esac
case $offset in
'') case $pwd in
$view/*) offset=${pwd *) offset=${bwd esac
;;
esac
if [[ -r $view$offset/$file ]]
then if [[ ! -d $view$offset/$file ]]
then var=$view$offset/$file
return 0
fi
for i in $view$offset/$file/*
do if [[ -f $i ]]
then var=$view$offset/$file
return 0
fi
break
done
fi
done
var=
return 1
}
function INCLUDE {
typeset f v
for f
do if VIEW v $f || [[ $PREFIX && $f != /* ]] && VIEW v $PREFIX$f
then . $v
else FATAL $f: not found
fi
done
}
function UMASK {
if (( $ then UMASK=$1
else UMASK=$UMASK_ORIG
fi
}
function PIPE {
IO $0 "$@"
}
function FIFO {
IO $0 "$@"
}
function IF {
[[ $GROUP == $TEST_select ]] || return
RUN
(( COND++ ))
COND_LINE[COND]=$LINE
if (( COND > 1 && COND_SKIP[COND-1] ))
then (( COND_KEPT[COND] = 1 ))
(( COND_SKIP[COND] = 1 ))
elif eval "{ $1 ;} >/dev/null 2>&1"
then (( COND_KEPT[COND] = 1 ))
(( COND_SKIP[COND] = 0 ))
[[ $2 && ! $TEST_quiet ]] && print -u2 "NOTE $2"
else (( COND_KEPT[COND] = 0 ))
(( COND_SKIP[COND] = 1 ))
fi
}
function ELIF {
[[ $GROUP == $TEST_select ]] || return
RUN
if (( COND <= 0 ))
then FATAL line $LINE: no matching IF for ELIF
fi
if (( COND_KEPT[COND] ))
then (( COND_SKIP[COND] = 0 ))
elif eval "$* > /dev/null 2>&1"
then (( COND_KEPT[COND] = 1 ))
(( COND_SKIP[COND] = 0 ))
[[ $2 && ! $TEST_quiet ]] && print -u2 "NOTE $2"
else (( COND_SKIP[COND] = 1 ))
fi
}
function ELSE {
[[ $GROUP == $TEST_select ]] || return
RUN
if (( COND <= 0 ))
then FATAL line $LINE: no matching IF for ELSE
fi
if (( COND_KEPT[COND] ))
then (( COND_SKIP[COND] = 1 ))
else (( COND_KEPT[COND] = 1 ))
(( COND_SKIP[COND] = 0 ))
[[ $1 && ! $TEST_quiet ]] && print -u2 "NOTE $1"
fi
}
function FI
{
[[ $GROUP == $TEST_select ]] || return
RUN
if (( COND <= 0 ))
then FATAL line $LINE: no matching IF for FI on line $LINE
fi
(( ! COND_KEPT[COND] )) && [[ $1 && ! $TEST_quiet ]] && print -u2 "NOTE $1"
(( COND-- ))
}
integer ERRORS=0 ENVIRONS=0 EXPORTS=0 TESTS=0 SUBTESTS=0 LINE=0 TESTLINE=0
integer ITEM=0 LASTITEM=0 COND=0 UNIT_READONLY=0 COUNT
typeset ARGS COMMAND COPY DIAGNOSTICS ERROR EXEC FLUSHED=0 GROUP=INIT
typeset IGNORE INPUT KEEP OUTPUT TEST SOURCE MOVE NOTE UMASK UMASK_ORIG
typeset ARGS_ORIG COMMAND_ORIG TITLE UNIT ARGV PREFIX OFFSET IGNORESPACE
typeset COMPARE MAIN JOBPID='' JOBSTATUS=''
typeset TEST_file TEST_keep TEST_pipe_input TEST_pipe_io TEST_pipe_output TEST_local
typeset TEST_quiet TEST_regular=1 TEST_rmflags='-rf --' TEST_rmu TEST_select
typeset -A SAME VIEWS FILE TYPE READONLY
typeset -a COND_LINE COND_SKIP COND_KEPT ENVIRON EXPORT
typeset -Z LAST=00
unset FIGNORE
while getopts -a $command "$USAGE" OPT
do case $OPT in
b) (( $OPTARG )) && IGNORESPACE=-b
;;
i) SET - pipe-input=$OPTARG
;;
k) SET - keep=$OPTARG
;;
l) SET - local
;;
o) SET - pipe-output=$OPTARG
;;
p) SET - pipe-io=$OPTARG
;;
q) SET - quiet=$OPTARG
;;
r) SET - regular=$OPTARG
;;
t) if [[ $TEST_select ]]
then TEST_select="$TEST_select|${OPTARG//,/\|}"
else TEST_select="${OPTARG//,/\|}"
fi
;;
x) SET - trace=$OPTARG
;;
v) SET - verbose=$OPTARG
;;
*) GROUP=FINI
exit 2
;;
esac
done
shift $OPTIND-1
case $0) FATAL test unit name omitted ;;
esac
export COLUMNS=80
SOURCE=$PWD
PATH=$SOURCE:${PATHPATH=${PATH%%:?(.)}:/usr/5bin:/bin:/usr/bin
UNIT=$1
shift
if [[ -f $UNIT && ! -x $UNIT ]]
then REGRESS=$UNIT
else REGRESS=${UNIT%.tst}
REGRESS=$REGRESS.tst
[[ -f $REGRESS ]] || FATAL $REGRESS: regression tests not found
fi
UNIT=${UNITUNIT=${UNIT%.tst}
MAIN=$UNIT
if [[ $VPATH ]]
then set -A VIEWS ${VPATH//:/' '}
OFFSET=${SOURCE if [[ $OFFSET ]]
then OFFSET=${OFFSET fi
fi
if [[ $REGRESS == */* ]]
then PREFIX=${REGRESS%/*}
if [[ ${ then for i in ${VIEWS[@]}
do PREFIX=${PREFIX done
fi
PREFIX=${PREFIX if [[ $PREFIX ]]
then PREFIX=$PREFIX/
fi
fi
TWD=$PWD/$UNIT.tmp
PMP=$(pwd -P)/$UNIT.tmp
UMASK_ORIG=$(umask)
UMASK=$UMASK_ORIG
ARGV=("$@")
if [[ ${ARGV[0]} && ${ARGV[0]} != [-+]* ]]
then UNIT "${ARGV[@]}"
UNIT_READONLY=1
fi
trap 'code=$?; CLEANUP $code' EXIT
if [[ ! $TEST_select ]]
then TEST_select="[0123456789]*"
fi
TEST_select="@($TEST_select|+(0))"
if [[ $TEST_trace ]]
then export PS4=':$LINENO: '
typeset -ft $(typeset +f)
set -x
fi
if [[ $TEST_verbose ]]
then typeset SHOW
else typeset -L70 SHOW
fi
if [[ $TEST_keep ]] && (ulimit -c 0) >/dev/null 2>&1
then ulimit -c 0
fi
set --pipefail
alias BODY='BODY=BODY; function BODY'
alias CONTINUE='LINE=$LINENO; CONTINUE'
alias DO='(( $ITEM != $FLUSHED )) && RUN DO; DO &&'
alias DONE='DONE=DONE; function DONE'
alias EXEC='LINE=$LINENO; EXEC'
alias EXITED='LINE=$LINENO; EXITED'
alias INIT='INIT=INIT; function INIT'
alias JOB='LINE=$LINENO; JOB'
alias KILL='LINE=$LINENO; KILL'
alias PROG='LINE=$LINENO; FLUSH; PROG'
alias TEST='TESTLINE=$LINENO; TEST'
alias IF='LINE=$LINENO; FLUSH; IF'
alias ELIF='LINE=$LINENO; FLUSH; ELIF'
alias ELSE='LINE=$LINENO; FLUSH; ELSE'
alias FI='LINE=$LINENO; FLUSH; FI'
. $REGRESS
RUN
GROUP=FINI