######################################################################## # # # This software is part of the ast package # # Copyright (c) 1982-2012 AT&T Intellectual Property # # and is licensed under the # # Eclipse Public License, Version 1.0 # # by AT&T Intellectual Property # # # # A copy of the License is available at # # http://www.eclipse.org/org/documents/epl-v10.html # # (with md5 checksum b35adb5213ca9657e911e9befb180842) # # # # Information and Software Systems Research # # AT&T Research # # Florham Park NJ # # # # David Korn # # # ######################################################################## . "${SHTESTS_COMMON:-${0%/*}/_common}" # to avoid spurious test failures with 'whence -a' tests, we need # to remove any duplicate paths to the same directory from $PATH. function rm_path_dups { typeset IFS=':' p seen s newpath set -o noglob p=$PATH: # IFS field splitting discards a final empty field; add one to avoid that for p in $p do if [[ -z $p ]] then # empty $PATH element == current dir p='.' fi for s in $seen do [[ $p -ef $s ]] && continue 2 done newpath=${newpath:+$newpath:}$p seen=$seen$p: done PATH=$newpath } rm_path_dups PATH_orig=$PATH # output all paths to a command, skipping duplicates in $PATH # (reimplementation of 'whence -a -p', useful for testing 'whence') function all_paths { typeset IFS=':' CDPATH='' p seen set -o noglob p=$PATH: # IFS field splitting discards a final empty field; add one to avoid that for p in $p do if [[ -z $p ]] then # empty $PATH element == current dir p='.' fi if [[ $p != /* ]] then # get absolute directory p=$(cd -L -- "$p" 2>/dev/null && print -r -- "${PWD}X") && p=${p%X} || continue fi if [[ :$seen: == *:"$p":* ]] then continue fi if [[ -f $p/$1 && -x $p/$1 ]] then print -r "$p/$1" fi seen=${seen:+$seen:}$p done } type /xxxxxx > out1 2> out2 [[ -s out1 ]] && err_exit 'type should not write on stdout for not found case' [[ -s out2 ]] || err_exit 'type should write on stderr for not found case' mkdir dir1 dir2 cat > dir1/foobar << '+++' foobar() { print foobar1 >foobar1.txt; cat dir2/foobar << '+++' foobar() { print foobar2;} function dir2 { print dir2;} +++ chmod +x dir[12]/foobar p=$PATH FPATH=$PWD/dir1 PATH=$FPATH:$p [[ $( foobar) == foobar1 ]] || err_exit 'foobar should output foobar1' FPATH=$PWD/dir2 PATH=$FPATH:$p [[ $(foobar) == foobar2 ]] || err_exit 'foobar should output foobar2' FPATH=$PWD/dir1 PATH=$FPATH:$p [[ $(foobar) == foobar1 ]] || err_exit 'foobar should output foobar1 again' FPATH=$PWD/dir2 PATH=$FPATH:$p [[ ${ foobar;} == foobar2 ]] || err_exit 'foobar should output foobar2 with ${}' [[ ${ dir2;} == dir2 ]] || err_exit 'should be dir2' [[ ${ dir1;} == dir1 ]] 2> /dev/null && err_exit 'should not be dir1' FPATH=$PWD/dir1 PATH=$FPATH:$p [[ ${ foobar;} == foobar1 ]] || err_exit 'foobar should output foobar1 with ${}' [[ ${ dir1;} == dir1 ]] || err_exit 'should be dir1' [[ ${ dir2;} == dir2 ]] 2> /dev/null && err_exit 'should not be dir2' FPATH=$PWD/dir2 PATH=$FPATH:$p [[ ${ foobar;} == foobar2 ]] || err_exit 'foobar should output foobar2 with ${} again' PATH=$p (PATH="/bin") [[ $($SHELL -c 'print -r -- "$PATH"') == "$PATH" ]] || err_exit 'export PATH lost in subshell' cat > bug1 <<- EOF print print ok > $tmp/ok command -p chmod 755 $tmp/ok function a { typeset -x PATH=$tmp ok } path=\$PATH unset PATH a PATH=\$path } EOF [[ $($SHELL ./bug1 2>/dev/null) == ok ]] || err_exit "PATH in function not working" cat > bug1 <<- \EOF function lock_unlock { typeset PATH=/usr/bin typeset -x PATH='' } PATH=/usr/bin : $(PATH=/usr/bin getconf PATH) typeset -ft lock_unlock lock_unlock EOF ($SHELL ./bug1) 2> /dev/null || err_exit "path_delete bug" mkdir tdir if $SHELL tdir > /dev/null 2>&1 then err_exit 'not an error to run ksh on a directory' fi print 'print hi' > ls if [[ $($SHELL ls 2> /dev/null) != hi ]] then err_exit "$SHELL name not executing version in current directory" fi if [[ $(ls -d . 2>/dev/null) == . && $(PATH=/bin:/usr/bin:$PATH ls -d . 2>/dev/null) != . ]] then err_exit 'PATH export in command substitution not working' fi pwd=$PWD # get rid of leading and trailing : and trailing :. PATH=${PATH%.} PATH=${PATH%:} PATH=${PATH#.} PATH=${PATH#:} path=$PATH var=$(whence date) dir=$(basename "$var") for i in 1 2 3 4 5 6 7 8 9 0 do if ! whence notfound$i 2> /dev/null then cmd=notfound$i break fi done print 'print hello' > date chmod +x date print 'print notfound' > $cmd chmod +x "$cmd" > foo chmod 755 foo for PATH in $path :$path $path: .:$path $path: $path:. $PWD::$path $PWD:.:$path $path:$PWD $path:.:$PWD do # print path=$PATH $(whence date) # print path=$PATH $(whence "$cmd") date "$cmd" done > /dev/null 2>&1 builtin -d date 2> /dev/null if [[ $(PATH=:/usr/bin; date) != 'hello' ]] then err_exit "leading : in path not working" fi ( PATH=$PWD: builtin chmod print 'print cannot execute' > noexec chmod 644 noexec if [[ ! -x noexec ]] then noexec > /dev/null 2>&1 else exit 126 fi ) status=$? [[ $status == 126 ]] || err_exit "exit status of non-executable is $status -- 126 expected" builtin -d rm 2> /dev/null chmod=$(whence chmod) rm=$(whence rm) d=$(dirname "$rm") chmod=$(whence chmod) for cmd in date foo do exp="$cmd found" print print $exp > $cmd $chmod +x $cmd got=$($SHELL -c "unset FPATH; PATH=/dev/null; $cmd" 2>&1) [[ $got == "$exp" ]] && err_exit "$cmd as last command should not find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" exp=$PWD/./$cmd got=$($SHELL -c "unset FPATH; PATH=/dev/null; $cmd" 2>&1) [[ $got == "$exp" ]] && err_exit "$cmd should not find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" exp=$PWD/$cmd got=$(unset FPATH; PATH=/dev/null; whence ./$cmd) [[ $got == "$exp" ]] || err_exit "whence $cmd should find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" got=$(unset FPATH; PATH=/dev/null; whence $PWD/$cmd) [[ $got == "$exp" ]] || err_exit "whence \$PWD/$cmd should find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" done exp='' got=$($SHELL -c "unset FPATH; PATH=/dev/null; whence ./notfound" 2>&1) [[ $got == "$exp" ]] || err_exit "whence ./$cmd failed -- expected '$exp', got '$got'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" got=$($SHELL -c "unset FPATH; PATH=/dev/null; whence $PWD/notfound" 2>&1) [[ $got == "$exp" ]] || err_exit "whence \$PWD/$cmd failed -- expected '$exp', got '$got'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" unset FPATH PATH=/dev/null for cmd in date foo do exp="$cmd found" print print $exp > $cmd $chmod +x $cmd got=$($cmd 2>&1) [[ $got == "$exp" ]] && err_exit "$cmd as last command should not find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" got=$($cmd 2>&1; :) [[ $got == "$exp" ]] && err_exit "$cmd should not find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" exp=$PWD/$cmd got=$(whence ./$cmd) [[ $got == "$exp" ]] || err_exit "whence ./$cmd should find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" got=$(whence $PWD/$cmd) [[ $got == "$exp" ]] || err_exit "whence \$PWD/$cmd should find ./$cmd with PATH=/dev/null" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" got=$(cd / && whence "${OLDPWD#/}/$cmd") [[ $got == "$exp" ]] || err_exit "whence output should not start with '//' if PWD is '/'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" done exp='' got=$(whence ./notfound) [[ $got == "$exp" ]] || err_exit "whence ./$cmd failed -- expected '$exp', got '$got'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" got=$(whence $PWD/notfound) [[ $got == "$exp" ]] || err_exit "whence \$PWD/$cmd failed -- expected '$exp', got '$got'" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" PATH=$d: command -p cp "$rm" kshrm got=$(whence kshrm) exp=$PWD/kshrm [[ $got == "$exp" ]] || err_exit 'trailing : in pathname not working' \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" command -p cp "$rm" rm PATH=:$d got=$(whence rm) exp=$PWD/rm [[ $got == "$exp" ]] || err_exit 'leading : in pathname not working' \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" PATH=$d: whence rm > /dev/null got=$(whence rm) exp=$PWD/rm [[ $got == "$exp" ]] || err_exit 'pathname not restored after scoping' \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" command -p mkdir bin print 'print ok' > bin/tst command -p chmod +x bin/tst if [[ $(PATH=$PWD/bin tst 2>/dev/null) != ok ]] then err_exit '(PATH=$PWD/bin foo) does not find $PWD/bin/foo' fi cd / if whence ls > /dev/null then PATH= if [[ $(whence rm) ]] then err_exit 'setting PATH to Null not working' fi unset PATH if [[ $(whence rm) != /*rm ]] then err_exit 'unsetting PATH not working' fi fi PATH=/dev:$tmp export PATH x=$(whence rm) typeset foo=$(PATH=/xyz:/abc :) y=$(whence rm) [[ $x != "$y" ]] && err_exit 'PATH not restored after command substitution' whence getconf > /dev/null && err_exit 'getconf should not be found' builtin /bin/getconf PATH=/bin PATH=$(getconf PATH) x=$(whence ls) PATH=.:$PWD:${x%/ls} [[ $(whence ls) == "$x" ]] || err_exit 'PATH search bug when .:$PWD in path' PATH=$PWD:.:${x%/ls} [[ $(whence ls) == "$x" ]] || err_exit 'PATH search bug when :$PWD:. in path' cd "${x%/ls}" [[ $(whence ls) == /* ]] || err_exit 'whence not generating absolute pathname' status=$($SHELL -c $'trap \'print $?\' EXIT;/xxx/a/b/c/d/e 2> /dev/null') [[ $status == 127 ]] || err_exit "not found command exit status $status -- expected 127" status=$($SHELL -c $'trap \'print $?\' EXIT;/dev/null 2> /dev/null') [[ $status == 126 ]] || err_exit "non executable command exit status $status -- expected 126" status=$($SHELL -c $'trap \'print $?\' ERR;/xxx/a/b/c/d/e 2> /dev/null') [[ $status == 127 ]] || err_exit "not found command with ERR trap exit status $status -- expected 127" status=$($SHELL -c $'trap \'print $?\' ERR;/dev/null 2> /dev/null') [[ $status == 126 ]] || err_exit "non executable command ERR trap exit status $status -- expected 126" # universe via PATH builtin getconf getconf UNIVERSE - att # override sticky default 'UNIVERSE = foo' [[ $(PATH=/usr/ucb/bin:/usr/bin echo -n ucb) == 'ucb' ]] || err_exit "ucb universe echo ignores -n option" [[ $(PATH=/usr/xpg/bin:/usr/bin echo -n att) == '-n att' ]] || err_exit "att universe echo does not ignore -n option" PATH=$path scr=$tmp/script exp=126 if [[ $(id -u) == '0' ]]; then warning "running as root: skipping tests involving unreadable scripts" else : > $scr chmod a=x $scr { got=$($scr; print $?); } 2>/dev/null [[ "$got" == "$exp" ]] || err_exit "unreadable empty script should fail -- expected $exp, got $got" { got=$(command $scr; print $?); } 2>/dev/null [[ "$got" == "$exp" ]] || err_exit "command of unreadable empty script should fail -- expected $exp, got $got" [[ "$(:; $scr; print $?)" == "$exp" ]] 2>/dev/null || err_exit "unreadable empty script in [[ ... ]] should fail -- expected $exp" [[ "$(:; command $scr; print $?)" == "$exp" ]] 2>/dev/null || err_exit "command unreadable empty script in [[ ... ]] should fail -- expected $exp" got=$($SHELL -c "$scr; print \$?" 2>/dev/null) [[ "$got" == "$exp" ]] || err_exit "\$SHELL -c of unreadable empty script should fail -- expected $exp, got" $got got=$($SHELL -c "command $scr; print \$?" 2>/dev/null) [[ "$got" == "$exp" ]] || err_exit "\$SHELL -c of command of unreadable empty script should fail -- expected $exp, got" $got rm -f $scr print : > $scr chmod a=x $scr { got=$($scr; print $?); } 2>/dev/null [[ "$got" == "$exp" ]] || err_exit "unreadable non-empty script should fail -- expected $exp, got $got" { got=$(command $scr; print $?); } 2>/dev/null [[ "$got" == "$exp" ]] || err_exit "command of unreadable non-empty script should fail -- expected $exp, got $got" [[ "$(:; $scr; print $?)" == "$exp" ]] 2>/dev/null || err_exit "unreadable non-empty script in [[ ... ]] should fail -- expected $exp" [[ "$(:; command $scr; print $?)" == "$exp" ]] 2>/dev/null || err_exit "command unreadable non-empty script in [[ ... ]] should fail -- expected $exp" got=$($SHELL -c "$scr; print \$?" 2>/dev/null) [[ "$got" == "$exp" ]] || err_exit "\$SHELL -c of unreadable non-empty script should fail -- expected $exp, got" $got got=$($SHELL -c "command $scr; print \$?" 2>/dev/null) [[ "$got" == "$exp" ]] || err_exit "\$SHELL -c of command of unreadable non-empty script should fail -- expected $exp, got" $got fi # if [[ $(id -u) == '0' ]] # whence -a bug fix cd "$tmp" ifs=$IFS IFS=$'\n' PATH=$PATH: > ls chmod +x ls ok= for i in $(whence -a ls) do if [[ $i == *"$PWD/ls" ]] then ok=1 break; fi done [[ $ok ]] || err_exit 'whence -a not finding all executables' rm -f ls PATH=${PATH%:} #whence -p bug fix function foo { : } [[ $(whence -p foo) == foo ]] && err_exit 'whence -p foo should not find function foo' # whence -q bug fix $SHELL -c 'whence -q cat' & pid=$! sleep .1 kill $! 2> /dev/null && err_exit 'whence -q appears to be hung' FPATH=$PWD print 'function foobar { :;}' > foobar autoload foobar; exec {m}< /dev/null for ((i=0; i < 25; i++)) do ( foobar ) done exec {m}<& - exec {n}< /dev/null (( n > m )) && err_exit 'autoload function in subshell leaves file open' # whence -a bug fix rmdir=rmdir if mkdir "$rmdir" then rm=${ whence rm;} cp "$rm" "$rmdir" { PATH=:${rm%/rm} $SHELL -c "cd \"$rmdir\";whence -a rm";} > /dev/null 2>&1 exitval=$? (( exitval==0 )) || err_exit "whence -a has exitval $exitval" fi [[ ! -d bin ]] && mkdir bin [[ ! -d fun ]] && mkdir fun print 'FPATH=../fun' > bin/.paths cat <<- \EOF > fun/myfun function myfun { print myfun } EOF x=$(FPATH= PATH=$PWD/bin $SHELL -c ': $(whence less);myfun') 2> /dev/null [[ $x == myfun ]] || err_exit 'function myfun not found' command -p cat >user_to_group_relationship.hdr.query < $tmp/new/bin/.paths print FPATH=../xxfun > $tmp/bin/.paths cp "$(whence -p echo)" $tmp/new/bin PATH=$tmp/bin:$tmp/new/bin:$PATH x=$(whence -p echo 2> /dev/null) [[ $x == "$tmp/new/bin/echo" ]] || err_exit 'nonexistent FPATH directory in .paths file causes path search to fail' $SHELL 2> /dev/null <<- \EOF || err_exit 'path search problem with non-existent directories in PATH' builtin getconf PATH=$(getconf PATH) PATH=/dev/null/nogood1/bin:/dev/null/nogood2/bin:$PATH tail /dev/null && tail /dev/null EOF ( PATH=/dev/null command -p cat << END >/dev/null 2>&1 ${.sh.version} END ) || err_exit '${.sh.xxx} variables causes cat not be found' PATH=$PATH_orig # ====== # Check that 'command -p' searches the default OS utility PATH. expect=/dev/null actual=$(PATH=/dev/null "$SHELL" -c 'command -p ls /dev/null' 2>&1) [[ $actual == "$expect" ]] || err_exit 'command -p fails to find standard utility' \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # ====== # ksh segfaults if $PATH contains a .paths directory mkdir -p $tmp/paths-dir-crash/ cat > $tmp/paths-dir-crash/run.sh <<- EOF mkdir -p $tmp/paths-dir-crash/.paths export PATH=$tmp/paths-dir-crash:$PATH print ok EOF ofile=$tmp/dotpaths.out trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "PATH containing .paths directory: shell hung"' TERM trap 'kill $sleep_pid; while kill -9 $pid; do :; done 2>/dev/null; trap - INT; kill -s INT $$"' INT { sleep 5; kill $$; } & sleep_pid=$! "$SHELL" "$tmp/paths-dir-crash/run.sh" >$ofile 2>&1 & pid=$! { wait $pid; } 2>>$ofile e=$? trap - TERM INT [[ $sleep_pid ]] && kill $sleep_pid ((!e)) && [[ $(<$ofile) == ok ]] || err_exit "PATH containing .paths directory:" \ "got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$(<$ofile)")" # ====== # Check that 'command -p' and 'command -p -v' do not use the hash table (a.k.a. tracked aliases). print 'echo "wrong path used"' > $tmp/ls chmod +x $tmp/ls expect=/dev/null actual=$(set +x; PATH=$tmp; redirect 2>&1; hash ls; command -p ls /dev/null) [[ $actual == "$expect" ]] || err_exit "'command -p' fails to search default path if tracked alias exists" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" actual=$(set +x; PATH=$tmp; redirect 2>&1; hash ls; command -p ls /dev/null; exit) # the 'exit' disables subshell optimization [[ $actual == "$expect" ]] || err_exit "'command -p' fails to search default path if tracked alias exists" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" expect=$(builtin getconf; PATH=$(getconf PATH); whence -p ls) actual=$(set +x; PATH=$tmp; redirect 2>&1; hash ls; command -p -v ls) [[ $actual == "$expect" ]] || err_exit "'command -p -v' fails to search default path if tracked alias exists" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # Check that adding '-x' makes '-v'/'-V' look up external commands if ((.sh.version >= 20210130)) then exp=$tmp/echo touch "$exp" chmod +x "$exp" got=$(PATH=$tmp; command -vx echo 2>&1) [[ $got == "$exp" ]] || err_exit "'command -v -x' failed to look up external command in \$PATH" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" exp="echo is a tracked alias for $exp" got=$(PATH=$tmp; command -Vx echo 2>&1) [[ $got == "$exp" ]] || err_exit "'command -V -x' failed to look up external command in \$PATH" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" exp=$(PATH=${ getconf PATH 2>/dev/null; }; whence -p echo) got=$(PATH=$tmp; command -pvx echo 2>&1) [[ $got == "$exp" ]] || err_exit "'command -p -v -x' failed to look up external command in default path" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" exp="echo is $exp" got=$(PATH=$tmp; command -pVx echo 2>&1) [[ $got == "$exp" ]] || err_exit "'command -p -V -x' failed to look up external command in default path" \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" fi # ====== # 'command -x' used to hang in an endless E2BIG loop on Linux and macOS ofile=$tmp/command_x_chunks.sh trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "'\''command -x'\'' hung"' TERM trap 'kill $sleep_pid; while kill -9 $pid; do :; done 2>/dev/null; trap - INT; kill -s INT $$"' INT { sleep 60; kill $$; } & sleep_pid=$! ( export LC_ALL=C unset IFS; set +f builtin getconf && arg_max=$(getconf ARG_MAX) && let arg_max || { err_exit "getconf ARG_MAX not working"; exit 1; } set +x # trust me, you don't want to xtrace what follows # let's try to use a good variety of argument lengths set -- $(typeset -p) $(functions) /dev/* /tmp/* /* * args=$* while let "${#args} < 3 * arg_max" do set -- "$RANDOM" "$@" "$RANDOM" "$@" "$RANDOM" args=$* done print "chunks=0 expargs=$# args=0 expsize=$((${#args}+1)) size=0" unset args command -x "$SHELL" -c ' integer i size=0 numargs=${#}-2 for ((i=numargs; i; i--)) do let "size += ${#1} + 1" shift done print "let chunks++ args+=$numargs size+=$size" if [[ $0 != static_argzero || $1 != final_static_arg_1 || $2 != final_static_arg_2 ]] then print "err_exit \"'\''command -x'\'': static arguments for chunk \$chunks incorrect\"" fi ' static_argzero "$@" final_static_arg_1 final_static_arg_2 ) >$ofile & pid=$! { wait $pid; } 2>/dev/null # wait and suppress signal messages e=$? trap - TERM INT [[ $sleep_pid ]] && kill $sleep_pid if [[ ${ kill -l "$e"; } == KILL ]] then warning "'command -x' test killed, probably due to lack of memory; skipping test" else if let "e > 0" then err_exit "'command -x' test yielded exit status $e$( let "e>128" && print -n / && kill -l "$e")" fi if [[ ! -s $ofile ]] then err_exit "'command -x' test failed to produce output" else save_Command=$Command Command+=": ${ofile##*/}" . "$ofile" Command=$save_Command let "args == expargs && size == expsize" || err_exit "'command -x' did not correctly divide arguments" \ "(expected $expargs args of total size $expsize, got $args args of total size $size;" \ "divided in $chunks chunks)" fi fi # ====== # whence -a/-v tests # wrong path to tracked aliases after loading builtin: https://github.com/ksh93/ksh/pull/25 actual=$("$SHELL" -c ' hash chmod builtin chmod whence -a chmod ') expect=$'chmod is a shell builtin\n'$(all_paths chmod | sed '1 s/^/chmod is a tracked alias for /; 2,$ s/^/chmod is /') [[ $actual == "$expect" ]] || err_exit "'whence -a' does not work correctly with tracked aliases" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # spurious 'undefined function' message: https://github.com/ksh93/ksh/issues/26 actual=$("$SHELL" -c 'whence -a printf') expect=$'printf is a shell builtin\n'$(all_paths printf | sed 's/^/printf is /') [[ $actual == "$expect" ]] || err_exit "'whence -a': incorrect output" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # 'whence -a'/'type -a' failed to list builtin if function exists: https://github.com/ksh93/ksh/issues/83 actual=$(printf() { :; }; whence -a printf) expect="printf is a function printf is a shell builtin $(all_paths printf | sed 's/^/printf is /')" [[ $actual == "$expect" ]] || err_exit "'whence -a': incorrect output for function+builtin" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" actual=$(autoload printf; whence -a printf) expect="printf is an undefined function printf is a shell builtin $(all_paths printf | sed 's/^/printf is /')" [[ $actual == "$expect" ]] || err_exit "'whence -a': incorrect output for autoload+builtin" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # 'whence -v' canonicalized paths improperly: https://github.com/ksh93/ksh/issues/84 cmdpath=${ whence -p printf; } actual=$(cd /; whence -v "${cmdpath#/}") expect="${cmdpath#/} is $cmdpath" [[ $actual == "$expect" ]] || err_exit "'whence -v': incorrect canonicalization of initial /" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" dotdot= num=$(set -f; IFS=/; set -- $PWD; echo $#) for ((i=1; i$tmp/ls expect=$'/dev/null\n/dev/null' actual=$(FPATH=$tmp; ls /dev/null; whence -a ls >/dev/null; ls /dev/null) [[ $actual == "$expect" ]] || err_exit "'whence -a': mistaken \$FPATH function autoload (non-executable file)" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" chmod +x "$tmp/ls" actual=$(FPATH=$tmp; ls /dev/null; whence -a ls >/dev/null; ls /dev/null) [[ $actual == "$expect" ]] || err_exit "'whence -a': mistaken \$FPATH function autoload (executable file)" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # "tracked aliases" (known on other shells as hash table entries) are really just cached PATH search # results; they should be reported independently from real aliases, as they're actually completely # different things, and "tracked aliases" are actually used when bypassing an alias (with e.g. \ls). expect=$'ls is an alias for \'echo ALL UR F1LEZ R G0N3\'\n'$(all_paths ls|sed '1 s/^/ls is a tracked alias for /;2,$ s/^/ls is /') actual=$(hash -r; alias ls='echo ALL UR F1LEZ R G0N3'; hash ls; whence -a ls) [[ $actual == "$expect" ]] || err_exit "'whence -a' does not report tracked alias if alias exists" \ "(expected $(printf %q "$expect"), got $(printf %q "$actual"))" # Unlike pdksh, ksh93 didn't report the path to autoloadable functions, which was an annoying omission. if ((.sh.version >= 20200925)) then fundir=$tmp/whencefun mkdir $fundir echo "whence_FPATH_test() { echo I\'m just on FPATH; }" >$fundir/whence_FPATH_test echo "whence_autoload_test() { echo I was explicitly autoloaded; }" >$fundir/whence_autoload_test echo "function chmod { echo Hi, I\'m your new chmod!; }" >$fundir/chmod echo "function ls { echo Hi, I\'m your new ls!; }" >$fundir/ls actual=$("$SHELL" -c 'FPATH=$1 autoload chmod whence_autoload_test whence -a chmod whence_FPATH_test whence_autoload_test ls cp whence_FPATH_test whence_autoload_test cp --totally-invalid-option 2>/dev/null ls --totally-invalid-option /dev/null/foo 2>/dev/null chmod --totally-invalid-option' \ whence_autoload_test "$fundir" 2>&1) expect="chmod is an undefined function (autoload from $fundir/chmod)" expect+=$'\n'$(all_paths chmod | sed 's/^/chmod is /') expect+=$'\n'"whence_FPATH_test is an undefined function (autoload from $fundir/whence_FPATH_test)" expect+=$'\n'"whence_autoload_test is an undefined function (autoload from $fundir/whence_autoload_test)" expect+=$'\n'$(all_paths ls | sed '1 s/^/ls is a tracked alias for /; 2,$ s/^/ls is /') expect+=$'\n'"ls is an undefined function (autoload from $fundir/ls)" expect+=$'\n'$(all_paths cp | sed '1 s/^/cp is a tracked alias for /; 2,$ s/^/cp is /') expect+=$'\n'"I'm just on FPATH" expect+=$'\n'"I was explicitly autoloaded" expect+=$'\n'"Hi, I'm your new chmod!" [[ $actual == "$expect" ]] || err_exit "failure in reporting or running autoloadable functions" \ $'-- diff follows:\n'"$(diff -u <(print -r -- "$expect") <(print -r -- "$actual") | sed $'s/^/\t| /')" fi # ====== # Very long nonexistent command names used to crash # https://github.com/ksh93/ksh/issues/144 ofile=$tmp/longname.err trap 'sleep_pid=; while kill -9 $pid; do :; done 2>/dev/null; err_exit "Long nonexistent command name: shell hung"' TERM trap 'kill $sleep_pid; while kill -9 $pid; do :; done 2>/dev/null; trap - INT; kill -s INT $$"' INT { sleep 5; kill $$; } & sleep_pid=$! PATH=/dev/null FPATH=/dev/null "$SHELL" -c "$(awk -v ORS= 'BEGIN { for(i=0;i<10000;i++) print "xxxxxxxxxx"; }')" 2>/dev/null & pid=$! { wait $pid; } 2>$ofile e=$? trap - TERM INT [[ $sleep_pid ]] && kill $sleep_pid ((e == 127)) || err_exit "Long nonexistent command name:" \ "got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$(<$ofile)")" # ====== # A function autoload recursion loop used to crash # https://github.com/ksh93/ksh/issues/136 mkdir "$tmp/fun.$$" && cd "$tmp/fun.$$" || exit echo 'self2' >self echo 'self3' >self2 echo 'self4' >self3 echo 'self' >self4 cd ~- || exit exp="$SHELL: line 2: autoload loop: self in $tmp/fun.$$/self $SHELL: function, built-in or type definition for self4 not found in $tmp/fun.$$/self4 $SHELL: function, built-in or type definition for self3 not found in $tmp/fun.$$/self3 $SHELL: function, built-in or type definition for self2 not found in $tmp/fun.$$/self2 $SHELL: function, built-in or type definition for self not found in $tmp/fun.$$/self" got=$({ FPATH=$tmp/fun.$$ "$SHELL" -c self; } 2>&1) (((e = $?) == 126)) || err_exit 'Function autoload recursion loop:' \ "got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got")" # ====== # If a shared-state ${ command substitution; } changed the value of $PATH, the variable # would change but the internal pathlist would not, making path searches inconsistent. savePATH=$PATH got=${ PATH=/dev/null; } got=$(whence ls) PATH=$savePATH [[ -z $got ]] || err_exit "PATH search inconsistent after changing PATH in subshare (got $(printf %q "$got"))" # ====== exit $((Errors<125?Errors:125))