Both redo and minimal/do were doing slightly weird things with symlinked directories, especially when combined with "..". For example, if x is a link to ., then x/x/x/x/../y should resolve to "../y", which is quite non-obvious. Added some tests to make sure this stays fixed.
485 lines
12 KiB
Text
485 lines
12 KiB
Text
#
|
|
# Most of these tests were inspired by:
|
|
# http://www.gnu.org/software/hello/manual/autoconf/Shell-Substitutions.html
|
|
#
|
|
# Note that this file isn't exactly a test for POSIX compliance. It's a test
|
|
# for usefulness-compliance, that is, interpreting POSIX in a particular way
|
|
# for consistency, so that users of redo can depend on all the following
|
|
# functionality.
|
|
#
|
|
# Where POSIX makes a clear statement about something working a particular
|
|
# way, POSIX wins. But where POSIX is unclear on a point, I don't mind
|
|
# picking a particular way and requiring it here, if that makes writing
|
|
# scripts easier for everyone in the future.
|
|
#
|
|
exec >&2
|
|
set +e
|
|
|
|
rm -f shelltest.failed shelltest.warned
|
|
|
|
fail()
|
|
{
|
|
echo " failed: $1"
|
|
: >shelltest.failed
|
|
}
|
|
|
|
warn()
|
|
{
|
|
echo " warning: $1"
|
|
: >shelltest.warned
|
|
}
|
|
|
|
# For things that should be a warning, but which are just too common
|
|
skip()
|
|
{
|
|
echo " skip: $1"
|
|
}
|
|
|
|
quiet_stderr()
|
|
{
|
|
if [ -n "$SHELLTEST_QUIET" ]; then
|
|
"$@" 2>/dev/null
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
|
|
name=foo.o.o
|
|
ext=.o
|
|
[ "${name#foo.o}" = ".o" ] || fail 3
|
|
|
|
|
|
spacey="this has * and spaces"
|
|
case $spacey in
|
|
*) spaceout=$name$spacey ;;
|
|
esac
|
|
[ "$spaceout" = "$name$spacey" ] || fail 4
|
|
|
|
|
|
n() { echo "$#$@"; }
|
|
f=" - "
|
|
out=$(n - ""$f"" -)
|
|
[ "$out" = "5- - -" ] || warn 5
|
|
|
|
|
|
n1() { echo $#; }
|
|
n2() { n1 "$@"; }
|
|
t1=$(n1)
|
|
t2=$(n2)
|
|
[ "$t1" = "0" ] || fail 6
|
|
[ "$t2" = "0" ] || fail 7
|
|
|
|
|
|
n1() { for i in "$@"; do echo $i; done; }
|
|
n2() { for i in ${1+"$@"}; do echo $i; done; }
|
|
t1=$(n1 "Hello World" "!")
|
|
t2=$(n2 "Hello World" "!")
|
|
WANT="Hello World
|
|
!"
|
|
[ "$t1" = "$WANT" ] || fail 8
|
|
[ "$t2" = "$WANT" ] || fail 9
|
|
|
|
|
|
n() { echo ${10}; }
|
|
t1=$(n 1 2 3 4 5 6 7 8 9 xx yy)
|
|
[ "$t1" = "xx" ] || fail 10
|
|
|
|
|
|
chicken1=`echo " $spacey" | sed s/a/./g`
|
|
chicken2="`quiet_stderr echo " $spacey" | sed s/a/./g`"
|
|
chicken3=$(echo " $spacey" | sed s/a/./g)
|
|
chicken4="$(echo " $spacey" | sed s/a/./g)"
|
|
[ "$chicken1" = " this h.s * .nd sp.ces" ] || fail 11
|
|
[ "$chicken2" = " this h.s * .nd sp.ces" ] || fail 12
|
|
[ "$chicken3" = " this h.s * .nd sp.ces" ] || fail 13
|
|
[ "$chicken4" = " this h.s * .nd sp.ces" ] || fail 14
|
|
|
|
|
|
f1=
|
|
f2=goo
|
|
g1=
|
|
g2=goo
|
|
out=$(echo ${f1:-foo} ${f2:-foo} ${g1:=foo} ${g2:=foo})
|
|
: ${f1:-roo} ${f2:-roo} ${g1:=roo} ${g2:=roo}
|
|
[ "$out" = "foo goo foo goo" ] || fail 16
|
|
[ "$f1$f2$g1$g2" = "gooroogoo" ] || fail 17
|
|
|
|
|
|
# This is really a test that ${x:=} can span multiple lines without requiring
|
|
# quoting, which is apparently required by POSIX but not supported in some
|
|
# shells. But to make ash/dash not abort the test script *entirely* when it
|
|
# fails, we use the $() + eval nonsense.
|
|
f3=$(quiet_stderr eval ': ${f3:=
|
|
a
|
|
b
|
|
}' && echo "$f3")
|
|
g3="
|
|
a
|
|
b"
|
|
# This is kind of a major problem, but rejecting dash and busybox sh because
|
|
# of this may cause more trouble than it's worth: people end up writing .do
|
|
# scripts with bashisms unnecessarily :(
|
|
[ "$f3" = "$g3" ] || warn 18
|
|
|
|
|
|
# Note: assignment of $@ in this context is unspecified (what do you even expect
|
|
# it to do?) so we don't test it. But we should expect $* to work.
|
|
set "a b" "c d" "*"
|
|
t1=$*
|
|
[ "$t1" = "a b c d *" ] || fail 19
|
|
|
|
|
|
unset a
|
|
t1=$(echo ${a-b c})
|
|
t2=$(echo ${a-'b c'})
|
|
t3=$(echo "${a-b c}")
|
|
t4=$(echo "${a-"b c"}")
|
|
t5=$(cat <<EOF
|
|
${a-b c}
|
|
EOF
|
|
)
|
|
[ "$t1" = "b c" ] || fail 21
|
|
[ "$t2" = "b c" ] || fail 22
|
|
[ "$t3" = "b c" ] || fail 23
|
|
[ "$t4" = "b c" ] || fail 24 # freebsd 11.2 sh fails this
|
|
[ "$t5" = "b c" ] || fail 25
|
|
|
|
|
|
unset a
|
|
t1=$(echo ${a=b c})
|
|
t2=$(echo ${a='b c'})
|
|
t3=$(echo "${a=b c}")
|
|
t4=$(echo "${a="b c"}")
|
|
t5=$(cat <<EOF
|
|
${a=b c}
|
|
EOF
|
|
)
|
|
[ "$t1" = "b c" ] || fail 31
|
|
[ "$t2" = "b c" ] || warn 32
|
|
[ "$t3" = "b c" ] || fail 33
|
|
[ "$t4" = "b c" ] || fail 34
|
|
[ "$t5" = "b c" ] || fail 35
|
|
|
|
|
|
unset foo
|
|
foo=${foo='}'} # unconfuse syntax highlighting: '
|
|
[ "$foo" = "}" ] || fail 41
|
|
foo=${foo='}'} # unconfuse syntax highlighting: '
|
|
[ "$foo" = "}" ] || fail 42
|
|
|
|
|
|
default="yu,yaa"
|
|
unset var
|
|
: ${var="$default"}
|
|
t1=$(cat <<EOF
|
|
$var
|
|
EOF
|
|
)
|
|
[ "$t1" = "yu,yaa" ] || fail 43
|
|
|
|
|
|
default="a b c"
|
|
unset list1 list2
|
|
: ${list1="$default"} ${list2=$default}
|
|
t1=$(for c in $list1; do echo $c; done)
|
|
t2=$(for c in $list2; do echo $c; done)
|
|
WANT="a
|
|
b
|
|
c"
|
|
[ "$t1" = "$WANT" ] || fail 44
|
|
[ "$t2" = "$WANT" ] || fail 45
|
|
|
|
|
|
# Arguably, 'export' and 'local' shouldn't change variable assignment
|
|
# quoting rules, but in many shells they do, and POSIX doesn't say anything
|
|
# about it. This leads to really confusing whitespace bugs in some shells,
|
|
# where
|
|
# local x; x=$a
|
|
# and
|
|
# local x=$a
|
|
# mean two different things when $a contains whitespace.
|
|
bob="a b *"
|
|
bob=$(quiet_stderr eval 'export bob=$bob:hello'; echo "$bob")
|
|
[ "$bob" = "a b *:hello" ] || skip 46
|
|
bob="a b *"
|
|
nob=$(eval 'f() { local nob=$bob:hello; echo "$nob"; }'; quiet_stderr f)
|
|
[ "$nob" = "a b *:hello" ] || skip 47
|
|
|
|
|
|
# Someone pointed out that temporary variable assignments aren't
|
|
# temporary anymore, if the thing you're calling is a function or builtin.
|
|
f() { ls >/dev/null; }
|
|
g=1 h=1 i=1
|
|
g=2 f
|
|
h=2 :
|
|
i=2 ls >/dev/null
|
|
[ "$g" = "2" ] || warn 48 # freebsd dash 0.5.10.2 fails this
|
|
[ "$h" = "2" ] || fail 49
|
|
[ "$i" = "1" ] || fail 50
|
|
|
|
|
|
var='a a b b'
|
|
var2='*a'
|
|
t1=${#var}
|
|
t2=${var#a* }
|
|
t3=${var##a* }
|
|
t4=${var%b*}
|
|
t5=${var%%b*}
|
|
t6="${var2#'*'}"
|
|
t7="${var2#"*"}"
|
|
[ "$t1" = "7" ] || fail 51
|
|
[ "$t2" = "a b b" ] || fail 52
|
|
[ "$t3" = "b" ] || fail 53
|
|
[ "$t4" = "a a b " ] || fail 54
|
|
[ "$t5" = "a a " ] || fail 55
|
|
|
|
# shells I tested work with #"*" but dash doesn't work with #'*' . The
|
|
# workaround is to simply use double quotes, so I'll make that one a warning.
|
|
[ "$t6" = "a" ] || warn 56
|
|
[ "$t7" = "a" ] || fail 56
|
|
|
|
|
|
in="a
|
|
b
|
|
"
|
|
t1=$(echo "$in" | tr a A)
|
|
t2="$(echo "$in" | tr a A)"
|
|
[ "$t1" = "A
|
|
b" ] || exit 57
|
|
[ "$t2" = "A
|
|
b" ] || exit 58
|
|
|
|
|
|
echo "`printf 'foo\r\n'` bar" >broken
|
|
echo "`printf 'foo\r\n'`"" bar" | diff -q - broken || fail 59
|
|
|
|
|
|
#
|
|
# This one is too obnoxious. dash and ash pass the test, but most shells
|
|
# don't, and this case is just too dumb to care about. Just don't do that!
|
|
#
|
|
t=`echo $(case x in x) echo hello;; esac)`
|
|
[ "$t" = "hello" ] || skip 60
|
|
#
|
|
# Note that with the little-known optional left-paren, this *does* work.
|
|
# Let's try it to make sure that remains true.
|
|
t=`echo $(case x in (x) echo hello;; esac)`
|
|
[ "$t" = "hello" ] || fail 60
|
|
|
|
|
|
x=5
|
|
t1=$(($x + 4))
|
|
t2=$(echo $(( 010 + 0x10 )))
|
|
[ "$t1" = "9" ] || fail 61
|
|
[ "$t2" = "24" ] || fail 62
|
|
|
|
|
|
t=$(echo hello ^ cat)
|
|
[ "$t" = "hello ^ cat" ] || fail 65
|
|
|
|
|
|
t1=$(for d in this-glob-does-*-not-exist; do echo "$d"; done)
|
|
t2=$(for d in this-glob-does-*-not-exist; do echo "$d"; done)
|
|
|
|
|
|
# http://www.gnu.org/software/hello/manual/autoconf/Assignments.html
|
|
false || foo=bar; [ "$?" = 0 ] || fail 71
|
|
foo=`exit 1`; [ "$?" != 0 ] || fail 72
|
|
|
|
|
|
# http://www.gnu.org/software/hello/manual/autoconf/Shell-Functions.html
|
|
f1() { echo 1; }
|
|
f2(){ echo 2;}
|
|
f3()(echo 3)
|
|
f4()if true; then echo 4; fi
|
|
f5() ( exit 5 )
|
|
[ "$(f1)" = 1 ] || fail 81
|
|
[ "$(f2)" = 2 ] || fail 82
|
|
[ "$(f3)" = 3 ] || fail 83
|
|
[ "$(f4)" = 4 ] || fail 84
|
|
f5 && fail 85
|
|
f6() (
|
|
f6b() { return 1; }
|
|
set -e
|
|
f6b
|
|
fail 86
|
|
)
|
|
f6
|
|
f7() { :; }; f7=; f7 || fail 87
|
|
a=
|
|
f8() { echo $a; };
|
|
t8=$(a=1 f8)
|
|
[ "$t8" = "1" ] || fail 88
|
|
|
|
|
|
# Some versions of zsh (eg. 5.0.8 on MacOS, but not 5.3.1
|
|
# on Debian) fail this one. It seems to be very specific:
|
|
# "set -e" aborts the subshell, if a
|
|
# function-within-a-function returns an error code, even
|
|
# though there's an "|| true" there, but only if in the
|
|
# else clause, and only if the outer () does *not* have an
|
|
# "|| something" clause.
|
|
#
|
|
# "x || true" should not abort when set -e, even if x is
|
|
# a function.
|
|
(
|
|
set -e
|
|
f() { false; }
|
|
g() { true | f; }
|
|
if false; then
|
|
:
|
|
else
|
|
! g || true
|
|
fi
|
|
)
|
|
rv=$?
|
|
[ "$rv" = 0 ] || fail 89
|
|
|
|
|
|
rm -f shellfail
|
|
echo "false; true" >shellfail
|
|
|
|
# 'false' should *not* abort because outer context has "||".
|
|
# Then 'true' gives success.
|
|
set +e
|
|
(
|
|
set -e
|
|
false
|
|
true
|
|
) || fail 89a
|
|
|
|
# 'false' still should not abort even in a subshell.
|
|
set +e
|
|
(
|
|
set -e
|
|
( false; true )
|
|
[ "$?" = 0 ] || fail 89b1
|
|
true
|
|
) || fail 89b
|
|
|
|
# Check whether "." scripts inherit "set -e" setting *and* the outer "||"
|
|
# context.
|
|
set +e
|
|
(
|
|
set -e
|
|
( . ./shellfail )
|
|
# There seems to be widespread disagreement on what should happen
|
|
# here. All shells seem to inherit "set -e" into a "." script, but
|
|
# they disagree about whether the outer "||" context (which
|
|
# effectively disables "set -e") should be inherited. ksh93, bash,
|
|
# and zsh inherit it, while ash, dash, and mksh (among others) throw
|
|
# it away, causing "set -e" to abort.
|
|
#
|
|
# Honestly, the latter behaviour seems a lot more like what I'd
|
|
# expect (a separate script doesn't want its "set -e" behaviour
|
|
# to depend on whether it's deep in a context with an "||" somewhere
|
|
# outside it), so let's warn on the former. But I'm willing to hear
|
|
# arguments either way. -- apenwarr
|
|
[ "$?" = 0 ] && warn 89c
|
|
# even if "." aborted and returned nonzero, our own context should
|
|
# not have aborted, so we expect to get here and avoid the outer
|
|
# fail below.
|
|
true
|
|
) || fail 89c1
|
|
|
|
|
|
# http://www.gnu.org/software/hello/manual/autoconf/Limitations-of-Builtins.html
|
|
. /dev/null || fail 90
|
|
(! : | :) && fail 91 || true
|
|
(! { :; }) && fail 92 || true
|
|
t3=none
|
|
case frog.c in
|
|
(*.c) t3=c ;;
|
|
(*) t3=all ;;
|
|
esac
|
|
[ "$t3" = "c" ] || fail 93
|
|
t4=$(echo '\n' | wc -l) # echo should *not* be parsing backslash-escapes
|
|
[ "$t4" -eq 1 ] || warn 94
|
|
f5() {
|
|
for arg; do
|
|
echo $arg
|
|
done
|
|
}
|
|
t5=$(f5 a=5 b c)
|
|
[ "$t5" = "a=5
|
|
b
|
|
c" ] || fail 95
|
|
t6=$(printf -- '%d %d' 5 6)
|
|
[ "$t6" = "5 6" ] || fail 96
|
|
echo 'word\ game stuff' >shellfile
|
|
read t7a t7b <shellfile
|
|
read -r t8a t8b <shellfile
|
|
[ "$t7a" = 'word game' ] || fail 97
|
|
[ "$t7b" = 'stuff' ] || fail 97
|
|
[ "$t8a" = 'word\' ] || fail 98
|
|
[ "$t8b" = 'game stuff' ] || fail 98
|
|
test -e shellfile || fail 99
|
|
[ "-a" = "-a" ] || fail 100
|
|
[ "-a" != "-b" ] || fail 101
|
|
[ "-a" -a "!" ] || fail 102
|
|
(unset foo && unset foo && unset foo) || fail 103
|
|
|
|
# http://www.gnu.org/software/hello/manual/autoconf/Limitations-of-Usual-Tools.html
|
|
rm -f || fail 110
|
|
|
|
# reported by singpolyma in busybox-w32's issue tracker
|
|
(
|
|
set -e
|
|
HOME="$PWD/space home dir"
|
|
rm -rf "$HOME"
|
|
mkdir "$HOME"
|
|
quiet_stderr cd ~ || exit 1
|
|
cd ..
|
|
rmdir 'space home dir'
|
|
) || warn 111
|
|
|
|
# reported by Alex Bradbury:
|
|
# http://www.debian.org/doc/debian-policy/ch-files.html#s-scripts
|
|
# Debian says that 'local' isn't POSIX, but I like it, so let's reject
|
|
# any shell where it doesn't exist.
|
|
x=5 y=6 z=7
|
|
lt() { x=1; local y=2 z=3; }
|
|
lt
|
|
[ "$x" = "1" ] || fail 112
|
|
[ "$y$z" = "67" ] || fail 113
|
|
|
|
# reported by Tim Allen:
|
|
# some shells don't return 0 from an empty '.' script; they return the
|
|
# previous command's exit code instead.
|
|
# See:
|
|
# http://pubs.opengroup.org/onlinepubs/009695399/utilities/dot.html
|
|
# http://austingroupbugs.net/view.php?id=114
|
|
false
|
|
. ./nothing.od || warn 114
|
|
|
|
# this is actually a bash/kshism, but is allowed by POSIX: the parameters to
|
|
# '.' should be passed to the sub-script.
|
|
#
|
|
# We used to require this, because it's so useful despite being optional in
|
|
# POSIX. But unfortunately, too many shells (including dash) can't do it,
|
|
# so we ended up always using bash, which leads people to write .do scripts
|
|
# with bashisms.
|
|
set x y z
|
|
# dotparams.od might warn 115
|
|
. ./dotparams.od a b || fail 117
|
|
[ "$1-$2-$3" = "x-y-z" ] || fail 116
|
|
|
|
# Warn about the way bash allows '==' in its test command.
|
|
# This is the #1 bashism I've observed, and totally unnecessary, so let's
|
|
# dock it some points so it doesn't always end up as the primary shell.
|
|
[ 1 == 1 ] 2>/dev/null && warn 118
|
|
|
|
# Some shells apparently don't support left padding in printf
|
|
x=$(printf "a%-5sc" "b")
|
|
[ "$x" = "ab c" ] || warn 119
|
|
|
|
# Make sure cd supports -L and -P options properly
|
|
rm -f shlink
|
|
ln -s . shlink
|
|
(cd -L shlink/shlink/shlink/../shlink) || fail 120
|
|
(cd -P shlink/shlink/shlink/../shlink) && fail 121
|
|
|
|
[ -e shelltest.failed ] && exit 41
|
|
[ -e shelltest.warned ] && exit 42
|
|
exit 40
|