1#!/bin/zsh -f 2# The line above is just for convenience. Normally tests will be run using 3# a specified version of zsh. With dynamic loading, any required libraries 4# must already have been installed in that case. 5# 6# Takes one argument: the name of the test file. Currently only one such 7# file will be processed each time ztst.zsh is run. This is slower, but 8# much safer in terms of preserving the correct status. 9# To avoid namespace pollution, all functions and parameters used 10# only by the script begin with ZTST_. 11# 12# Options (without arguments) may precede the test file argument; these 13# are interpreted as shell options to set. -x is probably the most useful. 14 15# Produce verbose messages if non-zero. 16# If 1, produce reports of tests executed; if 2, also report on progress. 17# Defined in such a way that any value from the environment is used. 18: ${ZTST_verbose:=0} 19 20# We require all options to be reset, not just emulation options. 21# Unfortunately, due to the crud which may be in /etc/zshenv this might 22# still not be good enough. Maybe we should trick it somehow. 23emulate -R zsh 24 25# Ensure the locale does not screw up sorting. Don't supply a locale 26# unless there's one set, to minimise problems. 27[[ -n $LC_ALL ]] && LC_ALL=C 28[[ -n $LC_COLLATE ]] && LC_COLLATE=C 29[[ -n $LC_NUMERIC ]] && LC_NUMERIC=C 30[[ -n $LC_MESSAGES ]] && LC_MESSAGES=C 31[[ -n $LANG ]] && LANG=C 32 33# Don't propagate variables that are set by default in the shell. 34typeset +x WORDCHARS 35 36# Set the module load path to correspond to this build of zsh. 37# This Modules directory should have been created by "make check". 38[[ -d Modules/zsh ]] && module_path=( $PWD/Modules ) 39# Allow this to be passed down. 40export MODULE_PATH 41 42# We need to be able to save and restore the options used in the test. 43# We use the $options variable of the parameter module for this. 44zmodload -i zsh/parameter 45 46# Note that both the following are regular arrays, since we only use them 47# in whole array assignments to/from $options. 48# Options set in test code (i.e. by default all standard options) 49ZTST_testopts=(${(kv)options}) 50 51setopt extendedglob nonomatch 52while [[ $1 = [-+]* ]]; do 53 set $1 54 shift 55done 56# Options set in main script 57ZTST_mainopts=(${(kv)options}) 58 59# We run in the current directory, so remember it. 60ZTST_testdir=$PWD 61ZTST_testname=$1 62 63integer ZTST_testfailed 64 65# This is POSIX nonsense. Because of the vague feeling someone, somewhere 66# may one day need to examine the arguments of "tail" using a standard 67# option parser, every Unix user in the world is expected to switch 68# to using "tail -n NUM" instead of "tail -NUM". Older versions of 69# tail don't support this. 70tail() { 71 emulate -L zsh 72 73 if [[ -z $TAIL_SUPPORTS_MINUS_N ]]; then 74 local test 75 test=$(echo "foo\nbar" | command tail -n 1 2>/dev/null) 76 if [[ $test = bar ]]; then 77 TAIL_SUPPORTS_MINUS_N=1 78 else 79 TAIL_SUPPORTS_MINUS_N=0 80 fi 81 fi 82 83 integer argi=${argv[(i)-<->]} 84 85 if [[ $argi -le $# && $TAIL_SUPPORTS_MINUS_N = 1 ]]; then 86 argv[$argi]=(-n ${argv[$argi][2,-1]}) 87 fi 88 89 command tail "$argv[@]" 90} 91 92# The source directory is not necessarily the current directory, 93# but if $0 doesn't contain a `/' assume it is. 94if [[ $0 = */* ]]; then 95 ZTST_srcdir=${0%/*} 96else 97 ZTST_srcdir=$PWD 98fi 99[[ $ZTST_srcdir = /* ]] || ZTST_srcdir="$ZTST_testdir/$ZTST_srcdir" 100 101# Set the function autoload paths to correspond to this build of zsh. 102fpath=( $ZTST_srcdir/../Functions/*~*/CVS(/) 103 $ZTST_srcdir/../Completion 104 $ZTST_srcdir/../Completion/*/*~*/CVS(/) ) 105 106: ${TMPPREFIX:=/tmp/zsh} 107# Temporary files for redirection inside tests. 108ZTST_in=${TMPPREFIX}.ztst.in.$$ 109# hold the expected output 110ZTST_out=${TMPPREFIX}.ztst.out.$$ 111ZTST_err=${TMPPREFIX}.ztst.err.$$ 112# hold the actual output from the test 113ZTST_tout=${TMPPREFIX}.ztst.tout.$$ 114ZTST_terr=${TMPPREFIX}.ztst.terr.$$ 115 116ZTST_cleanup() { 117 cd $ZTST_testdir 118 rm -rf $ZTST_testdir/dummy.tmp $ZTST_testdir/*.tmp(N) \ 119 ${TMPPREFIX}.ztst*$$(N) 120} 121 122# This cleanup always gets performed, even if we abort. Later, 123# we should try and arrange that any test-specific cleanup 124# always gets called as well. 125##trap 'print cleaning up... 126##ZTST_cleanup' INT QUIT TERM 127# Make sure it's clean now. 128rm -rf dummy.tmp *.tmp 129 130# Report failure. Note that all output regarding the tests goes to stdout. 131# That saves an unpleasant mixture of stdout and stderr to sort out. 132ZTST_testfailed() { 133 print -r "Test $ZTST_testname failed: $1" 134 if [[ -n $ZTST_message ]]; then 135 print -r "Was testing: $ZTST_message" 136 fi 137 print -r "$ZTST_testname: test failed." 138 if [[ -n $ZTST_failmsg ]]; then 139 print -r "The following may (or may not) help identifying the cause: 140$ZTST_failmsg" 141 fi 142 ZTST_testfailed=1 143 return 1 144} 145 146# Print messages if $ZTST_verbose is non-empty 147ZTST_verbose() { 148 local lev=$1 149 shift 150 if [[ -n $ZTST_verbose && $ZTST_verbose -ge $lev ]]; then 151 print -r -u $ZTST_fd -- $* 152 fi 153} 154ZTST_hashmark() { 155 if [[ ZTST_verbose -le 0 && -t $ZTST_fd ]]; then 156 print -n -u$ZTST_fd -- ${(pl:SECONDS::\#::\#\r:)} 157 fi 158 (( SECONDS > COLUMNS+1 && (SECONDS -= COLUMNS) )) 159} 160 161if [[ ! -r $ZTST_testname ]]; then 162 ZTST_testfailed "can't read test file." 163 exit 1 164fi 165 166exec {ZTST_fd}>&1 167exec {ZTST_input}<$ZTST_testname 168 169# The current line read from the test file. 170ZTST_curline='' 171# The current section being run 172ZTST_cursect='' 173 174# Get a new input line. Don't mangle spaces; set IFS locally to empty. 175# We shall skip comments at this level. 176ZTST_getline() { 177 local IFS= 178 while true; do 179 read -u $ZTST_input -r ZTST_curline || return 1 180 [[ $ZTST_curline == \#* ]] || return 0 181 done 182} 183 184# Get the name of the section. It may already have been read into 185# $curline, or we may have to skip some initial comments to find it. 186# If argument present, it's OK to skip the reset of the current section, 187# so no error if we find garbage. 188ZTST_getsect() { 189 local match mbegin mend 190 191 while [[ $ZTST_curline != '%'(#b)([[:alnum:]]##)* ]]; do 192 ZTST_getline || return 1 193 [[ $ZTST_curline = [[:blank:]]# ]] && continue 194 if [[ $# -eq 0 && $ZTST_curline != '%'[[:alnum:]]##* ]]; then 195 ZTST_testfailed "bad line found before or after section: 196$ZTST_curline" 197 exit 1 198 fi 199 done 200 # have the next line ready waiting 201 ZTST_getline 202 ZTST_cursect=${match[1]} 203 ZTST_verbose 2 "ZTST_getsect: read section name: $ZTST_cursect" 204 return 0 205} 206 207# Read in an indented code chunk for execution 208ZTST_getchunk() { 209 # Code chunks are always separated by blank lines or the 210 # end of a section, so if we already have a piece of code, 211 # we keep it. Currently that shouldn't actually happen. 212 ZTST_code='' 213 # First find the chunk. 214 while [[ $ZTST_curline = [[:blank:]]# ]]; do 215 ZTST_getline || break 216 done 217 while [[ $ZTST_curline = [[:blank:]]##[^[:blank:]]* ]]; do 218 ZTST_code="${ZTST_code:+${ZTST_code} 219}${ZTST_curline}" 220 ZTST_getline || break 221 done 222 ZTST_verbose 2 "ZTST_getchunk: read code chunk: 223$ZTST_code" 224 [[ -n $ZTST_code ]] 225} 226 227# Read in a piece for redirection. 228ZTST_getredir() { 229 local char=${ZTST_curline[1]} fn 230 ZTST_redir=${ZTST_curline[2,-1]} 231 while ZTST_getline; do 232 [[ $ZTST_curline[1] = $char ]] || break 233 ZTST_redir="${ZTST_redir} 234${ZTST_curline[2,-1]}" 235 done 236 ZTST_verbose 2 "ZTST_getredir: read redir for '$char': 237$ZTST_redir" 238 239 case $char in 240 ('<') fn=$ZTST_in 241 ;; 242 ('>') fn=$ZTST_out 243 ;; 244 ('?') fn=$ZTST_err 245 ;; 246 (*) ZTST_testfailed "bad redir operator: $char" 247 return 1 248 ;; 249 esac 250 if [[ $ZTST_flags = *q* && $char = '<' ]]; then 251 # delay substituting output until variables are set 252 print -r -- "${(e)ZTST_redir}" >>$fn 253 else 254 print -r -- "$ZTST_redir" >>$fn 255 fi 256 257 return 0 258} 259 260# Execute an indented chunk. Redirections will already have 261# been set up, but we need to handle the options. 262ZTST_execchunk() { 263 options=($ZTST_testopts) 264 eval "$ZTST_code" 265 ZTST_status=$? 266 # careful... ksh_arrays may be in effect. 267 ZTST_testopts=(${(kv)options[*]}) 268 options=(${ZTST_mainopts[*]}) 269 ZTST_verbose 2 "ZTST_execchunk: status $ZTST_status" 270 return $ZTST_status 271} 272 273# Functions for preparation and cleaning. 274# When cleaning up (non-zero string argument), we ignore status. 275ZTST_prepclean() { 276 # Execute indented code chunks. 277 while ZTST_getchunk; do 278 ZTST_execchunk >/dev/null || [[ -n $1 ]] || { 279 [[ -n "$ZTST_unimplemented" ]] || 280 ZTST_testfailed "non-zero status from preparation code: 281$ZTST_code" && return 0 282 } 283 done 284} 285 286# diff wrapper 287ZTST_diff() { 288 emulate -L zsh 289 setopt extendedglob 290 291 local diff_out 292 integer diff_pat diff_ret 293 294 case $1 in 295 (p) 296 diff_pat=1 297 ;; 298 299 (d) 300 ;; 301 302 (*) 303 print "Bad ZTST_diff code: d for diff, p for pattern match" 304 ;; 305 esac 306 shift 307 308 if (( diff_pat )); then 309 local -a diff_lines1 diff_lines2 310 integer failed i 311 312 diff_lines1=("${(f)$(<$argv[-2])}") 313 diff_lines2=("${(f)$(<$argv[-1])}") 314 if (( ${#diff_lines1} != ${#diff_lines2} )); then 315 failed=1 316 else 317 for (( i = 1; i <= ${#diff_lines1}; i++ )); do 318 if [[ ${diff_lines2[i]} != ${~diff_lines1[i]} ]]; then 319 failed=1 320 break 321 fi 322 done 323 fi 324 if (( failed )); then 325 print -rl "Pattern match failed:" \<${^diff_lines1} \>${^diff_lines2} 326 diff_ret=1 327 fi 328 else 329 diff_out=$(diff "$@") 330 diff_ret="$?" 331 if [[ "$diff_ret" != "0" ]]; then 332 print -r "$diff_out" 333 fi 334 fi 335 336 return "$diff_ret" 337} 338 339ZTST_test() { 340 local last match mbegin mend found substlines 341 local diff_out diff_err 342 343 while true; do 344 rm -f $ZTST_in $ZTST_out $ZTST_err 345 touch $ZTST_in $ZTST_out $ZTST_err 346 ZTST_message='' 347 ZTST_failmsg='' 348 found=0 349 diff_out=d 350 diff_err=d 351 352 ZTST_verbose 2 "ZTST_test: looking for new test" 353 354 while true; do 355 ZTST_verbose 2 "ZTST_test: examining line: 356$ZTST_curline" 357 case $ZTST_curline in 358 (%*) if [[ $found = 0 ]]; then 359 break 2 360 else 361 last=1 362 break 363 fi 364 ;; 365 ([[:space:]]#) 366 if [[ $found = 0 ]]; then 367 ZTST_getline || break 2 368 continue 369 else 370 break 371 fi 372 ;; 373 ([[:space:]]##[^[:space:]]*) ZTST_getchunk 374 if [[ $ZTST_curline == (#b)([-0-9]##)([[:alpha:]]#)(:*)# ]]; then 375 ZTST_xstatus=$match[1] 376 ZTST_flags=$match[2] 377 ZTST_message=${match[3]:+${match[3][2,-1]}} 378 else 379 ZTST_testfailed "expecting test status at: 380$ZTST_curline" 381 return 1 382 fi 383 ZTST_getline 384 found=1 385 ;; 386 ('<'*) ZTST_getredir || return 1 387 found=1 388 ;; 389 ('*>'*) 390 ZTST_curline=${ZTST_curline[2,-1]} 391 diff_out=p 392 ;& 393 ('>'*) 394 ZTST_getredir || return 1 395 found=1 396 ;; 397 ('*?'*) 398 ZTST_curline=${ZTST_curline[2,-1]} 399 diff_err=p 400 ;& 401 ('?'*) 402 ZTST_getredir || return 1 403 found=1 404 ;; 405 ('F:'*) ZTST_failmsg="${ZTST_failmsg:+${ZTST_failmsg} 406} ${ZTST_curline[3,-1]}" 407 ZTST_getline 408 found=1 409 ;; 410 (*) ZTST_testfailed "bad line in test block: 411$ZTST_curline" 412 return 1 413 ;; 414 esac 415 done 416 417 # If we found some code to execute... 418 if [[ -n $ZTST_code ]]; then 419 ZTST_hashmark 420 ZTST_verbose 1 "Running test: $ZTST_message" 421 ZTST_verbose 2 "ZTST_test: expecting status: $ZTST_xstatus" 422 ZTST_verbose 2 "Input: $ZTST_in, output: $ZTST_out, error: $ZTST_terr" 423 424 ZTST_execchunk <$ZTST_in >$ZTST_tout 2>$ZTST_terr 425 426 # First check we got the right status, if specified. 427 if [[ $ZTST_xstatus != - && $ZTST_xstatus != $ZTST_status ]]; then 428 ZTST_testfailed "bad status $ZTST_status, expected $ZTST_xstatus from: 429$ZTST_code${$(<$ZTST_terr):+ 430Error output: 431$(<$ZTST_terr)}" 432 return 1 433 fi 434 435 ZTST_verbose 2 "ZTST_test: test produced standard output: 436$(<$ZTST_tout) 437ZTST_test: and standard error: 438$(<$ZTST_terr)" 439 440 # Now check output and error. 441 if [[ $ZTST_flags = *q* && -s $ZTST_out ]]; then 442 substlines="$(<$ZTST_out)" 443 rm -rf $ZTST_out 444 print -r -- "${(e)substlines}" >$ZTST_out 445 fi 446 if [[ $ZTST_flags != *d* ]] && ! ZTST_diff $diff_out -c $ZTST_out $ZTST_tout; then 447 ZTST_testfailed "output differs from expected as shown above for: 448$ZTST_code${$(<$ZTST_terr):+ 449Error output: 450$(<$ZTST_terr)}" 451 return 1 452 fi 453 if [[ $ZTST_flags = *q* && -s $ZTST_err ]]; then 454 substlines="$(<$ZTST_err)" 455 rm -rf $ZTST_err 456 print -r -- "${(e)substlines}" >$ZTST_err 457 fi 458 if [[ $ZTST_flags != *D* ]] && ! ZTST_diff $diff_err -c $ZTST_err $ZTST_terr; then 459 ZTST_testfailed "error output differs from expected as shown above for: 460$ZTST_code" 461 return 1 462 fi 463 fi 464 ZTST_verbose 1 "Test successful." 465 [[ -n $last ]] && break 466 done 467 468 ZTST_verbose 2 "ZTST_test: all tests successful" 469 470 # reset message to keep ZTST_testfailed output correct 471 ZTST_message='' 472} 473 474 475# Remember which sections we've done. 476typeset -A ZTST_sects 477ZTST_sects=(prep 0 test 0 clean 0) 478 479print "$ZTST_testname: starting." 480 481# Now go through all the different sections until the end. 482# prep section may set ZTST_unimplemented, in this case the actual 483# tests will be skipped 484ZTST_skipok= 485ZTST_unimplemented= 486while [[ -z "$ZTST_unimplemented" ]] && ZTST_getsect $ZTST_skipok; do 487 case $ZTST_cursect in 488 (prep) if (( ${ZTST_sects[prep]} + ${ZTST_sects[test]} + \ 489 ${ZTST_sects[clean]} )); then 490 ZTST_testfailed "\`prep' section must come first" 491 exit 1 492 fi 493 ZTST_prepclean 494 ZTST_sects[prep]=1 495 ;; 496 (test) 497 if (( ${ZTST_sects[test]} + ${ZTST_sects[clean]} )); then 498 ZTST_testfailed "bad placement of \`test' section" 499 exit 1 500 fi 501 # careful here: we can't execute ZTST_test before || or && 502 # because that affects the behaviour of traps in the tests. 503 ZTST_test 504 (( $? )) && ZTST_skipok=1 505 ZTST_sects[test]=1 506 ;; 507 (clean) 508 if (( ${ZTST_sects[test]} == 0 || ${ZTST_sects[clean]} )); then 509 ZTST_testfailed "bad use of \`clean' section" 510 else 511 ZTST_prepclean 1 512 ZTST_sects[clean]=1 513 fi 514 ZTST_skipok= 515 ;; 516 *) ZTST_testfailed "bad section name: $ZTST_cursect" 517 ;; 518 esac 519done 520 521if [[ -n "$ZTST_unimplemented" ]]; then 522 print "$ZTST_testname: skipped ($ZTST_unimplemented)" 523 ZTST_testfailed=2 524elif (( ! $ZTST_testfailed )); then 525 print "$ZTST_testname: all tests successful." 526fi 527ZTST_cleanup 528exit $(( ZTST_testfailed )) 529