diff --git a/Test/Makefile.in b/Test/Makefile.in new file mode 100644 index 000000000..d7ce57b8a --- /dev/null +++ b/Test/Makefile.in @@ -0,0 +1,62 @@ +# +# Makefile for Test subdirectory +# +# Copyright (c) 1999 Peter Stephensons +# All rights reserved. +# +# Permission is hereby granted, without written agreement and without +# license or royalty fees, to use, copy, modify, and distribute this +# software and to distribute modified versions of this software for any +# purpose, provided that the above copyright notice and the following +# two paragraphs appear in all copies of this software. +# +# In no event shall Peter Stephenson or the Zsh Development Group be liable +# to any party for direct, indirect, special, incidental, or consequential +# damages arising out of the use of this software and its documentation, +# even if Peter Stephenson and the Zsh Development Group have been advised of +# the possibility of such damage. +# +# Peter Stephenson and the Zsh Development Group specifically disclaim any +# warranties, including, but not limited to, the implied warranties of +# merchantability and fitness for a particular purpose. The software +# provided hereunder is on an "as is" basis, and Peter Stephenson and the +# Zsh Development Group have no obligation to provide maintenance, +# support, updates, enhancements, or modifications. +# + +subdir = Test +dir_top = .. +SUBDIRS = + +@VERSION_MK@ + +# source/build directories +VPATH = @srcdir@ +sdir = @srcdir@ +sdir_top = @top_srcdir@ +INSTALL = @INSTALL@ + +@DEFS_MK@ + +# ========== DEPENDENCIES FOR TESTING ========== + +tests: + for f in *.ztst; do \ + ../Src/zsh ztst.zsh $$f; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +@CLEAN_MK@ + +mostlyclean-here: + rm -rf *.tmp + +distclean-here: + rm -f Makefile + +realclean-here: + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +@CONFIG_MK@ diff --git a/Test/cd.ztst b/Test/cd.ztst new file mode 100644 index 000000000..142628ae9 --- /dev/null +++ b/Test/cd.ztst @@ -0,0 +1,97 @@ +# This file serves as a model for how to write tests, so is more heavily +# commented that the others. All tests are run in the Test subdirectory +# of the distribution, which must be writable. They should end with +# the suffix `.ztst': this is not required by the test harness itself, +# but it is needed by the Makefile to run all the tests. + +# Blank lines with no other special meaning (e.g. separating chunks of +# code) and all those with a `#' in the first column are ignored. + +# All section names start with a % in the first column. The names +# must be in the expected order, though not all sections are required. +# The sections are %prep (preparatory setup: code executed should return +# status 0, but no other tests are performed), %test (the main tests), and +# %clean (to cleanup: the code is simply unconditionally executed). +# +# Literal shell code to be evaluated must be indented with any number +# of spaces and/or tabs, to differentiate it from tags with a special +# meaning to the test harness. Note that this is true even in sections +# where there are no such tags. Also note that file descriptor 9 +# is reserved for input from the test script; if ZTST_verbose is set, +# output is sent to the original stdout via fd 8. Option settings +# are preserved between the execution of different code chunks; +# initially, all standard zsh options (the effect of `emulate -R zsh') +# are set. + +%prep +# This optional section prepares the test, creating directories and files +# and so on. Chunks of code are separated by blank lines (which is not +# necessary before the end of the section); each chunk of code is evaluated +# in one go and must return status 0, or the preparation is deemed to have +# failed and the test ends with an appropriate error message. Standard +# output from this section is redirected to /dev/null, but standard error +# is not redirected. +# +# Tests should use subdirectories ending in `.tmp'. These will be +# removed with all the contents even if the test is aborted. + mkdir cdtst.tmp cdtst.tmp/real cdtst.tmp/sub + + ln -s ../real cdtst.tmp/sub/fake + + mydir=$PWD + +%test +# This is where the tests are run. It consists of blocks separated +# by blank lines. Each block has the same format and there may be any +# number of them. It consists of indented code, plus optional sets of lines +# beginning '<', '>' and '?' which may appear in any order. These correspond +# to stdin (fed to the code), stdout (compared with code output) and +# stderr (compared with code error output) respectively. These subblocks +# may occur in any order, but the natural one is: code, stdin, stdout, +# stderr. +# +# The rules for '<', '>' and '?' lines are the same: only the first +# character is stripped, with subsequent whitespace being significant; +# lines are subject to ordinary quoted shell expansion (i.e. not globbing). +# +# Each chunk of indented code is to be evaluated in one go and is to +# be followed by a line starting (in the first column) with +# the expected status returned by the code when run, or - if it is +# irrelevant. This can be followed by a `:' and a message describing the +# test, which will be printed if the test fails, along with a +# description of the failure that occurred. The `:' and message are +# optional, but highly recommended. +# +# If either or both of the '>' and '?' sets of lines is absent, it is +# assumed the corresponding output should be empty and it is an error if it +# is not. If '<' is empty, stdin is an empty (but opened) file. +# +# TODO: flags to the post-code status line indicating that diffs are +# not to be performed. + cd cdtst.tmp/sub/fake && + pwd && + print $PWD +0:Preserving symbolic links in the current directory string +>$mydir/cdtst.tmp/sub/fake +>$mydir/cdtst.tmp/sub/fake + + cd ../../.. && + pwd && + print $PWD +0:Changing directory up through symbolic links without following them +>$mydir +>$mydir + + setopt chaselinks + cd cdtst.tmp/sub/fake && + pwd && + print $PWD +0:Resolving symbolic links with chaselinks set +>$mydir/cdtst.tmp/real +>$mydir/cdtst.tmp/real + +%clean +# This optional section cleans up after the test, if necessary, +# e.g. killing processes etc. This is in addition to the removal of *.tmp +# subdirectories. This is essentially like %prep, except that status +# return values are ignored. diff --git a/Test/ztst.zsh b/Test/ztst.zsh new file mode 100755 index 000000000..d3d03e883 --- /dev/null +++ b/Test/ztst.zsh @@ -0,0 +1,316 @@ +#!/usr/local/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. +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 + +# 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 -i 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 + +# Temporary files for redirection inside tests. +ZTST_in=${TMPPREFIX-:/tmp/zsh}.ztst.in.$$ +# hold the expected output +ZTST_out=${TMPPREFIX-:/tmp/zsh}.ztst.out.$$ +ZTST_err=${TMPPREFIX-:/tmp/zsh}.ztst.err.$$ +# hold the actual output from the test +ZTST_tout=${TMPPREFIX-:/tmp/zsh}.ztst.tout.$$ +ZTST_terr=${TMPPREFIX-:/tmp/zsh}.ztst.terr.$$ + +ZTST_cleanup() { + rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp \ + $ZTST_in $ZTST_out $ZTST_err $ZTST_tout $ZTST_terr +} + +# 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 "Test $ZTST_testname failed: $1" + if [[ -n $ZTST_message ]]; then + print "Was testing: $ZTST_message" + fi + ZTST_cleanup + exit 1 +} + +# Print messages if $ZTST_verbose is non-empty +ZTST_verbose() { + local lev=$1 + shift + [[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]] && print $* >&8 +} + +[[ ! -r $ZTST_testname ]] && ZTST_testfailed "can't read test file." + +[[ -n $ZTST_verbose && $ZTST_verbose -ge 0 ]] && exec 8>&1 +exec 9<$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 ZTST_curline <&9 || 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. +ZTST_getsect() { + local match mbegin mend + + while [[ $ZTST_curline != '%'(#b)([[:alnum:]]##)* ]]; do + ZTST_getline || return 1 + [[ $ZTST_curline = [[:blank:]]# ]] && continue + if [[ $ZTST_curline != '%'[[:alnum:]]##* ]]; then + ZTST_testfailed "bad line found before or after section: +$ZTST_curline" + 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]} + 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" +} + +# Execute an indented chunk. Redirections will already have +# been set up, but we need to handle the options. +ZTST_execchunk() { + options=($ZTST_testopts) + eval "$ZTST_code" + ZTST_status=$? + ZTST_verbose 2 "ZTST_execchunk: status $ZTST_status" + ZTST_testopts=(${(kv)options}) + options=($ZTST_mainopts) + 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 ]] || + ZTST_testfailed "non-zero status from preparation code: +$ZTST_code" + done +} + +ZTST_test() { + local last match mbegin mend found + + while true; do + rm -f $ZTST_in $ZTST_out $ZTST_err + touch $ZTST_in $ZTST_out $ZTST_err + ZTST_message='' + found=0 + + 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 + [[ $ZTST_curline != [-0-9]* ]] && + ZTST_testfailed "expecting test status at: +$ZTST_curline" + ZTST_xstatus=$ZTST_curline + if [[ $ZTST_curline == (#b)([^:]##):(*) ]]; then + ZTST_xstatus=$match[1] + ZTST_message=$match[2] + fi + ZTST_getline + found=1 + ;; + '<'*) ZTST_getredir + print -r "${(e)ZTST_redir}" >>$ZTST_in + found=1 + ;; + '>'*) ZTST_getredir + print -r "${(e)ZTST_redir}" >>$ZTST_out + found=1 + ;; + '?'*) ZTST_getredir + print -r "${(e)ZTST_redir}" >>$ZTST_err + found=1 + ;; + *) ZTST_testfailed "bad line in test block: +$ZTST_curline" + ;; + esac + done + + # If we found some code to execute... + if [[ -n $ZTST_code ]]; then + ZTST_verbose 1 "Running test: +$ZTST_message" + ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus" + + ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr + + # 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" + 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 ! diff -c $ZTST_out $ZTST_tout; then + ZTST_testfailed "output differs from expected as shown above for: +$ZTST_code" + fi + if ! diff -c $ZTST_err $ZTST_terr; then + ZTST_testfailed "error output differs from expected as shown above for: +$ZTST_code" + 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) + +# Now go through all the different sections until the end. +while ZTST_getsect; do + case $ZTST_cursect in + prep) if (( ${ZTST_sects[prep]} + ${ZTST_sects[test]} + \ + ${ZTST_sects[clean]} )); then + ZTST_testfailed "\`prep' section must come first" + fi + ZTST_prepclean + ZTST_sects[prep]=1 + ;; + test) + if (( ${ZTST_sects[test]} + ${ZTST_sects[clean]} )); then + ZTST_testfailed "bad placement of \`test' section" + fi + ZTST_test + ZTST_sects[test]=1 + ;; + clean) + if (( ${ZTST_sects[test]} == 0 || ${ZTST_sects[clean]} )); then + ZTST_testfailed "bad use of \`clean' section" + fi + ZTST_prepclean 1 + ZTST_sects[clean]=1 + ;; + *) ZTST_testfailed "bad section name: $ZTST_cursect" + ;; + esac +done + +print "$ZTST_testname: all tests successful." +ZTST_cleanup +exit 0