[Toybox] Shell tests for reference.

enh enh at google.com
Tue Dec 17 10:10:37 PST 2019


this sounds like a good time to mention what i've been up to with
testing. you may recall i've talked about the mksh test suite before,
and that -- despite being a thousand lines of perl -- that what they
had was the least worst solution i'd seen for this problem?

  https://cs.android.com/android/platform/superproject/+/master:external/mksh/src/check.t

here's all the options mksh needed for testing mksh:

  https://cs.android.com/android/platform/superproject/+/master:external/mksh/src/check.pl;l=50

i've recently reimplemented an extended subset of that for easier
testing of Android command-line tools:

  https://cs.android.com/android/platform/superproject/+/master:system/core/cli-test/?q=cli-test

here's the example unzip tests, which i think are quite a bit more readable:

  https://cs.android.com/android/platform/superproject/+/master:system/core/libziparchive/cli-tests/unzip.test

the implementation is pretty minimal (and significantly shorter than
the Perl original), but the tests are actually surprisingly readable
and expressive. (i've been particularly pleased with the way `after:`
subsumes a lot of different "does file f exist?"/"is d a directory?"
type of stuff.)

i'll doubtless need some regex matching at some point, but just the
freedom from quoting is worth the entry price. as is being able to say
one thing per line, rather than trying to cram every assertion into
one line. (those have been the two most problematic parts of the
toybox tests, i think.) the "Future Directions" in the cli-test
README.md lists some of the more obvious gaps, but none of them are
hard to add if/when i actually need them, and what i have is already
pretty useful.

