#!/bin/echo no
# Testing shell corner cases _within_ a shell script is kind of hard.
[ -f testing.sh ] && . testing.sh
# TODO make fake pty wrapper for test infrastructure
#testing "name" "command" "result" "infile" "stdin"
# texpect "name" "command" E/O/I"string"
# Use "bash" name for host, "sh" for toybox
[ -z "$SH" ] && { [ -z "$TEST_HOST" ] && SH="sh" || export SH="bash" ; }
# Prompt changes for root/normal user
[ $(id -u) -eq 0 ] && P='# ' || P='$ '
# run sufficiently isolated shell child process to get predictable results
SS="env -i PATH=${PATH@Q} PS1='\\$ ' $SH --noediting --noprofile --norc -is"
txpect "prompt and exit" "$SS" "E$P" "Iexit\n" X0
txpect "prompt and echo" "$SS" "E$P" "Iecho hello\n" "Ohello"$'\n' "E$P" X0
txpect "redirect err" "$SS" "E$P" "Iecho > /dev/full\n" "E" "E$P" X0
txpect "wait for <(exit)" "$SS" "E$P" "Icat <(echo hello 1>&2)\n" $'Ehello\n' \
"E$P" X0
# Test the sh -c stuff before changing EVAL
testing '-c "" exit status 0' '$SH -c "" && echo $?' '0\n' '' ''
testing '-c args' "\$SH -c 'echo \$0,\$1,\$2,\$3' one two three four five" \
"one,two,three,four\n" "" ""
testing '-c arg split' \
"$SH -c 'for i in a\"\$@\"b;do echo =\$i=;done;echo \$0' 123 456 789" \
"=a456=\n=789b=\n123\n" "" ""
testing '-c arg count' "$SH -c 'echo \$#' 9 8 7 6 1 2 3 4" "7\n" "" ""
testing "exec3" '$C -c "{ exec readlink /proc/self/fd/0;} < /proc/self/exe"' \
"$(readlink -f $C)\n" "" ""
testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" ""
testing 'simple script' '$SH input' 'input\n' 'echo $0' ''
testing 'simple script2' '$SH ./input two;echo $?' './input+two\n42\n' \
'\necho $0+$1\n\nexit 42' ''
mkdir sub
echo echo hello > sub/script
testing 'simple script in PATH' "PATH='$PWD/sub:$PATH' $SH script" \
'hello\n' '' ''
rm -rf sub
testing "script file" "chmod +x input; ./input" "hello\n" "#!$C\necho hello" ""
testing "IFS $*" "sh -c 'IFS=xy; echo \"\$*\"' one two tyree" "twoxtyree\n" \
"" ""
# Change EVAL to call sh -c for us, using "bash" explicitly for the host.
export EVAL="$SH -c"
testing "smoketest" "echo hello" "hello\n" "" ""
testing "eval" "eval echo hello" "hello\n" "" ""
testing "eval2" "eval 'echo hello'; echo $?" "hello\n0\n" "" ""
testing "eval3" 'X="echo hello"; eval "$X"' "hello\n" "" ""
testing "exec" "exec echo hello" "hello\n" "" ""
testing "exec2" "exec echo hello; echo $?" "hello\n" "" ""
# ; | && ||
testing "semicolon" "echo one;echo two" "one\ntwo\n" "" ""
testing "simple pipe" "echo hello | cat" "hello\n" "" ""
testing "&&" "true && echo hello" "hello\n" "" ""
testing "&&2" "false && echo hello" "" "" ""
testing "||" "true || echo hello" "" "" ""
testing "||2" "false || echo hello" "hello\n" "" ""
testing "&& ||" "true && false && potato || echo hello" "hello\n" "" ""
# redirection
testing "redir1" "cat < input" "hello\n" "hello\n" ""
testing "redir2" "echo blah >out; cat out" "blah\n" "" ""
testing "redir3" "echo more >>out; cat out" "blah\nmore\n" "" ""
testing "redir4" "touch /not/exist 2>out||grep -o /not/exist out" \
"/not/exist\n" "" ""
testing "redir5" "ls out /not/exist &> out2 || wc -l < out2" "2\n" "" ""
testing "redir6" "ls out /not/exist |& wc -l" "2\n" "" ""
testing "redir7" 'echo -n $(A variable occurred
testing "expand" 'echo $PWD' "$(pwd)\n" "" ""
testing "expand2" 'echo "$PWD"' "$(pwd)\n" "" ""
testing "expand3" 'echo "$"PWD' '$PWD\n' "" ""
testing "expand4" 'P=x; echo "$P"WD' 'xWD\n' "" ""
testing "dequote" "echo one 'two' ''three 'fo'ur '\\'" \
'one two three four \\\n' '' ''
testing "leading variable assignment" 'abc=def env | grep ^abc=; echo $abc' \
"abc=def\n\n" "" ""
testing "leading variable assignments" \
"abc=def ghi=jkl env | egrep '^(abc|ghi)=' | sort; echo \$abc \$ghi" \
"abc=def\nghi=jkl\n\n" "" ""
testing "{1..5}" "echo {1..5}" "1 2 3 4 5\n" "" ""
testing "{5..1}" "echo {5..1}" "5 4 3 2 1\n" "" ""
testing "{5..1..2}" "echo {5..1..2}" "5 3 1\n" "" ""
testing "{a..z..-3}" "echo {a..z..-3}" "a d g j m p s v y\n" "" ""
#$ IFS=x X=xyxz; for i in abc${X}def; do echo =$i=; done
#=abc=
#=y=
#=zdef=
testing "IFS whitespace before/after" \
'IFS=" x"; A=" x " B=" x" C="x " D=x E=" "; for i in $A $B $C $D L$A L$B L$C L$D $A= $B= $C= $D= L$A= L$B= L$C= L$D=; do echo -n {$i}; done' \
"{}{}{}{}{L}{L}{L}{L}{}{=}{}{=}{}{=}{}{=}{L}{=}{L}{=}{L}{=}{L}{=}" "" ""
testing "quotes and whitespace" \
'A=" abc def "; for i in ""$A""; do echo =$i=; done' \
"==\n=abc=\n=def=\n==\n" "" ""
testing "quotes and whitespace2" \
'A=" abc def "; for i in """"$A""; do echo =$i=; done' \
"==\n=abc=\n=def=\n==\n" "" ""
testing "quotes and whitespace3" \
'A=" abc def "; for i in ""x""$A""; do echo =$i=; done' \
"=x=\n=abc=\n=def=\n==\n" "" ""
testing "IFS" 'IFS=x; A=abx; echo -n "$A"' "abx" "" ""
testing "IFS2" 'IFS=x; A=abx; echo -n $A' "ab" "" ""
testing "IFS3" 'IFS=x; echo "$(echo abx)"' "abx\n" "" ""
testing "IFS4" "IFS=x; echo \"\$(echo ab' ')\"" "ab \n" "" ""
testing "IFS5" 'IFS=xy; for i in abcxdefyghi; do echo =$i=; done' \
"=abc def ghi=\n" "" ""
testing '$*' 'cc(){ for i in $*;do echo =$i=;done;};cc "" "" "" "" ""' \
"" "" ""
testing '$*2' 'cc(){ for i in "$*";do echo =$i=;done;};cc ""' \
"==\n" "" ""
testing '$*3... Flame. Flames. Flames, on the side of my face...' \
'cc(){ for i in "$*";do echo =$i=;done;};cc "" ""' "= =\n" "" ""
testing 'why... oh.' \
'cc() { echo ="$*"=; for i in =$*=; do echo -$i-; done;}; cc "" ""; echo and; cc ""' \
'= =\n-=-\n-=-\nand\n==\n-==-\n' "" ""
testing 'really?' 'cc() { for i in $*; do echo -$i-; done;}; cc "" "" ""' \
"" "" ""
testing 'Sigh.' 'cc() { echo =$1$2=;}; cc "" ""' "==\n" "" ""
testing '$*4' 'cc(){ for i in "$*";do echo =$i=;done;};cc "" "" "" "" ""' \
"= =\n" "" ""
testing '$*5' 'cc(){ for i in "$*";do echo =$i=;done;};cc "" "abc" ""' \
"= abc =\n" "" ""
# creating empty arguments without quotes
testing '$* + IFS' \
'IFS=x; cc(){ for i in $*; do echo =$i=;done;};cc xabcxx' \
"==\n=abc=\n==\n" "" ""
testing '$@' 'cc(){ for i in "$@";do echo =$i=;done;};cc "" "" "" "" ""' \
"==\n==\n==\n==\n==\n" "" ""
testing "IFS10" 'IFS=bcd; A=abcde; for i in $A; do echo =$i=; done' \
"=a=\n==\n==\n=e=\n" "" ""
testing "IFS11" \
'IFS=x; chicken() { for i in $@$@; do echo =$i=; done;}; chicken one "" abc dxf ghi' \
"=one=\n==\n=abc=\n=d=\n=f=\n=ghione=\n==\n=abc=\n=d=\n=f=\n=ghi=\n" "" ""
testing "IFS combinations" \
'IFS=" x"; A=" x " B=" x" C="x " D=x E=" "; for i in $A $B $C $D L$A L$B L$C L$D $A= $B= $C= $D= L$A= L$B= L$C= L$D=; do echo -n {$i}; done' \
"{}{}{}{}{L}{L}{L}{L}{}{=}{}{=}{}{=}{}{=}{L}{=}{L}{=}{L}{=}{L}{=}" "" ""
testing "! isn't special" "echo !" "!\n" "" ""
testing "! by itself" '!; echo $?' "1\n" "" ""
testing "! true" '! true; echo $?' "1\n" "" ""
testing "! ! true" '! ! true; echo $?' "0\n" "" ""
testing "! syntax err" '! echo 2>/dev/null < doesnotexist; echo $?' "0\n" "" ""
# The bash man page doesn't say quote removal here, and yet:
testing "case quoting" 'case a in "a") echo hello;; esac' 'hello\n' "" ""
testing "subshell splitting" 'for i in $(true); do echo =$i=; done' "" "" ""
#testing "subshell split 2"
# variable assignment argument splitting only performed for "$@"
testing "assignment splitting" 'X="one two"; Y=$X; echo $Y' "one two\n" "" ""
testing "argument splitting" \
'chicken() { for i in a"$@"b;do echo =$i=;done;}; chicken 123 456 789' \
"=a123=\n=456=\n=789b=\n" "" ""
testing "assignment splitting2" 'pop(){ X="$@";};pop one two three; echo $X' \
"one two three\n" "" ""
#testing "leading assignments don't affect current line" \
# 'VAR=12345 echo ${VAR}a' "a\n" "" ""
#testing "can't have space before first : but yes around arguments" \
# 'BLAH=abcdefghi; echo ${BLAH: 1 : 3 }' "bcd\n" "" ""
testing "subshell exit err" '(exit 42); echo $?' "42\n" "" ""
# Same thing twice, but how do we cmp if exec exited?
#testing 'exec and $$' testing 'echo $$;exec readlink /proc/self'
X="$(realpath $(which readlink))"
testing "exec in paren" \
'(exec readlink /proc/self/exe);echo hello' "$X\nhello\n" "" ""
testing "exec in brackets" \
"{ exec readlink /proc/self/exe;};echo hi" "$X\n" "" ""
NOSPACE=1 testing "curly brackets and pipe" \
'{ echo one; echo two ; } | tee blah.txt; wc blah.txt' \
"one\ntwo\n2 2 8 blah.txt\n" "" ""
NOSPACE=1 testing "parentheses and pipe" \
'(echo two;echo three)|tee blah.txt;wc blah.txt' \
"two\nthree\n2 2 10 blah.txt\n" "" ""
#testing "pipe into parentheses" \
# 'echo hello | (read i " \
I$'}\n' O$'a c\n' X0
txpect 'here1' "$SS" "E$P" I'POTATO=123; cat << EOF'$'\n' "E> " \
I'$POTATO'$'\n' "E> " I'EOF'$'\n' O'123'$'\n' X0
txpect 'here2' "$SS" "E$P" I'POTATO=123; cat << E"O"F'$'\n' "E> " \
I'$POTATO'$'\n' "E> " I'EOF'$'\n' O'$POTATO'$'\n' X0
testing 'here3' 'abc(){ cat <<< x"$@"yz;};abc one two "three four"' \
"xone two three fouryz\n" "" ""
testing '${#}' 'X=abcdef; echo ${#X}' "6\n" "" ""
# $# $? $- $$ $! $0
# always exported: PWD SHLVL _
# ./bash -c 'echo $_' prints $BASH, but PATH search shows path? Hmmm...
# ro: UID PPID EUID $
# IFS LINENO
# PATH HOME SHELL USER LOGNAME SHLVL HOSTNAME HOSTTYPE MACHTYPE OSTYPE OLDPWD
# PS0 PS1='$ ' PS2='> ' PS3 PS4 BASH BASH_VERSION
# ENV - if [ -n "$ENV" ]; then . "$ENV"; fi # BASH_ENV - synonym for ENV
# FUNCNEST - maximum function nesting level (abort when above)
# REPLY - set by input with no args
# OPTARG OPTIND - set by getopts builtin
# OPTERR
# maybe not: EXECIGNORE, FIGNORE, GLOBIGNORE
#BASHPID - synonym for $$ HERE
#BASH_SUBSHELL - SHLVL synonym
#BASH_EXECUTION_STRING - -c argument
#
#automatically set:
#OPTARG - set by getopts builtin
#OPTIND - set by getopts builtin
#
#PROMPT_COMMAND PROMPT_DIRTRIM PS0 PS1 PS2 PS3 PS4
#
#unsettable (assignments ignored before then)
#LINENO SECONDS RANDOM
#GROUPS - id -g
#HISTCMD - history number
#
#TMOUT - used by read