summary refs log tree commit diff
path: root/bin/cash/tests/expansion
diff options
context:
space:
mode:
Diffstat (limited to 'bin/cash/tests/expansion')
-rw-r--r--bin/cash/tests/expansion/Makefile108
-rw-r--r--bin/cash/tests/expansion/arith1.030
-rw-r--r--bin/cash/tests/expansion/arith10.035
-rw-r--r--bin/cash/tests/expansion/arith11.012
-rw-r--r--bin/cash/tests/expansion/arith12.04
-rw-r--r--bin/cash/tests/expansion/arith13.06
-rw-r--r--bin/cash/tests/expansion/arith14.040
-rw-r--r--bin/cash/tests/expansion/arith2.077
-rw-r--r--bin/cash/tests/expansion/arith3.014
-rw-r--r--bin/cash/tests/expansion/arith4.020
-rw-r--r--bin/cash/tests/expansion/arith5.017
-rw-r--r--bin/cash/tests/expansion/arith6.016
-rw-r--r--bin/cash/tests/expansion/arith7.012
-rw-r--r--bin/cash/tests/expansion/arith8.04
-rw-r--r--bin/cash/tests/expansion/arith9.020
-rw-r--r--bin/cash/tests/expansion/assign1.037
-rw-r--r--bin/cash/tests/expansion/cmdsubst1.048
-rw-r--r--bin/cash/tests/expansion/cmdsubst10.051
-rw-r--r--bin/cash/tests/expansion/cmdsubst11.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst12.06
-rw-r--r--bin/cash/tests/expansion/cmdsubst13.012
-rw-r--r--bin/cash/tests/expansion/cmdsubst14.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst15.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst16.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst17.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst18.06
-rw-r--r--bin/cash/tests/expansion/cmdsubst19.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst2.043
-rw-r--r--bin/cash/tests/expansion/cmdsubst20.06
-rw-r--r--bin/cash/tests/expansion/cmdsubst21.06
-rw-r--r--bin/cash/tests/expansion/cmdsubst22.06
-rw-r--r--bin/cash/tests/expansion/cmdsubst23.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst24.024
-rw-r--r--bin/cash/tests/expansion/cmdsubst25.07
-rw-r--r--bin/cash/tests/expansion/cmdsubst26.06
-rw-r--r--bin/cash/tests/expansion/cmdsubst3.023
-rw-r--r--bin/cash/tests/expansion/cmdsubst4.04
-rw-r--r--bin/cash/tests/expansion/cmdsubst5.05
-rw-r--r--bin/cash/tests/expansion/cmdsubst6.053
-rw-r--r--bin/cash/tests/expansion/cmdsubst7.031
-rw-r--r--bin/cash/tests/expansion/cmdsubst8.017
-rw-r--r--bin/cash/tests/expansion/cmdsubst9.011
-rw-r--r--bin/cash/tests/expansion/export1.013
-rw-r--r--bin/cash/tests/expansion/export2.024
-rw-r--r--bin/cash/tests/expansion/export3.030
-rw-r--r--bin/cash/tests/expansion/heredoc1.025
-rw-r--r--bin/cash/tests/expansion/heredoc2.015
-rw-r--r--bin/cash/tests/expansion/ifs1.035
-rw-r--r--bin/cash/tests/expansion/ifs2.024
-rw-r--r--bin/cash/tests/expansion/ifs3.021
-rw-r--r--bin/cash/tests/expansion/ifs4.039
-rw-r--r--bin/cash/tests/expansion/ifs5.04
-rw-r--r--bin/cash/tests/expansion/ifs6.06
-rw-r--r--bin/cash/tests/expansion/ifs7.05
-rw-r--r--bin/cash/tests/expansion/length1.012
-rw-r--r--bin/cash/tests/expansion/length2.04
-rw-r--r--bin/cash/tests/expansion/length3.010
-rw-r--r--bin/cash/tests/expansion/length4.011
-rw-r--r--bin/cash/tests/expansion/length5.027
-rw-r--r--bin/cash/tests/expansion/length6.08
-rw-r--r--bin/cash/tests/expansion/length7.014
-rw-r--r--bin/cash/tests/expansion/length8.014
-rw-r--r--bin/cash/tests/expansion/local1.028
-rw-r--r--bin/cash/tests/expansion/local2.034
-rw-r--r--bin/cash/tests/expansion/pathname1.065
-rw-r--r--bin/cash/tests/expansion/pathname2.035
-rw-r--r--bin/cash/tests/expansion/pathname3.029
-rw-r--r--bin/cash/tests/expansion/pathname4.028
-rw-r--r--bin/cash/tests/expansion/pathname5.03
-rw-r--r--bin/cash/tests/expansion/pathname6.029
-rw-r--r--bin/cash/tests/expansion/plus-minus1.076
-rw-r--r--bin/cash/tests/expansion/plus-minus2.04
-rw-r--r--bin/cash/tests/expansion/plus-minus3.044
-rw-r--r--bin/cash/tests/expansion/plus-minus4.038
-rw-r--r--bin/cash/tests/expansion/plus-minus5.031
-rw-r--r--bin/cash/tests/expansion/plus-minus6.034
-rw-r--r--bin/cash/tests/expansion/plus-minus7.026
-rw-r--r--bin/cash/tests/expansion/plus-minus8.05
-rw-r--r--bin/cash/tests/expansion/plus-minus9.08
-rw-r--r--bin/cash/tests/expansion/question1.022
-rw-r--r--bin/cash/tests/expansion/readonly1.07
-rw-r--r--bin/cash/tests/expansion/redir1.026
-rw-r--r--bin/cash/tests/expansion/set-u1.029
-rw-r--r--bin/cash/tests/expansion/set-u2.012
-rw-r--r--bin/cash/tests/expansion/set-u3.06
-rw-r--r--bin/cash/tests/expansion/tilde1.056
-rw-r--r--bin/cash/tests/expansion/tilde2.090
-rw-r--r--bin/cash/tests/expansion/trim1.085
-rw-r--r--bin/cash/tests/expansion/trim10.07
-rw-r--r--bin/cash/tests/expansion/trim11.07
-rw-r--r--bin/cash/tests/expansion/trim2.055
-rw-r--r--bin/cash/tests/expansion/trim3.046
-rw-r--r--bin/cash/tests/expansion/trim4.015
-rw-r--r--bin/cash/tests/expansion/trim5.028
-rw-r--r--bin/cash/tests/expansion/trim6.022
-rw-r--r--bin/cash/tests/expansion/trim7.016
-rw-r--r--bin/cash/tests/expansion/trim8.075
-rw-r--r--bin/cash/tests/expansion/trim9.061
98 files changed, 2375 insertions, 0 deletions
diff --git a/bin/cash/tests/expansion/Makefile b/bin/cash/tests/expansion/Makefile
new file mode 100644
index 00000000..08dc6e71
--- /dev/null
+++ b/bin/cash/tests/expansion/Makefile
@@ -0,0 +1,108 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/Makefile 333927 2018-05-20 17:25:52Z jilles $
+
+PACKAGE=	tests
+
+TESTSDIR=	${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=	functional_test
+
+${PACKAGE}FILES+=	arith1.0
+${PACKAGE}FILES+=	arith2.0
+${PACKAGE}FILES+=	arith3.0
+${PACKAGE}FILES+=	arith4.0
+${PACKAGE}FILES+=	arith5.0
+${PACKAGE}FILES+=	arith6.0
+${PACKAGE}FILES+=	arith7.0
+${PACKAGE}FILES+=	arith8.0
+${PACKAGE}FILES+=	arith9.0
+${PACKAGE}FILES+=	arith10.0
+${PACKAGE}FILES+=	arith11.0
+${PACKAGE}FILES+=	arith12.0
+${PACKAGE}FILES+=	arith13.0
+${PACKAGE}FILES+=	arith14.0
+${PACKAGE}FILES+=	assign1.0
+${PACKAGE}FILES+=	cmdsubst1.0
+${PACKAGE}FILES+=	cmdsubst2.0
+${PACKAGE}FILES+=	cmdsubst3.0
+${PACKAGE}FILES+=	cmdsubst4.0
+${PACKAGE}FILES+=	cmdsubst5.0
+${PACKAGE}FILES+=	cmdsubst6.0
+${PACKAGE}FILES+=	cmdsubst7.0
+${PACKAGE}FILES+=	cmdsubst8.0
+${PACKAGE}FILES+=	cmdsubst9.0
+${PACKAGE}FILES+=	cmdsubst10.0
+${PACKAGE}FILES+=	cmdsubst11.0
+${PACKAGE}FILES+=	cmdsubst12.0
+${PACKAGE}FILES+=	cmdsubst13.0
+${PACKAGE}FILES+=	cmdsubst14.0
+${PACKAGE}FILES+=	cmdsubst15.0
+${PACKAGE}FILES+=	cmdsubst16.0
+${PACKAGE}FILES+=	cmdsubst17.0
+${PACKAGE}FILES+=	cmdsubst18.0
+${PACKAGE}FILES+=	cmdsubst19.0
+${PACKAGE}FILES+=	cmdsubst20.0
+${PACKAGE}FILES+=	cmdsubst21.0
+${PACKAGE}FILES+=	cmdsubst22.0
+${PACKAGE}FILES+=	cmdsubst23.0
+${PACKAGE}FILES+=	cmdsubst24.0
+${PACKAGE}FILES+=	cmdsubst25.0
+${PACKAGE}FILES+=	cmdsubst26.0
+${PACKAGE}FILES+=	export1.0
+${PACKAGE}FILES+=	export2.0
+${PACKAGE}FILES+=	export3.0
+${PACKAGE}FILES+=	heredoc1.0
+${PACKAGE}FILES+=	heredoc2.0
+${PACKAGE}FILES+=	ifs1.0
+${PACKAGE}FILES+=	ifs2.0
+${PACKAGE}FILES+=	ifs3.0
+${PACKAGE}FILES+=	ifs4.0
+${PACKAGE}FILES+=	ifs5.0
+${PACKAGE}FILES+=	ifs6.0
+${PACKAGE}FILES+=	ifs7.0
+${PACKAGE}FILES+=	length1.0
+${PACKAGE}FILES+=	length2.0
+${PACKAGE}FILES+=	length3.0
+${PACKAGE}FILES+=	length4.0
+${PACKAGE}FILES+=	length5.0
+${PACKAGE}FILES+=	length6.0
+${PACKAGE}FILES+=	length7.0
+${PACKAGE}FILES+=	length8.0
+${PACKAGE}FILES+=	local1.0
+${PACKAGE}FILES+=	local2.0
+${PACKAGE}FILES+=	pathname1.0
+${PACKAGE}FILES+=	pathname2.0
+${PACKAGE}FILES+=	pathname3.0
+${PACKAGE}FILES+=	pathname4.0
+${PACKAGE}FILES+=	pathname5.0
+${PACKAGE}FILES+=	pathname6.0
+${PACKAGE}FILES+=	plus-minus1.0
+${PACKAGE}FILES+=	plus-minus2.0
+${PACKAGE}FILES+=	plus-minus3.0
+${PACKAGE}FILES+=	plus-minus4.0
+${PACKAGE}FILES+=	plus-minus5.0
+${PACKAGE}FILES+=	plus-minus6.0
+${PACKAGE}FILES+=	plus-minus7.0
+${PACKAGE}FILES+=	plus-minus8.0
+${PACKAGE}FILES+=	plus-minus9.0
+${PACKAGE}FILES+=	question1.0
+${PACKAGE}FILES+=	readonly1.0
+${PACKAGE}FILES+=	redir1.0
+${PACKAGE}FILES+=	set-u1.0
+${PACKAGE}FILES+=	set-u2.0
+${PACKAGE}FILES+=	set-u3.0
+${PACKAGE}FILES+=	tilde1.0
+${PACKAGE}FILES+=	tilde2.0
+${PACKAGE}FILES+=	trim1.0
+${PACKAGE}FILES+=	trim2.0
+${PACKAGE}FILES+=	trim3.0
+${PACKAGE}FILES+=	trim4.0
+${PACKAGE}FILES+=	trim5.0
+${PACKAGE}FILES+=	trim6.0
+${PACKAGE}FILES+=	trim7.0
+${PACKAGE}FILES+=	trim8.0
+${PACKAGE}FILES+=	trim9.0
+${PACKAGE}FILES+=	trim10.0
+${PACKAGE}FILES+=	trim11.0
+
+.include <bsd.test.mk>
diff --git a/bin/cash/tests/expansion/arith1.0 b/bin/cash/tests/expansion/arith1.0
new file mode 100644
index 00000000..3d296de1
--- /dev/null
+++ b/bin/cash/tests/expansion/arith1.0
@@ -0,0 +1,30 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith1.0 201259 2009-12-30 15:59:40Z jilles $
+
+failures=0
+
+check() {
+	if [ $(($1)) != $2 ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $2 actual $(($1))"
+	fi
+}
+
+check "0&&0" 0
+check "1&&0" 0
+check "0&&1" 0
+check "1&&1" 1
+check "2&&2" 1
+check "1&&2" 1
+check "1<<40&&1<<40" 1
+check "1<<40&&4" 1
+
+check "0||0" 0
+check "1||0" 1
+check "0||1" 1
+check "1||1" 1
+check "2||2" 1
+check "1||2" 1
+check "1<<40||1<<40" 1
+check "1<<40||4" 1
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/arith10.0 b/bin/cash/tests/expansion/arith10.0
new file mode 100644
index 00000000..21715068
--- /dev/null
+++ b/bin/cash/tests/expansion/arith10.0
@@ -0,0 +1,35 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith10.0 218469 2011-02-08 23:23:55Z jilles $
+
+failures=0
+
+check() {
+	if [ $(($1)) != $2 ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $2 actual $(($1))"
+	fi
+}
+
+readonly ro=4
+rw=1
+check "0 && 0 / 0" 0
+check "1 || 0 / 0" 1
+check "0 && (ro = 2)" 0
+check "ro" 4
+check "1 || (ro = -1)" 1
+check "ro" 4
+check "0 && (rw += 1)" 0
+check "rw" 1
+check "1 || (rw += 1)" 1
+check "rw" 1
+check "0 ? 44 / 0 : 51" 51
+check "0 ? ro = 3 : 52" 52
+check "ro" 4
+check "0 ? rw += 1 : 52" 52
+check "rw" 1
+check "1 ? 68 : 30 / 0" 68
+check "2 ? 1 : (ro += 2)" 1
+check "ro" 4
+check "4 ? 1 : (rw += 1)" 1
+check "rw" 1
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/arith11.0 b/bin/cash/tests/expansion/arith11.0
new file mode 100644
index 00000000..dc3f07a3
--- /dev/null
+++ b/bin/cash/tests/expansion/arith11.0
@@ -0,0 +1,12 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith11.0 218626 2011-02-12 23:44:05Z jilles $
+# Try to divide the smallest integer by -1.
+# On amd64 this causes SIGFPE, so make sure the shell checks.
+
+# Calculate the minimum possible value, assuming two's complement and
+# a certain interpretation of overflow when shifting left.
+minint=1
+while [ $((minint <<= 1)) -gt 0 ]; do
+	:
+done
+v=$( eval ': $((minint / -1))' 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/cash/tests/expansion/arith12.0 b/bin/cash/tests/expansion/arith12.0
new file mode 100644
index 00000000..23eca2dc
--- /dev/null
+++ b/bin/cash/tests/expansion/arith12.0
@@ -0,0 +1,4 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith12.0 232839 2012-03-11 22:12:05Z jilles $
+
+_x=4 y_=5 z_z=6
+[ "$((_x*100+y_*10+z_z))" = 456 ]
diff --git a/bin/cash/tests/expansion/arith13.0 b/bin/cash/tests/expansion/arith13.0
new file mode 100644
index 00000000..834d95ec
--- /dev/null
+++ b/bin/cash/tests/expansion/arith13.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith13.0 254806 2013-08-24 20:06:00Z jilles $
+# Pre-increment and pre-decrement in arithmetic expansion are not in POSIX.
+# Require either an error or a correct implementation.
+
+! (eval 'x=4; [ $((++x)) != 5 ] || [ $x != 5 ]') 2>/dev/null &&
+! (eval 'x=2; [ $((--x)) != 1 ] || [ $x != 1 ]') 2>/dev/null
diff --git a/bin/cash/tests/expansion/arith14.0 b/bin/cash/tests/expansion/arith14.0
new file mode 100644
index 00000000..5fa2dece
--- /dev/null
+++ b/bin/cash/tests/expansion/arith14.0
@@ -0,0 +1,40 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith14.0 270029 2014-08-15 22:36:41Z jilles $
+# Check that <</>> use the low bits of the shift count.
+
+if [ $((1<<16<<16)) = 0 ]; then
+	width=32
+elif [ $((1<<32<<32)) = 0 ]; then
+	width=64
+elif [ $((1<<64<<64)) = 0 ]; then
+	width=128
+elif [ $((1<<64>>64)) = 1 ]; then
+	# Integers are wider than 128 bits; assume arbitrary precision.
+	# Nothing to test here.
+	exit 0
+else
+	echo "Cannot determine integer width"
+	exit 2
+fi
+
+twowidth=$((width * 2))
+j=43 k=$((1 << (width - 2))) r=0
+
+i=0
+while [ $i -lt $twowidth ]; do
+	if [ "$((j << i))" != "$((j << (i + width)))" ]; then
+		echo "Problem with $j << $i"
+		r=2
+	fi
+	i=$((i + 1))
+done
+
+i=0
+while [ $i -lt $twowidth ]; do
+	if [ "$((k >> i))" != "$((k >> (i + width)))" ]; then
+		echo "Problem with $k >> $i"
+		r=2
+	fi
+	i=$((i + 1))
+done
+
+exit $r
diff --git a/bin/cash/tests/expansion/arith2.0 b/bin/cash/tests/expansion/arith2.0
new file mode 100644
index 00000000..d5ddbd0d
--- /dev/null
+++ b/bin/cash/tests/expansion/arith2.0
@@ -0,0 +1,77 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith2.0 209652 2010-07-02 21:31:24Z jilles $
+
+failures=0
+
+check() {
+	if [ $(($1)) != $2 ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $2 actual $(($1))"
+	fi
+}
+
+# variables
+unset v
+check "v=2" 2
+check "v" 2
+check "$(($v))" 2
+check "v+=1" 3
+check "v" 3
+
+# constants
+check "4611686018427387904" 4611686018427387904
+check "0x4000000000000000" 4611686018427387904
+check "0400000000000000000000" 4611686018427387904
+check "0x4Ab0000000000000" 5381801554707742720
+check "010" 8
+
+# try out all operators
+v=42
+check "!v" 0
+check "!!v" 1
+check "!0" 1
+check "~0" -1
+check "~(-1)" 0
+check "-0" 0
+check "-v" -42
+check "v*v" 1764
+check "v/2" 21
+check "v%10" 2
+check "v+v" 84
+check "v-4" 38
+check "v<<1" 84
+check "v>>1" 21
+check "v<43" 1
+check "v>42" 0
+check "v<=43" 1
+check "v>=43" 0
+check "v==41" 0
+check "v!=42" 0
+check "v&3" 2
+check "v^3" 41
+check "v|3" 43
+check "v>=40&&v<=44" 1
+check "v<40||v>44" 0
+check "(v=42)&&(v+=1)==43" 1
+check "v" 43
+check "(v=42)&&(v-=1)==41" 1
+check "v" 41
+check "(v=42)&&(v*=2)==84" 1
+check "v" 84
+check "(v=42)&&(v/=10)==4" 1
+check "v" 4
+check "(v=42)&&(v%=10)==2" 1
+check "v" 2
+check "(v=42)&&(v<<=1)==84" 1
+check "v" 84
+check "(v=42)&&(v>>=2)==10" 1
+check "v" 10
+check "(v=42)&&(v&=32)==32" 1
+check "v" 32
+check "(v=42)&&(v^=32)==10" 1
+check "v" 10
+check "(v=42)&&(v|=32)==42" 1
+check "v" 42
+
+# missing: ternary
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/arith3.0 b/bin/cash/tests/expansion/arith3.0
new file mode 100644
index 00000000..43d5bf06
--- /dev/null
+++ b/bin/cash/tests/expansion/arith3.0
@@ -0,0 +1,14 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith3.0 204017 2010-02-17 22:25:22Z jilles $
+
+failures=0
+
+check() {
+	if [ $(($1)) != $2 ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $2 actual $(($1))"
+	fi
+}
+
+check "1 << 1 + 1 | 1" 5
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/arith4.0 b/bin/cash/tests/expansion/arith4.0
new file mode 100644
index 00000000..d4188640
--- /dev/null
+++ b/bin/cash/tests/expansion/arith4.0
@@ -0,0 +1,20 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith4.0 206167 2010-04-04 16:29:48Z jilles $
+
+failures=0
+
+check() {
+	if [ $(($1)) != $2 ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $2 actual $(($1))"
+	fi
+}
+
+check '20 / 2 / 2' 5
+check '20 - 2 - 2' 16
+unset a b c d
+check "a = b = c = d = 1" 1
+check "a == 1 && b == 1 && c == 1 && d == 1" 1
+check "a += b += c += d" 4
+check "a == 4 && b == 3 && c == 2 && d == 1" 1
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/arith5.0 b/bin/cash/tests/expansion/arith5.0
new file mode 100644
index 00000000..4ec65a0c
--- /dev/null
+++ b/bin/cash/tests/expansion/arith5.0
@@ -0,0 +1,17 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith5.0 206168 2010-04-04 16:48:33Z jilles $
+
+failures=0
+
+check() {
+	if [ "$2" != "$3" ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $3 actual $2"
+	fi
+}
+
+unset a
+check '$((1+${a:-$((7+2))}))' "$((1+${a:-$((7+2))}))" 10
+check '$((1+${a:=$((2+2))}))' "$((1+${a:=$((2+2))}))" 5
+check '$a' "$a" 4
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/arith6.0 b/bin/cash/tests/expansion/arith6.0
new file mode 100644
index 00000000..5cb1e8ff
--- /dev/null
+++ b/bin/cash/tests/expansion/arith6.0
@@ -0,0 +1,16 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith6.0 215550 2010-11-19 22:25:32Z jilles $
+
+v1=1\ +\ 1
+v2=D
+v3=C123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+f() { v4="$*"; }
+
+while [ ${#v2} -lt 1250 ]; do
+	eval $v2=$((3+${#v2})) $v3=$((4-${#v2}))
+	eval f $(($v2+ $v1 +$v3))
+	if [ $v4 -ne 9 ]; then
+		echo bad: $v4 -ne 9 at ${#v2}
+	fi
+	v2=x$v2
+	v3=y$v3
+done
diff --git a/bin/cash/tests/expansion/arith7.0 b/bin/cash/tests/expansion/arith7.0
new file mode 100644
index 00000000..f8a4bec0
--- /dev/null
+++ b/bin/cash/tests/expansion/arith7.0
@@ -0,0 +1,12 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith7.0 216395 2010-12-12 16:56:16Z jilles $
+
+v=1+
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+[ "$(cat <<EOF
+$(($v 1))
+EOF
+)" = 1025 ]
diff --git a/bin/cash/tests/expansion/arith8.0 b/bin/cash/tests/expansion/arith8.0
new file mode 100644
index 00000000..31a7306c
--- /dev/null
+++ b/bin/cash/tests/expansion/arith8.0
@@ -0,0 +1,4 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith8.0 216547 2010-12-18 23:03:51Z jilles $
+
+v=$( (eval ': $((08))') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/cash/tests/expansion/arith9.0 b/bin/cash/tests/expansion/arith9.0
new file mode 100644
index 00000000..3915a1a0
--- /dev/null
+++ b/bin/cash/tests/expansion/arith9.0
@@ -0,0 +1,20 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/arith9.0 218469 2011-02-08 23:23:55Z jilles $
+
+failures=0
+
+check() {
+	if [ $(($1)) != $2 ]; then
+		failures=$((failures+1))
+		echo "For $1, expected $2 actual $(($1))"
+	fi
+}
+
+check "0 ? 44 : 51" 51
+check "1 ? 68 : 30" 68
+check "2 ? 1 : -5" 1
+check "0 ? 4 : 0 ? 5 : 6" 6
+check "0 ? 4 : 1 ? 5 : 6" 5
+check "1 ? 4 : 0 ? 5 : 6" 4
+check "1 ? 4 : 1 ? 5 : 6" 4
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/assign1.0 b/bin/cash/tests/expansion/assign1.0
new file mode 100644
index 00000000..6b00d61c
--- /dev/null
+++ b/bin/cash/tests/expansion/assign1.0
@@ -0,0 +1,37 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/assign1.0 204842 2010-03-07 18:43:29Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'v=; set -- ${v=a b} $v'		'0|'
+testcase 'unset v; set -- ${v=a b} $v'		'4|a|b|a|b'
+testcase 'v=; set -- ${v:=a b} $v'		'4|a|b|a|b'
+testcase 'v=; set -- "${v:=a b}" "$v"'		'2|a b|a b'
+# expect sensible behaviour, although it disagrees with POSIX
+testcase 'v=; set -- ${v:=a\ b} $v'		'4|a|b|a|b'
+testcase 'v=; set -- ${v:=$p} $v'		'2|/etc/|/etc/'
+testcase 'v=; set -- "${v:=$p}" "$v"'		'2|/et[c]/|/et[c]/'
+testcase 'v=; set -- "${v:=a\ b}" "$v"'		'2|a\ b|a\ b'
+testcase 'v=; set -- ${v:="$p"} $v'		'2|/etc/|/etc/'
+# whether $p is quoted or not shouldn't really matter
+testcase 'v=; set -- "${v:="$p"}" "$v"'		'2|/et[c]/|/et[c]/'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/cmdsubst1.0 b/bin/cash/tests/expansion/cmdsubst1.0
new file mode 100644
index 00000000..83644988
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst1.0
@@ -0,0 +1,48 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst1.0 201366 2010-01-01 18:17:46Z jilles $
+
+failures=0
+
+check() {
+	if ! eval "[ $* ]"; then
+		echo "Failed: $*"
+		: $((failures += 1))
+	fi
+}
+
+check '"$(echo abcde)" = "abcde"'
+check '"$(echo abcde; :)" = "abcde"'
+
+check '"$(printf abcde)" = "abcde"'
+check '"$(printf abcde; :)" = "abcde"'
+
+# regular
+check '-n "$(umask)"'
+check '-n "$(umask; :)"'
+check '-n "$(umask 2>&1)"'
+check '-n "$(umask 2>&1; :)"'
+
+# special
+check '-n "$(times)"'
+check '-n "$(times; :)"'
+check '-n "$(times 2>&1)"'
+check '-n "$(times 2>&1; :)"'
+
+# regular
+check '".$(umask -@ 2>&1)." = ".umask: Illegal option -@."'
+check '".$(umask -@ 2>&1; :)." = ".umask: Illegal option -@."'
+check '".$({ umask -@; } 2>&1)." = ".umask: Illegal option -@."'
+
+# special
+check '".$(shift xyz 2>&1)." = ".shift: Illegal number: xyz."'
+check '".$(shift xyz 2>&1; :)." = ".shift: Illegal number: xyz."'
+check '".$({ shift xyz; } 2>&1)." = ".shift: Illegal number: xyz."'
+
+v=1
+check '-z "$(v=2 :)"'
+check '"$v" = 1'
+check '-z "$(v=3)"'
+check '"$v" = 1'
+check '"$(v=4 eval echo \$v)" = 4'
+check '"$v" = 1'
+
+exit $((failures > 0))
diff --git a/bin/cash/tests/expansion/cmdsubst10.0 b/bin/cash/tests/expansion/cmdsubst10.0
new file mode 100644
index 00000000..4e3b2fc4
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst10.0
@@ -0,0 +1,51 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst10.0 216826 2010-12-30 22:33:55Z jilles $
+
+a1=$(alias)
+: $(alias testalias=abcd)
+a2=$(alias)
+[ "$a1" = "$a2" ] || echo Error at line $LINENO
+
+alias testalias2=abcd
+a1=$(alias)
+: $(unalias testalias2)
+a2=$(alias)
+[ "$a1" = "$a2" ] || echo Error at line $LINENO
+
+[ "$(command -V pwd)" = "$(command -V pwd; exit $?)" ] || echo Error at line $LINENO
+
+v=1
+: $(export v=2)
+[ "$v" = 1 ] || echo Error at line $LINENO
+
+rotest=1
+: $(readonly rotest=2)
+[ "$rotest" = 1 ] || echo Error at line $LINENO
+
+set +u
+: $(set -u)
+case $- in
+*u*) echo Error at line $LINENO ;;
+esac
+set +u
+
+set +u
+: $(set -o nounset)
+case $- in
+*u*) echo Error at line $LINENO ;;
+esac
+set +u
+
+set +u
+: $(command set -u)
+case $- in
+*u*) echo Error at line $LINENO ;;
+esac
+set +u
+
+umask 77
+u1=$(umask)
+: $(umask 022)
+u2=$(umask)
+[ "$u1" = "$u2" ] || echo Error at line $LINENO
+
+dummy=$(exit 3); [ $? -eq 3 ] || echo Error at line $LINENO
diff --git a/bin/cash/tests/expansion/cmdsubst11.0 b/bin/cash/tests/expansion/cmdsubst11.0
new file mode 100644
index 00000000..36e26d4a
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst11.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst11.0 223163 2011-06-16 21:50:28Z jilles $
+
+# Not required by POSIX but useful for efficiency.
+
+[ $$ = $(eval '${SH} -c echo\ \$PPID') ]
diff --git a/bin/cash/tests/expansion/cmdsubst12.0 b/bin/cash/tests/expansion/cmdsubst12.0
new file mode 100644
index 00000000..e5ad0b09
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst12.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst12.0 230121 2012-01-14 23:10:18Z jilles $
+
+f() {
+	echo x$(printf foo >&2)y
+}
+[ "$(f 2>&1)" = "fooxy" ]
diff --git a/bin/cash/tests/expansion/cmdsubst13.0 b/bin/cash/tests/expansion/cmdsubst13.0
new file mode 100644
index 00000000..82ebb9a2
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst13.0
@@ -0,0 +1,12 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst13.0 230121 2012-01-14 23:10:18Z jilles $
+
+x=1 y=2
+[ "$(
+	case $((x+=1)) in
+	($((y+=1)))	echo bad1 ;;
+	($((y-1)))	echo $x.$y ;;
+	($((y=2)))	echo bad2 ;;
+	(*)		echo bad3 ;;
+	esac
+)" = "2.3" ] || echo "Error at $LINENO"
+[ "$x.$y" = "1.2" ] || echo "Error at $LINENO"
diff --git a/bin/cash/tests/expansion/cmdsubst14.0 b/bin/cash/tests/expansion/cmdsubst14.0
new file mode 100644
index 00000000..3ee48ef3
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst14.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst14.0 245381 2013-01-13 19:19:40Z jilles $
+
+! v=`false
+
+`
diff --git a/bin/cash/tests/expansion/cmdsubst15.0 b/bin/cash/tests/expansion/cmdsubst15.0
new file mode 100644
index 00000000..7e180d47
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst15.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst15.0 245381 2013-01-13 19:19:40Z jilles $
+
+! v=`false;
+
+`
diff --git a/bin/cash/tests/expansion/cmdsubst16.0 b/bin/cash/tests/expansion/cmdsubst16.0
new file mode 100644
index 00000000..1d807c05
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst16.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst16.0 245392 2013-01-13 22:35:51Z jilles $
+
+f() { return 3; }
+f
+[ `echo $?` = 3 ]
diff --git a/bin/cash/tests/expansion/cmdsubst17.0 b/bin/cash/tests/expansion/cmdsubst17.0
new file mode 100644
index 00000000..6b8b5bb5
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst17.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst17.0 245422 2013-01-14 12:20:55Z jilles $
+
+f() { return 3; }
+f
+[ `echo $?; :` = 3 ]
diff --git a/bin/cash/tests/expansion/cmdsubst18.0 b/bin/cash/tests/expansion/cmdsubst18.0
new file mode 100644
index 00000000..5b108896
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst18.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst18.0 314637 2017-03-03 22:46:20Z jilles $
+
+x=X
+unset n
+r=${x+$(echo a)}${x-$(echo b)}${n+$(echo c)}${n-$(echo d)}$(echo e)
+[ "$r" = aXde ]
diff --git a/bin/cash/tests/expansion/cmdsubst19.0 b/bin/cash/tests/expansion/cmdsubst19.0
new file mode 100644
index 00000000..b68509ec
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst19.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst19.0 314637 2017-03-03 22:46:20Z jilles $
+
+b=200 c=30 d=5 x=4
+r=$(echo a)$(($(echo b) + ${x+$(echo c)} + ${x-$(echo d)}))$(echo e)
+[ "$r" = a234e ]
diff --git a/bin/cash/tests/expansion/cmdsubst2.0 b/bin/cash/tests/expansion/cmdsubst2.0
new file mode 100644
index 00000000..f3bbcfbb
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst2.0
@@ -0,0 +1,43 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst2.0 205105 2010-03-12 23:23:46Z jilles $
+
+failures=0
+
+check() {
+	if ! eval "[ $* ]"; then
+		echo "Failed: $*"
+		: $((failures += 1))
+	fi
+}
+
+check '`echo /et[c]/` = "/etc/"'
+check '`printf /var/empty%s /et[c]/` = "/var/empty/etc/"'
+check '"`echo /et[c]/`" = "/etc/"'
+check '`echo "/et[c]/"` = "/etc/"'
+check '`printf /var/empty%s "/et[c]/"` = "/var/empty/et[c]/"'
+check '`printf /var/empty/%s \"/et[c]/\"` = "/var/empty/\"/et[c]/\""'
+check '"`echo \"/et[c]/\"`" = "/et[c]/"'
+check '"`echo "/et[c]/"`" = "/et[c]/"'
+check '`echo $$` = $$'
+check '"`echo $$`" = $$'
+check '`echo \$\$` = $$'
+check '"`echo \$\$`" = $$'
+
+# Command substitutions consisting of a single builtin may be treated
+# differently.
+check '`:; echo /et[c]/` = "/etc/"'
+check '`:; printf /var/empty%s /et[c]/` = "/var/empty/etc/"'
+check '"`:; echo /et[c]/`" = "/etc/"'
+check '`:; echo "/et[c]/"` = "/etc/"'
+check '`:; printf /var/empty%s "/et[c]/"` = "/var/empty/et[c]/"'
+check '`:; printf /var/empty/%s \"/et[c]/\"` = "/var/empty/\"/et[c]/\""'
+check '"`:; echo \"/et[c]/\"`" = "/et[c]/"'
+check '"`:; echo "/et[c]/"`" = "/et[c]/"'
+check '`:; echo $$` = $$'
+check '"`:; echo $$`" = $$'
+check '`:; echo \$\$` = $$'
+check '"`:; echo \$\$`" = $$'
+
+check '`set -f; echo /et[c]/` = "/etc/"'
+check '"`set -f; echo /et[c]/`" = "/et[c]/"'
+
+exit $((failures > 0))
diff --git a/bin/cash/tests/expansion/cmdsubst20.0 b/bin/cash/tests/expansion/cmdsubst20.0
new file mode 100644
index 00000000..08aff108
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst20.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst20.0 314637 2017-03-03 22:46:20Z jilles $
+
+set -T
+trapped=''
+trap "trapped=x$trapped" USR1
+[ "x$(kill -USR1 $$)y" = xy ] && [ "$trapped" = x ]
diff --git a/bin/cash/tests/expansion/cmdsubst21.0 b/bin/cash/tests/expansion/cmdsubst21.0
new file mode 100644
index 00000000..d34f9352
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst21.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst21.0 314686 2017-03-04 22:58:34Z jilles $
+
+set -T
+trapped=''
+trap "trapped=x$trapped" TERM
+[ "x$($SH -c "kill $$")y" = xy ] && [ "$trapped" = x ]
diff --git a/bin/cash/tests/expansion/cmdsubst22.0 b/bin/cash/tests/expansion/cmdsubst22.0
new file mode 100644
index 00000000..cafc428d
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst22.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst22.0 314686 2017-03-04 22:58:34Z jilles $
+
+set -T
+trapped=''
+trap "trapped=x$trapped" TERM
+[ "x$(:; kill $$)y" = xy ] && [ "$trapped" = x ]
diff --git a/bin/cash/tests/expansion/cmdsubst23.0 b/bin/cash/tests/expansion/cmdsubst23.0
new file mode 100644
index 00000000..9ffb2eed
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst23.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst23.0 315005 2017-03-10 16:04:00Z jilles $
+
+unset n
+x=abcd
+[ "X${n#$(echo a)}X${x#$(echo ab)}X$(echo abc)X" = XXcdXabcX ]
diff --git a/bin/cash/tests/expansion/cmdsubst24.0 b/bin/cash/tests/expansion/cmdsubst24.0
new file mode 100644
index 00000000..e13a673f
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst24.0
@@ -0,0 +1,24 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst24.0 317347 2017-04-23 21:58:17Z jilles $
+# POSIX leaves the effect of NUL bytes in command substitution output
+# unspecified but we have always discarded them.
+
+failures=0
+
+check() {
+	if [ "$2" != "$3" ]; then
+		printf "Failed at line %s: got \"%s\" expected \"%s\"\n" "$1" "$2" "$3"
+		: $((failures += 1))
+	fi
+}
+
+fmt='\0a\0 \0b\0c d\0'
+assign_builtin=$(printf "$fmt")
+check "$LINENO" "$assign_builtin" "a bc d"
+assign_pipeline=$(printf "$fmt" | cat)
+check "$LINENO" "$assign_pipeline" "a bc d"
+set -- $(printf "$fmt") $(printf "$fmt" | cat) "$(printf "$fmt")" "$(printf "$fmt" | cat)" 
+IFS=@
+splits="$*"
+check "$LINENO" "$splits" "a@bc@d@a@bc@d@a bc d@a bc d"
+
+[ "$failures" = 0 ]
diff --git a/bin/cash/tests/expansion/cmdsubst25.0 b/bin/cash/tests/expansion/cmdsubst25.0
new file mode 100644
index 00000000..d08a6a73
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst25.0
@@ -0,0 +1,7 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst25.0 317514 2017-04-27 18:52:18Z jilles $
+
+IFS=' '
+set -- `printf '\n '`
+IFS=:
+[ "$*" = '
+' ]
diff --git a/bin/cash/tests/expansion/cmdsubst26.0 b/bin/cash/tests/expansion/cmdsubst26.0
new file mode 100644
index 00000000..170ab903
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst26.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst26.0 317514 2017-04-27 18:52:18Z jilles $
+
+nl='
+'
+v=$nl`printf '\n'`
+[ "$v" = "$nl" ]
diff --git a/bin/cash/tests/expansion/cmdsubst3.0 b/bin/cash/tests/expansion/cmdsubst3.0
new file mode 100644
index 00000000..f8a5d3c3
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst3.0
@@ -0,0 +1,23 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst3.0 218819 2011-02-18 20:37:09Z jilles $
+
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+
+e=
+for i in 0 1 2 3; do
+	for j in 0 1 2 3 4 5 6 7; do
+		for k in 0 1 2 3 4 5 6 7; do
+			case $i$j$k in
+			000) continue ;;
+			esac
+			e="$e\n\\$i$j$k"
+		done
+	done
+done
+e1=$(printf "$e")
+e2="$(printf "$e")"
+[ "${#e1}" = 510 ] || echo length bad
+[ "$e1" = "$e2" ] || echo e1 != e2
+[ "$e1" = "$(printf "$e")" ] || echo quoted bad
+IFS=
+[ "$e1" = $(printf "$e") ] || echo unquoted bad
diff --git a/bin/cash/tests/expansion/cmdsubst4.0 b/bin/cash/tests/expansion/cmdsubst4.0
new file mode 100644
index 00000000..c1bb8f75
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst4.0
@@ -0,0 +1,4 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst4.0 216747 2010-12-27 23:56:03Z jilles $
+
+exec 2>/dev/null
+! y=$(: </var/empty/nonexistent)
diff --git a/bin/cash/tests/expansion/cmdsubst5.0 b/bin/cash/tests/expansion/cmdsubst5.0
new file mode 100644
index 00000000..76b480fa
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst5.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst5.0 216761 2010-12-28 13:28:24Z jilles $
+
+unset v
+exec 2>/dev/null
+! y=$(: ${v?})
diff --git a/bin/cash/tests/expansion/cmdsubst6.0 b/bin/cash/tests/expansion/cmdsubst6.0
new file mode 100644
index 00000000..f7fd85fd
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst6.0
@@ -0,0 +1,53 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst6.0 216763 2010-12-28 14:58:08Z jilles $
+# This tests if the cmdsubst optimization is still used if possible.
+
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+
+	unset v
+	eval "pid=\$(dummy=$code echo \$(\$SH -c echo\ \\\$PPID))"
+
+	if [ "$pid" = "$$" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "Failure for $code"
+	fi
+}
+
+unset v
+w=1
+testcase '$w'
+testcase '1${w+1}'
+testcase '1${w-1}'
+testcase '1${v+1}'
+testcase '1${v-1}'
+testcase '1${w:+1}'
+testcase '1${w:-1}'
+testcase '1${v:+1}'
+testcase '1${v:-1}'
+testcase '${w?}'
+testcase '${w:?}'
+testcase '${w#x}'
+testcase '${w##x}'
+testcase '${w%x}'
+testcase '${w%%x}'
+
+testcase '$((w))'
+testcase '$(((w+4)*2/3))'
+testcase '$((w==1))'
+testcase '$((w>=0 && w<=5 && w!=2))'
+testcase '$((${#w}))'
+testcase '$((${#IFS}))'
+testcase '$((${#w}>=1))'
+testcase '$(($$))'
+testcase '$(($#))'
+testcase '$(($?))'
+
+testcase '$(: $((w=4)))'
+testcase '$(: ${v=2})'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/cmdsubst7.0 b/bin/cash/tests/expansion/cmdsubst7.0
new file mode 100644
index 00000000..ede904aa
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst7.0
@@ -0,0 +1,31 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst7.0 216778 2010-12-28 21:27:08Z jilles $
+
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+
+	unset v
+	eval ": \$($code)"
+
+	if [ "${v:+bad}" = "" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "Failure for $code"
+	fi
+}
+
+testcase ': ${v=0}'
+testcase ': ${v:=0}'
+testcase ': $((v=1))'
+testcase ': $((v+=1))'
+w='v=1'
+testcase ': $(($w))'
+testcase ': $((${$+v=1}))'
+testcase ': $((v${$+=1}))'
+testcase ': $((v $(echo =) 1))'
+testcase ': $(($(echo $w)))'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/cmdsubst8.0 b/bin/cash/tests/expansion/cmdsubst8.0
new file mode 100644
index 00000000..4d3d533e
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst8.0
@@ -0,0 +1,17 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst8.0 216819 2010-12-30 15:04:59Z jilles $
+# Not required by POSIX (although referenced in a non-normative section),
+# but possibly useful.
+
+: hi there &
+p=$!
+q=$(jobs -l $p)
+
+# Change tabs to spaces.
+set -f
+set -- $q
+r="$*"
+
+case $r in
+*" $p "*) ;;
+*) echo Pid missing; exit 3 ;;
+esac
diff --git a/bin/cash/tests/expansion/cmdsubst9.0 b/bin/cash/tests/expansion/cmdsubst9.0
new file mode 100644
index 00000000..4a9a5094
--- /dev/null
+++ b/bin/cash/tests/expansion/cmdsubst9.0
@@ -0,0 +1,11 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/cmdsubst9.0 216819 2010-12-30 15:04:59Z jilles $
+
+set -e
+
+cd /
+dummy=$(cd /bin)
+[ "$(pwd)" = / ]
+
+v=1
+dummy=$(eval v=2)
+[ "$v" = 1 ]
diff --git a/bin/cash/tests/expansion/export1.0 b/bin/cash/tests/expansion/export1.0
new file mode 100644
index 00000000..05474992
--- /dev/null
+++ b/bin/cash/tests/expansion/export1.0
@@ -0,0 +1,13 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/export1.0 238430 2012-07-13 22:29:02Z jilles $
+
+w='@ vv=6'
+
+v=0 vv=0
+export \v=$w
+[ "$v" = "@" ] || echo "Expected @ got $v"
+[ "$vv" = "6" ] || echo "Expected 6 got $vv"
+
+HOME=/known/value
+
+export \v=~
+[ "$v" = \~ ] || echo "Expected ~ got $v"
diff --git a/bin/cash/tests/expansion/export2.0 b/bin/cash/tests/expansion/export2.0
new file mode 100644
index 00000000..3ed91e76
--- /dev/null
+++ b/bin/cash/tests/expansion/export2.0
@@ -0,0 +1,24 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/export2.0 238468 2012-07-15 10:19:43Z jilles $
+
+w='@ @'
+check() {
+	[ "$v" = "$w" ] || echo "Expected $w got $v"
+}
+
+export v=$w
+check
+
+HOME=/known/value
+check() {
+	[ "$v" = ~ ] || echo "Expected $HOME got $v"
+}
+
+export v=~
+check
+
+check() {
+	[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+}
+
+export v=x:~
+check
diff --git a/bin/cash/tests/expansion/export3.0 b/bin/cash/tests/expansion/export3.0
new file mode 100644
index 00000000..abe137a3
--- /dev/null
+++ b/bin/cash/tests/expansion/export3.0
@@ -0,0 +1,30 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/export3.0 238468 2012-07-15 10:19:43Z jilles $
+
+w='@ @'
+check() {
+	[ "$v" = "$w" ] || echo "Expected $w got $v"
+}
+
+command export v=$w
+check
+command command export v=$w
+check
+
+HOME=/known/value
+check() {
+	[ "$v" = ~ ] || echo "Expected $HOME got $v"
+}
+
+command export v=~
+check
+command command export v=~
+check
+
+check() {
+	[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+}
+
+command export v=x:~
+check
+command command export v=x:~
+check
diff --git a/bin/cash/tests/expansion/heredoc1.0 b/bin/cash/tests/expansion/heredoc1.0
new file mode 100644
index 00000000..cb623e51
--- /dev/null
+++ b/bin/cash/tests/expansion/heredoc1.0
@@ -0,0 +1,25 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/heredoc1.0 222715 2011-06-05 12:46:26Z jilles $
+
+f() { return $1; }
+
+[ `f 42; { cat; } <<EOF
+$?
+EOF
+` = 42 ] || echo compound command bad
+
+[ `f 42; (cat) <<EOF
+$?
+EOF
+` = 42 ] || echo subshell bad
+
+long=`printf %08192d 0`
+
+[ `f 42; { cat; } <<EOF
+$long.$?
+EOF
+` = $long.42 ] || echo long compound command bad
+
+[ `f 42; (cat) <<EOF
+$long.$?
+EOF
+` = $long.42 ] || echo long subshell bad
diff --git a/bin/cash/tests/expansion/heredoc2.0 b/bin/cash/tests/expansion/heredoc2.0
new file mode 100644
index 00000000..e1a7e391
--- /dev/null
+++ b/bin/cash/tests/expansion/heredoc2.0
@@ -0,0 +1,15 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/heredoc2.0 222716 2011-06-05 14:13:15Z jilles $
+
+f() { return $1; }
+
+[ `f 42; cat <<EOF
+$?
+EOF
+` = 42 ] || echo simple command bad
+
+long=`printf %08192d 0`
+
+[ `f 42; cat <<EOF
+$long.$?
+EOF
+` = $long.42 ] || echo long simple command bad
diff --git a/bin/cash/tests/expansion/ifs1.0 b/bin/cash/tests/expansion/ifs1.0
new file mode 100644
index 00000000..bc6d5100
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs1.0
@@ -0,0 +1,35 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs1.0 194981 2009-06-25 17:36:08Z jilles $
+
+c=: e= s=' '
+failures=''
+ok=''
+
+check_result() {
+	if [ "x$2" = "x$3" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $1, expected $3 actual $2"
+	fi
+}
+
+IFS=' 	
+'
+set -- a ''
+set -- "$@"
+check_result 'set -- "$@"' "($#)($1)($2)" "(2)(a)()"
+
+set -- a ''
+set -- "$@"$e
+check_result 'set -- "$@"$e' "($#)($1)($2)" "(2)(a)()"
+
+set -- a ''
+set -- "$@"$s
+check_result 'set -- "$@"$s' "($#)($1)($2)" "(2)(a)()"
+
+IFS="$c"
+set -- a ''
+set -- "$@"$c
+check_result 'set -- "$@"$c' "($#)($1)($2)" "(2)(a)()"
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/ifs2.0 b/bin/cash/tests/expansion/ifs2.0
new file mode 100644
index 00000000..db6542a2
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs2.0
@@ -0,0 +1,24 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs2.0 211341 2010-08-15 17:14:05Z jilles $
+
+failures=0
+i=1
+set -f
+while [ "$i" -le 127 ]; do
+	# A different byte still in the range 1..127.
+	i2=$((i^2+(i==2)))
+	# Add a character to work around command substitution's removal of
+	# final newlines, then remove it again.
+	c=$(printf \\"$(printf %o@ "$i")")
+	c=${c%@}
+	c2=$(printf \\"$(printf %o@ "$i2")")
+	c2=${c2%@}
+	IFS=$c
+	set -- $c2$c$c2$c$c2
+	if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] ||
+	    [ "$3" != "$c2" ]; then
+		echo "Bad results for separator $i (word $i2)" >&2
+		: $((failures += 1))
+	fi
+	i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/bin/cash/tests/expansion/ifs3.0 b/bin/cash/tests/expansion/ifs3.0
new file mode 100644
index 00000000..a26e7570
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs3.0
@@ -0,0 +1,21 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs3.0 211622 2010-08-22 13:09:12Z jilles $
+
+failures=0
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+i=128
+set -f
+while [ "$i" -le 255 ]; do
+	i2=$((i^2))
+	c=$(printf \\"$(printf %o "$i")")
+	c2=$(printf \\"$(printf %o "$i2")")
+	IFS=$c
+	set -- $c2$c$c2$c$c2
+	if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] ||
+	    [ "$3" != "$c2" ]; then
+		echo "Bad results for separator $i (word $i2)" >&2
+		: $((failures += 1))
+	fi
+	i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/bin/cash/tests/expansion/ifs4.0 b/bin/cash/tests/expansion/ifs4.0
new file mode 100644
index 00000000..e487d513
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs4.0
@@ -0,0 +1,39 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs4.0 222361 2011-05-27 15:56:13Z jilles $
+
+c=: e= s=' '
+failures=''
+ok=''
+
+check_result() {
+	if [ "x$2" = "x$3" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $1, expected $3 actual $2"
+	fi
+}
+
+IFS=' 	
+'
+set -- a b '' c
+set -- $@
+check_result 'set -- $@' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()"
+
+IFS=''
+set -- a b '' c
+set -- $@
+check_result 'set -- $@' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()"
+
+set -- a b '' c
+set -- $*
+check_result 'set -- $*' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()"
+
+set -- a b '' c
+set -- "$@"
+check_result 'set -- "$@"' "($#)($1)($2)($3)($4)" "(4)(a)(b)()(c)"
+
+set -- a b '' c
+set -- "$*"
+check_result 'set -- "$*"' "($#)($1)($2)($3)($4)" "(1)(abc)()()()"
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/ifs5.0 b/bin/cash/tests/expansion/ifs5.0
new file mode 100644
index 00000000..1a31bfb4
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs5.0
@@ -0,0 +1,4 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs5.0 278806 2015-02-15 19:48:29Z jilles $
+
+set -- $(echo a b c d)
+[ "$#" = 4 ]
diff --git a/bin/cash/tests/expansion/ifs6.0 b/bin/cash/tests/expansion/ifs6.0
new file mode 100644
index 00000000..fcf82810
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs6.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs6.0 280920 2015-03-31 20:59:37Z jilles $
+
+IFS=': '
+x=': :'
+set -- $x
+[ "$#|$1|$2|$3" = "2|||" ]
diff --git a/bin/cash/tests/expansion/ifs7.0 b/bin/cash/tests/expansion/ifs7.0
new file mode 100644
index 00000000..d04514c1
--- /dev/null
+++ b/bin/cash/tests/expansion/ifs7.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/ifs7.0 280920 2015-03-31 20:59:37Z jilles $
+
+IFS=2
+set -- $((123))
+[ "$#|$1|$2|$3" = "2|1|3|" ]
diff --git a/bin/cash/tests/expansion/length1.0 b/bin/cash/tests/expansion/length1.0
new file mode 100644
index 00000000..70840955
--- /dev/null
+++ b/bin/cash/tests/expansion/length1.0
@@ -0,0 +1,12 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length1.0 219611 2011-03-13 16:20:38Z jilles $
+
+v=abcd
+[ "${#v}" = 4 ] || echo '${#v} wrong'
+v=$$
+[ "${#$}" = "${#v}" ] || echo '${#$} wrong'
+[ "${#!}" = 0 ] || echo '${#!} wrong'
+set -- 01 2 3 4 5 6 7 8 9 10 11 12 0013
+[ "${#1}" = 2 ] || echo '${#1} wrong'
+[ "${#13}" = 4 ] || echo '${#13} wrong'
+v=$0
+[ "${#0}" = "${#v}" ] || echo '${#0} wrong'
diff --git a/bin/cash/tests/expansion/length2.0 b/bin/cash/tests/expansion/length2.0
new file mode 100644
index 00000000..2c33fafc
--- /dev/null
+++ b/bin/cash/tests/expansion/length2.0
@@ -0,0 +1,4 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length2.0 219611 2011-03-13 16:20:38Z jilles $
+
+v=$-
+[ "${#-}" = "${#v}" ] || echo '${#-} wrong'
diff --git a/bin/cash/tests/expansion/length3.0 b/bin/cash/tests/expansion/length3.0
new file mode 100644
index 00000000..82f1d1ca
--- /dev/null
+++ b/bin/cash/tests/expansion/length3.0
@@ -0,0 +1,10 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length3.0 219611 2011-03-13 16:20:38Z jilles $
+
+set -- 1 2 3 4 5 6 7 8 9 10 11 12 13
+[ "$#" = 13 ] || echo '$# wrong'
+[ "${#}" = 13 ] || echo '${#} wrong'
+[ "${##}" = 2 ] || echo '${##} wrong'
+set --
+[ "$#" = 0 ] || echo '$# wrong'
+[ "${#}" = 0 ] || echo '${#} wrong'
+[ "${##}" = 1 ] || echo '${##} wrong'
diff --git a/bin/cash/tests/expansion/length4.0 b/bin/cash/tests/expansion/length4.0
new file mode 100644
index 00000000..32b004f3
--- /dev/null
+++ b/bin/cash/tests/expansion/length4.0
@@ -0,0 +1,11 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length4.0 220655 2011-04-15 15:26:05Z jilles $
+
+# The construct ${#?} is ambiguous in POSIX.1-2008: it could be the length
+# of $? or it could be $# giving an error in the (impossible) case that it
+# is not set.
+# We use the former interpretation; it seems more useful.
+
+:
+[ "${#?}" = 1 ] || echo '${#?} wrong'
+(exit 42)
+[ "${#?}" = 2 ] || echo '${#?} wrong'
diff --git a/bin/cash/tests/expansion/length5.0 b/bin/cash/tests/expansion/length5.0
new file mode 100644
index 00000000..faf2b3f8
--- /dev/null
+++ b/bin/cash/tests/expansion/length5.0
@@ -0,0 +1,27 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length5.0 220656 2011-04-15 15:33:24Z jilles $
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+e=
+for i in 0 1 2 3; do
+	for j in 0 1 2 3 4 5 6 7; do
+		for k in 0 1 2 3 4 5 6 7; do
+			case $i$j$k in
+			000) continue ;;
+			esac
+			e="$e\\$i$j$k"
+		done
+	done
+done
+ee=`printf "$e"`
+[ ${#ee} = 255 ] || echo bad 1
+[ "${#ee}" = 255 ] || echo bad 2
+[ $((${#ee})) = 255 ] || echo bad 3
+[ "$((${#ee}))" = 255 ] || echo bad 4
+set -- "$ee"
+[ ${#1} = 255 ] || echo bad 5
+[ "${#1}" = 255 ] || echo bad 6
+[ $((${#1})) = 255 ] || echo bad 7
+[ "$((${#1}))" = 255 ] || echo bad 8
diff --git a/bin/cash/tests/expansion/length6.0 b/bin/cash/tests/expansion/length6.0
new file mode 100644
index 00000000..bef0d7bc
--- /dev/null
+++ b/bin/cash/tests/expansion/length6.0
@@ -0,0 +1,8 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length6.0 220903 2011-04-20 22:24:54Z jilles $
+
+x='!@#$%^&*()[]'
+[ ${#x} = 12 ] || echo bad 1
+[ "${#x}" = 12 ] || echo bad 2
+IFS=2
+[ ${#x} = 1 ] || echo bad 3
+[ "${#x}" = 12 ] || echo bad 4
diff --git a/bin/cash/tests/expansion/length7.0 b/bin/cash/tests/expansion/length7.0
new file mode 100644
index 00000000..ed7eab56
--- /dev/null
+++ b/bin/cash/tests/expansion/length7.0
@@ -0,0 +1,14 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length7.0 221602 2011-05-07 14:32:16Z jilles $
+
+unset LC_ALL
+LC_CTYPE=en_US.UTF-8
+export LC_CTYPE
+
+# a umlaut
+s=$(printf '\303\244')
+# euro sign
+s=$s$(printf '\342\202\254')
+# some sort of 't' outside BMP
+s=$s$(printf '\360\235\225\245')
+set -- "$s"
+[ ${#s} = 3 ] && [ ${#1} = 3 ]
diff --git a/bin/cash/tests/expansion/length8.0 b/bin/cash/tests/expansion/length8.0
new file mode 100644
index 00000000..0ea7e53f
--- /dev/null
+++ b/bin/cash/tests/expansion/length8.0
@@ -0,0 +1,14 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/length8.0 221602 2011-05-07 14:32:16Z jilles $
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+# a umlaut
+s=$(printf '\303\244')
+# euro sign
+s=$s$(printf '\342\202\254')
+# some sort of 't' outside BMP
+s=$s$(printf '\360\235\225\245')
+set -- "$s"
+[ ${#s} = 9 ] && [ ${#1} = 9 ]
diff --git a/bin/cash/tests/expansion/local1.0 b/bin/cash/tests/expansion/local1.0
new file mode 100644
index 00000000..79c29b11
--- /dev/null
+++ b/bin/cash/tests/expansion/local1.0
@@ -0,0 +1,28 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/local1.0 238468 2012-07-15 10:19:43Z jilles $
+
+run_test() {
+	w='@ @'
+	check() {
+		[ "$v" = "$w" ] || echo "Expected $w got $v"
+	}
+
+	local v=$w
+	check
+
+	HOME=/known/value
+	check() {
+		[ "$v" = ~ ] || echo "Expected $HOME got $v"
+	}
+
+	local v=~
+	check
+
+	check() {
+		[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+	}
+
+	local v=x:~
+	check
+}
+
+run_test
diff --git a/bin/cash/tests/expansion/local2.0 b/bin/cash/tests/expansion/local2.0
new file mode 100644
index 00000000..98f1aa07
--- /dev/null
+++ b/bin/cash/tests/expansion/local2.0
@@ -0,0 +1,34 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/local2.0 238468 2012-07-15 10:19:43Z jilles $
+
+run_test() {
+	w='@ @'
+	check() {
+		[ "$v" = "$w" ] || echo "Expected $w got $v"
+	}
+
+	command local v=$w
+	check
+	command command local v=$w
+	check
+
+	HOME=/known/value
+	check() {
+		[ "$v" = ~ ] || echo "Expected $HOME got $v"
+	}
+
+	command local v=~
+	check
+	command command local v=~
+	check
+
+	check() {
+		[ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+	}
+
+	command local v=x:~
+	check
+	command command local v=x:~
+	check
+}
+
+run_test
diff --git a/bin/cash/tests/expansion/pathname1.0 b/bin/cash/tests/expansion/pathname1.0
new file mode 100644
index 00000000..449eb579
--- /dev/null
+++ b/bin/cash/tests/expansion/pathname1.0
@@ -0,0 +1,65 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/pathname1.0 302937 2016-07-16 13:26:18Z ache $
+
+unset LC_ALL
+LC_COLLATE=C
+export LC_COLLATE
+
+failures=0
+
+check() {
+	testcase=$1
+	expect=$2
+	eval "set -- $testcase"
+	actual="$*"
+	if [ "$actual" != "$expect" ]; then
+		failures=$((failures+1))
+		printf '%s\n' "For $testcase, expected $expect actual $actual"
+	fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+mkdir testdir testdir2 'testdir/*' 'testdir/?' testdir/a testdir/b testdir2/b
+mkdir testdir2/.c
+touch testf 'testdir/*/1' 'testdir/?/1' testdir/a/1 testdir/b/1 testdir2/b/.a
+
+check '' ''
+check 'testdir/b' 'testdir/b'
+check 'testdir/c' 'testdir/c'
+check '\*' '*'
+check '\?' '?'
+check '*' 'testdir testdir2 testf'
+check '*""' 'testdir testdir2 testf'
+check '""*' 'testdir testdir2 testf'
+check '*/' 'testdir/ testdir2/'
+check 'testdir*/a' 'testdir/a'
+check 'testdir*/b' 'testdir/b testdir2/b'
+check '*/.c' 'testdir2/.c'
+check 'testdir2/*' 'testdir2/b'
+check 'testdir2/b/*' 'testdir2/b/*'
+check 'testdir/*' 'testdir/* testdir/? testdir/a testdir/b'
+check 'testdir/*/1' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir/"*/1' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check 'testdir/\*/*' 'testdir/*/1'
+check 'testdir/\?/*' 'testdir/?/1'
+check 'testdir/"?"/*' 'testdir/?/1'
+check '"testdir"/"?"/*' 'testdir/?/1'
+check '"testdir"/"?"*/*' 'testdir/?/1'
+check '"testdir"/*"?"/*' 'testdir/?/1'
+check '"testdir/?"*/*' 'testdir/?/1'
+check 'testdir/\*/' 'testdir/*/'
+check 'testdir/\?/' 'testdir/?/'
+check 'testdir/"?"/' 'testdir/?/'
+check '"testdir"/"?"/' 'testdir/?/'
+check '"testdir"/"?"*/' 'testdir/?/'
+check '"testdir"/*"?"/' 'testdir/?/'
+check '"testdir/?"*/' 'testdir/?/'
+check 'testdir/[*]/' 'testdir/*/'
+check 'testdir/[?]/' 'testdir/?/'
+check 'testdir/[*?]/' 'testdir/*/ testdir/?/'
+check '[tz]estdir/[*]/' 'testdir/*/'
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/pathname2.0 b/bin/cash/tests/expansion/pathname2.0
new file mode 100644
index 00000000..af6fa1da
--- /dev/null
+++ b/bin/cash/tests/expansion/pathname2.0
@@ -0,0 +1,35 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/pathname2.0 302937 2016-07-16 13:26:18Z ache $
+
+unset LC_ALL
+LC_COLLATE=C
+export LC_COLLATE
+
+failures=0
+
+check() {
+	testcase=$1
+	expect=$2
+	eval "set -- $testcase"
+	actual="$*"
+	if [ "$actual" != "$expect" ]; then
+		failures=$((failures+1))
+		printf '%s\n' "For $testcase, expected $expect actual $actual"
+	fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+mkdir testdir testdir2 'testdir/*' 'testdir/?' testdir/a testdir/b testdir2/b
+mkdir testdir2/.c
+touch testf 'testdir/*/1' 'testdir/?/1' testdir/a/1 testdir/b/1 testdir2/b/.a
+
+check '*\/' 'testdir/ testdir2/'
+check '"testdir/"*"/1"' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir/"*"/"*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir/"*\/*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir"*"/"*"/"*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/pathname3.0 b/bin/cash/tests/expansion/pathname3.0
new file mode 100644
index 00000000..a703838c
--- /dev/null
+++ b/bin/cash/tests/expansion/pathname3.0
@@ -0,0 +1,29 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/pathname3.0 211155 2010-08-10 22:45:59Z jilles $
+
+v=12345678
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+# 8192 bytes
+v=${v##???}
+[ /*/$v = "/*/$v" ] || exit 1
+
+s=////
+s=$s$s$s$s
+s=$s$s$s$s
+s=$s$s$s$s
+s=$s$s$s$s
+# 1024 bytes
+s=${s##??????????}
+[ /var/empt[y]/$s/$v = "/var/empt[y]/$s/$v" ] || exit 2
+while [ ${#s} -lt 1034 ]; do
+	set -- /.${s}et[c]
+	[ ${#s} -gt 1018 ] || [ "$1" = /.${s}etc ] || exit 3
+	set -- /.${s}et[c]/
+	[ ${#s} -gt 1017 ] || [ "$1" = /.${s}etc/ ] || exit 4
+	set -- /.${s}et[c]/.
+	[ ${#s} -gt 1016 ] || [ "$1" = /.${s}etc/. ] || exit 5
+	s=$s/
+done
diff --git a/bin/cash/tests/expansion/pathname4.0 b/bin/cash/tests/expansion/pathname4.0
new file mode 100644
index 00000000..586bced7
--- /dev/null
+++ b/bin/cash/tests/expansion/pathname4.0
@@ -0,0 +1,28 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/pathname4.0 211646 2010-08-22 21:18:21Z jilles $
+
+failures=0
+
+check() {
+	testcase=$1
+	expect=$2
+	eval "set -- $testcase"
+	actual="$*"
+	if [ "$actual" != "$expect" ]; then
+		failures=$((failures+1))
+		printf '%s\n' "For $testcase, expected $expect actual $actual"
+	fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+mkdir !!a
+touch !!a/fff
+
+chmod u-r .
+check '!!a/ff*' '!!a/fff'
+chmod u+r .
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/pathname5.0 b/bin/cash/tests/expansion/pathname5.0
new file mode 100644
index 00000000..775403c8
--- /dev/null
+++ b/bin/cash/tests/expansion/pathname5.0
@@ -0,0 +1,3 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/pathname5.0 278806 2015-02-15 19:48:29Z jilles $
+
+[ `echo '/[e]tc'` = /etc ]
diff --git a/bin/cash/tests/expansion/pathname6.0 b/bin/cash/tests/expansion/pathname6.0
new file mode 100644
index 00000000..a044915e
--- /dev/null
+++ b/bin/cash/tests/expansion/pathname6.0
@@ -0,0 +1,29 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/pathname6.0 302937 2016-07-16 13:26:18Z ache $
+
+unset LC_ALL
+LC_COLLATE=en_US.US-ASCII
+export LC_COLLATE
+
+failures=0
+
+check() {
+	testcase=$1
+	expect=$2
+	eval "set -- $testcase"
+	actual="$*"
+	if [ "$actual" != "$expect" ]; then
+		failures=$((failures+1))
+		printf '%s\n' "For $testcase, expected $expect actual $actual"
+	fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+touch A B a b
+
+check '*' 'a A b B'
+
+exit $((failures != 0))
diff --git a/bin/cash/tests/expansion/plus-minus1.0 b/bin/cash/tests/expansion/plus-minus1.0
new file mode 100644
index 00000000..1daa8642
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus1.0
@@ -0,0 +1,76 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus1.0 216738 2010-12-27 15:57:41Z emaste $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- a b'				'2|a|b'
+testcase 'set --'				'0|'
+testcase 'set -- ${e}'				'0|'
+testcase 'set -- "${e}"'			'1|'
+
+testcase 'set -- $p'				'1|/etc/'
+testcase 'set -- "$p"'				'1|/et[c]/'
+testcase 'set -- ${s+$p}'			'1|/etc/'
+testcase 'set -- "${s+$p}"'			'1|/et[c]/'
+testcase 'set -- ${s+"$p"}'			'1|/et[c]/'
+# Dquotes in dquotes is undefined for Bourne shell operators
+#testcase 'set -- "${s+"$p"}"'			'1|/et[c]/'
+testcase 'set -- ${e:-$p}'			'1|/etc/'
+testcase 'set -- "${e:-$p}"'			'1|/et[c]/'
+testcase 'set -- ${e:-"$p"}'			'1|/et[c]/'
+# Dquotes in dquotes is undefined for Bourne shell operators
+#testcase 'set -- "${e:-"$p"}"'			'1|/et[c]/'
+testcase 'set -- ${e:+"$e"}'			'0|'
+testcase 'set -- ${e:+$w"$e"}'			'0|'
+testcase 'set -- ${w:+"$w"}'			'1|a b c'
+testcase 'set -- ${w:+$w"$w"}'			'3|a|b|ca b c'
+
+testcase 'set -- "${s+a b}"'			'1|a b'
+testcase 'set -- "${e:-a b}"'			'1|a b'
+testcase 'set -- ${e:-\}}'			'1|}'
+testcase 'set -- ${e:+{}}'			'1|}'
+testcase 'set -- "${e:+{}}"'			'1|}'
+
+testcase 'set -- ${e+x}${e+x}'			'1|xx'
+testcase 'set -- "${e+x}"${e+x}'		'1|xx'
+testcase 'set -- ${e+x}"${e+x}"'		'1|xx'
+testcase 'set -- "${e+x}${e+x}"'		'1|xx'
+testcase 'set -- "${e+x}""${e+x}"'		'1|xx'
+
+testcase 'set -- ${e:-${e:-$p}}'		'1|/etc/'
+testcase 'set -- "${e:-${e:-$p}}"'		'1|/et[c]/'
+testcase 'set -- ${e:-"${e:-$p}"}'		'1|/et[c]/'
+testcase 'set -- ${e:-${e:-"$p"}}'		'1|/et[c]/'
+testcase 'set -- ${e:-${e:-${e:-$w}}}'		'3|a|b|c'
+testcase 'set -- ${e:-${e:-${e:-"$w"}}}'	'1|a b c'
+testcase 'set -- ${e:-${e:-"${e:-$w}"}}'	'1|a b c'
+testcase 'set -- ${e:-"${e:-${e:-$w}}"}'	'1|a b c'
+testcase 'set -- "${e:-${e:-${e:-$w}}}"'	'1|a b c'
+
+testcase 'shift $#; set -- ${1+"$@"}'		'0|'
+testcase 'set -- ""; set -- ${1+"$@"}'		'1|'
+testcase 'set -- "" a; set -- ${1+"$@"}'	'2||a'
+testcase 'set -- a ""; set -- ${1+"$@"}'	'2|a|'
+testcase 'set -- a b; set -- ${1+"$@"}'		'2|a|b'
+testcase 'set -- a\ b; set -- ${1+"$@"}'	'1|a b'
+testcase 'set -- " " ""; set -- ${1+"$@"}'	'2| |'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/plus-minus2.0 b/bin/cash/tests/expansion/plus-minus2.0
new file mode 100644
index 00000000..34057f94
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus2.0
@@ -0,0 +1,4 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus2.0 206145 2010-04-03 20:55:56Z jilles $
+
+e=
+test "${e:-\}}" = '}'
diff --git a/bin/cash/tests/expansion/plus-minus3.0 b/bin/cash/tests/expansion/plus-minus3.0
new file mode 100644
index 00000000..0e551060
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus3.0
@@ -0,0 +1,44 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus3.0 206817 2010-04-18 22:13:45Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+# We follow original ash behaviour for quoted ${var+-=?} expansions:
+# a double-quote in one switches back to unquoted state.
+# This allows expanding a variable as a single word if it is set
+# and substituting multiple words otherwise.
+# It is also close to the Bourne and Korn shells.
+# POSIX leaves this undefined, and various other shells treat
+# such double-quotes as introducing a second level of quoting
+# which does not do much except quoting close braces.
+
+testcase 'set -- "${p+"/et[c]/"}"'		'1|/etc/'
+testcase 'set -- "${p-"/et[c]/"}"'		'1|/et[c]/'
+testcase 'set -- "${p+"$p"}"'			'1|/etc/'
+testcase 'set -- "${p-"$p"}"'			'1|/et[c]/'
+testcase 'set -- "${p+"""/et[c]/"}"'		'1|/etc/'
+testcase 'set -- "${p-"""/et[c]/"}"'		'1|/et[c]/'
+testcase 'set -- "${p+"""$p"}"'			'1|/etc/'
+testcase 'set -- "${p-"""$p"}"'			'1|/et[c]/'
+testcase 'set -- "${p+"\@"}"'			'1|@'
+testcase 'set -- "${p+"'\''/et[c]/'\''"}"'	'1|/et[c]/'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/plus-minus4.0 b/bin/cash/tests/expansion/plus-minus4.0
new file mode 100644
index 00000000..f8f9984c
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus4.0
@@ -0,0 +1,38 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus4.0 211080 2010-08-08 17:03:23Z jilles $
+
+# These may be a bit unclear in the POSIX spec or the proposed revisions,
+# and conflict with bash's interpretation, but I think ksh93's interpretation
+# makes most sense. In particular, it makes no sense to me that single-quotes
+# must match but are not removed.
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- ${e:-'"'"'}'"'"'}'		'1|}'
+testcase "set -- \${e:-\\'}"			"1|'"
+testcase "set -- \${e:-\\'\\'}"			"1|''"
+testcase "set -- \"\${e:-'}\""			"1|'"
+testcase "set -- \"\${e:-'}'}\""		"1|''}"
+testcase "set -- \"\${e:-''}\""			"1|''"
+testcase 'set -- ${e:-\a}'			'1|a'
+testcase 'set -- "${e:-\a}"'			'1|\a'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/plus-minus5.0 b/bin/cash/tests/expansion/plus-minus5.0
new file mode 100644
index 00000000..c3b97570
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus5.0
@@ -0,0 +1,31 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus5.0 214492 2010-10-28 22:34:49Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- ${e:-"{x}"}'			'1|{x}'
+testcase 'set -- "${e:-"{x}"}"'			'1|{x}'
+testcase 'set -- ${h+"{x}"}'			'1|{x}'
+testcase 'set -- "${h+"{x}"}"'			'1|{x}'
+testcase 'set -- ${h:-"{x}"}'			'1|##'
+testcase 'set -- "${h:-"{x}"}"'			'1|##'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/plus-minus6.0 b/bin/cash/tests/expansion/plus-minus6.0
new file mode 100644
index 00000000..fccbbaeb
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus6.0
@@ -0,0 +1,34 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus6.0 214512 2010-10-29 13:42:18Z jilles $
+
+failures=0
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+nl='
+'
+i=1
+set -f
+while [ "$i" -le 255 ]; do
+	# A different byte still in the range 1..255.
+	i2=$((i^2+(i==2)))
+	# Add a character to work around command substitution's removal of
+	# final newlines, then remove it again.
+	c=$(printf \\"$(printf %o@ "$i")")
+	c=${c%@}
+	c2=$(printf \\"$(printf %o@ "$i2")")
+	c2=${c2%@}
+	case $c in
+		[\'$nl'$}();&|\"`']) c=M
+	esac
+	case $c2 in
+		[\'$nl'$}();&|\"`']) c2=N
+	esac
+	IFS=$c
+	command eval "set -- \${\$+$c2$c$c2$c$c2}"
+	if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] ||
+	    [ "$3" != "$c2" ]; then
+		echo "Bad results for separator $i (word $i2)" >&2
+		: $((failures += 1))
+	fi
+	i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/bin/cash/tests/expansion/plus-minus7.0 b/bin/cash/tests/expansion/plus-minus7.0
new file mode 100644
index 00000000..a5e0e770
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus7.0
@@ -0,0 +1,26 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus7.0 216738 2010-12-27 15:57:41Z emaste $
+
+e= s='foo'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- ${s+a b}'			'2|a|b'
+testcase 'set -- ${e:-a b}'			'2|a|b'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/plus-minus8.0 b/bin/cash/tests/expansion/plus-minus8.0
new file mode 100644
index 00000000..bdd4ff5f
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus8.0
@@ -0,0 +1,5 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus8.0 219623 2011-03-13 20:02:39Z jilles $
+
+set -- 1 2 3 4 5 6 7 8 9 10 11 12 13
+[ "${#+hi}" = hi ] || echo '${#+hi} wrong'
+[ "${#-hi}" = 13 ] || echo '${#-hi} wrong'
diff --git a/bin/cash/tests/expansion/plus-minus9.0 b/bin/cash/tests/expansion/plus-minus9.0
new file mode 100644
index 00000000..8a3cb58d
--- /dev/null
+++ b/bin/cash/tests/expansion/plus-minus9.0
@@ -0,0 +1,8 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/plus-minus9.0 333927 2018-05-20 17:25:52Z jilles $
+
+a=1
+b=${a+
+}
+n='
+'
+[ "$b" = "$n" ]
diff --git a/bin/cash/tests/expansion/question1.0 b/bin/cash/tests/expansion/question1.0
new file mode 100644
index 00000000..e89aed94
--- /dev/null
+++ b/bin/cash/tests/expansion/question1.0
@@ -0,0 +1,22 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/question1.0 213738 2010-10-12 18:20:38Z obrien $
+
+x=a\ b
+[ "$x" = "${x?}" ] || exit 1
+set -- ${x?}
+{ [ "$#" = 2 ] && [ "$1" = a ] && [ "$2" = b ]; } || exit 1
+unset x
+(echo ${x?abcdefg}) 2>&1 | grep -q abcdefg || exit 1
+${SH} -c 'unset foo; echo ${foo?}' 2>/dev/null && exit 1
+${SH} -c 'foo=; echo ${foo:?}' 2>/dev/null && exit 1
+${SH} -c 'foo=; echo ${foo?}' >/dev/null || exit 1
+${SH} -c 'foo=1; echo ${foo:?}' >/dev/null || exit 1
+${SH} -c 'echo ${!?}' 2>/dev/null && exit 1
+${SH} -c ':& echo ${!?}' >/dev/null || exit 1
+${SH} -c 'echo ${#?}' >/dev/null || exit 1
+${SH} -c 'echo ${*?}' 2>/dev/null && exit 1
+${SH} -c 'echo ${*?}' ${SH} x >/dev/null || exit 1
+${SH} -c 'echo ${1?}' 2>/dev/null && exit 1
+${SH} -c 'echo ${1?}' ${SH} x >/dev/null || exit 1
+${SH} -c 'echo ${2?}' ${SH} x 2>/dev/null && exit 1
+${SH} -c 'echo ${2?}' ${SH} x y >/dev/null || exit 1
+exit 0
diff --git a/bin/cash/tests/expansion/readonly1.0 b/bin/cash/tests/expansion/readonly1.0
new file mode 100644
index 00000000..7040df89
--- /dev/null
+++ b/bin/cash/tests/expansion/readonly1.0
@@ -0,0 +1,7 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/readonly1.0 238468 2012-07-15 10:19:43Z jilles $
+
+w='@ @'
+
+v=0 HOME=/known/value
+readonly v=~:~/:$w
+[ "$v" = "$HOME:$HOME/:$w" ] || echo "Expected $HOME/:$w got $v"
diff --git a/bin/cash/tests/expansion/redir1.0 b/bin/cash/tests/expansion/redir1.0
new file mode 100644
index 00000000..d951b4ca
--- /dev/null
+++ b/bin/cash/tests/expansion/redir1.0
@@ -0,0 +1,26 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/redir1.0 273920 2014-10-31 22:28:10Z jilles $
+
+bad=0
+for i in 0 1 2 3; do
+	for j in 0 1 2 3 4 5 6 7; do
+		for k in 0 1 2 3 4 5 6 7; do
+			case $i$j$k in
+			000) continue ;;
+			esac
+			set -- "$(printf \\$i$j$k@)"
+			set -- "${1%@}"
+			ff=
+			for f in /dev/null /dev/zero /; do
+				if [ -e "$f" ] && [ ! -e "$f$1" ]; then
+					ff=$f
+				fi
+			done
+			[ -n "$ff" ] || continue
+			if { true <$ff$1; } 2>/dev/null; then
+				echo "Bad: $i$j$k ($ff)" >&2
+				: $((bad += 1))
+			fi
+		done
+	done
+done
+exit $((bad ? 2 : 0))
diff --git a/bin/cash/tests/expansion/set-u1.0 b/bin/cash/tests/expansion/set-u1.0
new file mode 100644
index 00000000..39f7e175
--- /dev/null
+++ b/bin/cash/tests/expansion/set-u1.0
@@ -0,0 +1,29 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/set-u1.0 213738 2010-10-12 18:20:38Z obrien $
+
+${SH} -uc 'unset foo; echo $foo' 2>/dev/null && exit 1
+${SH} -uc 'foo=; echo $foo' >/dev/null || exit 1
+${SH} -uc 'foo=1; echo $foo' >/dev/null || exit 1
+# -/+/= are unaffected by set -u
+${SH} -uc 'unset foo; echo ${foo-}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo+}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo=}' >/dev/null || exit 1
+# length/trimming are affected
+${SH} -uc 'unset foo; echo ${#foo}' 2>/dev/null && exit 1
+${SH} -uc 'foo=; echo ${#foo}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo#?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo#?}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo##?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo##?}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo%?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo%?}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo%%?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo%%?}' >/dev/null || exit 1
+
+${SH} -uc 'echo $!' 2>/dev/null && exit 1
+${SH} -uc ':& echo $!' >/dev/null || exit 1
+${SH} -uc 'echo $#' >/dev/null || exit 1
+${SH} -uc 'echo $1' 2>/dev/null && exit 1
+${SH} -uc 'echo $1' ${SH} x >/dev/null || exit 1
+${SH} -uc 'echo $2' ${SH} x 2>/dev/null && exit 1
+${SH} -uc 'echo $2' ${SH} x y >/dev/null || exit 1
+exit 0
diff --git a/bin/cash/tests/expansion/set-u2.0 b/bin/cash/tests/expansion/set-u2.0
new file mode 100644
index 00000000..2bd065d2
--- /dev/null
+++ b/bin/cash/tests/expansion/set-u2.0
@@ -0,0 +1,12 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/set-u2.0 198454 2009-10-24 21:20:04Z jilles $
+
+set -u
+: $* $@ "$@" "$*"
+set -- x
+: $* $@ "$@" "$*"
+shift $#
+: $* $@ "$@" "$*"
+set -- y
+set --
+: $* $@ "$@" "$*"
+exit 0
diff --git a/bin/cash/tests/expansion/set-u3.0 b/bin/cash/tests/expansion/set-u3.0
new file mode 100644
index 00000000..42ef481c
--- /dev/null
+++ b/bin/cash/tests/expansion/set-u3.0
@@ -0,0 +1,6 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/set-u3.0 221463 2011-05-04 22:12:22Z jilles $
+
+set -u
+unset x
+v=$( (eval ': $((x))') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/cash/tests/expansion/tilde1.0 b/bin/cash/tests/expansion/tilde1.0
new file mode 100644
index 00000000..1ce2aeb3
--- /dev/null
+++ b/bin/cash/tests/expansion/tilde1.0
@@ -0,0 +1,56 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/tilde1.0 206149 2010-04-03 21:56:24Z jilles $
+
+HOME=/tmp
+roothome=~root
+if [ "$roothome" = "~root" ]; then
+	echo "~root is not expanded!"
+	exit 2
+fi
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- ~'				'1|/tmp'
+testcase 'set -- ~/foo'				'1|/tmp/foo'
+testcase 'set -- x~'				'1|x~'
+testcase 'set -- ~root'				"1|$roothome"
+h=~
+testcase 'set -- "$h"'				'1|/tmp'
+ooIFS=$IFS
+IFS=m
+testcase 'set -- ~'				'1|/tmp'
+testcase 'set -- ~/foo'				'1|/tmp/foo'
+testcase 'set -- $h'				'2|/t|p'
+IFS=$ooIFS
+t=\~
+testcase 'set -- $t'				'1|~'
+r=$(cat <<EOF
+~
+EOF
+)
+testcase 'set -- $r'				'1|~'
+r=$(cat <<EOF
+${t+~}
+EOF
+)
+testcase 'set -- $r'				'1|~'
+r=$(cat <<EOF
+${t+~/.}
+EOF
+)
+testcase 'set -- $r'				'1|~/.'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/tilde2.0 b/bin/cash/tests/expansion/tilde2.0
new file mode 100644
index 00000000..eb7e63e0
--- /dev/null
+++ b/bin/cash/tests/expansion/tilde2.0
@@ -0,0 +1,90 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/tilde2.0 206150 2010-04-03 22:04:44Z jilles $
+
+HOME=/tmp
+roothome=~root
+if [ "$roothome" = "~root" ]; then
+	echo "~root is not expanded!"
+	exit 2
+fi
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- ${$+~}'			'1|/tmp'
+testcase 'set -- ${$+~/}'			'1|/tmp/'
+testcase 'set -- ${$+~/foo}'			'1|/tmp/foo'
+testcase 'set -- ${$+x~}'			'1|x~'
+testcase 'set -- ${$+~root}'			"1|$roothome"
+testcase 'set -- ${$+"~"}'			'1|~'
+testcase 'set -- ${$+"~/"}'			'1|~/'
+testcase 'set -- ${$+"~/foo"}'			'1|~/foo'
+testcase 'set -- ${$+"x~"}'			'1|x~'
+testcase 'set -- ${$+"~root"}'			"1|~root"
+testcase 'set -- "${$+~}"'			'1|~'
+testcase 'set -- "${$+~/}"'			'1|~/'
+testcase 'set -- "${$+~/foo}"'			'1|~/foo'
+testcase 'set -- "${$+x~}"'			'1|x~'
+testcase 'set -- "${$+~root}"'			"1|~root"
+testcase 'set -- ${HOME#~}'			'0|'
+h=~
+testcase 'set -- "$h"'				'1|/tmp'
+f=~/foo
+testcase 'set -- "$f"'				'1|/tmp/foo'
+testcase 'set -- ${f#~}'			'1|/foo'
+testcase 'set -- ${f#~/}'			'1|foo'
+
+ooIFS=$IFS
+IFS=m
+testcase 'set -- ${$+~}'			'1|/tmp'
+testcase 'set -- ${$+~/foo}'			'1|/tmp/foo'
+testcase 'set -- ${$+$h}'			'2|/t|p'
+testcase 'set -- ${HOME#~}'			'0|'
+IFS=$ooIFS
+
+t=\~
+testcase 'set -- ${$+$t}'			'1|~'
+r=$(cat <<EOF
+${HOME#~}
+EOF
+)
+testcase 'set -- $r'				'0|'
+r=$(cat <<EOF
+${HOME#'~'}
+EOF
+)
+testcase 'set -- $r'				'1|/tmp'
+r=$(cat <<EOF
+${t#'~'}
+EOF
+)
+testcase 'set -- $r'				'0|'
+r=$(cat <<EOF
+${roothome#~root}
+EOF
+)
+testcase 'set -- $r'				'0|'
+r=$(cat <<EOF
+${f#~}
+EOF
+)
+testcase 'set -- $r'				'1|/foo'
+r=$(cat <<EOF
+${f#~/}
+EOF
+)
+testcase 'set -- $r'				'1|foo'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/trim1.0 b/bin/cash/tests/expansion/trim1.0
new file mode 100644
index 00000000..78227afd
--- /dev/null
+++ b/bin/cash/tests/expansion/trim1.0
@@ -0,0 +1,85 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim1.0 206143 2010-04-03 20:14:10Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- ${t%t}'			'1|texttex'
+testcase 'set -- "${t%t}"'			'1|texttex'
+testcase 'set -- ${t%e*}'			'1|textt'
+testcase 'set -- "${t%e*}"'			'1|textt'
+testcase 'set -- ${t%%e*}'			'1|t'
+testcase 'set -- "${t%%e*}"'			'1|t'
+testcase 'set -- ${t%%*}'			'0|'
+testcase 'set -- "${t%%*}"'			'1|'
+testcase 'set -- ${t#t}'			'1|exttext'
+testcase 'set -- "${t#t}"'			'1|exttext'
+testcase 'set -- ${t#*x}'			'1|ttext'
+testcase 'set -- "${t#*x}"'			'1|ttext'
+testcase 'set -- ${t##*x}'			'1|t'
+testcase 'set -- "${t##*x}"'			'1|t'
+testcase 'set -- ${t##*}'			'0|'
+testcase 'set -- "${t##*}"'			'1|'
+testcase 'set -- ${t%e$a}'			'1|textt'
+
+set -f
+testcase 'set -- ${s%[?]*}'			'1|ast*que'
+testcase 'set -- "${s%[?]*}"'			'1|ast*que'
+testcase 'set -- ${s%[*]*}'			'1|ast'
+testcase 'set -- "${s%[*]*}"'			'1|ast'
+set +f
+
+testcase 'set -- $b'				'1|{{(#)}}'
+testcase 'set -- ${b%\}}'			'1|{{(#)}'
+testcase 'set -- ${b#{}'			'1|{(#)}}'
+testcase 'set -- "${b#{}"'			'1|{(#)}}'
+# Parentheses are special in ksh, check that they can be escaped
+testcase 'set -- ${b%\)*}'			'1|{{(#'
+testcase 'set -- ${b#{}'			'1|{(#)}}'
+testcase 'set -- $h'				'1|##'
+testcase 'set -- ${h#\#}'			'1|#'
+testcase 'set -- ${h###}'			'1|#'
+testcase 'set -- "${h###}"'			'1|#'
+testcase 'set -- ${h%#}'			'1|#'
+testcase 'set -- "${h%#}"'			'1|#'
+
+set -f
+testcase 'set -- ${s%"${s#?}"}'			'1|a'
+testcase 'set -- ${s%"${s#????}"}'		'1|ast*'
+testcase 'set -- ${s%"${s#????????}"}'		'1|ast*que?'
+testcase 'set -- ${s#"${s%?}"}'			'1|n'
+testcase 'set -- ${s#"${s%????}"}'		'1|?non'
+testcase 'set -- ${s#"${s%????????}"}'		'1|*que?non'
+set +f
+testcase 'set -- "${s%"${s#?}"}"'		'1|a'
+testcase 'set -- "${s%"${s#????}"}"'		'1|ast*'
+testcase 'set -- "${s%"${s#????????}"}"'	'1|ast*que?'
+testcase 'set -- "${s#"${s%?}"}"'		'1|n'
+testcase 'set -- "${s#"${s%????}"}"'		'1|?non'
+testcase 'set -- "${s#"${s%????????}"}"'	'1|*que?non'
+testcase 'set -- ${p#${p}}'			'1|/etc/'
+testcase 'set -- "${p#${p}}"'			'1|/et[c]/'
+testcase 'set -- ${p#*[[]}'			'1|c]/'
+testcase 'set -- "${p#*[[]}"'			'1|c]/'
+testcase 'set -- ${p#*\[}'			'1|c]/'
+testcase 'set -- ${p#*"["}'			'1|c]/'
+testcase 'set -- "${p#*"["}"'			'1|c]/'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/trim10.0 b/bin/cash/tests/expansion/trim10.0
new file mode 100644
index 00000000..f163c1a3
--- /dev/null
+++ b/bin/cash/tests/expansion/trim10.0
@@ -0,0 +1,7 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim10.0 333927 2018-05-20 17:25:52Z jilles $
+
+a='z
+'
+b=${a%
+}
+[ "$b" = z ]
diff --git a/bin/cash/tests/expansion/trim11.0 b/bin/cash/tests/expansion/trim11.0
new file mode 100644
index 00000000..89210f4d
--- /dev/null
+++ b/bin/cash/tests/expansion/trim11.0
@@ -0,0 +1,7 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim11.0 333927 2018-05-20 17:25:52Z jilles $
+
+a='z
+'
+b="${a%
+}"
+[ "$b" = z ]
diff --git a/bin/cash/tests/expansion/trim2.0 b/bin/cash/tests/expansion/trim2.0
new file mode 100644
index 00000000..61560f61
--- /dev/null
+++ b/bin/cash/tests/expansion/trim2.0
@@ -0,0 +1,55 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim2.0 206147 2010-04-03 21:07:50Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+set -f
+testcase 'set -- $s'				'1|ast*que?non'
+testcase 'set -- ${s%\?*}'			'1|ast*que'
+testcase 'set -- "${s%\?*}"'			'1|ast*que'
+testcase 'set -- ${s%\**}'			'1|ast'
+testcase 'set -- "${s%\**}"'			'1|ast'
+testcase 'set -- ${s%"$q"*}'			'1|ast*que'
+testcase 'set -- "${s%"$q"*}"'			'1|ast*que'
+testcase 'set -- ${s%"$a"*}'			'1|ast'
+testcase 'set -- "${s%"$a"*}"'			'1|ast'
+testcase 'set -- ${s%"$q"$a}'			'1|ast*que'
+testcase 'set -- "${s%"$q"$a}"'			'1|ast*que'
+testcase 'set -- ${s%"$a"$a}'			'1|ast'
+testcase 'set -- "${s%"$a"$a}"'			'1|ast'
+set +f
+
+testcase 'set -- "${b%\}}"'			'1|{{(#)}'
+# Parentheses are special in ksh, check that they can be escaped
+testcase 'set -- "${b%\)*}"'			'1|{{(#'
+testcase 'set -- "${h#\#}"'			'1|#'
+
+testcase 'set -- ${p%"${p#?}"}'			'1|/'
+testcase 'set -- ${p%"${p#??????}"}'		'1|/etc'
+testcase 'set -- ${p%"${p#???????}"}'		'1|/etc/'
+testcase 'set -- "${p%"${p#?}"}"'		'1|/'
+testcase 'set -- "${p%"${p#??????}"}"'		'1|/et[c]'
+testcase 'set -- "${p%"${p#???????}"}"'		'1|/et[c]/'
+testcase 'set -- ${p#"${p}"}'			'0|'
+testcase 'set -- "${p#"${p}"}"'			'1|'
+testcase 'set -- "${p#*\[}"'			'1|c]/'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/trim3.0 b/bin/cash/tests/expansion/trim3.0
new file mode 100644
index 00000000..35f2b580
--- /dev/null
+++ b/bin/cash/tests/expansion/trim3.0
@@ -0,0 +1,46 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim3.0 207127 2010-04-23 17:26:49Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##' c='\\\\'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+# This doesn't make much sense, but it fails in dash so I'm adding it here:
+testcase 'set -- "${w%${w#???}}"'		'1|a b'
+
+testcase 'set -- ${p#/et[}'			'1|c]/'
+testcase 'set -- "${p#/et[}"'			'1|c]/'
+testcase 'set -- "${p%${p#????}}"'		'1|/et['
+
+testcase 'set -- ${b%'\'}\''}'			'1|{{(#)}'
+
+testcase 'set -- ${c#\\}'			'1|\\\'
+testcase 'set -- ${c#\\\\}'			'1|\\'
+testcase 'set -- ${c#\\\\\\}'			'1|\'
+testcase 'set -- ${c#\\\\\\\\}'			'0|'
+testcase 'set -- "${c#\\}"'			'1|\\\'
+testcase 'set -- "${c#\\\\}"'			'1|\\'
+testcase 'set -- "${c#\\\\\\}"'			'1|\'
+testcase 'set -- "${c#\\\\\\\\}"'		'1|'
+testcase 'set -- "${c#"$c"}"'			'1|'
+testcase 'set -- ${c#"$c"}'			'0|'
+testcase 'set -- "${c%"$c"}"'			'1|'
+testcase 'set -- ${c%"$c"}'			'0|'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/trim4.0 b/bin/cash/tests/expansion/trim4.0
new file mode 100644
index 00000000..82e38e20
--- /dev/null
+++ b/bin/cash/tests/expansion/trim4.0
@@ -0,0 +1,15 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim4.0 213814 2010-10-13 23:29:09Z obrien $
+
+v1=/homes/SOME_USER
+v2=
+v3=C123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+
+# Trigger bug in VSTRIMRIGHT processing STADJUST() call in expand.c:subevalvar()
+while [ ${#v2} -lt 2000 ]; do
+	v4="${v2} ${v1%/*} $v3"
+	if [ ${#v4} -ne $((${#v2} + ${#v3} + 8)) ]; then
+		echo bad: ${#v4} -ne $((${#v2} + ${#v3} + 8))
+	fi
+	v2=x$v2
+	v3=y$v3
+done
diff --git a/bin/cash/tests/expansion/trim5.0 b/bin/cash/tests/expansion/trim5.0
new file mode 100644
index 00000000..572e5b0c
--- /dev/null
+++ b/bin/cash/tests/expansion/trim5.0
@@ -0,0 +1,28 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim5.0 214490 2010-10-28 21:51:14Z jilles $
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- "${b%'\'}\''}"'		'1|{{(#)}'
+testcase 'set -- ${b%"}"}'			'1|{{(#)}'
+testcase 'set -- "${b%"}"}"'			'1|{{(#)}'
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/trim6.0 b/bin/cash/tests/expansion/trim6.0
new file mode 100644
index 00000000..a0c55a85
--- /dev/null
+++ b/bin/cash/tests/expansion/trim6.0
@@ -0,0 +1,22 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim6.0 214524 2010-10-29 19:34:57Z jilles $
+
+e=
+for i in 0 1 2 3; do
+	for j in 0 1 2 3 4 5 6 7; do
+		for k in 0 1 2 3 4 5 6 7; do
+			case $i$j$k in
+			000) continue ;;
+			esac
+			e="$e\\$i$j$k"
+		done
+	done
+done
+e=$(printf "$e")
+v=@$e@$e@
+y=${v##*"$e"}
+yq="${v##*"$e"}"
+[ "$y" = @ ] || echo "error when unquoted in non-splitting context"
+[ "$yq" = @ ] || echo "error when quoted in non-splitting context"
+[ "${v##*"$e"}" = @ ] || echo "error when quoted in splitting context"
+IFS=
+[ ${v##*"$e"} = @ ] || echo "error when unquoted in splitting context"
diff --git a/bin/cash/tests/expansion/trim7.0 b/bin/cash/tests/expansion/trim7.0
new file mode 100644
index 00000000..0bea44f7
--- /dev/null
+++ b/bin/cash/tests/expansion/trim7.0
@@ -0,0 +1,16 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim7.0 219623 2011-03-13 20:02:39Z jilles $
+
+set -- 1 2 3 4 5 6 7 8 9 10 11 12 13
+[ "${##1}" = 3 ] || echo '${##1} wrong'
+[ "${###1}" = 3 ] || echo '${###1} wrong'
+[ "${###}" = 13 ] || echo '${###} wrong'
+[ "${#%3}" = 1 ] || echo '${#%3} wrong'
+[ "${#%%3}" = 1 ] || echo '${#%%3} wrong'
+[ "${#%%}" = 13 ] || echo '${#%%} wrong'
+set --
+[ "${##0}" = "" ] || echo '${##0} wrong'
+[ "${###0}" = "" ] || echo '${###0} wrong'
+[ "${###}" = 0 ] || echo '${###} wrong'
+[ "${#%0}" = "" ] || echo '${#%0} wrong'
+[ "${#%%0}" = "" ] || echo '${#%%0} wrong'
+[ "${#%%}" = 0 ] || echo '${#%%} wrong'
diff --git a/bin/cash/tests/expansion/trim8.0 b/bin/cash/tests/expansion/trim8.0
new file mode 100644
index 00000000..91cf0eeb
--- /dev/null
+++ b/bin/cash/tests/expansion/trim8.0
@@ -0,0 +1,75 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim8.0 221646 2011-05-08 11:32:20Z jilles $
+
+unset LC_ALL
+LC_CTYPE=en_US.UTF-8
+export LC_CTYPE
+
+c1=e
+# a umlaut
+c2=$(printf '\303\244')
+# euro sign
+c3=$(printf '\342\202\254')
+# some sort of 't' outside BMP
+c4=$(printf '\360\235\225\245')
+
+s=$c1$c2$c3$c4
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'set -- "$s"'				"1|$s"
+testcase 'set -- "${s#$c2}"'			"1|$s"
+testcase 'set -- "${s#*}"'			"1|$s"
+testcase 'set -- "${s#$c1}"'			"1|$c2$c3$c4"
+testcase 'set -- "${s#$c1$c2}"'			"1|$c3$c4"
+testcase 'set -- "${s#$c1$c2$c3}"'		"1|$c4"
+testcase 'set -- "${s#$c1$c2$c3$c4}"'		"1|"
+testcase 'set -- "${s#?}"'			"1|$c2$c3$c4"
+testcase 'set -- "${s#??}"'			"1|$c3$c4"
+testcase 'set -- "${s#???}"'			"1|$c4"
+testcase 'set -- "${s#????}"'			"1|"
+testcase 'set -- "${s#*$c3}"'			"1|$c4"
+testcase 'set -- "${s%$c4}"'			"1|$c1$c2$c3"
+testcase 'set -- "${s%$c3$c4}"'			"1|$c1$c2"
+testcase 'set -- "${s%$c2$c3$c4}"'		"1|$c1"
+testcase 'set -- "${s%$c1$c2$c3$c4}"'		"1|"
+testcase 'set -- "${s%?}"'			"1|$c1$c2$c3"
+testcase 'set -- "${s%??}"'			"1|$c1$c2"
+testcase 'set -- "${s%???}"'			"1|$c1"
+testcase 'set -- "${s%????}"'			"1|"
+testcase 'set -- "${s%$c2*}"'			"1|$c1"
+testcase 'set -- "${s##$c2}"'			"1|$s"
+testcase 'set -- "${s##*}"'			"1|"
+testcase 'set -- "${s##$c1}"'			"1|$c2$c3$c4"
+testcase 'set -- "${s##$c1$c2}"'		"1|$c3$c4"
+testcase 'set -- "${s##$c1$c2$c3}"'		"1|$c4"
+testcase 'set -- "${s##$c1$c2$c3$c4}"'		"1|"
+testcase 'set -- "${s##?}"'			"1|$c2$c3$c4"
+testcase 'set -- "${s##??}"'			"1|$c3$c4"
+testcase 'set -- "${s##???}"'			"1|$c4"
+testcase 'set -- "${s##????}"'			"1|"
+testcase 'set -- "${s##*$c3}"'			"1|$c4"
+testcase 'set -- "${s%%$c4}"'			"1|$c1$c2$c3"
+testcase 'set -- "${s%%$c3$c4}"'		"1|$c1$c2"
+testcase 'set -- "${s%%$c2$c3$c4}"'		"1|$c1"
+testcase 'set -- "${s%%$c1$c2$c3$c4}"'		"1|"
+testcase 'set -- "${s%%?}"'			"1|$c1$c2$c3"
+testcase 'set -- "${s%%??}"'			"1|$c1$c2"
+testcase 'set -- "${s%%???}"'			"1|$c1"
+testcase 'set -- "${s%%????}"'			"1|"
+testcase 'set -- "${s%%$c2*}"'			"1|$c1"
+
+test "x$failures" = x
diff --git a/bin/cash/tests/expansion/trim9.0 b/bin/cash/tests/expansion/trim9.0
new file mode 100644
index 00000000..fa32954c
--- /dev/null
+++ b/bin/cash/tests/expansion/trim9.0
@@ -0,0 +1,61 @@
+# $FreeBSD: releng/12.0/bin/sh/tests/expansion/trim9.0 292758 2015-12-26 22:27:48Z jilles $
+
+# POSIX does not specify these but they occasionally occur in the wild.
+# This just serves to keep working what currently works.
+
+failures=''
+ok=''
+
+testcase() {
+	code="$1"
+	expected="$2"
+	oIFS="$IFS"
+	eval "$code"
+	IFS='|'
+	result="$#|$*"
+	IFS="$oIFS"
+	if [ "x$result" = "x$expected" ]; then
+		ok=x$ok
+	else
+		failures=x$failures
+		echo "For $code, expected $expected actual $result"
+	fi
+}
+
+testcase 'shift $#; set -- "${*#Q}"'		'1|'
+testcase 'shift $#; set -- "${*##Q}"'		'1|'
+testcase 'shift $#; set -- "${*%Q}"'		'1|'
+testcase 'shift $#; set -- "${*%%Q}"'		'1|'
+testcase 'set -- Q R; set -- "${*#Q}"'		'1| R'
+testcase 'set -- Q R; set -- "${*##Q}"'		'1| R'
+testcase 'set -- Q R; set -- "${*%R}"'		'1|Q '
+testcase 'set -- Q R; set -- "${*%%R}"'		'1|Q '
+testcase 'set -- Q R; set -- "${*#S}"'		'1|Q R'
+testcase 'set -- Q R; set -- "${*##S}"'		'1|Q R'
+testcase 'set -- Q R; set -- "${*%S}"'		'1|Q R'
+testcase 'set -- Q R; set -- "${*%%S}"'		'1|Q R'
+testcase 'set -- Q R; set -- ${*#Q}'		'1|R'
+testcase 'set -- Q R; set -- ${*##Q}'		'1|R'
+testcase 'set -- Q R; set -- ${*%R}'		'1|Q'
+testcase 'set -- Q R; set -- ${*%%R}'		'1|Q'
+testcase 'set -- Q R; set -- ${*#S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${*##S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${*%S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${*%%S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${@#Q}'		'1|R'
+testcase 'set -- Q R; set -- ${@##Q}'		'1|R'
+testcase 'set -- Q R; set -- ${@%R}'		'1|Q'
+testcase 'set -- Q R; set -- ${@%%R}'		'1|Q'
+testcase 'set -- Q R; set -- ${@#S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${@##S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${@%S}'		'2|Q|R'
+testcase 'set -- Q R; set -- ${@%%S}'		'2|Q|R'
+testcase 'set -- Q R; set -- "${@#Q}"'		'2||R'
+testcase 'set -- Q R; set -- "${@%R}"'		'2|Q|'
+testcase 'set -- Q R; set -- "${@%%R}"'		'2|Q|'
+testcase 'set -- Q R; set -- "${@#S}"'		'2|Q|R'
+testcase 'set -- Q R; set -- "${@##S}"'		'2|Q|R'
+testcase 'set -- Q R; set -- "${@%S}"'		'2|Q|R'
+testcase 'set -- Q R; set -- "${@%%S}"'		'2|Q|R'
+
+test "x$failures" = x