1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-01-19 11:31:26 +01:00
zsh/Test/A01grammar.ztst
Daniel Shahaf a0c0aa41d2 45111: zshmisc(1): Clarify the documentation of 'return' and 'exit' in conjunction with try/always
Having reviewed 20076, 20084, 21734, and 21735, my understanding is that
the original intention was:

- A 'return' in a function does run always-list
- An 'exit' outside a function does not run always-list
- A 'return' outside a function is treated as an 'exit'

All of which are the case today.  The remaining case, of 'exit' used
inside a function, was not specified by the referenced -workers@ posts;
does, as implemented, run the always-list; and furthermore, based in
21734 it's fair to assume that the original documentation was assuming
that 'exit' would be used outside of any function, just like it assumed
'return' would be used inside a function.

Therefore, have the documentation specify only the behaviour of 'exit'
outside any function, and leave the behaviour of 'exit' inside
a function unspecified.  Anyone who relied on the documentation of 'exit'
as documented until this commit would have run into the
documentation/implementation discrepancy described in 45075.
2019-12-22 03:21:09 +00:00

930 lines
18 KiB
Text

#
# This file contains tests corresponding to the `Shell Grammar' texinfo node.
#
%prep
mkdir basic.tmp && cd basic.tmp
touch foo bar
echo "'" >unmatched_quote.txt
%test
#
# Tests for `Simple Commands and Pipelines'
#
# Test skipping early to ensure we run the remainder...
if [[ -n $ZTST_test_skip ]]; then
ZTST_skip="Test system verification for skipping"
else
print "This is standard output"
print "This is standard error" >&2
false
fi
1:Test skipping if ZTST_test_skip is set
>This is standard output
?This is standard error
echo foo | cat | sed 's/foo/bar/'
0:Basic pipeline handling
>bar
false | true
0:Exit status of pipeline with builtins (true)
true | false
1:Exit status of pipeline with builtins (false)
false
$nonexistent_variable
0:Executing command that evaluates to empty resets status
false
sleep 1 &
print $?
# a tidy test is a happy test
wait $!
0:Starting background command resets status
>0
false
. /dev/null
0:Sourcing empty file resets status
fn() { local foo; read foo; print $foo; }
coproc fn
print -p coproc test output
read -p bar
print $bar
0:Basic coprocess handling
>coproc test output
true | false && print true || print false
0:Basic sublist (i)
>false
false | true && print true || print false
0:Basic sublist (ii)
>true
(cd /NonExistentDirectory >&/dev/null) || print false
0:Basic subshell list with error
>false
{ cd /NonExistentDirectory >&/dev/null } || print false
0:Basic current shell list with error
>false
fn() { : && ! ; : }
functions -x3 fn
fn
0:End of sublist containing ! with no command
>fn () {
> : && !
> :
>}
if [[ m -eq y ]]; then
: && !
:
fi
0:! followed by no further commands
fn() { ! {!} && ! (!) || ! {!} }
functions -x2 fn
fn
0:exclamation marks without following commands
>fn () {
> ! {
> !
> } && ! (
> !
> ) || ! {
> !
> }
>}
! | true
1:! followed by no command but by a pipe
?(eval):1: parse error near `|'
#
# Tests for `Precommand Modifiers'
#
- $ZTST_testdir/../Src/zsh -fc "[[ \$0 = \"-$ZTST_testdir/../Src/zsh\" ]]"
0:`-' precommand modifier
echo f*
noglob echo f*
0:`noglob' precommand modifier
>foo
>f*
(exec /bin/sh; echo bar)
0:`exec' precommand modifier
(exec -l $ZTST_testdir/../Src/zsh -fc 'echo $0' | sed 's%/.*/%%' )
0:`exec' with -l option
>-zsh
(exec -a /bin/SPLATTER /bin/sh -c 'echo $0')
0:`exec' with -a option
>/bin/SPLATTER
(exec -a/bin/SPLOOSH /bin/sh -c 'echo $0')
0:`exec' with -a option, no space
>/bin/SPLOOSH
(exec -a foo* $ZTST_testdir/../Src/zsh -fc 'print -r -- ${(V)0}')
(exec -a "" $ZTST_testdir/../Src/zsh -fc 'print -r -- ${(V)0}')
0:rationalisation of arguments to exec -a
>foo*
>
(
opts=(-a /bin/WHOOOSH)
exec $opts /bin/sh -c 'echo $0'
)
0:`exec' with -a option from expansion
>/bin/WHOOOSH
(export FOO=bar; exec -c /bin/sh -c 'echo x${FOO}x')
0:`exec' with -c option
>xx
(\exec /bin/sh -c 'echo Test one'; print Not reached)
('exec' /bin/sh -c 'echo Test two'; print Not reached)
(\exec -c /bin/sh -c 'echo Test three'; print Not reached)
0:precommand modifiers with quotes
>Test one
>Test two
>Test three
cat() { echo Function cat executed; }
command cat && unfunction cat
0:`command' precommand modifier
<External command cat executed
>External command cat executed
(command -p echo this is output)
(\command -p echo this is more output)
('command' -p echo this is yet more output)
0: command -p without -v or -V
>this is output
>this is more output
>this is yet more output
command -pv cat
command -pv echo
command -p -V cat
command -p -V -- echo
0:command -p in combination
*>*/cat
>echo
>cat is /*/cat
>echo is a shell builtin
args=(
'command -pv cat'
'command -pv echo'
'command -p -V cat'
'command -p -V -- echo'
)
for arg in $args; do
${=arg}
done
0:command -p in combination, using expansion
*>*/cat
>echo
>cat is /*/cat
>echo is a shell builtin
cd() { echo Not cd at all; }
builtin cd . && unfunction cd
0:`builtin' precommand modifier
#
# Tests for `Complex Commands'
#
if true; then
print true-1
elif true; then
print true-2
else
print false
fi
0:`if ...' (i)
>true-1
if false; then
print true-1
elif true; then
print true-2
else
print false
fi
0:`if ...' (ii)
>true-2
if false; then
print true-1
elif false; then
print true-2
else
print false
fi
0:`if ...' (iii)
>false
if true;
:
fi
1d:`if ...' (iv)
?(eval):3: parse error near `fi'
for name in word to term; do
print $name
done
0:`for' loop
>word
>to
>term
for name
in word to term; do
print $name
done
0:`for' loop with newline before in keyword
>word
>to
>term
for (( name = 0; name < 3; name++ )); do
print $name
done
0:arithmetic `for' loop
>0
>1
>2
for (( $(true); ; )); do break; done
for (( ; $(true); )); do break; done
for (( ; ; $(true) )); do break; done
for (( ; $((1)); )); do break; done
0:regression test, nested cmdsubst in arithmetic `for' loop
for keyvar valvar in key1 val1 key2 val2; do
print key=$keyvar val=$valvar
done
0:enhanced `for' syntax with two loop variables
>key=key1 val=val1
>key=key2 val=val2
for keyvar valvar stuffvar in keyA valA stuffA keyB valB stuffB; do
print key=$keyvar val=$valvar stuff=$stuffvar
done
0:enhanced `for' syntax with three loop variables
>key=keyA val=valA stuff=stuffA
>key=keyB val=valB stuff=stuffB
for in in in in in stop; do
print in=$in
done
0:compatibility of enhanced `for' syntax with standard syntax
>in=in
>in=in
>in=in
>in=stop
name=0
while (( name < 3 )); do
print $name
(( name++ ))
done
0:`while' loop
>0
>1
>2
name=0
until (( name == 3 )); do
print $name
(( name++ ))
done
0:`until' loop
>0
>1
>2
repeat 3 do
echo over and over
done
0:`repeat' loop
>over and over
>over and over
>over and over
word=Trinity
case $word in
Michaelmas) print 0
;;
Hilary) print 1
;;
Trinity) print 2
;;
*) print 3
;;
esac
0:`case', old syntax
>2
word=Trinity
case $word in
(Michaelmas) print 0
;;
(Hilary) print 1
;;
(Trinity) print 2
;;
(*) print 3
;;
esac
0:`case', new syntax
>2
word=Hilary
case $word in
(Michaelmas) print 0
;;
(Hilary) print 1
;&
(Trinity) print 2
;&
(*) print 3
;;
esac
0:`case', new syntax, cascaded
>1
>2
>3
case whatever in
(*) print yeah, right ;&
esac
print but well
0:'case', redundant final ";&"
>yeah, right
>but well
## Select now reads from stdin if the shell is not interactive.
## Its own output goes to stderr.
(COLUMNS=80 LINES=3
PS3="input> "
select name in one two three; do
print $name
done)
0:`select' loop
<2
?1) one 2) two 3) three
?input> input>
>two
function name1 name2 () { print This is $0; }
name2
name1 name2() { print This is still $0; }
name2
0:`function' keyword
>This is name2
>This is still name2
(time cat) >&/dev/null
0:`time' keyword (status only)
TIMEFMT='%E %mE %uE %* %m%mm %u%uu'; time (:)
0:`time' keyword with custom TIMEFMT
*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us %\* %m%mm %u%uu
if [[ -f foo && -d . && -n $ZTST_testdir ]]; then
true
else
false
fi
0:basic [[ ... ]] test
#
# Current shell execution with try/always form.
# We put those with errors in subshells so that any unhandled error doesn't
# propagate.
#
{
print The try block.
} always {
print The always block.
}
print After the always block.
0:Basic `always' syntax
>The try block.
>The always block.
>After the always block.
({
print Position one.
print ${*this is an error*}
print Position two.
} always {
if (( TRY_BLOCK_ERROR )); then
print An error occurred.
else
print No error occurred.
fi
}
print Position three)
1:Always block with error not reset
>Position one.
>An error occurred.
?(eval):3: bad substitution
({
print Stelle eins.
print ${*voici une erreur}
print Posizione due.
} always {
if (( TRY_BLOCK_ERROR )); then
print Erratum factum est. Retro ponetur.
(( TRY_BLOCK_ERROR = 0 ))
else
print unray touay foay anguageslay
fi
}
print Status after always block is $?.)
0:Always block with error reset
>Stelle eins.
>Erratum factum est. Retro ponetur.
>Status after always block is 1.
?(eval):3: bad substitution
# Outputting of structures from the wordcode is distinctly non-trivial,
# we probably ought to have more like the following...
fn1() { { echo foo; } }
fn2() { { echo foo; } always { echo bar; } }
fn3() { ( echo foo; ) }
functions fn1 fn2 fn3
0:Output of syntactic structures with and without always blocks
>fn1 () {
> {
> echo foo
> }
>}
>fn2 () {
> {
> echo foo
> } always {
> echo bar
> }
>}
>fn3 () {
> (
> echo foo
> )
>}
#
# Tests for `Alternate Forms For Complex Commands'
#
if (true) { print true-1 } elif (true) { print true-2 } else { print false }
if (false) { print true-1 } elif (true) { print true-2 } else { print false }
if (false) { print true-1 } elif (false) { print true-2 } else { print false }
0:Alternate `if' with braces
>true-1
>true-2
>false
if { true } print true
if { false } print false
0:Short form of `if'
>true
eval "if"
1:Short form of `if' can't be too short
?(eval):1: parse error near `if'
for name ( word1 word2 word3 ) print $name
0:Form of `for' with parentheses.
>word1
>word2
>word3
for name in alpha beta gamma; print $name
0:Short form of `for'
>alpha
>beta
>gamma
for (( val = 2; val < 10; val *= val )) print $val
0:Short arithmetic `for'
>2
>4
foreach name ( verbiage words periphrasis )
print $name
end
0:Csh-like `for'
>verbiage
>words
>periphrasis
# see comment with braces used in if loops
val=0;
while (( val < 2 )) { print $((val++)); }
0:Alternative `while'
>0
>1
val=2;
until (( val == 0 )) { print $((val--)); }
0:Alternative `until'
>2
>1
repeat 3 print Hip hip hooray
0:Short `repeat'
>Hip hip hooray
>Hip hip hooray
>Hip hip hooray
case bravo {
(alpha) print schmalpha
;;
(bravo) print schmavo
;;
(charlie) print schmarlie
;;
}
0:`case' with braces
>schmavo
for word in artichoke bladderwort chrysanthemum Zanzibar
case $word in
(*der*) print $word contains the forbidden incantation der
;;
(a*) print $word begins with a
;&
([[:upper:]]*) print $word either begins with a or an upper case letter
;|
([[:lower:]]*) print $word begins with a lower case letter
;|
(*e*) print $word contains an e
;;
esac
0:`case' with mixed ;& and ;|
>artichoke begins with a
>artichoke either begins with a or an upper case letter
>artichoke begins with a lower case letter
>artichoke contains an e
>bladderwort contains the forbidden incantation der
>chrysanthemum begins with a lower case letter
>chrysanthemum contains an e
>Zanzibar either begins with a or an upper case letter
print -u $ZTST_fd 'This test hangs the shell when it fails...'
name=0
# The number 4375 here is chosen to produce more than 16384 bytes of output
while (( name < 4375 )); do
print -n $name
(( name++ ))
done < /dev/null | { read name; print done }
0:Bug regression: `while' loop with redirection and pipeline
>done
# This used to be buggy and print X at the end of each iteration.
for f in 1 2 3 4; do
print $f || break
done && print X
0:Handling of ||'s and &&'s with a for loop in between
>1
>2
>3
>4
>X
# Same bug for &&, used to print `no' at the end of each iteration
for f in 1 2 3 4; do
false && print strange
done || print no
0:Handling of &&'s and ||'s with a for loop in between
>no
$ZTST_testdir/../Src/zsh -f unmatched_quote.txt
1:Parse error with file causes non-zero exit status
?unmatched_quote.txt:2: unmatched '
$ZTST_testdir/../Src/zsh -f <unmatched_quote.txt
1:Parse error on standard input causes non-zero exit status
?zsh: unmatched '
$ZTST_testdir/../Src/zsh -f -c "'"
1:Parse error on inline command causes non-zero exit status
?zsh:1: unmatched '
$ZTST_testdir/../Src/zsh -f NonExistentScript
127q:Non-existent script causes exit status 127
?$ZTST_testdir/../Src/zsh: can't open input file: NonExistentScript
(setopt nonomatch
# use this to get stuff at start of line
contents=$'# comment \'\necho value #with " stuff\n# comment\n#comment
echo not#comment\n'
eval 'VAR=$('"$contents"')'
print -l $VAR)
0:comments within $(...)
>value
>not#comment
. ./nonexistent
127: Attempt to "." non-existent file.
?(eval):.:1: no such file or directory: ./nonexistent
echo '[[' >bad_syntax
. ./bad_syntax
126: Attempt to "." file with bad syntax.
?./bad_syntax:2: parse error near `\n'
# `
echo 'false' >dot_false
. ./dot_false
print $?
echo 'true' >dot_true
. ./dot_true
print $?
0:Last status of successfully executed "." file is retained
>1
>0
echo 'echo dot
until return 42; do
:
done' >until_dot
. ./until_dot
echo After dot
0:return in positive until test in dot file does not cause excess breaks
>dot
>After dot
echo 'echo $?' >dot_status
false
. ./dot_status
0:"." file sees status from previous command
>1
mkdir test_path_script
print "#!/bin/sh\necho Found the script." >test_path_script/myscript
chmod u+x test_path_script/myscript
path=($PWD/test_path_script $path)
export PATH
$ZTST_testdir/../Src/zsh -f -o pathscript myscript
0:PATHSCRIPT option
>Found the script.
$ZTST_testdir/../Src/zsh -f myscript
127q:PATHSCRIPT option not used.
?$ZTST_testdir/../Src/zsh: can't open input file: myscript
# '
$ZTST_testdir/../Src/zsh -fc 'echo $0; echo $1' myargzero myargone
0:$0 is traditionally if bizarrely set to the first argument with -c
>myargzero
>myargone
(setopt shglob
eval '
if ! (echo success1); then echo failure1; fi
if !(echo success2); then echo failure2; fi
print -l one two | while(read foo)do(print read it)done
')
0:Parentheses in shglob
>success1
>success2
>read it
>read it
fn() { { return } always { echo always 1 }; echo not executed }
fn
fn() { { echo try 2 } always { return }; echo not executed }
fn
0:Always block interaction with return
>always 1
>try 2
(
mywrap() { echo BEGIN; true; echo END }
mytest() { { exit 3 } always { mywrap }; print Exited before this }
mytest
print Exited before this, too
)
3:Exit and always block with functions: simple
>BEGIN
>END
F:Note that the behaviour of 'exit' inside try-list inside a function is unspecified.
(
mytrue() { echo mytrue; return 0 }
mywrap() { echo BEGIN; mytrue; echo END }
mytest() { { exit 4 } always { mywrap }; print Exited before this }
mytest
print Exited before this, too
)
4:Exit and always block with functions: nested
>BEGIN
>mytrue
>END
F:Note that the behaviour of 'exit' inside try-list inside a function is unspecified.
(emulate sh -c '
fn() {
case $1 in
( one | two | three )
print Matched $1
;;
( fo* | fi* | si* )
print Pattern matched $1
;;
( []x | a[b]* )
print Character class matched $1
;;
esac
}
'
which fn
fn one
fn two
fn three
fn four
fn five
fn six
fn abecedinarian
fn xylophone)
0: case word handling in sh emulation (SH_GLOB parentheses)
>fn () {
> case $1 in
> (one | two | three) print Matched $1 ;;
> (fo* | fi* | si*) print Pattern matched $1 ;;
> ([]x | a[b]*) print Character class matched $1 ;;
> esac
>}
>Matched one
>Matched two
>Matched three
>Pattern matched four
>Pattern matched five
>Pattern matched six
>Character class matched abecedinarian
case grumph in
( no | (grumph) )
print 1 OK
;;
esac
case snruf in
( fleer | (|snr(|[au]f)) )
print 2 OK
;;
esac
0: case patterns within words
>1 OK
>2 OK
case horrible in
([a-m])(|[n-z])rr(|ib(um|le|ah)))
print It worked
;;
esac
case "a string with separate words" in
(*with separate*))
print That worked, too
;;
esac
0:Unbalanced parentheses and spaces with zsh pattern
>It worked
>That worked, too
case horrible in
(([a-m])(|[n-z])rr(|ib(um|le|ah)))
print It worked
;;
esac
case "a string with separate words" in
(*with separate*)
print That worked, too
;;
esac
0:Balanced parentheses and spaces with zsh pattern
>It worked
>That worked, too
fn() {
typeset ac_file="the else branch"
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
*.* ) break;;
*)
;;
esac
print Stuff here
}
which fn
fn
0:Long case with parsed alternatives turned back into text
>fn () {
> typeset ac_file="the else branch"
> case $ac_file in
> (*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj) ;;
> (*.*) break ;;
> (*) ;;
> esac
> print Stuff here
>}
>Stuff here
(exit 37)
case $? in
(37) echo $?
;;
esac
0:case retains exit status for execution of cases
>37
false
case stuff in
(nomatch) foo
;;
esac
echo $?
0:case sets exit status to zero if no patterns are matched
>0
case match in
(match) true; false; (exit 37)
;;
esac
echo $?
0:case keeps exit status of last command executed in compound-list
>37
case '' in
burble) print No.
;;
spurble|) print Yes!
;;
|burble) print Not quite.
;;
esac
case '' in
burble) print No.
;;
|burble) print Wow!
;;
spurble|) print Sorry.
;;
esac
case '' in
gurgle) print No.
;;
wurgle||jurgle) print Yikes!
;;
durgle|) print Hmm.
;;
|zurgle) print Hah.
;;
esac
case '' in
# Useless doubled empty string to check special case.
||jurgle) print Ok.
;;
esac
0: case with no opening parentheses and empty string
>Yes!
>Wow!
>Yikes!
>Ok.
x=1
x=2 | echo $x
echo $x
0:Assignment-only current shell commands in LHS of pipelin
>1
>1
echo pipe | ; sed s/pipe/PIPE/
true && ; echo and true
false && ; echo and false
true || ; echo or true
false || ; echo or false
0:semicolon is equivalent to newline
>PIPE
>and true
>or false