mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-03-09 15:50:02 +00:00
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
469 lines
12 KiB
Text
Executable file
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))
|