: ksh regression test harness : USAGE_LICENSE="[-author?David Korn ][-author?Glenn Fowler ][-copyright?Copyright (c) 2000-2012 AT&T Intellectual Property][-license?http://www.eclipse.org/org/documents/epl-v10.html]" command=shtests setslocale='*@(locale).sh' timesensitive='*@(options|sigchld|subshell).sh' valgrindflags='--xml=yes --log-file=/dev/null --track-origins=yes --read-var-info=yes' USAGE=$' [-s8? @(#)$Id: shtests (AT&T Research/ksh93) 2020-08-11 $ ] '$USAGE_LICENSE$' [+NAME?shtests - ksh regression test harness] [+DESCRIPTION?\bshtests\b is the \bksh\b(1) regression test harness for \b$SHELL\b or \bksh\b if \bSHELL\b is not defined and exported. If none of the \b--posix --utf8 --compile\b options are specified then all three are enabled.] [+INPUT FILES?\bshtests\b regression test files are shell scripts that run in an environment controlled by \bshtests\b. An identification message is printed before and after each test on the standard output. The default environment settings are:] { [+unset LANG] [+unset LC_ALL] [+LC_NUMERIC=C?\b.\b radix point assumed by all test scripts.] [+VMALLOC_OPTIONS=abort?\bvmalloc\b(1) arena checking enabled with \babort(2)\b on error.] } [c:compile?Run test scripts using \bshcomp\b(1).] [d:debug?Enable \bshtests\b execution trace.] [k:keep?Keep temporary files after test run; shtests will report the location.] [l:locale?Disable \b--utf8\b and run the \b--posix\b and \b--compile\b tests, if enabled, in the locale of the caller. However, for locales where \b.\b is not the radix point, \bLC_NUMERIC\b is set to \bC\b to avoid invalid regressions.] [p:posix?Run the test scripts in the posix/C locale.] [t!:time?Include the current date/time in the test identification messages.] [u:utf8?Run the test scripts in the ast-specific C.UTF-8 locale.] [v!:vmalloc_options?Run tests with \bVMALLOC_OPTIONS=abort\b. Test script names matching \b'$timesensitive$'\b are run with \bVMALLOC_OPTIONS\b unset.] [V:valgrind?Set \b--novmalloc_options\b and run the test scripts with \bvalgrind\b(1) on \bksh\b. If \b$SHELL-g\b exists and is executable, then it is used instead of \b$SHELL\b.] [x:trace?Enable script execution trace.] [ test.sh ... ] [ name=value ... ] [+SEE ALSO?\bksh\b(1), \bregress\b(1), \brt\b(1)] ' function usage { OPTIND=0 getopts -a $command "$USAGE" OPT '--??long' exit 2 } function valxml { typeset state=INIT data dir file fn line what integer errors=0 #print === $1 ===; cat $1; print === === while read data do case $state in INIT) case $data in '') state=ERROR ;; esac ;; ERROR) case $data in ''Leak*'') state=SKIP ;; ''*'') state=KEEP what=UNKNOWN ;; esac ;; FRAME) case $data in ''*'') dir=${data#''} dir=${dir%''} ;; ''*'') file=${data#''} file=${file%''} ;; ''*'') fn=${data#''} fn=${fn%''} ;; ''*'') line=${data#''} line=${line%''} ;; '') [[ $dir ]] && dir+=/ dir+=$file [[ $dir ]] && dir+=: [[ $line ]] && dir+=$line: [[ $fn ]] && dir+=$fn [[ $dir ]] && echo $'\t '$dir state=KEEP ;; esac ;; KEEP) case $data in ''*'') what=${data#''} what=${what%''} echo $'\t'"$what" ;; '') state=FRAME dir= file= fn= line= ;; 'Syscall param mount(type) points to unaddressable byte(s)') state=SKIP ;; ''*'') (( errors++ )) what=${data#''} what=${what%''} echo $'\n\t'"$what" ;; '') state=WHAT ;; '') state=INIT ;; esac ;; SKIP) case $data in '') state=INIT ;; esac ;; WHAT) case $data in ''*'') (( errors++ )) what=${data#''} what=${what%''} echo $'\n\t'"$what" ;; '') state=KEEP ;; esac ;; esac done < "$1" (( errors )) && echo return $errors } unset DISPLAY FIGNORE HISTFILE export ENV=/./dev/null trap + PIPE # unadvertized -- set SIGPIPE to SIG_DFL # integer compile=-1 posix=-1 utf8=-1 integer debug=0 keep=0 locale=0 time=1 typeset vmalloc_options=abort trace= valgrind= vmalloc_options= #XXX# until multi-region vmalloc trace fixed #XXX# while getopts -a $command "$USAGE" OPT do case $OPT in c) if (( $OPTARG )) then compile=2 else compile=0 fi ;; d) debug=$OPTARG ;; k) keep=$OPTARG ;; l) locale=$OPTARG ;; p) posix=$OPTARG ;; t) time=$OPTARG ;; u) utf8=$OPTARG ;; v) if (( OPTARG )) then vmalloc_options=abort else vmalloc_options= fi ;; V) valgrind="${VALGRIND:-valgrind} ${VALGRINDFLAGS:-$valgrindflags}" vmalloc_options= ;; x) trace=-x ;; *) usage ;; esac done shift $OPTIND-1 if (( debug )) || [[ $trace ]] then export PS4='+ [${SECONDS:+${SECONDS%????}s|}${.sh.subshell:+S${.sh.subshell},}${.sh.file:+${.sh.file#${.sh.file%/*/*}/},}${.sh.fun:+${.sh.fun},}${LINENO:+L$LINENO,}e$?] ' if (( debug )) then set -x fi fi while [[ $1 == *=* ]] do eval export "$1" shift done if (( compile <= 0 && posix <= 0 && utf8 <= 0 )) then (( compile )) && compile=1 (( posix )) && posix=1 (( utf8 )) && utf8=1 fi (( compile < 0 )) && compile=0 (( posix < 0 )) && posix=0 (( utf8 < 0 )) && utf8=0 if (( locale )) then utf8=0 if [[ $LC_ALL ]] then export LANG=$LC_ALL unset ${!LC_*} fi if ! let 1.0 2>/dev/null then export LC_NUMERIC=C fi else unset LANG LC_ALL export LC_NUMERIC=C fi if [[ $VMALLOC_OPTIONS ]] then vmalloc_options=$VMALLOC_OPTIONS else VMALLOC_OPTIONS=$vmalloc_options fi [[ $VMALLOC_OPTIONS ]] || timesensitive=. export PATH PWD SHCOMP SHELL VMALLOC_OPTIONS PWD=$(pwd) SHELL=${SHELL-ksh} case $0 in /*) d=$(dirname $0);; */*) d=$PWD/$(dirname $0);; *) d=$PWD;; esac case $SHELL in /*) ;; */*) SHELL=$d/$SHELL;; *) SHELL=$(whence $SHELL);; esac PATH=$( PATH=/run/current-system/sw/bin:/usr/xpg7/bin:/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin:$PATH getconf PATH 2>/dev/null || { builtin getconf 2>/dev/null && getconf PATH; } ) || PATH=/bin:/usr/bin if [[ -d /usr/ucb ]] then PATH=$PATH:/usr/ucb fi PATH=$PATH:$d if [[ $INSTALLROOT && -r $INSTALLROOT/bin/.paths ]] then PATH=$INSTALLROOT/bin:$PATH fi if [[ ${SHELL%/*} != $INSTALLROOT/bin ]] then PATH=${SHELL%/*}:$PATH fi if [[ ! $SHCOMP ]] then s=${SHELL:##*sh} s=${SHELL:%/*}/shcomp$s if [[ -x $s ]] then SHCOMP=$s elif [[ -x ${s%-g} ]] then SHCOMP=${s%-g} else SHCOMP=shcomp fi fi tmp=$( d=${TMPDIR:-/tmp}/ksh93.shtests.$$.${RANDOM:-0} mkdir -m700 -- "$d" && CDPATH= cd -P -- "$d" && pwd ) || { echo 'mkdir failed' >&2 exit 1 } if (( keep )) then trap 'printf "\nTemporary files left in: %s\n" "$tmp"' EXIT else trap 'cd / && rm -rf "$tmp"' EXIT fi # for interactive shell ('ksh -i') tests: avoid affecting ~/.sh_history export HISTFILE=$tmp/sh_history if (( compile )) then if whence $SHCOMP > /dev/null then : elif (( compile > 1 )) then echo $0: --compile: $SHCOMP not found >&2 exit 1 else compile=0 fi fi if [[ $valgrind ]] then if [[ -x $SHELL-g ]] then SHELL=$SHELL-g fi valxml=$tmp/valgrind.xml valgrind+=" --xml-file=$valxml" fi typeset -A tests typeset -i total_e=0 for i in ${*-*.sh} do [[ $i == *.sh ]] || i+='.sh' if [[ ! -r $i ]] then echo $0: $i: not found >&2 (( ++total_e )) continue fi t=$(grep -c err_exit $i) if (( t > 2 )) then (( t = t - 2 )) fi tests[$i]=$t T=test if (( t != 1 )) then T=${T}s fi u=${i##*/} u=${u%.sh} if [[ $i == $timesensitive ]] then VMALLOC_OPTIONS= fi if (( posix || utf8 )) then locales= (( posix )) && locales+=" ${LANG:-C}" [[ $utf8 == 0 || $i == $setslocale ]] || locales+=" C.UTF-8" for lang in $locales do o=$u tmp_s=$tmp/$u.$lang mkdir -m700 "$tmp_s" || exit if [[ $lang == C ]] then lang= else o="$o($lang)" lang=LANG=$lang fi echo test $o begins ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} ( export ${lang:+"$lang"} "tmp=$tmp_s" $valgrind $SHELL $trace $i ) e=$? if (( !keep )) then rm -rf "$tmp_s" fi if [[ $valgrind ]] then valxml $valxml (( e += $? )) fi if (( e > 128 && ++total_e )) then E="(killed by SIG$(kill -l "$e"))" elif (( e == 1 && ++total_e )) then E="1 error" else E="$e errors" (( total_e += e )) fi if (( e == 0 )) then echo test $o passed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} "[ $t $T $E ]" else echo test $o failed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} with exit code $e "[ $t $T $E ]" fi case $E in *INT\)) kill -s INT $$ ;; # if test was ^C'd, don't keep going but pass down SIGINT esac done fi if (( compile )) then c=$tmp/shcomp-$u.ksh o="$u(shcomp)" echo test $o begins ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} tmp_s=$tmp/$u.shcomp mkdir -m700 "$tmp_s" || exit if $SHCOMP $i > $c then if tmp=$tmp_s $valgrind $SHELL $trace $c then echo test $o passed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} "[ $t $T 0 errors ]" else e=$? if (( e > 128 && ++total_e )) then E="(killed by SIG$(kill -l "$e"))" elif (( e == 1 && ++total_e )) then E="1 error" else E="$e errors" (( total_e += e )) fi echo test $o failed ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} with exit code $e "[ $t $T $E ]" case $E in *INT\)) kill -s INT $$ ;; # if test was ^C'd, don't keep going but pass down SIGINT esac fi else e=$? (( ++total_e )) echo test $o failed to compile ${time:+"at $(date +%Y-%m-%d+%H:%M:%S)"} with exit code $e "[ 1 test 1 error ]" fi if (( !keep )) then rm -rf "$tmp_s" "$c" fi if [[ $i == $timesensitive ]] then VMALLOC_OPTIONS=$vmalloc_options fi fi done print "Total errors: $total_e" # This test harness should continue to work with old and buggy versions of ksh93, so check # if we have the 'times' builtin and can use process substitution with output redirection. # See: https://github.com/ksh93/ksh/commit/65d363fd # https://github.com/ksh93/ksh/issues/2 if ( ulimit -t unlimited # fork to circumvent old ksh bugs unalias times PATH=/dev/null whence -q times || exit cd "$tmp" || exit echo ok > >(cat > procsubst_test) wait [[ $(< procsubst_test) == ok ]] ) 2>/dev/null then # Transforming 'times' output requires a process substitution. # A regular 'times | ...' pipeline would cause 'times' to be # run in a subshell, which would reset all the times to zero. times > >( t[0]='CPU time' t[1]='user:' t[2]='system:' t[3]='main:'; read t[4] t[5] t[6]='tests:'; read t[7] t[8] printf '%-8s%12s %12s\n' "${t[@]}" ) wait "$!" # process substitutions are executed asynchronously else # No pretty-printing. times fi exit $((total_e > 125 ? 125 : total_e))