summaryrefslogtreecommitdiff
path: root/dotfiles/system/.zsh/modules/Test/ztst.zsh
diff options
context:
space:
mode:
Diffstat (limited to 'dotfiles/system/.zsh/modules/Test/ztst.zsh')
-rwxr-xr-xdotfiles/system/.zsh/modules/Test/ztst.zsh547
1 files changed, 547 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Test/ztst.zsh b/dotfiles/system/.zsh/modules/Test/ztst.zsh
new file mode 100755
index 0000000..f172ae1
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Test/ztst.zsh
@@ -0,0 +1,547 @@
+#!/bin/zsh -f
+# The line above is just for convenience. Normally tests will be run using
+# a specified version of zsh. With dynamic loading, any required libraries
+# must already have been installed in that case.
+#
+# Takes one argument: the name of the test file. Currently only one such
+# file will be processed each time ztst.zsh is run. This is slower, but
+# much safer in terms of preserving the correct status.
+# To avoid namespace pollution, all functions and parameters used
+# only by the script begin with ZTST_.
+#
+# Options (without arguments) may precede the test file argument; these
+# are interpreted as shell options to set. -x is probably the most useful.
+
+# Produce verbose messages if non-zero.
+# If 1, produce reports of tests executed; if 2, also report on progress.
+# Defined in such a way that any value from the environment is used.
+: ${ZTST_verbose:=0}
+
+# We require all options to be reset, not just emulation options.
+# Unfortunately, due to the crud which may be in /etc/zshenv this might
+# still not be good enough. Maybe we should trick it somehow.
+emulate -R zsh
+
+# Ensure the locale does not screw up sorting. Don't supply a locale
+# unless there's one set, to minimise problems.
+[[ -n $LC_ALL ]] && LC_ALL=C
+[[ -n $LC_COLLATE ]] && LC_COLLATE=C
+[[ -n $LC_NUMERIC ]] && LC_NUMERIC=C
+[[ -n $LC_MESSAGES ]] && LC_MESSAGES=C
+[[ -n $LANG ]] && LANG=C
+
+# Don't propagate variables that are set by default in the shell.
+typeset +x WORDCHARS
+
+# Set the module load path to correspond to this build of zsh.
+# This Modules directory should have been created by "make check".
+[[ -d Modules/zsh ]] && module_path=( $PWD/Modules )
+# Allow this to be passed down.
+export MODULE_PATH
+
+# We need to be able to save and restore the options used in the test.
+# We use the $options variable of the parameter module for this.
+zmodload zsh/parameter
+
+# Note that both the following are regular arrays, since we only use them
+# in whole array assignments to/from $options.
+# Options set in test code (i.e. by default all standard options)
+ZTST_testopts=(${(kv)options})
+
+setopt extendedglob nonomatch
+while [[ $1 = [-+]* ]]; do
+ set $1
+ shift
+done
+# Options set in main script
+ZTST_mainopts=(${(kv)options})
+
+# We run in the current directory, so remember it.
+ZTST_testdir=$PWD
+ZTST_testname=$1
+
+integer ZTST_testfailed
+
+# This is POSIX nonsense. Because of the vague feeling someone, somewhere
+# may one day need to examine the arguments of "tail" using a standard
+# option parser, every Unix user in the world is expected to switch
+# to using "tail -n NUM" instead of "tail -NUM". Older versions of
+# tail don't support this.
+tail() {
+ emulate -L zsh
+
+ if [[ -z $TAIL_SUPPORTS_MINUS_N ]]; then
+ local test
+ test=$(echo "foo\nbar" | command tail -n 1 2>/dev/null)
+ if [[ $test = bar ]]; then
+ TAIL_SUPPORTS_MINUS_N=1
+ else
+ TAIL_SUPPORTS_MINUS_N=0
+ fi
+ fi
+
+ integer argi=${argv[(i)-<->]}
+
+ if [[ $argi -le $# && $TAIL_SUPPORTS_MINUS_N = 1 ]]; then
+ argv[$argi]=(-n ${argv[$argi][2,-1]})
+ fi
+
+ command tail "$argv[@]"
+}
+
+# The source directory is not necessarily the current directory,
+# but if $0 doesn't contain a `/' assume it is.
+if [[ $0 = */* ]]; then
+ ZTST_srcdir=${0%/*}
+else
+ ZTST_srcdir=$PWD
+fi
+[[ $ZTST_srcdir = /* ]] || ZTST_srcdir="$ZTST_testdir/$ZTST_srcdir"
+
+# Set the function autoload paths to correspond to this build of zsh.
+fpath=( $ZTST_srcdir/../Functions/*~*/CVS(/)
+ $ZTST_srcdir/../Completion
+ $ZTST_srcdir/../Completion/*/*~*/CVS(/) )
+
+: ${TMPPREFIX:=/tmp/zsh}
+ZTST_tmp=${TMPPREFIX}.ztst.$$
+if ! rm -f $ZTST_tmp || ! mkdir -p $ZTST_tmp || ! chmod go-w $ZTST_tmp; then
+ print "Can't create $ZTST_tmp for exclusive use." >&2
+ exit 1
+fi
+# Temporary files for redirection inside tests.
+ZTST_in=${ZTST_tmp}/ztst.in
+# hold the expected output
+ZTST_out=${ZTST_tmp}/ztst.out
+ZTST_err=${ZTST_tmp}/ztst.err
+# hold the actual output from the test
+ZTST_tout=${ZTST_tmp}/ztst.tout
+ZTST_terr=${ZTST_tmp}/ztst.terr
+
+ZTST_cleanup() {
+ cd $ZTST_testdir
+ rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp(N) ${ZTST_tmp}
+}
+
+# This cleanup always gets performed, even if we abort. Later,
+# we should try and arrange that any test-specific cleanup
+# always gets called as well.
+##trap 'print cleaning up...
+##ZTST_cleanup' INT QUIT TERM
+# Make sure it's clean now.
+rm -rf dummy.tmp *.tmp
+
+# Report failure. Note that all output regarding the tests goes to stdout.
+# That saves an unpleasant mixture of stdout and stderr to sort out.
+ZTST_testfailed() {
+ print -r "Test $ZTST_testname failed: $1"
+ if [[ -n $ZTST_message ]]; then
+ print -r "Was testing: $ZTST_message"
+ fi
+ print -r "$ZTST_testname: test failed."
+ if [[ -n $ZTST_failmsg ]]; then
+ print -r "The following may (or may not) help identifying the cause:
+$ZTST_failmsg"
+ fi
+ ZTST_testfailed=1
+ return 1
+}
+
+# Print messages if $ZTST_verbose is non-empty
+ZTST_verbose() {
+ local lev=$1
+ shift
+ if [[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]]; then
+ print -r -u $ZTST_fd -- $*
+ fi
+}
+ZTST_hashmark() {
+ if [[ ZTST_verbose -le 0 && -t $ZTST_fd ]]; then
+ print -n -u$ZTST_fd -- ${(pl:SECONDS::\#::\#\r:)}
+ fi
+ (( SECONDS > COLUMNS+1 && (SECONDS -= COLUMNS) ))
+}
+
+if [[ ! -r $ZTST_testname ]]; then
+ ZTST_testfailed "can't read test file."
+ exit 1
+fi
+
+exec {ZTST_fd}>&1
+exec {ZTST_input}<$ZTST_testname
+
+# The current line read from the test file.
+ZTST_curline=''
+# The current section being run
+ZTST_cursect=''
+
+# Get a new input line. Don't mangle spaces; set IFS locally to empty.
+# We shall skip comments at this level.
+ZTST_getline() {
+ local IFS=
+ while true; do
+ read -u $ZTST_input -r ZTST_curline || return 1
+ [[ $ZTST_curline == \#* ]] || return 0
+ done
+}
+
+# Get the name of the section. It may already have been read into
+# $curline, or we may have to skip some initial comments to find it.
+# If argument present, it's OK to skip the reset of the current section,
+# so no error if we find garbage.
+ZTST_getsect() {
+ local match mbegin mend
+
+ while [[ $ZTST_curline != '%'(#b)([[:alnum:]]##)* ]]; do
+ ZTST_getline || return 1
+ [[ $ZTST_curline = [[:blank:]]# ]] && continue
+ if [[ $# -eq 0 && $ZTST_curline != '%'[[:alnum:]]##* ]]; then
+ ZTST_testfailed "bad line found before or after section:
+$ZTST_curline"
+ exit 1
+ fi
+ done
+ # have the next line ready waiting
+ ZTST_getline
+ ZTST_cursect=${match[1]}
+ ZTST_verbose 2 "ZTST_getsect: read section name: $ZTST_cursect"
+ return 0
+}
+
+# Read in an indented code chunk for execution
+ZTST_getchunk() {
+ # Code chunks are always separated by blank lines or the
+ # end of a section, so if we already have a piece of code,
+ # we keep it. Currently that shouldn't actually happen.
+ ZTST_code=''
+ # First find the chunk.
+ while [[ $ZTST_curline = [[:blank:]]# ]]; do
+ ZTST_getline || break
+ done
+ while [[ $ZTST_curline = [[:blank:]]##[^[:blank:]]* ]]; do
+ ZTST_code="${ZTST_code:+${ZTST_code}
+}${ZTST_curline}"
+ ZTST_getline || break
+ done
+ ZTST_verbose 2 "ZTST_getchunk: read code chunk:
+$ZTST_code"
+ [[ -n $ZTST_code ]]
+}
+
+# Read in a piece for redirection.
+ZTST_getredir() {
+ local char=${ZTST_curline[1]} fn
+ ZTST_redir=${ZTST_curline[2,-1]}
+ while ZTST_getline; do
+ [[ $ZTST_curline[1] = $char ]] || break
+ ZTST_redir="${ZTST_redir}
+${ZTST_curline[2,-1]}"
+ done
+ ZTST_verbose 2 "ZTST_getredir: read redir for '$char':
+$ZTST_redir"
+
+ case $char in
+ ('<') fn=$ZTST_in
+ ;;
+ ('>') fn=$ZTST_out
+ ;;
+ ('?') fn=$ZTST_err
+ ;;
+ (*) ZTST_testfailed "bad redir operator: $char"
+ return 1
+ ;;
+ esac
+ if [[ $ZTST_flags = *q* && $char = '<' ]]; then
+ # delay substituting output until variables are set
+ print -r -- "${(e)ZTST_redir}" >>$fn
+ else
+ print -r -- "$ZTST_redir" >>$fn
+ fi
+
+ return 0
+}
+
+# Execute an indented chunk. Redirections will already have
+# been set up, but we need to handle the options.
+ZTST_execchunk() {
+ setopt localloops # don't let continue & break propagate out
+ options=($ZTST_testopts)
+ () {
+ unsetopt localloops
+ eval "$ZTST_code"
+ }
+ ZTST_status=$?
+ # careful... ksh_arrays may be in effect.
+ ZTST_testopts=(${(kv)options[*]})
+ options=(${ZTST_mainopts[*]})
+ ZTST_verbose 2 "ZTST_execchunk: status $ZTST_status"
+ return $ZTST_status
+}
+
+# Functions for preparation and cleaning.
+# When cleaning up (non-zero string argument), we ignore status.
+ZTST_prepclean() {
+ # Execute indented code chunks.
+ while ZTST_getchunk; do
+ ZTST_execchunk >/dev/null || [[ -n $1 ]] || {
+ [[ -n "$ZTST_unimplemented" ]] ||
+ ZTST_testfailed "non-zero status from preparation code:
+$ZTST_code" && return 0
+ }
+ done
+}
+
+# diff wrapper
+ZTST_diff() {
+ emulate -L zsh
+ setopt extendedglob
+
+ local diff_out
+ integer diff_pat diff_ret
+
+ case $1 in
+ (p)
+ diff_pat=1
+ ;;
+
+ (d)
+ ;;
+
+ (*)
+ print "Bad ZTST_diff code: d for diff, p for pattern match"
+ ;;
+ esac
+ shift
+
+ if (( diff_pat )); then
+ local -a diff_lines1 diff_lines2
+ integer failed i
+
+ diff_lines1=("${(f)$(<$argv[-2])}")
+ diff_lines2=("${(f)$(<$argv[-1])}")
+ if (( ${#diff_lines1} != ${#diff_lines2} )); then
+ failed=1
+ else
+ for (( i = 1; i <= ${#diff_lines1}; i++ )); do
+ if [[ ${diff_lines2[i]} != ${~diff_lines1[i]} ]]; then
+ failed=1
+ break
+ fi
+ done
+ fi
+ if (( failed )); then
+ print -rl "Pattern match failed:" \<${^diff_lines1} \>${^diff_lines2}
+ diff_ret=1
+ fi
+ else
+ diff_out=$(diff "$@")
+ diff_ret="$?"
+ if [[ "$diff_ret" != "0" ]]; then
+ print -r -- "$diff_out"
+ fi
+ fi
+
+ return "$diff_ret"
+}
+
+ZTST_test() {
+ local last match mbegin mend found substlines
+ local diff_out diff_err
+ local ZTST_skip
+
+ while true; do
+ rm -f $ZTST_in $ZTST_out $ZTST_err
+ touch $ZTST_in $ZTST_out $ZTST_err
+ ZTST_message=''
+ ZTST_failmsg=''
+ found=0
+ diff_out=d
+ diff_err=d
+
+ ZTST_verbose 2 "ZTST_test: looking for new test"
+
+ while true; do
+ ZTST_verbose 2 "ZTST_test: examining line:
+$ZTST_curline"
+ case $ZTST_curline in
+ (%*) if [[ $found = 0 ]]; then
+ break 2
+ else
+ last=1
+ break
+ fi
+ ;;
+ ([[:space:]]#)
+ if [[ $found = 0 ]]; then
+ ZTST_getline || break 2
+ continue
+ else
+ break
+ fi
+ ;;
+ ([[:space:]]##[^[:space:]]*) ZTST_getchunk
+ if [[ $ZTST_curline == (#b)([-0-9]##)([[:alpha:]]#)(:*)# ]]; then
+ ZTST_xstatus=$match[1]
+ ZTST_flags=$match[2]
+ ZTST_message=${match[3]:+${match[3][2,-1]}}
+ else
+ ZTST_testfailed "expecting test status at:
+$ZTST_curline"
+ return 1
+ fi
+ ZTST_getline
+ found=1
+ ;;
+ ('<'*) ZTST_getredir || return 1
+ found=1
+ ;;
+ ('*>'*)
+ ZTST_curline=${ZTST_curline[2,-1]}
+ diff_out=p
+ ;&
+ ('>'*)
+ ZTST_getredir || return 1
+ found=1
+ ;;
+ ('*?'*)
+ ZTST_curline=${ZTST_curline[2,-1]}
+ diff_err=p
+ ;&
+ ('?'*)
+ ZTST_getredir || return 1
+ found=1
+ ;;
+ ('F:'*) ZTST_failmsg="${ZTST_failmsg:+${ZTST_failmsg}
+} ${ZTST_curline[3,-1]}"
+ ZTST_getline
+ found=1
+ ;;
+ (*) ZTST_testfailed "bad line in test block:
+$ZTST_curline"
+ return 1
+ ;;
+ esac
+ done
+
+ # If we found some code to execute...
+ if [[ -n $ZTST_code ]]; then
+ ZTST_hashmark
+ ZTST_verbose 1 "Running test: $ZTST_message"
+ ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus"
+ ZTST_verbose 2 "Input: $ZTST_in, output: $ZTST_out, error: $ZTST_terr"
+
+ ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr
+
+ if [[ -n $ZTST_skip ]]; then
+ ZTST_verbose 0 "Test case skipped: $ZTST_skip"
+ ZTST_skip=
+ if [[ -n $last ]]; then
+ break
+ else
+ continue
+ fi
+ fi
+
+ # First check we got the right status, if specified.
+ if [[ $ZTST_xstatus != - && $ZTST_xstatus != $ZTST_status ]]; then
+ ZTST_testfailed "bad status $ZTST_status, expected $ZTST_xstatus from:
+$ZTST_code${$(<$ZTST_terr):+
+Error output:
+$(<$ZTST_terr)}"
+ return 1
+ fi
+
+ ZTST_verbose 2 "ZTST_test: test produced standard output:
+$(<$ZTST_tout)
+ZTST_test: and standard error:
+$(<$ZTST_terr)"
+
+ # Now check output and error.
+ if [[ $ZTST_flags = *q* && -s $ZTST_out ]]; then
+ substlines="$(<$ZTST_out)"
+ rm -rf $ZTST_out
+ print -r -- "${(e)substlines}" >$ZTST_out
+ fi
+ if [[ $ZTST_flags != *d* ]] && ! ZTST_diff $diff_out -u $ZTST_out $ZTST_tout; then
+ ZTST_testfailed "output differs from expected as shown above for:
+$ZTST_code${$(<$ZTST_terr):+
+Error output:
+$(<$ZTST_terr)}"
+ return 1
+ fi
+ if [[ $ZTST_flags = *q* && -s $ZTST_err ]]; then
+ substlines="$(<$ZTST_err)"
+ rm -rf $ZTST_err
+ print -r -- "${(e)substlines}" >$ZTST_err
+ fi
+ if [[ $ZTST_flags != *D* ]] && ! ZTST_diff $diff_err -u $ZTST_err $ZTST_terr; then
+ ZTST_testfailed "error output differs from expected as shown above for:
+$ZTST_code"
+ return 1
+ fi
+ fi
+ ZTST_verbose 1 "Test successful."
+ [[ -n $last ]] && break
+ done
+
+ ZTST_verbose 2 "ZTST_test: all tests successful"
+
+ # reset message to keep ZTST_testfailed output correct
+ ZTST_message=''
+}
+
+
+# Remember which sections we've done.
+typeset -A ZTST_sects
+ZTST_sects=(prep 0 test 0 clean 0)
+
+print "$ZTST_testname: starting."
+
+# Now go through all the different sections until the end.
+# prep section may set ZTST_unimplemented, in this case the actual
+# tests will be skipped
+ZTST_skipok=
+ZTST_unimplemented=
+while [[ -z "$ZTST_unimplemented" ]] && ZTST_getsect $ZTST_skipok; do
+ case $ZTST_cursect in
+ (prep) if (( ${ZTST_sects[prep]} + ${ZTST_sects[test]} + \
+ ${ZTST_sects[clean]} )); then
+ ZTST_testfailed "\`prep' section must come first"
+ exit 1
+ fi
+ ZTST_prepclean
+ ZTST_sects[prep]=1
+ ;;
+ (test)
+ if (( ${ZTST_sects[test]} + ${ZTST_sects[clean]} )); then
+ ZTST_testfailed "bad placement of \`test' section"
+ exit 1
+ fi
+ # careful here: we can't execute ZTST_test before || or &&
+ # because that affects the behaviour of traps in the tests.
+ ZTST_test
+ (( $? )) && ZTST_skipok=1
+ ZTST_sects[test]=1
+ ;;
+ (clean)
+ if (( ${ZTST_sects[test]} == 0 || ${ZTST_sects[clean]} )); then
+ ZTST_testfailed "bad use of \`clean' section"
+ else
+ ZTST_prepclean 1
+ ZTST_sects[clean]=1
+ fi
+ ZTST_skipok=
+ ;;
+ *) ZTST_testfailed "bad section name: $ZTST_cursect"
+ ;;
+ esac
+done
+
+if [[ -n "$ZTST_unimplemented" ]]; then
+ print "$ZTST_testname: skipped ($ZTST_unimplemented)"
+ ZTST_testfailed=2
+elif (( ! $ZTST_testfailed )); then
+ print "$ZTST_testname: all tests successful."
+fi
+ZTST_cleanup
+exit $(( ZTST_testfailed ))