apenwarr-redo/t/shelltest.od
Avery Pennarun 0d174f92c3 redo-sh: downgrade failures that affected dash; add a bash warning.
I feel a little dirty doing this, but the way the code was before, redo
almost always picked bash as the shell.  bash is way too overpowered
and this led to bashisms in do scripts unnecessarily.  The two failures
in dash are things that I would really like to have, but they haven't
materialized after 6 years, so I guess we should be realistic.

To appropriately penalize bash for asking for trouble, I added a
warning about [ 1 == 1 ] syntax being valid (as opposed to the POSIX
correct [ 1 = 1 ]).  This allows dash to be selected ahead of bash.

I also moved 'sh' to the end of the list, because although it's the
weakest shell on some systems, on other systems it's just bash.  And I
put zsh in front of bash, because fewer people have zsh and we want
them to test zsh.
2018-10-12 05:18:25 -04:00

393 lines
9.1 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 really 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
FAIL=
fail()
{
echo " failed: $1"
FAIL=41
}
warn()
{
echo " warning: $1"
[ -n "$FAIL" ] || FAIL=42
}
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
[ "$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 almost all shells (except bash), they do, and POSIX doesn't say
# anything about it. So let's not bother testing this, other than just letting
# it check our syntax.
#
bob="a b *"
bob=$(quiet_stderr eval 'export bob=$bob:hello'; echo "$bob")
#[ "$bob" = "a b *:hello" ] || warn 46
bob="a b *"
nob=$(eval 'f() { local nob=$bob:hello; echo "$nob"; }'; quiet_stderr f)
#[ "$nob" = "a b *:hello" ] || warn 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" ] || fail 48
[ "$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" ] || fail 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
# 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)
[ "$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
[ -n "$FAIL" ] || exit 40
exit $FAIL