#!/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" shxpect() { X="$1" shift txpect "$X" "$SS" E"$P" "$@" X0 } shxpect "prompt and exit" I$'exit\n' shxpect "prompt and echo" I$'echo hello\n' O$'hello\n' E"$P" shxpect "redirect err" I$'echo > /dev/full\n' E E"$P" shxpect "wait for <(exit)" I$'cat <(echo hello 1>&2)\n' E$'hello\n' E"$P" # 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 args2' "\$SH -c 'echo \${10}' a b c d e f g h i j k l" "k\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 'arg shift' "$SH -c '"'for i in "" 2 1 1 1; do echo $? $1; shift $i; done'"' one two three four five" \ "0 two\n0 three\n0 five\n0\n1\n" "" "" testing '(subshell)' '$SH -c "(echo hello)"' 'hello\n' '' '' testing 'syntax' '$SH -c "if true; then echo hello | fi" 2>/dev/null || echo x'\ 'x\n' '' '' # The bash man page is lying when it says $_ starts with an absolute path. ln -s $(which $SH) bash testing 'non-absolute $_' "./bash -c 'echo \$_'" './bash\n' '' '' rm bash 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' '' testing '$LINENO 1' "$SH input" "1\n" 'echo $LINENO' '' 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" \ "" "" testing 'default exports' \ "env -i \"$(which $SH)\" --noprofile --norc -c env | sort" \ "PWD=$(pwd)\nSHLVL=1\n_=$(which env)\n" "" "" testing "leading assignment fail" \ "{ \$SH -c 'X=\${a?blah} > walroid';ls walroid;} 2>/dev/null" '' '' '' ######################################################################### # 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 "line break" $'ec\\\nho 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 "eval4" 'eval printf '=%s=' \" hello \"' "= hello =" "" "" NOSPACE=1 testing "eval5" 'eval echo \" hello \" | wc' ' 1 1 8' "" "" 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 &>>-abc || wc -l < ./-abc" "2\n" "" "" testing "redir7" "ls out /not/exist |& wc -l" "2\n" "" "" testing "redir8" 'echo -n $( out 2>/does/not/exist\n' E E"$P" \ I$'wc -l < out\n' O$'0\n' testing "redir10" 'echo hello 3<&3' "hello\n" "" "" testing "redir11" 'if :;then echo one;fi {abc}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 "leading assignment space" 'X="abc def"; Y=$X; echo "$Y"' \ "abc def\n" "" "" testing "leading assignment space2" \ 'chicken() { X="$@"; }; chicken a b c d e; echo "$X"' 'a b c d e\n' '' '' testing "leading assignment fail2" \ "{ 1blah=123 echo hello;} 2>/dev/null || echo no" "no\n" "" "" testing "leading assignment redirect" \ "blah=123 echo hello > walrus && ls walrus" "walrus\n" "" "" rm -f walrus 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" "" "" testing 'background pipe block' \ 'if true; then { sleep .25;bzcat "$FILES"/blkid/ntfs.bz2; }& fi | wc -c' \ '8388608\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 'empty $! is blank' 'echo $!' "\n" "" "" testing '$! = jobs -p' 'true & [ $(jobs -p) = $! ] && echo yes' "yes\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 "IFS12" 'IFS=3;chicken(){ return 3;}; chicken;echo 3$?3' '3 3\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 /dev/null; echo $abc' \ "\n" '' '' testing '${@:3:5}' 'chicken() { for i in "${@:3:5}"; do echo =$i=; done; } ; chicken ab cd ef gh ij kl mn op qr' \ '=ef=\n=gh=\n=ij=\n=kl=\n=mn=\n' '' '' testing '${@:3:5}' 'chicken() { for i in "${*:3:5}"; do unset IFS; echo =$i=; done; } ; IFS=x chicken ab cd ef gh ij kl mn op qr' \ '=efxghxijxklxmn=\n' '' '' testing 'sequence check' 'IFS=x; X=abxcd; echo ${X/bxc/g}' 'agd\n' '' '' # TODO: The txpect plumbing does not work right yet even on TEST_HOST #txpect "backtick0" "$SS" "E$P" 'IX=fred; echo `echo \\\\$x`'$'\n' 'Ofred' "E$P" X0 #txpect "backtick1" "$SS" "E$P" 'IX=fred; echo `echo $x`'$'\n' 'Ofred'$'\n' "E$P" X0 #txpect "backtick2" "$SS" "E$P" 'IX=fred; echo `x=y; echo $x`' $'Oy\n' "E$P" X0 shxpect '${ with newline' I$'HELLO=abc; echo ${HELLO/b/\n' E"> " I$'}\n' O$'a c\n' shxpect 'here1' I$'POTATO=123; cat << EOF\n' E"> " \ I$'$POTATO\n' E"> " I$'EOF\n' O$'123\n' shxpect 'here2' I$'POTATO=123; cat << E"O"F\n' E"> " \ I$'$POTATO\n' E"> " I$'EOF\n' O$'$POTATO\n' testing 'here3' 'abc(){ cat <<< x"$@"yz;};abc one two "three four"' \ "xone two three fouryz\n" "" "" testing '${var}' 'X=abcdef; echo ${X}' 'abcdef\n' '' '' testing '${#}' 'X=abcdef; echo ${#X}' "6\n" "" "" shxpect 'empty ${}' I$'echo ${}; echo hello\n' E I$'echo and\n' O$'and\n' testing '${!x}' 'X=abcdef Y=X; echo ${!Y}' 'abcdef\n' '' '' testing '${!x} err' '{ X=abcdef Y=X:2; echo ${!Y}; echo bang;} 2>/dev/null' \ '' '' '' testing '${!x*}' 'abcdef=1 abc=2 abcq=; echo "${!abc@}" | tr " " \\n | sort' \ 'abc\nabcdef\nabcq\n' '' '' testing '${!x*} none' 'echo "${!abc*}"' '\n' '' '' testing '${!x*} err' '{ echo "${!abc*x}"; echo boing;} 2>/dev/null' '' '' '' testing '${!x@Q}' 'ABC=123 X=ABC; echo ${!X@Q}' "'123'\n" '' '' # TODO: ${!abc@x} does _not_ error? And ${PWD@q} testing '${\}}' 'ABC=ab}cd; echo ${ABC/\}/x}' 'abxcd\n' '' '' shxpect '${/newline/}' I$'x=$\'\na\';echo ${x/\n' E'> ' I$'/b}\n' O$'ba\n' E'> ' shxpect 'line continuation' I$'echo "hello" \\\n' E'> ' I$'> blah\n' E"$P" \ I$'wc blah\n' O$'1 1 6 blah\n' shxpect 'line continuation2' I$'echo ABC\\\n' E'> ' I$'DEF\n' O$'ABCDEF\n' # Race condition (in bash, but not in toysh) can say 43. testing 'SECONDS' 'readonly SECONDS=41; sleep 1; echo $SECONDS' '42\n' '' '' # testing 'SECONDS2' 'readonly SECONDS; SECONDS=0; echo $SECONDS' '' '' '' #bash! testing 'SECONDS2' 'SECONDS=123+456; echo $SECONDS' '0\n' '' '' #bash!! testing '$LINENO 2' $'echo $LINENO\necho $LINENO' '0\n1\n' '' '' testing '$EUID' 'echo $EUID' "$(id -u)\n" '' '' testing '$UID' 'echo $UID' "$(id -ur)\n" '' '' testing 'readonly for' \ 'readonly i; for i in one two three; do echo $i; done 2>/dev/null; echo $?' \ '1\n' '' '' testing 'readonly {}<' \ 'readonly i; echo hello 2>/dev/null {i}/dev/null; echo $?' 'boink=123\n0\n' '' '' testing '$_ 1' 'echo walrus; echo $_' 'walrus\nwalrus\n' '' '' testing '$_ 2' 'unset _; echo $_' '_\n' '' '' # wildcards touch walrus wallpapers testing 'IFS wildcards' \ 'IFS=xy; ABC=abcywal*sxdef; echo $ABC | tr " " "\n" | sort' \ 'abc\ndef\nwallpapers\nwalrus\n' '' '' rm -f walrus wallpapers shxpect 'IFS +(extglob)' I$'shopt -s extglob\n' E"$P" \ I$'IFS=x; ABC=cxd; for i in +($ABC); do echo =$i=; done\n' \ O$'=+(c=\n' O$'=d)=\n' touch abc\)d shxpect 'IFS +(extglob) 2' I$'shopt -s extglob\n' E"$P" \ I$'ABC="c?d"; for i in ab+($ABC); do echo =$i=; done\n' \ O$'=abc)d=\n' # TODO finish variable list from shell init # $# $? $- $$ $! $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