| #!/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" |
| |
| [ -z "$SH" ] && { [ -z "$TEST_HOST" ] && SH="sh" || export SH="bash" ; } |
| |
| # 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 "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 |
| |
| # 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 $(<input)' "boing" "boing\n" "" |
| |
| testing "tilde expansion" "echo ~" "$HOME\n" "" "" |
| testing "tilde2" "echo ~/dir" "$HOME/dir\n" "" "" |
| testing "bracket expansion" \ |
| "echo {A{a,b}B{c,d}C}" "{AaBcC} {AaBdC} {AbBcC} {AbBdC}\n" "" "" |
| testing "brackets2" "echo {A{a,b}B{c,d}C,D}" "AaBcC AaBdC AbBcC AbBdC D\n" "" "" |
| testing "brackets3" 'echo {A"b,c"D}' "{Ab,cD}\n" "" "" |
| testing "brackets4" 'echo {A"bc",D}' "Abc D\n" "" "" |
| testing "brackets5" 'echo {A,B,C' "{A,B,C\n" "" "" |
| testing "brackets6" 'echo {{{{A,B},C}D},E}' "{AD} {BD} {CD} E\n" "" "" |
| testing "brackets7" 'echo {{{a,b},c,{d,e}},f}' "a b c d e f\n" "" "" |
| testing "brackets8" 'echo A{a{b,c{B,C}D}d{e,f},g{h,i}j}E' \ |
| "AabdeE AabdfE AacBDdeE AacBDdfE AacCDdeE AacCDdfE AghjE AgijE\n" "" "" |
| testing "brackets9" 'echo A{B{C,D}E{N,O},F{G,H}I}J{K,L}M' \ |
| "ABCENJKM ABCENJLM ABCEOJKM ABCEOJLM ABDENJKM ABDENJLM ABDEOJKM ABDEOJLM AFGIJKM AFGIJLM AFHIJKM AFHIJLM\n" "" "" |
| for i in /root /var/root /; do [ -e $i ] && EXPECT=$i && break; done |
| testing "bracket+tilde" "echo {~,~root}/pwd" "$HOME/pwd $EXPECT/pwd\n" "" "" |
| |
| #testing "backtick1" 'X=fred; echo `echo $x`' 'fred\n' "" "" |
| #testing "backtick2" 'X=fred; echo `x=y; echo $x`' 'y\n' "" "" |
| testing '$(( ) )' 'echo $((echo hello) | tr e x)' "hxllo\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" "" "" |
| |
| #$ 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 '$*' '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 "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" "" "" |
| |
| # 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 <input; echo $i; read i; echo $i)' \ |
| # "there\nhello\n" "there\n" "" |
| |
| # texpect "name" "command" E/O/I"string" |
| |
| # Prompt changes for root/normal user |
| [ $(id -u) -eq 0 ] && P='# ' || P='$ ' |
| # run sufficiently isolated shell child process to get predictable results |
| SH="env -i PATH=${PATH@Q} PS1='\\$ ' $SH --noediting --noprofile --norc -is" |
| |
| txpect "prompt and exit" "$SH" "E$P" "Iexit\n" X0 |
| txpect "prompt and echo" "$SH" "E$P" "Iecho hello\n" "Ohello"$'\n' "E$P" X0 |
| txpect "redirect err" "$SH" "E$P" "Iecho > /dev/full\n" "E" "E$P" X0 |
| txpect "wait for <(exit)" "$SH" "E$P" "Icat <(echo hello 1>&2)\n" $'Ehello\n' \ |
| "E$P" X0 |
| |
| # TODO: The txpect plumbing does not work right yet even on TEST_HOST |
| #txpect "backtick0" "$SH" "E$P" 'IX=fred; echo `echo \\\\$x`'$'\n' 'Ofred' "E$P" X0 |
| #txpect "backtick1" "$SH" "E$P" 'IX=fred; echo `echo $x`'$'\n' 'Ofred'$'\n' "E$P" X0 |
| #txpect "backtick2" "$SH" "E$P" 'IX=fred; echo `x=y; echo $x`' $'Oy\n' "E$P" X0 |
| |
| # $@ $* $# $? $- $$ $! $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 |