On Mon, Dec 16, 2019 at 9:09 PM Rob Landley <rob at landley.net> wrote:
>
> For those of you following my shell struggles at https://landley.net/notes.html
> here's what one of my shell test files looks like. I doubt this
> makes any sense to anyone other than me.
>
> Sorry it's been so quiet on here. I got my blog caught up, and am ruminating
> about shell stuff there instead. :)
>
> Rob
>
> /*
> alias bg cd command fc fg getopts hash jobs kill read type ulimit umask unalias
> wait exit if while for case export set unset trap exec function source
>
>   TOY_MAYFORK: echo test printf
>     no memory allocations, filehandles open... if signaled, no cleanup.
>     maybe: help ulimit
>
>   .toysh_history file
>   {abc,def}
>   $LINENO $RANDOM
>   "source" and "." check current dir after $PATH
>   job control: CTRL-Z, CTRL-C, fg, bg, kill %jobspec, wait
>   $(command)
>   $((math))  # integer attribute for variables, assignment does that
>     $((abc"def)) the " doesn't take effect so )) is recognized?
>   if then elif else fi
>   for while until do done
>   case esac
>   function () { commands ; }
>     local variables in functions
>   set -x (and -x command line option) to trace
>   export -n (unexports)
>   readonly
>   read set unset umask
>   time (whole pipeline)
>   command trap type times getopts
>
>   todo: echo | (cat) # as far as cat's concerned previous end was NULL
>
>   What to do about builtin vs $PATH:
>     check $PATH first _then_ builtin?
>     config opt?
>
> if files exist use 'em, otherwise:
>   /dev/fd/fd stdin stdout stderr tcp/host/port udp/host/port
> exec redirects in current shell
>
>  n<word
>  n>word
>  n>>word (append)
>  &>word  # stdout and stderr, same as >word 2>&1
>  &>>word (append)
>  >&word  # note: >&$BLAH when $BLAH is number or -
>  n<&word # number = duplicate, - = close, number- = move
>  n>&word # special case: no n and word not number or -, redirect stderr & out.
>  n<<word  # only quote removal, no expansion
>          # unquoted: parameter ex, command subst, arith ex, \\ \$ \` \newline
>  <<-word # strips leading tabs
>  <<<word # all expansions _except_ pathname expansion and word splitting
>  n<>word # open file word for reading & writing, create if necessary, n def 0
>
> // &> or >& = >word 2>&1
> // &>> append stdout & stderr == >> word 2>&1
> // <&# copy input fd (error if # not open for read)
> // >&# copy output fd (error if # not open for write)
> // <&#- move fd# to n
> // >&#- move fd# to n
>
>
> What does this mean?
>   env {abcdef}>&2 | grep abcdef
>
> Instead of number, precede with {varname}?
>
> brace  expansion,  tilde expansion,  parameter  and  variable  expansion,
> command substitution,
>        arithmetic expansion,  quote  removal,  pathname  expansion,  and  word
>        splitting.
>   << no path ex or word split
>
> test should work as a nofork, handle [[
> what does $(( ))
> handle ( ( echo hello ) ) | cat and (echo)|cat
>
>       if (then)
>         else elif (then)
>         fi
>       for select while until (do)
>       done
>       case (esac)
>       ( )
>       { }
>       [[ ]]
>     }
>
> // function
> // HERE document
> // ( ) - ) is always last argument of line
> // | & && || |&
>
>       // "if", "for", "while", "until", "select"
>       // "do" : "then";
>       // then else elif fi
>       // continue break case esac return do done function
>       // in time { } ]] ]]
>       // ! coproc
>       // function ()
>       // . source [ test [[ ( { ((
>
>       // [ pwd ulimit -- kill time
>       // cd exit -- pushd popd logout umask getopts eval exec
>       // fg bg jobs disown wait suspend
>       // alias unalias set export unset let local readonly read shift trap
>
>
>
> # Because of HERE documents we can't re-parse, have to retain state.
> # -- either that or our caller has to know about here document servicing?
> # -c input must be split into lines for HERE documents
> < > >> <<< WORD
> << HERE [HERE] # eat lines until HERE as _only_ word on line
> if [then]
> for while until select [do]
> case [esac]
> ( [)] # funky because ) not first word
> { [}]
> [[ []]]
> function NAME ( ) { # note expect { doesn't eat { be because need to expect }
>
> */
>
> # can't have space before first : but arguments can have lead/trail
> $ BLAH=abcdefghi; echo ${BLAH: 1 : 3 }
> # leading assignments don't affect current command line
> $ VAR=12345 echo $VARa
> $ bash -c 'ls $('
> bash: -c: line 0: unexpected EOF while looking for matching `)'
> bash: -c: line 1: syntax error: unexpected end of file
> $ ;
> bash: syntax error near unexpected token `;'
> $ ABC= ; env | grep ABC= ; unset ABC ; env | grep ABC=
> ABC=
> $ cat blah<(echo hello)thing
> cat: blah/dev/fd/63thing: No such file or directory
> $ "AB"="CD" echo $AB
> bash: AB=CD: command not found
> landley at driftwood:~/toybox/toybox$ AB="CD" echo $AB
> $ 'AB'="CD" echo $AB
> bash: AB=CD: command not found
> $ \AB="CD" echo $AB
> bash: AB=CD: command not found
> $ \AB="CD" echo $AB
> $ if echo hello; then fi
> bash: syntax error near unexpected token `fi'
> $ echo )
> bash: syntax error near unexpected token `)'
> $ ( echo hello ) | cat
> hello
> $ if
> > true
> > then
> > echo hello
> > fi
> hello
> $ if true; then echo hello; fi
> hello
> $ if false; then echo hello; fi
> $ false; X=47; echo $?
> 0
> $ if;
> bash: syntax error near unexpected token `;'
> $ if
> > true
> > echo
> > hello
> > fi
> bash: syntax error near unexpected token `fi'
> $ echo )))
> bash: syntax error near unexpected token `)'
> $ echo &&&
> bash: syntax error near unexpected token `&'
> $ echo |||
> bash: syntax error near unexpected token `|'
> $ echo hello | if true; then read i; echo i=$i; fi
> i=hello
> $ if true && false; then echo hi; fi
> while true ; do echo hello; done | tee blah
> if { echo hello; }; then echo hi; fi
> if if true; then true; fi; then echo hello; fi
> boom(){ echo hello; }; boom
> ( ( echo; thingy ) ) | cat
> $ echo hello | X=y env | grep -w X; echo $X
> X=y
> ;|x
> ;x|
> (echo)also
> (echo)(echo)
> $ if true esac; then echo hi; fi
> hi
> # note: exits top level shell anyway!
> $ (exit walrus)
> bash: exit: walrus: numeric argument required
> $ echo $?
> 2
> $ exit walrus 2>blah.txt
> $ echo ${abc:?error m essage}
> bash: abc: error m essage
> $ if blah () { echo bang; true; }; blah; then echo hello; fi; blah
> bang
> hello
> bang
> $ meep() { echo hello; klarg () { echo helloier; }; klarg; }
> $ meep
> hello
> helloier
>   $ meep() { echo hello; klarg() { echo helloier; } ; }
>   $ klarg
>   bash: klarg: command not found
>   $ meep
>   hello
>   $ klarg
>   helloier
>   $
>
> landley at driftwood:~/toybox/toybox$ thingy ()
> > ^C
> landley at driftwood:~/toybox/toybox$ echo (
> bash: syntax error near unexpected token `newline'
> landley at driftwood:~/toybox/toybox$ function
> bash: syntax error near unexpected token `newline'
> landley at driftwood:~/toybox/toybox$ function name () {
> > }
> bash: syntax error near unexpected token `}'
> landley at driftwood:~/toybox/toybox$ function name () { ;}
> bash: syntax error near unexpected token `;'
> landley at driftwood:~/toybox/toybox$ if ; then; echo hello; fi
> bash: syntax error near unexpected token `;'
> landley at driftwood:~/toybox/toybox$ func (); { echo hello; }
> bash: syntax error near unexpected token `;'
> $ if true |
> > then
> bash: syntax error
> echo one && if true; then echo hello; fi
> one
> hello
> $ echo one two \
> > three
> $ bash -c "$(echo -e 'cat << HERE\none two\nHERE')"
> one two
> $ cat << HERE; echo hello
> > boing
> > HERE
> boing
> hello
> $ cat << one << two
> > abc
> $ ls > file 2>&1 #redirects both
> $ ls 2>&1 >file # redirects only stdout, stderr goes to original stdout
> $ echo hello < nowhere
> bash: nowhere: No such file or directory
> $ cat << E"O"F
> > $PATH
> > EOF
> $PATH
> $ cat << HERE filename
> > HERE
> cat: filename: No such file or directory
> $ cat <<< here filename
> cat: filename: No such file or directory
>
>
> landley at driftwood:~/toybox/toybox$ bash -c "cat <<< HERE; echo hello; HERE"
> HERE
> hello
> bash: HERE: command not found
> landley at driftwood:~/toybox/toybox$ bash -c "cat << HERE; echo hello; HERE"
> bash: warning: here-document at line 0 delimited by end-of-file (wanted `HERE')
> hello
> bash: HERE: command not found
> landley at driftwood:~/toybox/toybox$ bash -c "$(echo 'cat << HERE\necho hello\nHERE')"
> bash: warning: here-document at line 0 delimited by end-of-file (wanted `HEREnecho')
> cat: hellonHERE: No such file or directory
> landley at driftwood:~/toybox/toybox$ bash -c "$(echo -e 'cat << HERE\necho
> hello\nHERE')"
> echo hello
> landley at driftwood:~/toybox/toybox$ X=3; echo $((X))
> 3
> landley at driftwood:~/toybox/toybox$ X=3; echo $((X))^C
> landley at driftwood:~/toybox/toybox$ echo |
> >
> > cat
>
> landley at driftwood:~/toybox/toybox$ echo &&
> > &&
> bash: syntax error near unexpected token `&&'
> landley at driftwood:~/toybox/toybox$ echo &&
> > ;
> bash: syntax error near unexpected token `;'
> landley at driftwood:~/toybox/toybox$ if
> > true
> > then
> > echo hello
> > fi
> hello
> landley at driftwood:~/toybox/toybox$ << EOF
> > echo hello
> > EOF
> $ echo &<2
> [1] 17850
>
> bash: 2: No such file or directory
> landley at driftwood:~/toybox/toybox$
> [1]+  Done                    echo
> landley at driftwood:~/toybox/toybox$ { echo hello; }
> hello
> landley at driftwood:~/toybox/toybox$ { { echo hello; };}
> hello
> landley at driftwood:~/toybox/toybox$ {{ echo hello; };}
> bash: syntax error near unexpected token `}'
> landley at driftwood:~/toybox/toybox$ { {echo hello; };}
> bash: syntax error near unexpected token `}'
> landley at driftwood:~/toybox/toybox$ { { echo hello; };}
> hello
> landley at driftwood:~/toybox/toybox$ {
> > {
> > echo hello
> > }
> > }
> hello
> $ if cat << EOF ; then
> > one two three
> > EOF
> > echo hello
> > fi
> one two three
> hello
> cat <(ls "$PWD"/a{b,c}*) &> /dev/tcp/127.0.0.1/80
> $ while grep -q << EOF walrus; do
> > walrus
> > EOF
> > echo hello; done | head -n 3
> hello
> hello
> hello
> $ if echo hello; then echo also; fi | tee and.txt # both get redirected
> hello
> also
> $ cat - << EOF <(echo
> > hello)
> > boing
> > EOF
> bash: hello: command not found
> boing
>
> $ ( ( ( echo abc ) echo ) )
> bash: syntax error near unexpected token `echo'
> $ ( ( ( echo abc )
> > echo also ) )
> abc
> also
> $ ( ( echo one ) > file )
> $ ( ( echo one ) > two ) > three
> $ ( echo ) >> blah
> $ if true; then echo; fi >> blah
> $ cat - /proc/self/fd/3 << BOING 3<<MEEP
> > one
> > BOING
> > aha
> > MEEP
> one
> aha
> $ cat - << BOING $(
> echo hello)
> abc
> BOING
> abc
> blah
> $ ( cat << EOF
> > hello
> > EOF
> > )
> hello
> $ if cat << EOF
> > hello
> > EOF
> > then
> > echo fi
> > fi
> hello
> fi
> $ echo 2>&1
> $ echo 2 >&1
> $ echo 2 >& 1
> $ if while true; do echo hello; done; then echo hi; fi
> $ (echo &&)
> $ (echo ;)
> $ if true; then echo $X; fi {X}</dev/null
> 10
> $ X=2; {X}<&-; boing # does not close stderr?
> bash: boing: command not found
> $ ls /proc/$$/fd
> $ exec 10<& -
> # >&; ;&>
> # prompts before echoing
> $ echo hello; if
> > echo two; then echo three
> > fi
> hello
> two
> three
> $ echo )hello
> bash: syntax error near unexpected token `)'
> $ echo {abc
> {abc
> $ echo {abc</dev/null
> {abc
> # innermost redirect wins
> $ if cat <<< moo ; then cat <<< also; fi <<< potato
> moo
> also
> $ (echo &&)
> bash: syntax error near unexpected token `)'
> $ funkiness() { env ; }
> $ POTATO=blah funkiness | grep POTATO
> POTATO=blah
> $ echo $POTATO
> $ chicken() { echo hello; chicken() { echo also ;}; chicken;}
> $ chicken
> hello
> also
> $ chicken
> also
> $ thingy() { echo hello; } | cat
> $ thingy
> bash: thingy: command not found
> $ echo hello > >(sed 's/.*/abc&def/') > >(sed 's/.*/ghi&jkl/')
> abcghihellojkldef
> $ echo hello > one > two
> $ yes | head -n 3 > >(wc -l >two) > >(wc -l >one)
> landley at driftwood:~$ cat one
> 3
> landley at driftwood:~$ cat two
> 0
> $ </dev/null echo hello
> hello
> $ echo {+}</dev/null
> {+}
> $ echo {}</dev/null
> {}
> $ echo <
> bash: syntax error near unexpected token `newline'
> $ murgle() { echo > "$@" ;}
> $ murgle one two three
> bash: "$@": ambiguous redirect
> $ murgle one
> $ cat << "   "
> > abc
> >
> abc
> $ cat << \"
> > boing
> > "
> boing
> $ echo hello &>> blah.txt
> $ potato() { echo "$@"; }; IFS=1234 potato one two three
> one two three
> $ cat <<< {one,two,three}
> {one,two,three}
> $ cat <<< one two three
> $ echo hello >&2-x && ls 2-x
> 2-x
> $ while true; do sleep 1; read a; echo $a; [ -z "$a" ] && break; done << EOF &
> > one
> > two
> > three
> > EOF
> [1] 30590
> $ (set -o noclobber; echo > /dev/null)
> $ (ln -s /dev/null blah; set -o noclobber; echo > blah)
> $ (touch walrus; ln -s walrus penguin; set -o noclobber; echo > penguin)
> $ funky() { A="$@" ;}; funky one two three; echo $A
> one two three
> $ export "()=42"
> bash: export: `()=42': not a valid identifier
> $ set hello="$@" > walrus
> $ X=$(false) || echo true
> true
> $ for((i=1;i<5;i++));do echo $i;done
> 1
> 2
> 3
> 4
> # when is (( a token vs ( (?
> $ ((echo a) | sed s/a/b/)
> b
> $ ((1
> > +2<1)); echo $?
> 1
> $ X=ii;for $X in a b c; do echo $ii; done
> bash: `$X': not a valid identifier
> $ for i in 1 2 3 & do echo hello; done
> bash: syntax error near unexpected token `&'
> $ if true; then false; else true; elif true; then false; fi
> bash: syntax error near unexpected token `elif'
> $ while false; true; do echo hello; done | head -n 3
> hello
> hello
> hello
> $ せ=42
> bash: せ=42: command not found
> $ function abc=def () {echo hello;} ; "abc=def"
> hello
> $ ((echo hello) )
> hello
> $ abc() { for name; do echo $name; done;}; abc X Y Z
> X
> Y
> Z
> # In a for loop ;; breaks down to ; ;
> $ for((i=0;;i++)); do echo $i; done | head -n 30
> 1
> 2
> $ (()); echo $?
> 1
> $ ((x=12)); echo $x
> 12
> $ (echo 123)  # redir prefix not used for non-redir end tokens
> 123
> $ x=42; ! ((x<3)) && ((x<43)) && echo yes
> yes
> # redirects aren't in math
> $ echo $((2<3))
> 1
> $ echo $((2&3))
> 2
> $ ((3<2)); echo $?
> 1
> $ X=X; echo $((X+2))
> bash: X: expression recursion level exceeded (error token is "X")
> $ X=3 ((3<2)) || echo hello
> bash: syntax error near unexpected token `('
> $ echo $((`echo 1`))
> 1
> $ X=3; (($X<3)) || echo no; (($X<4)) && echo yes
> no
> yes
> $ cat <(echo "$(((1<2)) && echo "hello")")
> hello
> $ THINGY=whoami; echo "$("${THINGY}")"
> landley
> $ burble() #comment
> > { echo hello;};burble
> hello
> $ for i=3
> > do echo hello; done
> bash: `i=3': not a valid identifier
> $ ((1<2)) > blat
> $ boing () { for name do echo $name; done }; boing one two three
> one
> two
> three
> $ cat <( "(" )
> bash: (: command not found
> $ break > potato 2>/dev/null || ls potato
> potato
>
> # too many error messages
> $ break 1 37
> bash: break: only meaningful in a `for', `while', or `until' loop
> $ for i in 1; do break walrus; done
> bash: break: walrus: numeric argument required
> $ for i in 1; do break 37 walrus; done
> bash: break: too many arguments
> $ for i in 1; do break 0; done
> bash: break: 0: loop count out of range
> $ if true; then if false; then echo one; elif ! echo two; then echo three; else
> echo four; fi; fi
> two
> four
> $ cat boing |& sed s/boing/walrus/
> cat: walrus: No such file or directory
> # runs printing "hi" until segfault
> bash -c "abc() { echo hi; def() { abc;echo lo;};def;}; abc"
> # word splitting test
> $ X="one two"; printf '%s\n' $X 2 3 | while read a; do echo $a; done
> one
> two
> 2
> 3
> $ fff() { echo abc$*def;}; fff one two three | xargs -n 1 echo
> abcone
> two
> threedef
> $ ABC=abcdefg
> $ echo ${ABC:+"}"}
> }
> $ for i in a b c d e; do break & done
> [1] 25533
> [2] 25534
> [3] 25535
> [4] 25536
> [5] 25537
> $ walrus=1; echo ${walrus+blah}
> blah
> $ unset walrus; echo ${walrus+blah}
> $
> $ echo echo hello | sh
> hello
> $ { { { echo hello;} ;} >blat ;} ; echo one; cat blat
> one
> hello
>
> $ ABC=1
> $ echo ${"AB"C}
> bash: ${"AB"C}: bad substitution
> $ echo ${"ABC"}
> bash: ${"ABC"}: bad substitution
> $ X='*'; echo $X
>
> $ <&2-; ls boing  # redirect undone at end of command
> ls: cannot access 'boing': No such file or directory
> $ ls boing <&2-   # redirect applies to command
> $ X=42 </dev/null ; echo $X # local assignment persists even with redirect
> 42
> $ bash -c 'read i >/dev/null <<< boing; echo $i'
> boing
> # The expansions unquoted << _doesn't_ do.
> $ cat << TEST
> > ~landley
> > *
> > potato/{one,two,three}
> > "hello"
> > TEST
> ~landley
> *
> potato/{one,two,three}
> "hello"
> $ X="|"; echo hello $X and
> hello | and
> $ for i in a b c; do for j in d e f; do for k in g h i; do echo $i $j $k;
> continue 3; done done done
> a d g
> b d g
> c d g
>
> _______________________________________________
> Toybox mailing list
> Toybox at lists.landley.net
> http://lists.landley.net/listinfo.cgi/toybox-landley.net



More information about the Toybox mailing list