1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00
cde/src/cmd/ksh93/tests/shtests
Martijn Dekker aad74597f7 Fixes for -G/--globstar (re: 5312a59d)
The fix for '.' and '..' in regular globbing broke '.' and '..' in
globstar. No globstar pattern that contains '.' or '..' as any
pathname component still matched. This commit fixes that.

This commit also makes symlink/** mostly work, which it never has
done in any ksh93 version. It is correct and expected that symlinks
found by patterns are not resolved, but symlinks were not resolved
even when specified as explicit non-pattern pathname components.
For example, /tmp/** breaks if /tmp is a symlink (e.g. on macOS),
which looks like a bug.

src/lib/libast/include/glob.h,
src/lib/libast/misc/glob.c: glob_dir():
- Make symlink/** work. we can check if the string pointed to by
  pat is exactly equal to *. If so, we are doing regular globbing
  for that particular pathname element, and it's okay to resolve
  symlinks. If not (if it's **), we're doing globstar and we should
  not be matching symlinks.
- Let's also introduce proper identification of symlinks (GLOB_SYM)
  and not lump them in with other special files (GLOB_DEV).
- Fix the bug with literal '.' and '..' components in globstar
  patterns. In preceding code, the matchdir pointer gets set to the
  complete glob pattern if we're doing globstar for the current
  pathname element, null if not. The pat pointer gets set to the
  elements of the pattern that are still left to be processed;
  already-done elements are trimmed from it by increasing the
  pointer. So, to do the right thing, we need to make sure that '.'
  or '..' is skipped if, and only if, it is the final element in
  the pattern (i.e., if pat does not contain a slash) and is not
  specified literally as '.' or '..', i.e., only if '.' or '..' was
  actually resolved from a glob pattern. After this change,
  '**/.*', '**/../.*', etc. do the right thing, showing all your
  hidden files and directories without undesirable '.' and '..'
  results; '.' and '..' are skipped as final elements, unless you
  literally specify '**/.', '**/..', '**/foo/bar/..', etc.

src/cmd/ksh93/COMPATIBILITY:
- Note the symlink/** globstar change.

src/cmd/ksh93/sh.1:
- Try to document the current globstar behaviour more exhausively.

src/cmd/ksh93/tests/glob.sh:
- Add tests. Try to cover all the corner cases.

src/cmd/ksh93/tests/shtests:
- Since tests in glob.sh do not use err_exit, they were not
  counted. Special-case glob.sh for counting the tests: count the
  lines starting with a test_* function call.

Resolves: https://github.com/ksh93/ksh/issues/146
2021-03-07 01:57:21 +00:00

469 lines
12 KiB
Text
Executable file

: ksh regression test harness :
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 (ksh 93u+m) 2021-03-06 $
]
[-author?David Korn <dgk@research.att.com>]
[-author?Glenn Fowler <gsf@research.att.com>]
[-copyright?(c) 2000-2012 AT&T Intellectual Property]
[-copyright?(c) 2020-2021 Contributors to https://github.com/ksh93/ksh]
[-license?http://www.eclipse.org/org/documents/epl-v10.html]
[+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
'<error>')
state=ERROR
;;
esac
;;
ERROR) case $data in
'<kind>'Leak*'</kind>')
state=SKIP
;;
'<kind>'*'</kind>')
state=KEEP
what=UNKNOWN
;;
esac
;;
FRAME) case $data in
'<dir>'*'</dir>')
dir=${data#'<dir>'}
dir=${dir%'</dir>'}
;;
'<file>'*'</file>')
file=${data#'<file>'}
file=${file%'</file>'}
;;
'<fn>'*'</fn>')
fn=${data#'<fn>'}
fn=${fn%'</fn>'}
;;
'<line>'*'</line>')
line=${data#'<line>'}
line=${line%'</line>'}
;;
'</frame>')
[[ $dir ]] && dir+=/
dir+=$file
[[ $dir ]] && dir+=:
[[ $line ]] && dir+=$line:
[[ $fn ]] && dir+=$fn
[[ $dir ]] && echo $'\t '$dir
state=KEEP
;;
esac
;;
KEEP) case $data in
'<auxwhat>'*'</auxwhat>')
what=${data#'<auxwhat>'}
what=${what%'</auxwhat>'}
echo $'\t'"$what"
;;
'<frame>')
state=FRAME
dir=
file=
fn=
line=
;;
'<what>Syscall param mount(type) points to unaddressable byte(s)</what>')
state=SKIP
;;
'<what>'*'</what>')
(( errors++ ))
what=${data#'<what>'}
what=${what%'</what>'}
echo $'\n\t'"$what"
;;
'<xwhat>')
state=WHAT
;;
'</error>')
state=INIT
;;
esac
;;
SKIP) case $data in
'</error>')
state=INIT
;;
esac
;;
WHAT) case $data in
'<text>'*'</text>')
(( errors++ ))
what=${data#'<text>'}
what=${what%'</text>'}
echo $'\n\t'"$what"
;;
'</xwhat>')
state=KEEP
;;
esac
;;
esac
done < "$1"
(( errors )) && echo
return $errors
}
command set +o posix 2>/dev/null
unset DISPLAY FIGNORE HISTFILE POSIXLY_CORRECT _AST_FEATURES
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.pid:+P${.sh.pid},}${.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=$(
builtin getconf 2>/dev/null || PATH=/run/current-system/sw/bin:/usr/xpg7/bin:/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin:$PATH
getconf PATH 2>/dev/null
) || 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
# (some tests must unset or modify $HISTFILE, so set $HOME instead)
export HOME=$tmp
# make the SHOPT_* macros available to the tests as environment variables
SHOPT()
{
[[ $1 == *=* ]] && eval "export SHOPT_${ printf %q "${1%%=*}"; }=${ printf %q "${1#*=}"; }"
}
. "${SHOPTFILE:-../SHOPT.sh}"
unset -f SHOPT
if (( !SHOPT_MULTIBYTE && utf8 && !posix && !compile ))
then echo "The -u/--utf8 option is unavailable as SHOPT_MULTIBYTE is turned off in ${SHOPTFILE:-SHOPT.sh}." >&2
exit 1
fi
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=$( case $i in
glob.sh) grep -c '^[[:blank:]]*test_[a-z]\{3,\}' $i ;;
*) grep -c err_exit $i ;;
esac )
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 ]] || ((!SHOPT_MULTIBYTE)) || locales+=" C.UTF-8"
for lang in $locales
do o=$u
tmp_s=$tmp/$u.$lang
mkdir -m700 "$tmp_s" || exit
cd "$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 "$OLDPWD/$i"
)
e=$?
cd "$OLDPWD" || exit
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
cd "$tmp_s" || exit
if $SHCOMP "$OLDPWD/$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
cd "$OLDPWD" || exit
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))