diff options
Diffstat (limited to 'Test')
45 files changed, 3743 insertions, 190 deletions
diff --git a/Test/A01grammar.ztst b/Test/A01grammar.ztst index 88fc8606e..d57085798 100644 --- a/Test/A01grammar.ztst +++ b/Test/A01grammar.ztst @@ -922,7 +922,7 @@ F:Note that the behaviour of 'exit' inside try-list inside a function is unspeci x=1 x=2 | echo $x echo $x -0:Assignment-only current shell commands in LHS of pipelin +0:Assignment-only current shell commands in LHS of pipeline >1 >1 @@ -944,3 +944,77 @@ F:Note that the behaviour of 'exit' inside try-list inside a function is unspeci if : ${(e)a}; then echo x; fi 1:Status on bad substitution in if without else ?(eval):2: bad substitution + + echo 'echo foo # comment + echo $( + echo bar # comment + )' >source_comments.zsh + $ZTST_testdir/../Src/zsh -f -o extendedglob -is -c '. ./source_comments.zsh' +0:Comments should be handled in command subst in interactively sourced files +>foo +>bar + + function 'ls,/' () {echo success} + {ls,/} +0:workers/47599: current-shell blocks masquerading as brace expansion +>success +F:This test was written to ensure the behaviour doesn't change silently. +F:If this test fails during development, it *might* be appropriate to change +F:its expectations. + + ( + export VALUE=first + print -l 'echo Value is $VALUE' 'VALUE=second sh' 'echo Value is $VALUE' | + $ZTST_testdir/../Src/zsh -f + ) +0:Non-interactive shell command input is line buffered +>Value is first +>Value is second + + fn() { + ! false + } +0:! inverts the status of implicit return + + fn () { + false + ! return + } + fn +1:! does not affect return status of explicit return + + msg=unset + for x in 1 2 3 4 5; do + continue && msg=set && print Not executed + print Not executed, neither. + done + print $msg +0:continue causes immediate continuation +>unset + + msg=unset + () { + return && msg=set && print Not executed + print Not executed, not nor neither. + } + print $msg +0:return causes immediate return +>unset + + msg=unset + for x in 1 2 3 4 5; do + ! continue || msg=set && print Not executed + print Not executed, neither. + done + print $msg +0:! continue causes immediate continuation +>unset + + msg=unset + () { + ! return || msg=set && print Not executed + print Not executed, not nor neither. + } + print $msg +0:! return causes immediate return +>unset diff --git a/Test/A02alias.ztst b/Test/A02alias.ztst index ca415fa39..1c6969e74 100644 --- a/Test/A02alias.ztst +++ b/Test/A02alias.ztst @@ -123,7 +123,12 @@ eval 'badalias() { print does not work; }') 1:ALIAS_FUNC_DEF off by default. ?(eval):1: defining function based on alias `badalias' -?(eval):1: parse error near `()' + + (alias firstalias=notacommand + alias secondalias=firstalias + eval 'secondalias() { print does not work either; }') +1:ALIAS_FUNC_DEF reports original alias if multiple +?(eval):1: defining function based on alias `secondalias' (alias goodalias=isafunc setopt ALIAS_FUNC_DEF diff --git a/Test/A04redirect.ztst b/Test/A04redirect.ztst index 993138e7d..dc62efab3 100644 --- a/Test/A04redirect.ztst +++ b/Test/A04redirect.ztst @@ -3,9 +3,8 @@ %prep mkdir redir.tmp && cd redir.tmp - myfd=99 - (echo >&$myfd) 2>msg - bad_fd_msg="${$(<msg)##*:}" + bad_fd_msg="${$( { exec 9>&-; echo >&9 } 2>&1)##*:}" + [[ -n "$bad_fd_msg" ]] %test @@ -440,7 +439,7 @@ # This tests the here-string to filename optimisation; we can't # test that it's actually being optimised, but we can test that it # still works. - cat =(<<<$'This string has been replaced\nby a file containing it.\n') + cat =(<<<$'This string has been replaced\nby a file containing it.') 0:Optimised here-string to filename >This string has been replaced >by a file containing it. diff --git a/Test/A05execution.ztst b/Test/A05execution.ztst index c65642988..07a24f9c8 100644 --- a/Test/A05execution.ztst +++ b/Test/A05execution.ztst @@ -2,7 +2,7 @@ storepath=($path) - mkdir command.tmp command.tmp/dir1 command.tmp/dir2 + mkdir command.tmp command.tmp/dir{1,2} command.tmp/{+,-}dir cd command.tmp @@ -21,7 +21,10 @@ print '#!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxnyyy' >tstcmd-interp-too-long print "#!${sh}\necho should not execute; exit 1" >xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxn - chmod 755 tstcmd dir1/tstcmd dir2/tstcmd + print 'echo no shebang in -dir' > -dir/tstcmd + print 'echo no shebang in +dir' > +dir/tstcmd + + chmod 755 tstcmd dir{1,2}/tstcmd ./{-,+}dir/tstcmd chmod 755 tstcmd-slashless tstcmd-arg tstcmd-interp-too-long chmod 755 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxn @@ -258,7 +261,7 @@ F:side of a pipe to block on write after the right side has exited print -u $ZTST_fd "Skipping pipe leak test, requires MONITOR option" print "[0] 0 0" fi -0:Bug regression: piping to anonymous function; piping to backround function +0:Bug regression: piping to anonymous function; piping to background function *>\[<->\] <-> <-> F:This test checks for two different bugs, a parser segfault piping to an F:anonymous function, and a descriptor leak when backgrounding a pipeline @@ -396,6 +399,13 @@ F:anonymous function, and a descriptor leak when backgrounding a pipeline # TBD: the 0 above is believed to be bogus and should also be turned # into 127 when the ccorresponding bug is fixed in the main shell. + sleep 2 & pid=$! + kill -STOP $pid + sleep 1 + kill -CONT $pid + wait $pid +0:wait for stopped and continued process + # Without the outer subshell, the test harness reports the pre-46060 behaviour # as "skipped" rather than "failed". (( exit 130 ) | { sleep 1; echo hello }) @@ -415,3 +425,12 @@ F:anonymous function, and a descriptor leak when backgrounding a pipeline (exit 4); repeat 0 do done 0:'repeat 0' resets lastval + -dir/tstcmd + +dir/tstcmd + PATH=-dir tstcmd + PATH=+dir tstcmd +0:shebang-less scripts are to be run by sh even when their file paths start with - or + (workers/52515) +>no shebang in -dir +>no shebang in +dir +>no shebang in -dir +>no shebang in +dir diff --git a/Test/A06assign.ztst b/Test/A06assign.ztst index f89edb888..3eff5331a 100644 --- a/Test/A06assign.ztst +++ b/Test/A06assign.ztst @@ -296,13 +296,26 @@ # tests of var+=(array) + a= + a+=(1 2 3) + print "${(q@)a}" +0:add array to empty parameter +>'' 1 2 3 + unset a a+=(1 2 3) - print -l $a + print "${(q@)a}" 0:add array to unset parameter ->1 ->2 ->3 +>1 2 3 + + () { + setopt localoptions typeset_to_unset + typeset a + a+=(1 2 3) + print "${(q@)a}" + } +0:add array to declared unset parameter +>1 2 3 a=(a) a+=(b) diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst index 8b3988151..914eea92b 100644 --- a/Test/B02typeset.ztst +++ b/Test/B02typeset.ztst @@ -311,7 +311,7 @@ print $OUTER 0:Export of tied parameters >i:n:n:e:r ->typeset -xT OUTER outer=( i n n e r ) +>local -xT OUTER outer=( i n n e r ) >typeset -aT OUTER outer=( i n n e r ) >OUTER=i:n:n:e:r >outer=( i n n e r ) @@ -959,6 +959,20 @@ > [three]='' >) + () { + local -h status + typeset -p status + } +0:parameter hiding preserved by "typeset -p" +>typeset -h status='' + + () { + local status + typeset -p status + } +0:read-only special params are output when localized +>typeset -i10 -r status=0 + (export PATH MANPATH path=(/bin) MANPATH=/ @@ -1085,12 +1099,12 @@ } 0: no array/hash in POSIX export/readonly -p >zsh: ->typeset -arx zsh_exported_readonly_array=( 2 ) ->typeset -Arx zsh_exported_readonly_hash=( [3]=3 ) ->typeset -rx zsh_exported_readonly_scalar=1 ->typeset -arx zsh_exported_readonly_array=( 2 ) ->typeset -Arx zsh_exported_readonly_hash=( [3]=3 ) ->typeset -rx zsh_exported_readonly_scalar=1 +>local -arx zsh_exported_readonly_array=( 2 ) +>local -Arx zsh_exported_readonly_hash=( [3]=3 ) +>local -rx zsh_exported_readonly_scalar=1 +>local -arx zsh_exported_readonly_array=( 2 ) +>local -Arx zsh_exported_readonly_hash=( [3]=3 ) +>local -rx zsh_exported_readonly_scalar=1 >sh: >export zsh_exported_readonly_scalar=1 >readonly zsh_exported_readonly_scalar=1 diff --git a/Test/B03print.ztst b/Test/B03print.ztst index 4d2cf9764..93a9669b0 100644 --- a/Test/B03print.ztst +++ b/Test/B03print.ztst @@ -305,8 +305,9 @@ foo+=$'\tone\ttwo\tthree\tfour\n' foo+=$'\t\tone\t\ttwo\t\tthree\t\tfour' foo+='\0' # regression test for multibyte tab expand - print -x4 $foo | tr '\0' Z # avoid raw nul byte in expected output below - print -X4 $foo | tr '\0' Z + # avoid raw nul byte in expected output below + print ${"$(print -x4 $foo)"/$'\0'/Z} + print ${"$(print -X4 $foo)"/$'\0'/Z} 0:Tab expansion by print >one two three four > one two three four diff --git a/Test/B04read.ztst b/Test/B04read.ztst index 25c3d4173..14bdaeef5 100644 --- a/Test/B04read.ztst +++ b/Test/B04read.ztst @@ -82,6 +82,16 @@ >Testing the >null hypothesis + read -ed '' <<<$'one\0two' +0:empty delimiter terminates at nulls +>one + + print -n $'first line\x80second line\x80' | + while read -d $'\x80' line; do print $line; done +0:read with a delimiter >= 0x80 +>first line +>second line + # Note that trailing NULLs are not stripped even if they are in # $IFS; only whitespace characters contained in $IFS are stripped. print -n $'Aaargh, I hate nulls.\0\0\0' | read line diff --git a/Test/B12limit.ztst b/Test/B12limit.ztst index 48d33e6e3..9dce59824 100644 --- a/Test/B12limit.ztst +++ b/Test/B12limit.ztst @@ -11,7 +11,7 @@ %test limit | grep UNKNOWN || print OK -0:Check if there is unknown resouce(s) in the system +0:Check if there is unknown resource(s) in the system >OK F:A failure here does not indicate any error in zsh. It just means there F:is a resource in your system that is unknown to zsh developers. Please diff --git a/Test/C01arith.ztst b/Test/C01arith.ztst index d0092fefa..ba9c65e5b 100644 --- a/Test/C01arith.ztst +++ b/Test/C01arith.ztst @@ -251,6 +251,14 @@ >5000 >255 + set -- {101..120} + _10=42 + echo $_10 : $1_0 + echo $(( _10 )) : $(( 1_0 )) +0:underscores in front of a numeric identifier is not a math constant +>42 : 101_0 +>42 : 10 + # Force floating point. for expr in "3/4" "0x100/0x200" "0x30/0x10"; do print $(( $expr )) diff --git a/Test/C02cond.ztst b/Test/C02cond.ztst index 4366b4142..daea5b4f8 100644 --- a/Test/C02cond.ztst +++ b/Test/C02cond.ztst @@ -111,10 +111,6 @@ if (( EUID == 0 )); then print -u$ZTST_fd 'Warning: Not testing [[ ! -r file ]] (root reads anything)' [[ -r zerolength && -r unmodish ]] - elif [[ $OSTYPE = cygwin ]]; then - print -u$ZTST_fd 'Warning: Not testing [[ ! -r file ]] - (all files created by user may be readable)' - [[ -r zerolength ]] else [[ -r zerolength && ! -r unmodish ]] fi @@ -148,9 +144,7 @@ print -ru $ZTST_fd 'This test may take two seconds...' touch $newnewnew - if [[ $OSTYPE == "cygwin" ]]; then - ZTST_skip="[[ -N file ]] not supported on Cygwin" - elif (( isnfs )); then + if (( isnfs )); then ZTST_skip="[[ -N file ]] not supported with NFS" elif ! zmodload -F zsh/stat b:zstat 2> /dev/null; then ZTST_skip='[[ -N file ]] not tested; zsh/stat not available' diff --git a/Test/C03traps.ztst b/Test/C03traps.ztst index 6f84e5db2..de57765a0 100644 --- a/Test/C03traps.ztst +++ b/Test/C03traps.ztst @@ -670,6 +670,22 @@ F:Must be tested with a top-level script rather than source or function >before-out >before-in + (set -o err_return + fn() { + print before-in + { false; true } && true + print after-in + } + print before-out + fn && true + print after-out + ) +0:ERR_RETURN not triggered on LHS of "&&" in function on LHS of "&&" (regression test) +>before-out +>before-in +>after-in +>after-out + mkdir -p zdotdir print >zdotdir/.zshenv ' setopt norcs errreturn @@ -713,7 +729,7 @@ F:Must be tested with a top-level script rather than source or function fi } fn() { - setopt err_return + setopt localoptions err_return fn2 || true } fn @@ -721,6 +737,37 @@ F:Must be tested with a top-level script rather than source or function >Good (setopt err_exit + ! true + print OK + ) +0:ERR_EXIT not triggered by "! true" +>OK + + (setopt err_exit + fn() { true } + ! fn + print OK + ) +0:ERR_EXIT not triggered by "! fn" +>OK + + (setopt err_exit + false && true + print OK + ) +0:ERR_EXIT not triggered by "false && true" +>OK + + (setopt err_exit + fn() { + false && true + } + fn + print OK + ) +1:ERR_EXIT not triggered by "false && true" but by return from fn + + (setopt err_exit for x in y; do false && true done @@ -730,14 +777,15 @@ F:Must be tested with a top-level script rather than source or function >OK (setopt err_exit - integer x=0 - while (( ! x++ )); do - false && true - done + fn() { + for x in y; do + false && true + done + } + fn print OK ) -0:ERR_EXIT not triggered by status 1 at end of while ->OK +1:ERR_EXIT not triggered by status 1 at end of for but by return from fn (setopt err_exit repeat 1; do @@ -749,6 +797,17 @@ F:Must be tested with a top-level script rather than source or function >OK (setopt err_exit + fn() { + repeat 1; do + false && true + done + } + fn + print OK + ) +1:ERR_EXIT not triggered by status 1 at end of repeat but by return from fn + + (setopt err_exit if true; then false && true fi @@ -758,6 +817,71 @@ F:Must be tested with a top-level script rather than source or function >OK (setopt err_exit + fn() { + if true; then + false && true + fi + } + fn + print OK + ) +1:ERR_EXIT not triggered by status 1 at end of if but by return from fn + + (setopt err_exit + loop=true + while print COND; $loop; do + loop=false + false && true + done + print OK + ) +0:ERR_EXIT not triggered by status 1 at end of while +>COND +>COND +>OK + + (setopt err_exit + fn() { + loop=true + while print COND; $loop; do + loop=false + false && true + done + } + fn + print OK + ) +1:ERR_EXIT not triggered by status 1 at end of while but by return from fn +>COND +>COND + + (setopt err_exit + { + false && true + } always { + print ALWAYS + } + print OK + ) +0:ERR_EXIT not triggered by status 1 at end of always +>ALWAYS +>OK + + (setopt err_exit + fn() { + { + false && true + } always { + print ALWAYS + } + } + fn + print OK + ) +1:ERR_EXIT not triggered by status 1 at end of always but by return from fn +>ALWAYS + + (setopt err_exit { false && true } @@ -766,6 +890,17 @@ F:Must be tested with a top-level script rather than source or function 0:ERR_EXIT not triggered by status 1 at end of { } >OK + (setopt err_exit + fn() { + { + false && true + } + } + fn + print OK + ) +1:ERR_EXIT not triggered by status 1 at end of { } but by return from fn + unsetopt err_exit err_return (setopt err_exit for x in y; do @@ -819,6 +954,47 @@ F:Must be tested with a top-level script rather than source or function 1:ERR_EXIT triggered by status 1 at end of anon func >Still functioning + (setopt err_exit + loop=true; while print loop $? >&2; $loop; do loop=false; false && true; done + print done $? >&2 + ) +0: ERR_EXIT neither triggered inside loop nor triggered by while statement +?loop 0 +?loop 1 +?done 1 + + (setopt err_exit + { loop=true; while print loop $? >&2; $loop; do loop=false; false && true; done } || false + print done $? >&2 + ) +1: ERR_EXIT not triggered inside loop but triggered by rhs of || +?loop 0 +?loop 1 + + (setopt err_exit + eval 'loop=true; while print loop $? >&2; $loop; do loop=false; false && true; done' + print done $? >&2 + ) +1: ERR_EXIT not triggered inside loop but triggered by eval +?loop 0 +?loop 1 + + (setopt err_exit + source <(echo 'loop=true; while print loop $? >&2; $loop; do loop=false; false && true; done') + print done $? >&2 + ) +1: ERR_EXIT not triggered inside loop but triggered by source +?loop 0 +?loop 1 + + (setopt err_exit + v=$(loop=true; while print loop $? >&2; $loop; do loop=false; false && true; done) + print done $? >&2 + ) +1: ERR_EXIT not triggered inside loop but triggered by command substitution +?loop 0 +?loop 1 + if zmodload zsh/system 2>/dev/null; then ( trap 'echo TERM; exit 2' TERM @@ -901,12 +1077,23 @@ F:Must be tested with a top-level script rather than source or function fn trap1 trap2 echo out2 ' --f:(workers/44007) function execution continues after 'exit' in trap +0:'exit' in trap causes calling function to return >out1 >fn1 >trap1 # As of 5.7.1-test-2, the output was "out1 fn1 trap1 fn2" (on separate lines). + TRAPEXIT() { echo This is TRAPEXIT; } + TRAPEXIT + TRAPEXIT + TRAPEXIT +0:No memory problems with explicit call to TRAPEXIT. +>This is TRAPEXIT +>This is TRAPEXIT +>This is TRAPEXIT +>This is TRAPEXIT +# Three explicit calls, one implicit call at function exit. + %clean rm -f TRAPEXIT diff --git a/Test/C04funcdef.ztst b/Test/C04funcdef.ztst index 88321c432..b8509b25c 100644 --- a/Test/C04funcdef.ztst +++ b/Test/C04funcdef.ztst @@ -53,6 +53,26 @@ >b: redirection >a: redirection + define_multiple() { + fn1 fn2 fn3() { + print This is $0 + } + } + which -x2 define_multiple + define_multiple + fn1 + fn2 + fn3 +0: Safe output of multiple function definitions +>define_multiple () { +> function fn1 fn2 fn3 { +> print This is $0 +> } +>} +>This is fn1 +>This is fn2 +>This is fn3 + functions -M m1 m1() { (( $# )) } print $(( m1() )) @@ -307,7 +327,7 @@ # lsfoo should not be expanded as an anonymous function argument alias lsfoo='This is not ls.' () (echo anon func; echo "$@") lsfoo -0:Anonmous function with arguments in a form nobody sane would ever use but unfortunately we have to support anyway +0:Anonymous function with arguments in a form nobody sane would ever use but unfortunately we have to support anyway >anon func >lsfoo diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst index 3fb27e620..55861cca1 100644 --- a/Test/D01prompt.ztst +++ b/Test/D01prompt.ztst @@ -257,3 +257,22 @@ ZTST_skip='Missing terminfo module or non-colour terminal' fi 0:Equivalence of terminal colour settings (background colour) + + A1=${(%):-%s} + A2=${(%):-%u} + A3=${(%):-%s%u%s} + [[ $A3 = $A1$A2 ]] +0:Attribute optimisation - preserve initial disabling of attribute but drop useless later one + + : ${(%):-%K{blue}} + A1="${(%):-%b}x" + : ${(%):-%k} + A2="${(%):-%b}x" + [[ $A1 = $A2 && -n $A1 && -n $A2 ]] +0:Don't restore attributes from earlier substitution after disabling bold + + (RPS1=foo; echo $RPS1 $RPROMPT) + (RPS2=bar; echo $RPS2 $RPROMPT2) +-fD:RPS1 and RPROMPT are aliases (regression from 5.0.6) (workers/49600) +>foo foo +>bar bar diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst index 72891a2a7..4d88e5c27 100644 --- a/Test/D02glob.ztst +++ b/Test/D02glob.ztst @@ -748,13 +748,21 @@ touch glob.tmp/secret-d$1/dir/file chmod $1 glob.tmp/secret-d$1 done - print -raC 2 -- glob.tmp/secret-*/* glob.tmp/secret-*/file + if (( EUID == 0 )); then + ZTST_skip='Not testing unreadable directories (root reads anything)' + else + print -raC 2 -- glob.tmp/secret-*/* glob.tmp/secret-*/file + fi 0:names inside unreadable directories can be globbed if searchable >glob.tmp/secret-d444/dir glob.tmp/secret-d444/file >glob.tmp/secret-s444/dir glob.tmp/secret-s444/file >glob.tmp/secret-d111/file glob.tmp/secret-s111/file - print -rC 2 -- glob.tmp/secret-*/dir/* + if (( EUID == 0 )); then + ZTST_skip='Not testing unreadable directories (root reads anything)' + else + print -rC 2 -- glob.tmp/secret-*/dir/* + fi 0:glob files in readable directories inside unreadable directories >glob.tmp/secret-d111/dir/file glob.tmp/secret-s111/dir/file @@ -809,6 +817,32 @@ *>*/glob.tmp/(flip|flop) *>*/glob.tmp/(flip|flop)/trailing/components +# The following set test an obscure problem with branches followed by +# exclusions that shows up when the exclusion matches against +# something other than the complete test string, hence the complicated +# double negative. + [[ ab = (|a*)~^(*b) ]] +0:Regression test for exclusion after branches: empty first alternative + + [[ ab = (b|a*)~^(*b) ]] +0:Regression test for exclusion after branches: non-empty first alternative + + [[ ab = (b*|a*)~^(*b) ]] +0:Regression test for exclusion after branches: full length first alternative + +# Corresponding tests where the exclusion should succeed, so the +# match fails. It's hard to know how to provoke bugs here... + [[ abc = (|a*)~^(*b) ]] +1:Regression test for exclusion after branches: failure case 1 + + [[ abc = (b|a*)~^(*b) ]] +1:Regression test for exclusion after branches: failure case 2 + + [[ abc = (b*|a*)~^(*b) ]] +1:Regression test for exclusion after branches: failure case 3 + +# Careful: extendedglob off from this point. + unsetopt extendedglob print -r -- ${(*)=${(@s.+.):-A+B}/(#b)(?)/-${(L)match[1]} ${match[1]}} 0:the '*' qualfier enables extended_glob for pattern matching diff --git a/Test/D03procsubst.ztst b/Test/D03procsubst.ztst index 1e5cd9f6c..d68db35fa 100644 --- a/Test/D03procsubst.ztst +++ b/Test/D03procsubst.ztst @@ -167,5 +167,25 @@ print -rC1 -- $TMPPREFIX*(N) } 0f:external command with =(...) on LHS of pipeline cleans up its tempfiles +F:subshells including pipe LHS do not pass through zexit() # (Expected result: no output.) +# Confirm tempfile exists in the function, but not after exit + () { + local -x TMPPREFIX=$PWD/exit + $ZTST_testdir/../Src/zsh -fc ' + () { + print -rC1 -- $TMPPREFIX* + exit + } =(sleep 5) + ' + print -rC1 -- $TMPPREFIX*(N) + } +0:regression test: exit in shell function cleans up tempfiles +F:see preceding test +*>*.tmp/exit* + + print -u $ZTST_fd 'This test hangs the shell when it fails...' + true | false =(nosuchcommand$$) +1:regression test: race condition with pipe and procsubst +*?\(eval\):2: command not found: nosuchcommand* diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst index b6b1f2e33..0e2a04eb5 100644 --- a/Test/D04parameter.ztst +++ b/Test/D04parameter.ztst @@ -110,6 +110,11 @@ *>*foo:1: 1: no arguments given >reached + message="expand me and remove quotes" + (: ${UNSET_PARAM?$message}) +1:${...?....} performs expansion on the message +?(eval):2: UNSET_PARAM: expand me and remove quotes + print ${set1:+word1} ${set1+word2} ${null1:+word3} ${null1+word4} print ${unset1:+word5} ${unset1+word6} 0:${...:+...}, ${...+...} @@ -1217,6 +1222,7 @@ typeset -T STRING string print $STRING $string unset string + typeset -p string STRING=x:y:z print $STRING $string STRING=a:b @@ -1693,6 +1699,38 @@ >b >c + () { + emulate -L sh + local a=( one two three ) + printf '<%s><%s>\n' ${a[*]:0:2} + printf '<%s><%s>\n' "${a[*]:0:2}" + printf '<%s><%s>\n' ${a[@]:0:2} + printf '<%s><%s>\n' "${a[@]:0:2}" + printf '<%s><%s>\n' "${a:0:2}" + printf '<%s><%s>\n' ${*:1:2} + printf '<%s><%s>\n' "${*:1:2}" + printf '<%s><%s>\n' ${@:1:2} + printf '<%s><%s>\n' "${@:1:2}" + printf '<%s><%s>\n' ${*:0:2} + printf '<%s><%s>\n' "${*:0:2}" + printf '<%s><%s>\n' ${@:0:2} + printf '<%s><%s>\n' "${@:0:2}" + } one two three +0:Bash-style offsets, quoted array +><one><two> +><one two><> +><one><two> +><one><two> +><on><> +><one><two> +><one two><> +><one><two> +><one><two> +><(anon)><one> +><(anon) one><> +><(anon)><one> +><(anon)><one> + printf "%n" '[0]' 1:Regression test for identifier test ?(eval):1: not an identifier: [0] @@ -2243,6 +2281,27 @@ F:We do not care what $OLDPWD is, as long as it does not cause an error F:As of this writing, var=$@ and var="$@" with null IFS have unspecified F:behavior, see http://austingroupbugs.net/view.php?id=888 + ( + IFS=$'\x80' + if [[ $IFS = $' \t\n\0' ]]; then + echo OK # if $'\x80' is illegal (e.g. Linux) + else # otherwise (e.g. macOS), it should work as a separator + s=$'foo\x80\bar' + [[ ${${=s}[1]} = foo ]] && echo OK + fi + ) +0D:reset IFS to default if it contains illegal character +>OK + + ( + unsetopt multibyte + IFS=$'\xc3\xa9' + s=$'foo\xc3bar\xa9boo' + echo ${${=s}[2]} + ) +0:eight bit chars in IFS should work if multibute option is off +>bar + () { setopt localoptions extendedglob [[ $- = [[:alnum:]]## ]] || print Failed 1 @@ -2270,6 +2329,13 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888 >x >y + a="string" + print ${(S)a//#%((#b)(*))/different} + print $match[1] +0:Fully anchored string must be fully searched +>different +>string + my_width=6 my_index=1 my_options=Option1 @@ -2695,3 +2761,76 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888 1:parameter expansion flags parsing error gives a clue ?(eval):1: error in flags near position 7 in '${(zZ+x+):-}' + slash='/' + print -r -- x${slash/'/'}y +0:(users/28784) substituting a single-quoted backslash, part #1: slash +>xy + + single_quote="'" + print -r -- x${single_quote/$'/'}y +0:(users/28784) substituting a single-quoted backslash, part #2: single quote +>x'y + + control="foobar" + print -r -- x${control/'bar'}y +0:(users/28784 inspired this) substituting a single-quoted backslash, part #3: control +>xfooy + + spacestring="string with spaces" + print ${spacestring:gs/[[:space:]]/ /} + print ${spacestring:g&} + print ${spacestring:gS/[[:space:]]//} + print ${spacestring:g&} +0:Different behaviour of :s and :S modifiers +>string with spaces +>string with spaces +>stringwithspaces +>stringwithspaces + + : ${(#X):-@} +1:${(#X)...}: bad math expression +?(eval):1: bad math expression: illegal character: @ + + echo a${(#):-@}z +0:${(#)...}: bad math expression +>az + + printf "a%sz\n" ${(#):-@} +0:${(#)...}: bad math expression, printf +>az + + a=( '1 +' '@' ) + : ${(#X)a} +1:${(#X)...}: array of bad math expressions +?(eval):2: bad math expression: operand expected at end of string + + printf "a%sz\n" ${(#)a} +0:${(#)...}: array of bad math expressions, printf +>az + + if [[ ! -o multibyte ]]; then + ZTST_skip='(#X) accepts any byte if multibyte is off' + else + : ${(#X):-0x80} + fi +1:${(#X)...}: out-of-range character +?(eval):4: character not in range + + [[ ${(#):-0x80} = $'\x80' ]] && echo OK +0:${(#)...}: out-of-range character +>OK + + a=( 0x80 0x81 ) + printf "%s\n" ${(#)a} | + while read x; do echo $(( #x )); done +0:${(#)...}: array of out-of-range characters +>128 +>129 + + if [[ ! -o multibyte ]]; then + ZTST_skip='(#X) accepts any byte if multibyte is off' + else + : ${(#X)a} + fi +1:${(#X)...}: array of out-of-range characters +?(eval):4: character not in range diff --git a/Test/D06subscript.ztst b/Test/D06subscript.ztst index adbd398c4..57cdc027c 100644 --- a/Test/D06subscript.ztst +++ b/Test/D06subscript.ztst @@ -294,3 +294,17 @@ F:Regression test for workers/42297 [[ ${a[$i]} = ${a[i]} ]] 0f:Math evaluation of commas in array subscripts F:In math, (($i)) should be the same as ((i)), see workers/47748. + + string=$'foo\0bar' + echo ${string[(pws:\0:)1]} +0:Word splitting by NUL +>foo + + string="a" + print ${string[(i)x]} + string="" + print ${string[(i)x]} +0:Can check off end of zero length string +F:Regression test for inconsistency of failed (i) on zero-length string +>2 +>1 diff --git a/Test/D07multibyte.ztst b/Test/D07multibyte.ztst index 7f046525a..413c4fe73 100644 --- a/Test/D07multibyte.ztst +++ b/Test/D07multibyte.ztst @@ -1,19 +1,7 @@ %prep -# Find a UTF-8 locale. - setopt multibyte -# Don't let LC_* override our choice of locale. - unset -m LC_\* - mb_ok= - langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8 - $(locale -a 2>/dev/null | egrep 'utf8|UTF-8')) - for LANG in $langs; do - if [[ é = ? ]]; then - mb_ok=1 - break; - fi - done - if [[ -z $mb_ok ]]; then + LANG=$(ZTST_find_UTF8) + if [[ -z $LANG ]]; then ZTST_unimplemented="no UTF-8 locale or multibyte mode is not implemented" else print -u $ZTST_fd Testing multibyte with locale $LANG @@ -224,6 +212,20 @@ >first >second + read -ed £ +0:read with multibyte delimiter where bytes of delimiter also occur in input +<one¤twoãthree£four +>one¤twoãthree + + read -ed $'\xa0' <<<$'first\xa0second' +0:read delimited by a byte that isn't a valid multibyte character +>first + + read -ed $'\xc2' +0:read delimited by a single byte terminates if the byte is part of a multibyte character +<one£two +>one + (IFS=« read -d » -A array print -l $array) @@ -347,6 +349,18 @@ 0:Multibyte characters in printf widths > főo +# TODO?: POSIX requires that printf should always compute width and +# precision of '%s' conversion in bytes, while zsh computes them in +# characters if multi-byte locale is in use. + ARGV0=sh $ZTST_testdir/../Src/zsh -c "printf '<%10s>\n' St$'\M-C\M-)'phane" +0f:POSIX: width in %s should be computed in bytes, not in characters +F:This is considered a bugfix in zsh +>< Stéphane> + + ARGV0=sh $ZTST_testdir/../Src/zsh -c "printf '<%7.5s>\n' St$'\M-C\M-)'phane" +0f:POSIX: precision should also be computed in bytes, not in characers +>< Stép> + # We ask for case-insensitive sorting here (and supply upper case # characters) so that we exercise the logic in the shell that lowers the # case of the string for case-insensitive sorting. @@ -615,3 +629,17 @@ F:support character sets outside the portable 7-bit range. 0:locale gets restored when locale parameters go out of scope (regression test for 45772) >❯ >❯ + + # Subshell for zmodload isolation + ( + zmodload zsh/stat + typeset -A sizes + touch 50150-é 50150-Ą + # Using +size solely in order to make it easier to write the expectations + zstat +size -A sizes -nor -- 50150-* + print -r -- 50150-Ą $sizes[50150-Ą] + print -r -- 50150-é $sizes[50150-é] + ) +0:(workers/50150) zsh/stat with Unicode and metafication +>50150-Ą 0 +>50150-é 0 diff --git a/Test/D08cmdsubst.ztst b/Test/D08cmdsubst.ztst index 04bf698aa..e415831a0 100644 --- a/Test/D08cmdsubst.ztst +++ b/Test/D08cmdsubst.ztst @@ -177,3 +177,11 @@ 0:Alias expansion needed in parsing substitutions >hi >bye + +# This should silently print a blank line; the original problem was +# a parse error as the last character of the unexpanded alias +# was erased, symptom: "command not found: W" + alias WI='while {false}' + eval 'echo $(WI blah)' +0:Aliases with braces in command substitution can cause havoc +> diff --git a/Test/D09brace.ztst b/Test/D09brace.ztst index 580ed430f..961947b67 100644 --- a/Test/D09brace.ztst +++ b/Test/D09brace.ztst @@ -116,3 +116,10 @@ print -r {1..10}{.. 0:Unmatched braces after matched braces are left alone. >1{.. 2{.. 3{.. 4{.. 5{.. 6{.. 7{.. 8{.. 9{.. 10{.. + + () { + setopt localoptions no_multibyte + echo -E {$'\x80'..$'\x81'} + } +0:range of 8bit chars, multibyte option unset +>\M-^@ \M-^A diff --git a/Test/D10nofork.ztst b/Test/D10nofork.ztst new file mode 100644 index 000000000..5bb10266f --- /dev/null +++ b/Test/D10nofork.ztst @@ -0,0 +1,515 @@ +# Tests for "nofork" command substitution. + +%prep + mkdir nofork.tmp + touch nofork.tmp/file{1,2}.txt + + purr() { print -r -- "$@" } + purl() { print -rl -- "$@" } + +%test + + REPLY=OUTER + purr ${| REPLY=INNER } $REPLY +0:Basic substitution and REPLY scoping +>INNER OUTER + + reply=(x OUTER x) + purl ${{reply} reply=(\{ INNER \})} $reply +0:Basic substitution, brace quoting, and array result +>{ +>INNER +>} +>{ +>INNER +>} + + () { + setopt localoptions ignorebraces + purl ${{reply} reply=({ INNER })} $reply + } +0:Basic substitution, ignorebraces, and array result +>{ +>INNER +>} +>{ +>INNER +>} + + purr ${| REPLY=first}:${| REPLY=second}:$REPLY +0:re-scoping of REPLY in one statement +>first:second:OUTER + + purr BEGIN${| printf -v REPLY '%s\n' one two three ; }END +0:Adjacent words +>BEGINone +>two +>three +>END + + purr "BEGIN${| printf -v REPLY '%s\n' one two three }END" +0:Adjacent words and quoting, part 1 +>BEGINone +>two +>three +>END + + purr BEGIN"${| printf -v REPLY '%s\n' one two three }"END +0:Adjacent words and quoting, part 2 +>BEGINone +>two +>three +>END + + purr BEGIN"${| + printf -v REPLY '%s\n'\ + one two three + }"END +0:Embedded newlines +>BEGINone +>two +>three +>END + + purr BEGIN"${| + printf -v REPLY $'%s\n' one two three + }"END +0:Embedded newlines and $'...' +>BEGINone +>two +>three +>END + + purl ${| print -v REPLY one word here; setopt shwordsplit } + purl ${| print -v REPLY three words here } + purl "and ${| print -v REPLY one word here }" + unsetopt shwordsplit +0:test word splitting on result +F:setting option inside is too late for that substitution +>one word here +>three +>words +>here +>and one word here + + ( + cd nofork.tmp + setopt globsubst + purr ${| REPLY=f* } + purr ${| REPLY=f? }* + unsetopt globsubst + purr ${| REPLY=f* } + purr ${| REPLY=f? }* + ) +1:globsubst on result +>file1.txt file2.txt +>file1.txt file2.txt +>f* +?(eval):8: no matches found: f?* + + purr ${| REPLY=$'trailing newlines remain\n\n' } +0:newline removal should not occur, part 1 +>trailing newlines remain +> +> + + purr ${ echo $'one trailing newline\nremoved\n\n\n' } +0:newline removal in ${ ... }, zsh mode +>one trailing newline +>removed +> +> +> + + () { + emulate -L ksh + purl ${ echo $'all trailing newlines\nremoved\n\n\n' } + purr "${ echo $'all trailing newlines\nremoved\n\n\n' }" + } +0:newline removal in ${ ... }, emulation mode, shwordsplit +>all +>trailing +>newlines +>removed +>all trailing newlines +>removed + + purr "${ echo $'no trailing newlines\nremoved\n\n\n' }" +0:newline removal should not occur, part 2 +>no trailing newlines +>removed +> +> +> +> + + () { + purr ${| REPLY=$* ; shift 2 } + purr $* + } these are arguments +0:access to context $argv +>these are arguments +>arguments + + purr ${:-${| REPLY=${REPLY:-buried}}} + purr ${:-"${| REPLY=${REPLY:-more buried}}"} +0:nofork inside parameter scope +>buried +>more buried + + : ${(e):-'${| REPLY=oops'} +1:unclosed braces are sometimes a bad substitution +F:This seems silly, but see A01grammar ${(e):-'${'} test +?(eval):1: bad substitution + + purr ${| REPLY=oops +1:other times lack of closing brace is merely unexpected +F:Why not use this error in the previous case as well? +?(eval):1: closing brace expected + +# Next tests check that the PS2 stack is properly managed on error + + purr ${| REPLY=${REPLY:-buried}}} +1:unbalanced braces, part 0 +?(eval):1: parse error near `}' + + purr ${:-${| REPLY=${REPLY:-buried}} +1:unbalanced braces, part 1 +?(eval):1: closing brace expected + + purr ${:-"${| REPLY=${REPLY:-more buried}"} +1:unbalanced braces, part 2 +?(eval):1: unmatched " + + purr ${:-"${| REPLY=${REPLY:-more buried"}}} +1:unbalanced braces, part 3 +?(eval):1: unmatched " + + purr ${:-"${| REPLY=${REPLY:-more buried}}}" +1:unbalanced braces, part 4 +?(eval):1: closing brace expected + +# Same tests with leading space (future-proofing) + + purr ${ purr ${REPLY:-buried}}} +1:unbalanced braces, part 0+ +?(eval):1: parse error near `}' + + purr ${:-${ purr ${REPLY:-buried}} +1:unbalanced braces, part 1+ +?(eval):1: closing brace expected + + purr ${:-"${ purr ${REPLY:-more buried}"} +1:unbalanced braces, part 2+ +?(eval):1: unmatched " + + purr ${:-"${ purr ${REPLY:-more buried"}}} +1:unbalanced braces, part 3+ +?(eval):1: unmatched " + + purr ${:-"${ purr ${REPLY:-more buried}}}" +1:unbalanced braces, part 4+ +?(eval):1: closing brace expected + + purr "${ purr STDOUT }" +0:capture stdout +>STDOUT +> + +# end PS2 stack tests + + purr $(purr outside ${| REPLY=inside }) + purr BEGIN$(purr outside ${| REPLY=inside })END + purr "BEGIN$(purr outside ${| REPLY=inside })END" + purr outside ${| REPLY=$(purr inside)} + purr "outside ${| REPLY=$(purr inside)}" +0:mixing with forking cmdsubst +>outside inside +>BEGINoutside insideEND +>BEGINoutside insideEND +>outside inside +>outside inside + + purr `purr outside ${| REPLY=inside }` + purr "outside `purr ${| REPLY=inside }`" + purr outside ${| REPLY=`purr inside`} + purr "outside ${| REPLY=`purr inside`}" + purr outside "`purr ${| REPLY="${:-inside}"}`" + purr "outside ${| REPLY=`purr ${:-inside}`}" +0:mixing with backticks +>outside inside +>outside inside +>outside inside +>outside inside +>outside inside +>outside inside + + purr ${| REPLY=$(( 9 + 17 )) } + purr $(( 9 + ${| REPLY=17 } )) +0:mixing with arithemetic +>26 +>26 + + unset reply + purl ${{reply} reply=(1 2 ${| REPLY=3 } 4) } + typeset -p reply +0:array behavior with global assignment +>1 +>2 +>3 +>4 +>typeset -g -a reply=( 1 2 3 4 ) + + unset outer + purr "${| + outer=OUTER + REPLY=INNER + return 7 + OUTER=NOTREACHED + } $outer $?" +0:return statement inside, part 1 +F:status of "print" should hide return +>INNER OUTER 7 + + unset outer + outer=${| REPLY=${| return 7}} +7:return status propages in assignment like $(...) + + unset outer + purr "${| + outer=OUTER + REPLY=INNER + return 7 + OUTER=NOTREACHED + } $outer $?" + print REACHED $OUTER +0:return statement inside, part 2 +>INNER OUTER 7 +>REACHED + + unset outer + purr "${| + # Localoptions needed to avoid breaking test harness? + # The setopt command affects surrounding context + setopt localoptions errreturn + outer=OUTER + REPLY=INNER + false + OUTER=NOTREACHED + } $outer $?" + print REACHED $OUTER ${options[errreturn]} +0:errreturn works inside and remains outside +>INNER OUTER 1 +>REACHED on + + ( + unset outer + purr "${| + outer=OUTER + REPLY=INNER + exit 7 + OUTER=NOTREACHED + } $outer $OUTER $?" + print NOT REACHED + ) +7:exit statement inside + + ( + unset outer + purr "${| + setopt errexit + outer=OUTER + REPLY=INNER + false + OUTER=NOTREACHED + } $outer $?" + print NOT REACHED + ) +1:errexit inside + + outer=GLOBAL + purr "${| + local outer=LOCAL + REPLY=INNER + } $outer $?" +0:local declaration inside +>INNER GLOBAL 0 + + unset zz + outer=GLOBAL + purr "${{zz} + local outer=LOCAL + zz=NONLOCAL + } $outer $?" + print $zz +0:local declaration, global assignment, part 1 +>NONLOCAL GLOBAL 0 +>NONLOCAL + + unset zz + outer=GLOBAL + purr "${${| + local outer=LOCAL + zz=NONLOCAL + }:-$zz} $outer $?" +0:local declaration, global assignment, part 2 (evaluation order) +>NONLOCAL GLOBAL 0 + + : ${| fn1() { () { print -v REPLY $'Defined Function' ;} ;} } + print "IN${| fn2() { () { print "${:-Second }${|fn1}" ;} ;} }OUT" + fn2 +0:function definition, brace nesting, quote nesting +>INOUT +>Second Defined Function + + <<-EOF + ${| REPLY=$'in a here document\n' } + EOF +0:here-document behavior +F:Fiddly here to get EOF past the test syntax +>in a here document +> + + <<<${| REPLY="in a here string" } +0:here-string behavior +>in a here string + + <<<"${ purr $'stdout as a here string' }" +0:another capture stdout +>stdout as a here string +> + + wrap=${| REPLY="REPLY in environment assignment" } typeset -p wrap + wrap=${ purr "capture in environment assignment" } typeset -p wrap +0:assignment context +>typeset -g wrap='REPLY in environment assignment' +>typeset -g wrap='capture in environment assignment' + +# Repeat return and exit tests with stdout capture + + purr "${ + print INNER + return 7 + } $?" +0:return statement inside, part 1+ +F:status of "print" should hide return +>INNER +> 7 + + unset outer + outer=${ return 7 } +7:return status propages in stdout capture + + unset outer + purr "${ + outer=OUTER + print INNER + return 7 + OUTER=NOTREACHED + } $outer $?" + print REACHED $OUTER +0:return statement inside, part 2+ +>INNER +> OUTER 7 +>REACHED + + unset outer + purr "${ + # Localoptions needed to avoid breaking test harness? + # The setopt command affects surrounding context + setopt localoptions errreturn + outer=OUTER + print INNER + false + OUTER=NOTREACHED + } $outer $?" + print REACHED $OUTER ${options[errreturn]} +0:errreturn works inside stdout capture +>INNER +> OUTER 1 +>REACHED on + + ( + unset outer + purr "${ + outer=OUTER + print INNER + exit 7 + OUTER=NOTREACHED + } $outer $OUTER $?" + print NOT REACHED + ) +7:exit statement inside stdout capture + + ( + unset outer + purr "${ + setopt errexit + outer=OUTER + print INNER + false + OUTER=NOTREACHED + } $outer $?" + print NOT REACHED + ) +1:errexit inside stdout capture + + setopt ignorebraces +0:dummy test to set option soon enough +F:must do this before evaluating the next test block + + purr ${| REPLY=${REPLY:-buried}}} +0:ignored braces, part 1 +>buried} + + # Global $REPLY still set from earlier test + purr "${ purr ${REPLY:+buried}}}" +0:ignored braces, part 2 +>buried +>} + + purr ${ { echo nested ;} } +0:ignored braces, part 3 +>nested + + purr ${ { echo nested } } DONE +1:ignored braces, part 4 +?(eval):3: parse error near `}' + + unsetopt ignorebraces + # "break" blocks function calls in outer loop + # Could use print, but that might get fixed + repeat 3 do purr ${ + for x in 1 2 3 4 + do (( x == 3 )) && break 2 + # use error output to confirm loop count + print -u 2 $x + done + } XX + done +0:break N propagates +?1 +?2 + + # Cannot "purr": break skips pending function calls + # Use "repeat" to avoid infinite loop on failure + repeat 3 do; echo ${|REPLY=x; break }; done + repeat 3 do; echo ${{x} x=y; break }; done + repeat 3 do; echo ${ echo z; break }; done +0:break after assignment completes the assignment +>x +>y +>z + + # Subshell because error exits + ( purr ${ echo ${unset?oops} } ) +1:error handling (without crashing) +*?*unset: oops + + purr ${ .zsh.cmdsubst=error } +1:reserved parameter name (without crashing) +*?*.zsh.cmdsubst: can't modify read-only parameter + +%clean + + unfunction purr purl diff --git a/Test/E01options.ztst b/Test/E01options.ztst index 72749e6ab..363846f5c 100644 --- a/Test/E01options.ztst +++ b/Test/E01options.ztst @@ -416,6 +416,9 @@ 1:NO_EXEC does recognize bad substitution syntax *?* bad substitution + (setopt noexec; : $(<nonexistentfile)) +0:NO_EXEC does not attempt to read files in $(<....) + setopt NO_eval_lineno eval 'print $LINENO' setopt eval_lineno @@ -558,12 +561,14 @@ foo=(one.c two.c three.c) print ${foo:s/#%(#b)t(*).c/T${match[1]}.X/} print *(#q:s/#(#b)tmp(*e)/'scrunchy${match[1]}'/) + print ${${:-"left[({})]over"}:fs/(\\{\\}|\\(\\)|\\[\\])//} unsetopt histsubstpattern 0:HIST_SUBST_PATTERN option >TINGcd TINGfile1 TINGfile2 homedir >THUMPcd THUMPfile1 THUMPfile2 >one.c Two.X Three.X >homedir scrunchyfile1 scrunchyfile2 tmpcd +>leftover setopt ignorebraces echo X{a,b}Y @@ -651,7 +656,7 @@ >noktarg1 >0 1 - showopt() { setopt | egrep 'localoptions|ksharrays'; } + showopt() { echo ${(FM)${(@f)"$(setopt)"}:#(localoptions|ksharrays)*} } f1() { setopt localoptions ksharrays; showopt } f2() { setopt ksharrays; showopt } setopt kshoptionprint @@ -752,6 +757,13 @@ >These are the contents of the file >These are the contents of the file +# Subshell to shield nullexec redirections + ( exec 3>&1 3>&2; print -u 3 some words ) + sleep 1 # let background multi thread catch up +0:regression test: multios with nullexec +>some words +?some words + # tried this with other things, but not on its own, so much. unsetopt nomatch print with nonomatch: flooble* @@ -1369,6 +1381,64 @@ F:Regression test for workers/41811 >1 >2 + pipefailfn1() { + emulate -L zsh + setopt errreturn pipefail + false | { true; } + print "Shouldn't get here, status $?" + } + pipefailfn1 +1:PIPE_FAIL causes ERR_RETURN with complex end of pipeline: braces + + pipefailfn2() { + emulate -L zsh + setopt errreturn pipefail + false | if true; then true; fi + print "Shouldn't get here, status $?" + } + pipefailfn2 || print Function failed, as expected +0:PIPE_FAIL causes ERR_RETURN with complex end of pipeline: if +>Function failed, as expected + + pipefailfn3() { + emulate -L zsh + setopt errreturn pipefail + false | while true; do break; done + print "Shouldn't get here, status $?" + } + pipefailfn3 || print Function failed, as expected +0:PIPE_FAIL causes ERR_RETURN with complex end of pipeline: while +>Function failed, as expected + + pipefailfn4() { + emulate -L zsh + setopt errreturn pipefail + false | true + print "Shouldn't get here, status $?" + } + pipefailfn4 +1:PIPE_FAIL causes ERR_RETURN in simple case + + pipefailfn5() { + emulate -L zsh + setopt errreturn pipefail + false | { true | true; } + print "Shouldn't get here, status $?" + } + pipefailfn5 || print Function failed as expected +0:PIPE_FAIL causes ERR_RETURN with nested successful pipe +>Function failed as expected + + pipefailfn6() { + emulate -L zsh + setopt errreturn pipefail + false | { false | true; } + print "Shouldn't get here, status $?" + } + pipefailfn6 || print Function failed as expected +0:PIPE_FAIL causes ERR_RETURN with nested failed pipe +>Function failed as expected + for (( i = 0; i < 10; i++ )); do () { print $i diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index 564afac29..6ac4d1732 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -149,18 +149,13 @@ F:This may also need to apply to multibyte whitespace ><1> ARGV0=sh $ZTST_testdir/../Src/zsh -c 'inf=42; echo $((inf))' -0f:All identifiers are variable references in POSIX arithmetic +0:All identifiers are variable references in POSIX arithmetic F:POSIX has neither math functions nor floating point >42 - ARGV0=sh $ZTST_testdir/../Src/zsh -c 'EUID=10; echo "$EUID"' + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'EUID=1; EUID=10; echo $EUID' -f:EUID is not a special variable >10 - ARGV0=sh $ZTST_testdir/../Src/zsh -c "printf '<%10s>\n' St$'\M-C\M-)'phane" -0f:Width of %s is computed in bytes not characters -F:This is considered a bugfix in zsh ->< Stéphane> - PPID=foo -f:PPID is not a readonly variable diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst new file mode 100644 index 000000000..bb0d11821 --- /dev/null +++ b/Test/K01nameref.ztst @@ -0,0 +1,893 @@ +# Tests for named references + +%prep + + # Required in order to declare an unset hash for substitution test + setopt TYPESET_TO_UNSET + + : ${ZTST_continue::=1} + +%test + + typeset -n ptr + typeset -n +0:minimal declaration +>ptr + + typeset -n ptr= + typeset -n +0:nameref placeholder +>ptr='' + + typeset -n ptr + ptr=var + typeset -n +0:assign nameref placeholder +>ptr=var + + unset ptr + typeset -n ptr + typeset -n ptr=var + typeset -n +0:assign placeholder with new typeset +>ptr=var + + typeset -n ptr1 + typeset -n ptr2=ptr1 + typeset -n +0:chain ending in placeholder +>ptr1 +>ptr2=ptr1 + + typeset ptr=var + typeset -n ptr + typeset -n +0:convert scalar to nameref +>ptr=var + + typeset -n ptr=var + typeset +n ptr + typeset -p ptr +0:remove nameref attribute +>typeset ptr=var + + typeset -n ptr=gvar + () { + local ptr + typeset -p ptr + } + typeset -p ptr +0:Local non-reference hides outside reference +>typeset ptr +>typeset -n ptr=gvar + + typeset -n ptr + typeset -t ptr + typeset -p ptr +0:change type of a placeholder +F:Other type changes are fatal errors, should this also be? +>typeset -n ptr='' +*?*ptr: can't change type of a named reference + + typeset -n ptr=var + typeset -t ptr + typeset -p ptr var +0:change type of referenced var +>typeset -n ptr=var +>typeset -t var + + typeset var + unset var + typeset -n ptr=var + typeset -t ptr + typeset -p ptr var +0:change type of unset referenced var +F:regression - at one time this incorrectly applied the tag to "ptr" +F:note this causes "var" to become set +>typeset -n ptr=var +>typeset -t var + + typeset -n ptr=var[2] + typeset -t ptr +1:change type of referenced array element +*?*var\[2\]: can't change type via subscript reference + + typeset -n ptr[1]=var +1:illegal nameref name +*?*reference variable cannot be an array + + typeset var=value + typeset -n ptr=var + print $ptr +0:basic nameref expansion, no braces +>value + + typeset var=value + typeset -n ptr=var + print ${ptr} +0:basic nameref expansion, braces +>value + + typeset var=(val1 val2) + typeset -n ptr=var + print $ptr +0:nameref array expansion +>val1 val2 + + typeset -A var=(val1 val2) + typeset -n ptr=var + print ${(kv)ptr} +0:nameref hash expansion +>val1 val2 + + typeset -n ptr=var + typeset var=value + typeset -p ptr var + ptr=newvalue + typeset -p ptr var +0:assign existing scalar via nameref +>typeset -n ptr=var +>typeset var=value +>typeset -n ptr=var +>typeset var=newvalue + + typeset -n ptr=var + typeset var=value + unset ptr + typeset -p var +0:unset via nameref + + typeset -n ptr=var + typeset var=value + unset -n ptr + typeset -p var ptr +0:unset of the nameref itself +F:If earlier tests change, might get "no such variable" here +>typeset var=value + + typeset -n ptr=var + typeset var=value + typeset -p ptr var + typeset ptr=newvalue + typeset -p ptr var +0:typeset existing scalar via nameref +>typeset -n ptr=var +>typeset var=value +>typeset -n ptr=var +>typeset var=newvalue + + typeset -n ptr=var + ptr=value + typeset -p var ptr +0:assign new scalar via nameref +>typeset -g var=value +>typeset -n ptr=var + + unset var + typeset -n ptr=var + typeset var=(val1 val2) + typeset -p ptr var + ptr=(new1 new2) + typeset -p ptr var +0:assign existing array via nameref +>typeset -n ptr=var +>typeset -a var=( val1 val2 ) +>typeset -n ptr=var +>typeset -a var=( new1 new2 ) + + typeset -p ptr ptr1 ptr2 var +1:check state of paramtab ONE +F:unexpected side-effects of previous tests +*?*no such variable: ptr +*?*no such variable: ptr1 +*?*no such variable: ptr2 +*?*no such variable: var + + typeset -n ptr=var + ptr=(val1 val2) + typeset -p var ptr +0:assign new array via nameref +>typeset -g -a var=( val1 val2 ) +>typeset -n ptr=var + + unset var + typeset -n ptr2=var + typeset -n ptr1=ptr2 + typeset var=value + typeset -p ptr1 ptr2 var + print $ptr1 +0:indirect nameref expansion +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset var=value +>value + + typeset -p ptr1 ptr2 var +1:check state of paramtab TWO +F:unexpected side-effects of previous tests +*?*no such variable: ptr1 +*?*no such variable: ptr2 +*?*no such variable: var + + typeset var + typeset -n ptr2=var + typeset -n ptr1=ptr2 + typeset ptr1=newvalue + typeset -p ptr1 ptr2 var +0:typeset existing parameter indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset var=newvalue + + typeset var=value + typeset -n ptr2=var + typeset -n ptr1=ptr2 + unset ptr1 + typeset -p ptr1 ptr2 var +0:unset parameter indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var + + typeset -n ptr2=var + typeset -n ptr1=ptr2 + typeset ptr1=newvalue + typeset -p ptr1 ptr2 var +0:typeset new parameter indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset var=newvalue + + unset var + typeset -n ptr2=var + typeset -n ptr1=ptr2 + typeset var=value + typeset -p ptr1 ptr2 var + ptr1=newvalue + typeset -p ptr1 ptr2 var +0:assign new parameter indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset var=value +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset var=newvalue + + typeset -p ptr1 ptr2 var +1:check state of paramtab THREE +F:unexpected side-effects of previous tests +*?*no such variable: ptr1 +*?*no such variable: ptr2 +*?*no such variable: var + + typeset -a var + typeset -n ptr2=var + typeset -n ptr1=ptr2 + typeset ptr1=(val1 val2) + typeset -p ptr1 ptr2 var +0:typeset existing array indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset -a var=( val1 val2 ) + + typeset -n ptr2=var + typeset -n ptr1=ptr2 + typeset ptr1=(val1 val2) + typeset -p ptr1 ptr2 var +0:typeset new array indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset -a var=( val1 val2 ) + + typeset -p ptr1 ptr2 +1:check state of paramtab FOUR +F:unexpected side-effects of previous tests +*?*no such variable: ptr1 +*?*no such variable: ptr2 + + unset var + typeset -n ptr2=var + typeset -n ptr1=ptr2 + ptr1=(val1 val2) + typeset -p ptr1 ptr2 var +0:assign new array indirectly +>typeset -n ptr1=ptr2 +>typeset -n ptr2=var +>typeset -g -a var=( val1 val2 ) + + typeset -n ptr1=ptr2 + typeset -n ptr2=ptr1 +1:direct nameref loop not allowed +*?*invalid self reference + + unset var + typeset -gn ptr1=var + typeset -p ptr1 +0:global reference to unset var +>typeset -g -n ptr1=var + + unset -n ptr1 + typeset -gn ptr1 + typeset -p ptr1 + ptr1=ptr1 +1:global direct reference +>typeset -g -n ptr1 +*?*invalid self reference + + typeset -n ptr1=ptr2 + typeset -n ptr2=ptr3 + typeset -n ptr3=ptr1 +1:indirect nameref loop not allowed +*?*invalid self reference + + typeset -n ptr1 ptr2 + ptr1=ptr2 + ptr2=ptr1 +1:looping assignment not allowed +*?*invalid self reference + + unset -n ptr2 + typeset -n ptr2='path[2]' + print -r -- $ptr2 +0q:nameref to array element, no braces +>${path[2]} + + unset -n ptr2 + typeset -n ptr2='path[2]' + print -r -- ${ptr2} +0q:nameref to array element, with braces +>${path[2]} + + unset -n ptr1 + typeset -A hash=(x MISS y HIT) + typeset -n ptr1='hash[y]' + print -r -- $ptr1 +0:nameref to hash element, no braces +>HIT + + unset -n ptr1 + typeset -A hash=(x MISS y HIT) + typeset -n ptr1='hash[y]' + print -r -- ${ptr1} +0:nameref to hash element, with braces +>HIT + + unset -n ptr2 + typeset -a ary=(1 2) + typeset -n ptr2='ary[2]' + ptr2=TWO + typeset -p ary +0:assign array element by nameref +>typeset -a ary=( 1 TWO ) + + unset -n ptr2 + typeset -n ptr2='ary[2]' + ptr2=TWO + typeset -p ary +0f:create array element by nameref +F:ksh93 does not implement this either +>typeset -a ary=( '' TWO ) + + unset -n ptr1 + typeset -A hash=(x MISS y MISS) + typeset -n ptr1='hash[y]' + ptr1=HIT + typeset -p hash +0:assign to hash element by nameref +>typeset -A hash=( [x]=MISS [y]=HIT ) + + unset -n ptr1 + typeset -A hash + typeset -n ptr1='hash[y]' + ptr1=HIT + typeset -p hash +0f:create hash by element nameref +F:ksh93 does not implement this either +>typeset -A hash=( [y]=HIT ) + + unset -n ptr1 + typeset -n ptr1='not[2]good' +1:invalid nameref +*?*invalid variable name: not\[2\]good + + unset -n ptr1 + unset hash + typeset -A hash + typeset -n ptr1='hash[y]' + print ${ptr1::=HIT} + typeset -p ptr1 hash +0f:create hash by element substitution +>HIT +>typeset -n ptr1='hash[y]' +>typeset -A hash=( [y]=HIT ) + + unset -n ptr + unset gval + typeset -n ptr=gval + gval=global + () { local gval=local; print $ptr; typeset -p ptr gval } +0:up-reference part 1 +>global +>typeset -g -n ptr=gval +>typeset gval=local + + typeset -p ptr ptr1 ptr2 val +1:check state of paramtab FIVE +F:unexpected side-effects of previous tests +*?*no such variable: ptr +*?*no such variable: ptr1 +*?*no such variable: ptr2 +*?*no such variable: val + + unset gval + typeset -n ptr1=gval + typeset gval + () { typeset gval=local; ptr1=global } + typeset -p ptr1 gval +0:up-reference assignment part 1 +F:All tests run inside a function, so "typeset gval" creates a local; +F:if that were omitted, ptr1= assignment would create a true global +F:and the output below would change to "typeset -g gval=global" +>typeset -n ptr1=gval +>typeset gval=global + + typeset -p ptr ptr1 ptr2 val gval +1:check state of paramtab SIX +F:unexpected side-effects of previous tests +*?*no such variable: ptr +*?*no such variable: ptr1 +*?*no such variable: ptr2 +*?*no such variable: val +*?*no such variable: gval + + typeset gval=global + () { + typeset -n ptr=gval + local gval=local + print $ptr + } + typeset -p ptr gval +1:up-reference part 2 +>global +*?*no such variable: ptr +>typeset gval=global + + typeset -n ptr=gval + () { + local lval=local + typeset -n ptr=lval + ptr=LOCAL + typeset -p lval gval ptr + } + typeset -p ptr +0:localized namerefs hide global namerefs +*?*no such variable: gval +>typeset lval=LOCAL +>typeset -n ptr=lval +>typeset -n ptr=gval + + typeset -A var=(myself outside) + () { + typeset -n myself=var[myself] + local -h var + print -r -- $myself + typeset -p var + } +0:up-reference part 3, hidden global +>outside +>typeset -h var + + () { + typeset notdef + unset notdef + () { + typeset -n ptr=notdef + ptr=(DEFINED) + } + typeset -p notdef + } +0:up-reference part 4, unset local and type change +>typeset -a notdef=( DEFINED ) + + () { + typeset -n ptr1=ptr2 + typeset -n ptr2 + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr2 +1:up-reference part 5, stacked namerefs, end not in scope +>typeset -n ptr1=ptr2 +>typeset -n ptr2 +>ptr1=ptr2 +>ptr2=val +>ptr1=LOCAL +>ptr2=LOCAL +>typeset -n ptr1=ptr2 +>typeset -n ptr2=val +*?*no such variable: ptr2 + + typeset ptr2 + () { + typeset -n ptr1=ptr2 + typeset -n ptr2 + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr2 +0:up-reference part 6, stacked namerefs, end is in scope +F:Same test, should part 5 output look like this? +>typeset -n ptr1=ptr2 +>typeset -n ptr2 +>ptr1=ptr2 +>ptr2 +>ptr1=val +>ptr2= +>typeset -n ptr1=ptr2 +>typeset -n ptr2 +>typeset ptr2=val + + () { + () { + local var + typeset -nu ptr1=var + ptr1=outer && print -u2 assignment expected to fail + typeset -n ptr2=var + ptr2=inner + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p var + } + typeset -p var +1:up-reference part 7, upscope namerefs, end not in scope +>ptr1=var +>ptr2=var +>ptr1= +>ptr2=inner +*?*typeset*: no such variable: var +*?*typeset*: no such variable: var + + typeset var + () { + () { + local var + typeset -nu ptr1=var + ptr1=outer || print -u2 assignment expected to succeed + typeset -n ptr2=var + ptr2=inner + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p var + } + typeset -p var +0:up-reference part 8, upscope namerefs, end in scope +>ptr1=var +>ptr2=var +>ptr1=outer +>ptr2=inner +>typeset -g var=outer +>typeset var=outer + + if zmodload zsh/parameter; then + () { + zmodload -u zsh/parameter + typeset -n myself=parameters[myself] + local -h parameters + print -r -- $myself + typeset -p parameters + } + else ZTST_skip='Cannot zmodload zsh/parameter, skipping autoload test' + fi +0:up-reference part 9, autoloading with hidden special +>nameref-local-nameref-local +>typeset -h parameters + + if [[ $options[typesettounset] != on ]]; then + ZTST_skip='Ignoring zmodload bug that resets TYPESET_TO_UNSET' + setopt typesettounset + fi +0:options reloaded +F:Checking for a bug in zmodload that affects later tests + + typeset ptr2=var2 + typeset var2=GLOBAL + () { + typeset -n ptr1=ptr2 + typeset ptr2=var1 + typeset var1=VAR1 + typeset var2=VAR2 + print -r -- ${(P)ptr1} + } +0:Order of evaluation with ${(P)...} +>VAR2 + + ary=(one two three four) + typeset -n ptr=ary + print -r ${(j.:.)ptr//o/0} +0:expansion flags and string replacement +>0ne:tw0:three:f0ur + + var=value + typeset -n ptr=var + myscalar=ptr + echo ${(P)myscalar} +0:named references with (P), as ${(P)name_of_nameref} +>value + + var=value + myscalar=var + typeset -n ptr=myscalar + echo ${(P)ptr} +0:named references with (P), as ${(P)nameref} +>value + + ary=( 'bry[1]' 'bry[2]' ) + bry=( lorem ipsum ) + typeset -n ptr='ary[2]' + print -r -- ${ptr} + print -r -- ${(P)ptr} +0:named references with (P), array element to array element +>bry[2] +>ipsum + + unset -n ref + unset var + typeset -n ref=var + typeset var=GLOBAL + () { + typeset -n ref=$1 + print -r $ref + ref=RESET + typeset -p ref var + } ref + typeset -p ref var +0:local reference points to same-name global reference, part 1 +>GLOBAL +>typeset -n ref=ref +>typeset -g var=RESET +>typeset -n ref=var +>typeset var=RESET + + unset -n ref + unset var + typeset -n ref=var + () { + typeset -n ref=$1 + print -r $ref + ref=RESET + typeset -p ref var + } ref + typeset -p ref var +0:local reference points to same-name global reference, part 2 +> +>typeset -n ref=ref +>typeset -g var=RESET +>typeset -n ref=var +>typeset -g var=RESET + + unset -n ref + unset one + typeset -n ref + typeset one=ONE + for ref in one ref two; do print -r $ref; done +1:for-loop variable is a reference, part 1 +>ONE +*?*ref: invalid self reference + + unset -n ref + unset one + typeset -n ref + () { + typeset one=ONE + for ref in one ref two; do print -r ${(t)ref}; done + } +1:for-loop variable is a reference, part 2 +>scalar-local +*?*ref: invalid self reference + + unset -n ref + unset one var + typeset -n ref=var + () { + typeset one=ONE + typeset -n ref=ref + for ref in one ref two; do + typeset -p ref + print -r $ref + done + typeset -p ref + } + typeset -p ref +0:for-loop variable is a reference, part 3 +>typeset -n ref=one +>ONE +>typeset -n ref=ref +> +>typeset -n ref=two +> +>typeset -n ref=two +>typeset -n ref=var + + typeset -g .K01.scalar='RW' + typeset -gA .K01.assoc=(x y) + typeset -ga .K01.array=(z) + typeset -gi .K01.integer=0 + typeset -gE .K01.double=0.0 + typeset -gF .K01.float=0.0 + typeset -gr .K01.readonly='RO' + typeset -n gref + for gref in ARGC .K01.{scalar,assoc,array,integer,double,float,readonly} + do + { unset gref } always { TRY_BLOCK_ERROR=0 } + done + typeset -p .K01.{scalar,assoc,array,integer,double,float,readonly} + unset .K01.{scalar,assoc,array,integer,double,float} +0:unset various types via nameref, including a readonly special +>typeset -g .K01.scalar +>typeset -g -A .K01.assoc +>typeset -g -a .K01.array +>typeset -g -i .K01.integer +>typeset -g -E .K01.double +>typeset -g -F .K01.float +>typeset -g -r .K01.readonly=RO +*?*read-only variable: ARGC +*?*read-only variable: .K01.readonly + + unset -n ref + unset one + typeset -n ref + () { + setopt localoptions warn_nested_var + typeset one=ONE + for ref in one two; do print -r ${(t)ref}; done + typeset -n ref + for ref in one two; do print -r ${(t)ref}; done + } +0:for-loop variable is a reference, part 4, warnings +>scalar-local +> +>scalar-local +> +*?*reference ref*to local variable one + + unset -n ref + typeset -n ref + () { + setopt localoptions warn_nested_var + typeset inner + ref=inner + } + typeset -p ref +0:Global variable is a reference, warning +>typeset -n ref=inner +*?*reference ref*to local variable inner + + typeset -n ptr='ary[$(echo 2)]' + typeset -a ary=(one two three) + print $ptr +1:attempt deferred command substitution in subscript +F:runs in `setopt noexec` so $(...) returns nothing +*?*bad math expression: empty string + + unset -n ref + typeset -n ref=GLOBAL + () { + typeset -gn ref=RESET + } + typeset -p ref +0:reset global reference within function +>typeset -n ref=RESET + + unset -n ref + typeset -rn ref=RO + typeset -p ref + (typeset -n ref=RW) + print status: $? expected: 1 + typeset +r -n ref + typeset -p ref + typeset -r +n ref + typeset -p ref + (typeset -rn ref) + print status: $? expected: 1 + typeset +r -n ref=RW # Assignment occurs after type change, + typeset -p ref RO # so RO=RW here. Potentially confusing. + typeset -r -n ref=RX # No type change, so referent changes ... + typeset -p ref RO # ... and previous refererent does not. + typeset +rn ref=RW # Here ref=RW, again type changed first. + typeset -p ref +0:add and remove readonly attribute with references +>typeset -rn ref=RO +*?*: ref: read-only reference +>status: 1 expected: 1 +>typeset -n ref=RO +>typeset -r ref=RO +*?*: ref: read-only variable +>status: 1 expected: 1 +>typeset -n ref=RO +>typeset -g RO=RW +>typeset -rn ref=RX +>typeset -g RO=RW +>typeset ref=RW + + () { + typeset -n r1 r2= + typeset -p r1 r2 + print -- ${(!)r1-unset} + print -- ${+r1} + typeset -p r1 + } +0:unset nameref remains unset when resolved +F:relies on global TYPESET_TO_UNSET in %prep +>typeset -n r1 +>typeset -n r2='' +>unset +>0 +>typeset -n r1 + + bar=xx + typeset -n foo=bar + () { + typeset -n foo; foo=zz + foo=zz || print -u2 foo: assignment failed + print $bar $zz + } + () { typeset -n foo; foo=zz; local zz; foo=zz; print $bar $zz } +0:regression: local nameref may not in-scope a global parameter +F:previously this could create an infinite recursion and crash +>xx +>xx zz +*?*foo: assignment failed + + typeset -nm foo=bar +1:create nameref by pattern match not allowed +*?*typeset:1: invalid reference + +# +# The following tests are run in interactive mode, using PS1 as an +# assignable special with side-effects. This crashed at one time. +# + + # Note bypassing TYPESET_TO_UNSET here + $ZTST_testdir/../Src/zsh -fis <<<$' + typeset -n p=PS1 + () { + typeset -p p + local p + typeset -p p + p=xx + typeset -p p + } + ' +0:regression: assign to local that shadows global named reference +>typeset -g -n p=PS1 +>typeset p='' +>typeset p=xx +*?* + + # Note bypassing TYPESET_TO_UNSET here + $ZTST_testdir/../Src/zsh -fis <<<$' + () { + typeset p=PS1 + typeset -n p + p=zz + } + typeset -p PS1 + ' +0:regression - converting a string into a named reference +>typeset PS1=zz +*?* + +%clean diff --git a/Test/K02parameter.ztst b/Test/K02parameter.ztst new file mode 100644 index 000000000..0b1a8dd4a --- /dev/null +++ b/Test/K02parameter.ztst @@ -0,0 +1,154 @@ +# Test parameter expansion with namespace syntax +# (heavily borrowed from D04parameter.ztst) + +%prep + +%test + + .k02.foo='the first parameter' + .k02.bar='the second parameter' + print -l $.k02.foo ${.k02.bar} +0:Basic scalars with namespace +F:Braces are required +>$.k02.foo +>the second parameter + + typeset .k02.bar='the second parameter' + print -l ${.k02.bar} +0:Scalar but with typeset +>the second parameter + + .k02.array1=(the first array) + .k02.array2=(the second array) + print -l $.k02.array1 ${.k02.array2} +0:Basic arrays with namespace +>$.k02.array1 +>the +>second +>array + + typeset -a .k02.array2=(the second array) + print -l ${.k02.array2} +0:Array but with typeset +>the +>second +>array + + setopt ksharrays + print -l ${.k02.array2} + unsetopt ksharrays +0:Basic ksharray with namespace +>the + + setopt shwordsplit + print -l ${.k02.foo} ${==.k02.bar} + unsetopt shwordsplit +0:Basic shwordsplit with namespace +>the +>first +>parameter +>the second parameter + + print ${+.k02.foo} ${+.k02.notappearinginthistest} +0:$+... and namespace +>1 0 + + .k02.x=() + print ${+.k02.x} ${+.k02.x[1]} ${+.k02.x[(r)foo]} ${+.k02.x[(r)bar]} + .k02.x=(foo) + print ${+.k02.x} ${+.k02.x[1]} ${+.k02.x[(r)foo]} ${+.k02.x[(r)bar]} +0:$+... with arrays and namespace +>1 0 0 0 +>1 1 1 0 + + # See D04 for complete explanation. + # For K02 we're just testing that flag syntax works. + .k02.foo='<five> {six} (seven) >eight< }nine{ |forty-two| $many$ )ten( more' + .k02.array=(${(z).k02.foo}) + print -l ${(Q).k02.array} +0:${(z)...} and ${(Q)...} for some hard to parse cases +>< +>five +>> +>{six} +>( +>seven +>) +>> +>eight +>< +>}nine{ +>| +>forty-two +>| +>$many$ +>) +>ten( more + + .k02.array=(characters in an array) + print ${(c)#.k02.array} +0:${(c)#...} +>22 + + () { + typeset -n .k02.ref=.k02.array + emulate -L ksh + print -l ${!.k02.ref} ${(!).k02.ref} ${.k02.ref} + } +0:namerefs with namespaces +>.k02.array +>.k02.array +>characters + + k.=empty + k.2=test + print ${k.} ${k.2} +0:Parse without leading dot (future proofing) +>empty test + + .k=OK + print ${.k} +0:Bare namespace is usable (ksh compatibility) +>OK + + .k.=empty +1:Namespace must precede an identifier, assignment +?(eval):1: not an identifier: .k. + + typeset .k.=empty +1:Namespace must precede an identifier, typeset +?(eval):typeset:1: not valid in this context: .k. + + print ${.k.} +1:Namespace must precede an identifier, reference +?(eval):1: bad substitution + + .2.b=not +1:Namespace identifier must not begin with a digit, assignment +?(eval):1: not an identifier: .2.b + + typeset .2.b=not +1:Namespace identifier must not begin with a digit, typeset +?(eval):typeset:1: not valid in this context: .2.b + + print ${.2.b} +1:Namespace identifier must not begin with a digit, reference +?(eval):1: bad substitution + + .not.2b=question +1:Identifier starting with a digit must be all digits, assignment +?(eval):1: not an identifier: .not.2b + + typeset .not.2b=question +1:Identifier starting with a digit must be all digits, typeset +?(eval):typeset:1: not valid in this context: .not.2b + + print ${.not.2b} +1:Identifier starting with a digit must be all digits, reference +?(eval):1: bad substitution + + integer .var.d=0 + float .var.f=.2 + print $((.var.x = ++.var.d - -.var.f)) +0:Namespaces in math context +>1.2 diff --git a/Test/P01privileged.ztst b/Test/P01privileged.ztst index 7c4a1be35..5d45c1a4c 100644 --- a/Test/P01privileged.ztst +++ b/Test/P01privileged.ztst @@ -26,23 +26,23 @@ %prep - # Mind your empty lines here. The logic in this %prep section is somewhat - # complex compared to most others; to avoid lots of nested/duplicated - # conditions we need to make sure that this all gets executed as a single - # function from which we can return early + # If ZTST_unimplemented is set to non-null in a chunk then all the + # remaining chunks (and all of %test and %clean sections) will be skipped. [[ $EUID == 0 || -n $ZSH_TEST_UNPRIVILEGED_UID$ZSH_TEST_UNPRIVILEGED_GID ]] || { ZTST_unimplemented='PRIVILEGED tests require super-user privileges (or env var)' - return 1 + return 0 } + (( $+commands[perl] )) || { # @todo Eliminate this dependency with a C wrapper? ZTST_unimplemented='PRIVILEGED tests require Perl' - return 1 + return 0 } + grep -qE '#define HAVE_SETRES?UID' $ZTST_testdir/../config.h || { ZTST_unimplemented='PRIVILEGED tests require setreuid()/setresuid()' - return 1 + return 0 } - # + ruid= euid= rgid= egid= # if [[ -n $ZSH_TEST_UNPRIVILEGED_UID ]]; then @@ -76,13 +76,14 @@ # [[ -n $ruid && -n $euid ]] || { ZTST_unimplemented='PRIVILEGED tests require unprivileged UID:EUID' - return 1 + return 0 } + [[ -n $rgid || -n $egid ]] || { ZTST_unimplemented='PRIVILEGED tests require unprivileged GID:EGID' - return 1 + return 0 } - # + print -ru$ZTST_fd \ "Using unprivileged UID $ruid, EUID $euid, GID $rgid, EGID $egid" # diff --git a/Test/README b/Test/README index 726d68e72..b9d393d7c 100644 --- a/Test/README +++ b/Test/README @@ -6,6 +6,7 @@ scripts names: C: shell commands with special syntax D: substititution E: options + K: features adopted from ksh P: privileged (needs super-user privileges) V: modules W: builtin interactive commands and constructs @@ -20,6 +21,13 @@ more information about the tests being performed with ZTST_verbose=1 make check (`test' is equivalent to `check') or change 1 to 2 for even more detail. +A test file is usually aborted on the first error. To continue to the +end, run with + ZTST_continue=1 make check +This can usefully be combined with ZTST_verbose. The test is always +aborted on a syntax error as in that case it is not obvoius how to +continue. + Individual or groups of tests can be performed with make TESTNUM=C02 check or diff --git a/Test/V07pcre.ztst b/Test/V07pcre.ztst index c9c844d2a..b8cd31c96 100644 --- a/Test/V07pcre.ztst +++ b/Test/V07pcre.ztst @@ -6,20 +6,8 @@ return 0 fi setopt rematch_pcre -# Find a UTF-8 locale. - setopt multibyte -# Don't let LC_* override our choice of locale. - unset -m LC_\* - mb_ok= - langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8 - $(locale -a 2>/dev/null | egrep 'utf8|UTF-8')) - for LANG in $langs; do - if [[ é = ? ]]; then - mb_ok=1 - break; - fi - done - if [[ -z $mb_ok ]]; then + LANG=$(ZTST_find_UTF8) + if [[ -z $LANG ]]; then ZTST_unimplemented="no UTF-8 locale or multibyte mode is not implemented" else print -u $ZTST_fd Testing PCRE multibyte with locale $LANG @@ -120,6 +108,11 @@ >0 xo→t →t >0 Xo→t →t + [[ foo =~ (pre)?f(o*)(opt(i)onal)?(y)* ]] + typeset -p match +0:Empty string for optional captures that don't match +>typeset -g -a match=( '' oo '' '' '' ) + string="The following zip codes: 78884 90210 99513" pcre_compile -m "\d{5}" pcre_match -b -- $string && print "$MATCH; ZPCRE_OP: $ZPCRE_OP" @@ -129,12 +122,17 @@ >78884; ZPCRE_OP: 25 30 >90210; ZPCRE_OP: 31 36 -# Embedded NULs allowed in plaintext, but not in RE (although \0 as two-chars allowed) +# Embedded NULs allowed in plaintext, in RE, pcre supports \0 as two-chars [[ $'a\0bc\0d' =~ '^(a\0.)(.+)$' ]] print "${#MATCH}; ${#match[1]}; ${#match[2]}" 0:ensure ASCII NUL passes in and out of matched plaintext >6; 3; 3 +# PCRE2 supports NULs also in the RE + [[ $'a\0b\0c' =~ $'^(.\0)+' ]] && print "${#MATCH}; ${#match[1]}" +0:ensure ASCII NUL works also in the regex +>4; 2 + # Ensure the long-form infix operator works [[ foo -pcre-match ^f..$ ]] print $? @@ -174,3 +172,37 @@ echo $match[2] ) 0:regression for segmentation fault, workers/38307 >test + + LANG_SAVE=$LANG + [[ é =~ '^.\z' ]]; echo $? + LANG=C + [[ é =~ '^..\z' ]]; echo $? + LANG=$LANG_SAVE + [[ é =~ '^.\z' ]]; echo $? +0:switch between C/UTF-8 locales +>0 +>0 +>0 + + [[ abc =~ 'a(d*)bc' ]] && print "$#MATCH; $#match; ${#match[1]}" +0:empty capture +>3; 1; 0 + + [[ category/name-12345 =~ '(?x)^ + (?<category> [^/]* ) / + (?<package> + (?<name> \w+ ) - + (?<version> \d+ ))$' ]] + typeset -p1 .pcre.match +0:named captures +>typeset -g -A .pcre.match=( +> [category]=category +> [name]=name +> [package]=name-12345 +> [version]=12345 +>) + + pcre_compile 'cat(er(pillar)?)?' + pcre_match -d 'the caterpillar catchment' && print $match +0:pcre_match -d +>caterpillar cater cat diff --git a/Test/V10private.ztst b/Test/V10private.ztst index 03e8259d5..26004a2dc 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -10,6 +10,8 @@ sed -e 's,# test_zsh_param_private,zmodload zsh/param/private,' < $ZTST_srcdir/B02typeset.ztst > private.TMP/B02 fi + setopt TYPESET_TO_UNSET + %test (zmodload -u zsh/param/private && zmodload zsh/param/private) @@ -26,7 +28,7 @@ print $scalar_test 0:basic scope hiding >toplevel ->local scalar_test +>local hide scalar_test >0 >toplevel @@ -52,7 +54,7 @@ print $+unset_test 0:variable defined only in scope >0 ->local unset_test +>local hide unset_test >setme >0 @@ -68,7 +70,7 @@ } print $array_test 0:nested scope with different type, correctly restored ->local array_test +>local hide array_test >in function >top level @@ -111,19 +113,19 @@ typeset -a hash_test=(top level) typeset -p hash_test inner () { - private -p hash_test + typeset -p hash_test print ${(t)hash_test} ${(kv)hash_test} } outer () { local -PA hash_test=(in function) - typeset -p hash_test + private + hash_test inner } outer print ${(kv)hash_test} 0:private hides value from surrounding scope in nested scope >typeset -a hash_test=( top level ) ->typeset -A hash_test=( [in]=function ) +>hash_test=( [in]=function ) >typeset -g -a hash_test=( top level ) >array-local top level >top level @@ -246,7 +248,7 @@ F:note "typeset" rather than "private" in output from outer 1:privates are not visible in anonymous functions, part 3 >X top level >array_test not set -?(anon):4: array_test: attempt to assign private in nested scope +?(anon):4: array_test: can't modify read-only parameter F:future revision will create a global with this assignment typeset -a array_test @@ -299,6 +301,256 @@ F:future revision will create a global with this assignment *>* *>* + typeset top=TOP + () { + local -P -n test=top + print $top + () { print UP: $test } + } +0:nameref can be declared private +>TOP +>UP: + + () { + typeset -a ary + local -P -n ref=ary + { + (){ + ref=XX # Should be an error + typeset -p ary ref + } + } always { + TRY_BLOCK_ERROR=0 + typeset -p ary ref + } + } + typeset -p ary +1:assignment to private nameref in wrong scope, part 1 +>typeset -a ary +>typeset -hn ref=ary +*?*ref: can't modify read-only parameter +*?*no such variable: ary + + () { + typeset -a ary + local -P -n ref=ary + { + (){ + typeset ref=XX # Should create a local + typeset -p ary ref + } + } always { + TRY_BLOCK_ERROR=0 + typeset -p ary ref + } + } + typeset -p ary +1:assignment to private nameref in wrong scope, part 2 +>typeset -g -a ary +>typeset ref=XX +>typeset -a ary +>typeset -hn ref=ary +*?*no such variable: ary + + () { + typeset -n ptr1=ptr2 + private -n ptr2 # TYPESET_TO_UNSET makes this not a "placeholder" + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val # Test dies here as ptr2 is private and unset + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr2 +1:up-reference for private namerefs, end unset and not in scope +F:See K01nameref.ztst up-reference part 5 +F:Here ptr1 finds private ptr2 by scope mismatch +>typeset -n ptr1=ptr2 +>typeset -hn ptr2 +*?*read-only variable: ptr2 + + () { + typeset -n ptr1=ptr2 + private -n ptr2= # Assignment makes this a placeholder, not unset + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val || print -u2 ptr1: assignment failed + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr2 +1:up-reference for private namerefs, end not in scope +F:See K01nameref.ztst up-reference part 5 +F:Here ptr1 finds private ptr2 by scope mismatch +>typeset -n ptr1=ptr2 +>typeset -hn ptr2='' +>ptr1=ptr2 +>ptr1= +>ptr2= +>typeset -n ptr1=ptr2 +>typeset -hn ptr2='' +*?*ptr1: assignment failed +*?*no such variable: ptr2 + + typeset ptr2 + () { + typeset -n ptr1=ptr2 + private -n ptr2 # Set/unset is irrelevant, not referenced + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr2 +0:up-reference for private namerefs, end is in scope +F:See K01typeset.ztst up-reference part 5 +F:Here ptr1 points to global ptr2 so assignment succeeds +>typeset -n ptr1=ptr2 +>typeset -hn ptr2 +>ptr1=ptr2 +>ptr2=val +>ptr1=val +>ptr2=val +>typeset -n ptr1=ptr2 +>typeset -hn ptr2 +>typeset ptr2=val + + () { + setopt localoptions errreturn + private -n ptr2 + typeset -n ptr1=ptr2 + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val + typeset -n + printf "v %s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr1 ptr2 +1:up-reference for private namerefs, end is in scope but private +F:Should we allow "public" namerefs to private parameters? +*?*ptr2: invalid reference +*?*no such variable: ptr1 +*?*no such variable: ptr2 + + () { + private x=1 + unset x + x=2 + } +0:regression test for unset private + + () { + private x=1 + unset x + private x=2 + print $x + } +0:private may be called twice +>2 + + () { + private x=1 + private -a x + print $x + } +1:private may not change parameter type +?(anon):private:2: can't change type of private param: x + + () { + private fd1 fd2 + exec {fd1}>&1 + print OK + () { exec {fd2}>&2 } + print BAD $fd2 + } +1:redirection cannot assign private in wrong scope +F:Better if caught in checkclobberparam() but exec.c doesn't know scope +>OK +?(anon): fd2: can't modify read-only parameter + + () { + private z=outer + print ${(t)z} $z + print ${| + print ${(t)z} $z + REPLY=$z + } + } +0:nofork may read private in calling function +>scalar-local-hide-special outer +>scalar-local-hide-special outer +>outer + + () { + private z=outer + print ${(t)z} $z + print ${| REPLY=${{z} z=nofork} } + print ${(t)z} $z + } +0:nofork may write to private in calling function +>scalar-local-hide-special outer +>nofork +>scalar-local-hide-special nofork + + () { + local q=outer + print ${| + private q=nofork + REPLY=${| REPLY=$q} + } + } +0:nofork cannot see private in surrounding nofork +>outer + + () { + private z=outer + print ${(t)z} $z + print ${{z} + private q + z=${{q} q=nofork} + } + print ${(t)z} $z + } +1:nofork may not change private in surrounding nofork +>scalar-local-hide-special outer +*?*: q: can't modify read-only parameter + + () { + private q=outer + print ${| + () { REPLY="{$q}" } + } + print ${{q} + () { q=nofork } + } + } +1:function may not access private from inside nofork +>{} +*?*: q: can't modify read-only parameter + + () { + print ${| + private q + () { q=nofork } + } + } +1:function may not access private declared in nofork +*?*: q: can't modify read-only parameter + %clean + unsetopt TYPESET_TO_UNSET rm -r private.TMP diff --git a/Test/V13zformat.ztst b/Test/V13zformat.ztst index 982866e13..035a0a495 100644 --- a/Test/V13zformat.ztst +++ b/Test/V13zformat.ztst @@ -58,6 +58,30 @@ 0:nested conditionals test >yes + () { + zformat -f 1 '%(w.zero.fail) %(x.fail.present) %(y.empty.fail) %(z.missing.fail)' w:0 x:1 y: + zformat -F 2 '%(w.zero.fail) %(x.present.fail) %(y.fail.empty) %(z.fail.missing)' w:0 x:1 y: + echo $1 + echo $2 + } +0:conditionals with empty and missing values +>zero present empty missing +>zero present empty missing + + () { + local l + for l in 0 1 2 3; do + zformat -F 1 "%$l(a.a.A)%$l(b.b.B)%$l(c.c.C)%$l(d.d.D)" a: b:1 c:12 d:123 + zformat -F 2 "%-$l(a.a.A)%-$l(b.b.B)%-$l(c.c.C)%-$l(d.d.D)" a: b:1 c:12 d:123 + print - $1 $2 + done + } +0:minimum and maximum widths +>Abcd aBCD +>ABcd abCD +>ABCd abcD +>ABCD abcd + zformat -a argv . foo:lorem ipsum:bar bazbaz '\\esc\:ape' print -rl -- "$@" 0:basic -a test diff --git a/Test/V14system.ztst b/Test/V14system.ztst index 100daab08..81253324f 100644 --- a/Test/V14system.ztst +++ b/Test/V14system.ztst @@ -5,10 +5,10 @@ if zmodload -s zsh/system && zmodload -s zsh/zselect; then tst_dir=V14.tmp mkdir -p -- $tst_dir + : > $tst_dir/file # File on which to acquire flock. else ZTST_unimplemented='the zsh/system and zsh/zselect modules are not available' fi - : > $tst_dir/file # File on which to acquire flock. %test @@ -147,3 +147,69 @@ F:This timing test might fail due to process scheduling issues unrelated to zsh. 0:zsystem flock successful wait test, fractional seconds ?elapsed time seems OK F:This timing test might fail due to process scheduling issues unrelated to zsh. + + unset chars REPLY + print -n a few words | sysread -i 0 -c chars + ret=$? + print -- $chars x${REPLY}x + return ret +0:sysread default +>11 xa few wordsx + + unset chars REPLY + sysread -i 9 -c chars + ret=$? + print -- $chars x${REPLY}x + return ret +2:sysread read error +>-1 xx + + REPLY="say nothing" + sysread -i 9 -c chars + ret=$? + print -- $chars x${REPLY}x + return ret +2f:sysread read error +F:The value of $REPLY should be empty or unset when nothing is read? +>-1 xx + + unset chars REPLY + print -n a few words | sysread -i 0 -o 9 -c chars + ret=$? + print -- $chars x${REPLY}x + return ret +3:sysread write error +>11 xx + + sleep 3 | sysread -i 0 -t 1 +4:sysread timeout + + sysread -i 0 </dev/null +5:sysread end of file + + unset chars oration + print -n a few words | sysread -i 0 -o 9 -c chars oration + ret=$? + print $chars x${oration}x $REPLY + return ret +3:regression test: sysread write error with both -o and a parameter +>11 xa few wordsx + + unset chars oration + print a few words | sysread -i 0 -o 1 -c chars oration + ret=$? + print -- $chars x${oration}x $REPLY + return ret +0:regression test: sucessful sysread with both -o and a parameter +>a few words +>12 xx + + oration="do not say these words" + print a few words | sysread -i 0 -o 1 -c chars oration + ret=$? + print -- $chars x${oration}x $REPLY + return ret +0f:successful sysread with both -o and a parameter +F:The value of $oration should be empty or unset when everything is written? +>a few words +>12 xx diff --git a/Test/W01history.ztst b/Test/W01history.ztst index 0b2f60d1e..1d3f3cf6f 100644 --- a/Test/W01history.ztst +++ b/Test/W01history.ztst @@ -88,3 +88,25 @@ F:Check that a history bug introduced by workers/34160 is working again. 0:Modifier :P >/my/path/for/testing >/my/path/for/testing + + $ZTST_testdir/../Src/zsh -fgis <<<' + SAVEHIST=7 + print -rs "one\\" + print -rs "two\\\\" + print -rs "three\\\\\\" + print -rs "four\\\\\\\\" + print -rs "five\\\\\\\\\\" + print -s "while false\ndo\ntrue\\\\\n && break\ndone" + print -s "echo one\\\\\ntwo" + fc -W hist + fc -p -R hist + fc -l + rm hist' 2>/dev/null +0:Lines ending in backslash saved and restored to history +> 1 one\ +> 2 two\\ +> 3 three\\\ +> 4 four\\\\ +> 5 five\\\\\ +> 6 while false\ndo\ntrue\\n && break\ndone +> 7 echo one\\ntwo diff --git a/Test/W02jobs.ztst b/Test/W02jobs.ztst index b09f2ac62..d52888dd9 100644 --- a/Test/W02jobs.ztst +++ b/Test/W02jobs.ztst @@ -144,12 +144,14 @@ zpty_start zpty_input 'sleep 3 &' zpty_input 'jobs -r' + zpty_input '(jobs -r)' zpty_input 'print -- -' zpty_input 'jobs -s' zpty_stop 0:`jobs -r` and `jobs -s` with running job *>\[1] [0-9]## *>\[1] + running*sleep* +*>\[1] + running*sleep* *>- *>zsh:*SIGHUPed* diff --git a/Test/W03jobparameters.ztst b/Test/W03jobparameters.ztst new file mode 100644 index 000000000..a6f7a09b1 --- /dev/null +++ b/Test/W03jobparameters.ztst @@ -0,0 +1,78 @@ +# Tests for interactive job control with parameter state + +%prep + + if zmodload zsh/zpty 2> /dev/null; then + zpty_start() { + export PS1= PS2= + zpty -d + zpty zsh "${(q)ZTST_testdir}/../Src/zsh -fiV +Z" + } + zpty_input() { + zpty -w zsh "${(F)@}" $'\n' + } + zpty_line() { + local REPLY + integer i + for (( i = 0; i < ${1:-1}; ++i )); do + zpty -r zsh REPLY + print -r -- ${REPLY%%($'\r\n'|$'\n')} + done + } + zpty_stop() { + # exit twice in case of check_jobs + zpty -w zsh $'exit\nexit\n' + # zpty gives no output when piped without these braces (?) + { zpty -r zsh } | sed $'/[^[:space:]]/!d; s/\r$//;' + zpty -d + : + } + if ! zmodload zsh/parameter 2> /dev/null; then + ZTST_unimplemented='the zsh/parameter module is not available' + fi + else + ZTST_unimplemented='the zsh/zpty module is not available' + fi + +%test + + zpty_start + zpty_input "MODULE_PATH=${(q)MODULE_PATH}" + zpty_input 'sleep 3 &' + zpty_input 'print $jobstates' + zpty_input '(print $jobstates)' + zpty_input 'jobs -s' + zpty_stop +0:$jobstate for running job in main shell and subshell +*>\[1] [0-9]## +*>running:+:*=running +*>running:+:*=running +*>zsh:*SIGHUPed* + +# $jobstates refers to a job started in the main shell unless +# one has been started in the subshell. In the latter case, +# the subshell has no job control so the job is not marked as current. + zpty_start + zpty_input "MODULE_PATH=${(q)MODULE_PATH}" + zpty_input 'sleep 3 &' + zpty_input '(print main; print $jobstates; sleep 2& print sub; print $jobstates)' + zpty_input 'jobs -s' + zpty_stop +0:$jobstate shows one job started in main shell or one started in subshell +*>\[1] [0-9]## +>main +*>running:+:*=running +>sub +*>running::*=running +*>zsh:*SIGHUPed* + +# output from zpty removes empty lines + zpty_start + zpty_input "MODULE_PATH=${(q)MODULE_PATH}" + zpty_input '(print main; print $jobstates; sleep 2& print sub; print $jobstates)' + zpty_input 'jobs -s' + zpty_stop +0:$jobstate shows no job started in main shell but one started in subshell +>main +>sub +*>running::*=running diff --git a/Test/X02zlevi.ztst b/Test/X02zlevi.ztst index 8146d6752..ccfb7b1c6 100644 --- a/Test/X02zlevi.ztst +++ b/Test/X02zlevi.ztst @@ -1,16 +1,7 @@ # Tests of the vi mode of ZLE %prep - unset -m LC_\* - ZSH_TEST_LANG= - langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8 - $(locale -a 2>/dev/null | egrep 'utf8|UTF-8')) - for LANG in $langs; do - if [[ é = ? ]]; then - ZSH_TEST_LANG=$LANG - break; - fi - done + ZSH_TEST_LANG=$(ZTST_find_UTF8) if ( zmodload zsh/zpty 2>/dev/null ); then . $ZTST_srcdir/comptest comptestinit -v -z $ZTST_testdir/../Src/zsh @@ -605,6 +596,13 @@ >BUFFER: 1ls `2` $(3) "4" $'5' ${6} >CURSOR: 0 + zpty_run 'bindkey -s -a "cw" "dwi"' + zletest $'one two\e0cwyksi' + zpty_run 'bindkey -r -a "cw"' +0:for a vi command, wait to allow a longer binding to be used +>BUFFER: yksitwo +>CURSOR: 4 + %clean zmodload -ui zsh/zpty diff --git a/Test/X03zlebindkey.ztst b/Test/X03zlebindkey.ztst index 3e299a337..1b63b3920 100644 --- a/Test/X03zlebindkey.ztst +++ b/Test/X03zlebindkey.ztst @@ -3,15 +3,7 @@ # into bindings. The latter is particularly tricky with multibyte sequences. %prep - ZSH_TEST_LANG= - langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8 - $(locale -a 2>/dev/null | egrep 'utf8|UTF-8')) - for LANG in $langs; do - if [[ é = ? ]]; then - ZSH_TEST_LANG=$LANG - break; - fi - done + ZSH_TEST_LANG=$(ZTST_find_UTF8) if ( zmodload zsh/zpty 2>/dev/null ); then . $ZTST_srcdir/comptest comptestinit -z $ZTST_testdir/../Src/zsh @@ -45,6 +37,28 @@ >"^Xy" "bar" >"^Xy" undefined-key + zpty_run 'bindkey -s "\e[" altbracket' + zletest $'$\C-A\e[17~' + zpty_run 'bindkey -r "\e["' +0:binding to CSI introduction is not used if a full sequence arrives +>BUFFER: $ +>CURSOR: 0 + + zpty_run 'bindkey -s "\e[1" altbracketone' + zletest $'$\C-A\e[17~' + zpty_run 'bindkey -r "\e[1"' +0:binding to longer prefix of a CSI sequence is used +# we assume the user knows what they're doing +>BUFFER: altbracketone7~$ +>CURSOR: 15 + + zpty_run 'bindkey -s "\e[" altbracket' + zletest $'$\C-A\e[177' + zpty_run 'bindkey -r "\e["' +0:use prefix binding where we don't have a CSI sequence +>BUFFER: altbracket177$ +>CURSOR: 13 + # As we're only looking at definitions here, we don't # bother using the pseudo-terminal; just test in the normal fashion. bindkey -e diff --git a/Test/X04zlehighlight.ztst b/Test/X04zlehighlight.ztst index f84c02505..87a59fde5 100644 --- a/Test/X04zlehighlight.ztst +++ b/Test/X04zlehighlight.ztst @@ -40,7 +40,7 @@ # Fix e^Mexit - match ((?)\r(?)), if \2 == \3, then replace with \2 # otherwise replace with \1 stripped out of leading/trailing [[:space:]] REPLY=${REPLY//(#b)((?(#c0,1))$cm(?(#c0,1)))/${${${(M)match[2]:#${match[3]}}:+${match[2]}}:-${${match[1]##[[:space:]]##}%%[[:space:]]##}}} - [[ -n "$REPLY" ]] && print -r -- ${${REPLY%%[[:space:]]##}##[[:space:]]##} + [[ -n "$REPLY" ]] && print -r -- ${${REPLY%%${~cm}*}##[[:space:]]##} done } zpty_stop() { @@ -79,7 +79,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:region highlight - standout overlapping on other region_highlight entry ->0m27m24mtr7mu27me word2 word3 +>0mtr7mu0me word2 word3 zpty_start zpty_input 'rh_widget() { BUFFER="true"; region_highlight+=( "0 4 fg=green" ); }' @@ -90,7 +90,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:basic region_highlight with 8 colors ->0m27m24mCDE|32|trueCDE|39| +>0mCDE|32|true zpty_start zpty_input 'rh_widget() { region_highlight+=( "0 4 fg=green memo=someplugin" ); typeset -p region_highlight }' @@ -145,7 +145,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:basic region_highlight with true-color (hex-triplets) ->0m27m24m38;2;4;8;16mtrueCDE|39| +>0m38;2;4;8;16mtrue zpty_start zpty_input 'zmodload zsh/nearcolor' @@ -157,7 +157,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:basic region_highlight with near-color (hex-triplets at input) ->0m27m24mCDE|3232|trueCDE|39| +>0mCDE|3232|true zpty_start zpty_input 'rh_widget() { BUFFER="true"; region_highlight+=( "0 4 fg=green" ); rh2; }' @@ -169,7 +169,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:overlapping region_highlight with 8 colors ->0m27m24mCDE|32|tCDE|31|rCDE|39|CDE|32|ueCDE|39| +>0mCDE|32|tCDE|31|rCDE|32|ue zpty_start zpty_input 'rh_widget() { BUFFER="true"; region_highlight+=( "0 4 fg=#00cc00" ); rh2; }' @@ -181,7 +181,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:overlapping region_highlight with true-color ->0m27m24m38;2;0;204;0mt38;2;204;0;0mrCDE|39|38;2;0;204;0mueCDE|39| +>0m38;2;0;204;0mt38;2;204;0;0mr38;2;0;204;0mue zpty_start zpty_input 'zmodload zsh/nearcolor' @@ -194,7 +194,7 @@ zpty_line 1 p # the line of interest, preserving escapes ("p") zpty_stop 0:overlapping region_highlight with near-color (hex-triplets at input) ->0m27m24mCDE|340|tCDE|3160|rCDE|39|CDE|340|ueCDE|39| +>0mCDE|340|tCDE|3160|rCDE|340|ue zpty_start zpty_input 'f () { zle clear-screen; zle g -f nolast; BUFFER=": ${(q)LASTWIDGET}" }; zle -N f' @@ -205,7 +205,7 @@ zpty_line 1 p zpty_stop 0:zle $widgetname -f nolast ->0m27m24m0m27m24m: clear-screen +>0m0m: clear-screen %clean diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst new file mode 100644 index 000000000..cd9817c82 --- /dev/null +++ b/Test/X05zleincarg.ztst @@ -0,0 +1,562 @@ +# Tests the incarg ZLE widget + +%prep + ZSH_TEST_LANG=$(ZTST_find_UTF8) + if ( zmodload zsh/zpty 2>/dev/null ); then + . $ZTST_srcdir/comptest + comptestinit -v -z $ZTST_testdir/../Src/zsh + else + ZTST_unimplemented="the zsh/zpty module is not available" + fi + zpty_run ' + autoload -Uz incarg + for name in {,vim-,vim-backward-}{,sync-}{inc,dec}arg; do + zle -N "$name" incarg + done + bindkey -v "^N" incarg + bindkey -v "^P" decarg + bindkey -v "^F" sync-incarg + bindkey -v "^B" sync-decarg + bindkey -a "^N" vim-incarg + bindkey -a "^P" vim-decarg + bindkey -a "^F" vim-sync-incarg + bindkey -a "^B" vim-sync-decarg + bindkey -a "^E" vim-backward-incarg + bindkey -a "^Y" vim-backward-decarg + unset TMUX_PANE ITERM_SESSION_ID + tmux() { + echo "$TMUX_PANE" + } + ' + +%test + +# Basic increment & decrement + + zletest $'0\C-n' +0:incarg increments an integer +>BUFFER: 1 +>CURSOR: 1 + + zletest $'0\C-p' +0:decarg decrements an integer +>BUFFER: -1 +>CURSOR: 2 + + zletest $'echo 0\e0\C-n' +0:vim-incarg increments an integer +>BUFFER: echo 1 +>CURSOR: 5 + + zletest $'echo 0\e0\C-p' +0:vim-decarg decrements an integer +>BUFFER: echo -1 +>CURSOR: 6 + + zletest $'echo 0 foo\e\C-e' +0:vim-backward-incarg increments an integer +>BUFFER: echo 1 foo +>CURSOR: 5 + + zletest $'echo 0 foo\e\C-y' +0:vim-backward-decarg decrements an integer +>BUFFER: echo -1 foo +>CURSOR: 6 + +# sync- variants + + zletest $'0\C-f' +0:sync-incarg does nothing on unsupported terminals +>BUFFER: 0 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=0' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE' +0:sync-incarg does nothing on tmux in pane 0 +>BUFFER: 0 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=1' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE' +0:sync-incarg increments by 1 on tmux in pane 1 +>BUFFER: 1 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=2' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE' +0:sync-incarg increments by 2 on tmux in pane 2 +>BUFFER: 2 +>CURSOR: 1 + + zpty_run 'ITERM_SESSION_ID=w0t0p0:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset ITERM_SESSION_ID' +0:sync-incarg does nothing on tmux in pane 0 +>BUFFER: 0 +>CURSOR: 1 + + zpty_run 'ITERM_SESSION_ID=w0t0p1:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset ITERM_SESSION_ID' +0:sync-incarg increments by 1 on tmux in pane 1 +>BUFFER: 1 +>CURSOR: 1 + + zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset ITERM_SESSION_ID' +0:sync-incarg increments by 2 on tmux in pane 2 +>BUFFER: 2 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=1' + zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE ITERM_SESSION_ID' +0:sync-incarg prioritizes tmux pane number over iTerm2's +>BUFFER: 1 +>CURSOR: 1 + + zletest $'0\e2\C-n' +0:incarg changes the incremented amount based on the numeric argument +>BUFFER: 2 +>CURSOR: 0 + + zpty_run 'incarg=3' + zletest $'0\e\C-n' + zpty_run 'unset incarg' +0:incarg changes the default incremented amount based on the incarg variable +>BUFFER: 3 +>CURSOR: 0 + + zpty_run 'incarg=3' + zletest $'0\e2\C-n' + zpty_run 'unset incarg' +0:incarg prioritizes the numeric argument over the incarg variable +>BUFFER: 2 +>CURSOR: 0 + + zpty_run 'TMUX_PANE=2' + zletest $'0\e2\C-f' + zpty_run 'unset TMUX_PANE' +0:The sync- variants of incarg takes the numeric argument into account +>BUFFER: 4 +>CURSOR: 0 + +# Leading zeros + + zletest $'000\C-n' +0:incarg preserves leading zeros of decimal integers +>BUFFER: 001 +>CURSOR: 3 + + zletest $'-001\C-n\C-n' +0:incarg preserves leading zeros when the digit turns from negative to positive +>BUFFER: 001 +>CURSOR: 3 + + zletest $'001\C-p\C-p' +0:incarg preserves leading zeros when the digit turns from positive to negative +>BUFFER: -001 +>CURSOR: 4 + + zletest $'001\e1000\C-n' +0:incarg works when the result has more number of digits than the original +>BUFFER: 1001 +>CURSOR: 3 + + zletest $'001\e2000\C-p' +0:decargs works on integers with leading zeros when the result has more digits than the original +>BUFFER: -1999 +>CURSOR: 4 + + zletest $'-000\C-n' +0:incarg produces the correct number of zeros when incrementing integers starting with -0 +>BUFFER: 001 +>CURSOR: 3 + + zletest $'-000\C-p' +0:decarg produces the correct number of zeros when incrementing integers starting with -0 +>BUFFER: -001 +>CURSOR: 4 + + zpty_run 'incarg=0' + zletest $'-000\C-n' + zpty_run 'unset incarg' +0:incarg removes the sign when the target integer starts with -0 and the increment amount is 0 +>BUFFER: 000 +>CURSOR: 3 + + zletest $'-0\C-n' +0:incarg turns -0 into 1 +>BUFFER: 1 +>CURSOR: 1 + + zletest $'-0\C-p' +0:decarg turns -0 into -1 +>BUFFER: -1 +>CURSOR: 2 + + zpty_run 'incarg=0' + zletest $'-0\C-n' + zpty_run 'unset incarg' +0:incarg turns -0 into 0 when the increment amount is 0 +>BUFFER: 0 +>CURSOR: 1 + +# Binaries + + zletest $'0b11\C-n' +0:incarg can increment a binary integer +>BUFFER: 0b100 +>CURSOR: 5 + + zletest $'0B11\C-n' +0:incarg can increment a binary integer with an upper case prefix +>BUFFER: 0B100 +>CURSOR: 5 + + zletest $'0b100\C-p' +0:decarg can decrement a binary integer +>BUFFER: 0b11 +>CURSOR: 4 + + zletest $'0b0011\C-n' +0:incarg can preserve leading zeros of binaries +>BUFFER: 0b0100 +>CURSOR: 6 + + zletest $'0b001\e8\C-n' +0:incarg works on binaries when the result has more zeros than the original +>BUFFER: 0b1001 +>CURSOR: 5 + + zletest $'0b0\C-p' +0:decarg fails to produce a negative binary value +>BUFFER: 0b0 +>CURSOR: 3 + +# Octals + + zletest $'0o7\C-n' +0:incarg can increment an octal integer +>BUFFER: 0o10 +>CURSOR: 4 + + zletest $'0O7\C-n' +0:incarg can increment an octal integer with an upper case prefix +>BUFFER: 0O10 +>CURSOR: 4 + + zletest $'0o10\C-p' +0:decarg can decrement an octal integer +>BUFFER: 0o7 +>CURSOR: 3 + + zletest $'0o0\C-p' +0:decarg fails to produce a negative octal value +>BUFFER: 0o0 +>CURSOR: 3 + +# Hexadecimals + + zletest $'0x9\C-n' +0:incarg can increment a hexadecimal integer +>BUFFER: 0xa +>CURSOR: 3 + + zletest $'0X9\C-n' +0:incarg can increment a hexadecimal integer with an upper case prefix +>BUFFER: 0XA +>CURSOR: 3 + + zletest $'0xf\C-n' +0:incarg can increment a hexadecimal integer with no numeric digit +>BUFFER: 0x10 +>CURSOR: 4 + + zletest $'0x10\C-p' +0:decarg can decrement a hexadecimal integer +>BUFFER: 0xf +>CURSOR: 3 + + zletest $'0x0\C-p' +0:decarg fails to produce a negative hexadecimal value +>BUFFER: 0x0 +>CURSOR: 3 + + zletest $'0x0b1\C-n' +0:incarg interprets integers starting with 0x0b as a hexadecimal +>BUFFER: 0x0b2 +>CURSOR: 5 + + zletest $'0x0b1\e\C-e' +0:vim-backward-incarg interprets integers starting with 0x0b as a hexadecimal +>BUFFER: 0x0b2 +>CURSOR: 4 + +# Cursor position - incarg + + zletest $'echo 012ab\eF i\C-n' +0:incarg does nothing when the cursor is placed just to the left of an integer +>BUFFER: echo 012ab +>CURSOR: 4 + + zletest $'echo 012ab\eF0i\C-n' +0:incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eF1i\C-n' +0:incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eF2i\C-n' +0:incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eFai\C-n' +0:incarg works when the cursor is placed just to the right of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\ei\C-n' +0:incarg does nothing when the cursor is placed more than a single letter away to the right +>BUFFER: echo 012ab +>CURSOR: 9 + + zletest $'10x9\e0\C-n' +0:incarg turns [0-9]0x[0-9a-f] into [0-9]1x[0-9a-f] when the cursor is at the left of x +>BUFFER: 11x9 +>CURSOR: 1 + + zletest $'10x9\eFx\C-n' +0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is on x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10x9\e\C-n' +0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10b1\e0\C-n' +0:incarg turns [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b +>BUFFER: 11b1 +>CURSOR: 1 + + zletest $'10b1\eFb\C-n' +0:incarg takes [0-9]0b[01] and increments the binary part when the cursor is on b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10b1\e\C-n' +0:incarg takes [0-9]0b[01] and increments binary part when the cursor is at the right of b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10o7\e0\C-n' +0:incarg turns [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o +>BUFFER: 11o7 +>CURSOR: 1 + + zletest $'10o7\eFo\C-n' +0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is on o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'10o7\e\C-n' +0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is at the right of o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'0b0x9\eF0\C-n' +0:incarg takes 0b0x[0-9a-f] and increments the binary part when the cursor is at the left of x +>BUFFER: 0b1x9 +>CURSOR: 2 + + zletest $'0b0x9\eFx\C-n' +0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is on x +>BUFFER: 0b0xa +>CURSOR: 4 + + zletest $'0b0x9\e\C-n' +0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x +>BUFFER: 0b0xa +>CURSOR: 4 + +# Cursor position - vim-incarg + + zletest $'echo 012ab\eF \C-n' +0:vim-incarg works when the cursor is placed to the left of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF0\C-n' +0:vim-incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF1\C-n' +0:vim-incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF2\C-n' +0:incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eFa\C-n' +0:vim-incarg does nothing when the cursor is placed to the right of an integer +>BUFFER: echo 012ab +>CURSOR: 8 + + zletest $'echo 012ab\ei\C-n' +0:vim-incarg does nothing when the cursor is placed more than a single letter away to the right +>BUFFER: echo 012ab +>CURSOR: 9 + +# Cursor position - vim-backward-incarg + + zletest $'echo 012ab\eF \C-e' +0:vim-backward-incarg does nothing when the cursor is placed just to the left of an integer +>BUFFER: echo 012ab +>CURSOR: 4 + + zletest $'echo 012ab\eF0\C-e' +0:vim-backward-incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF1\C-e' +0:vim-backward-incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF2\C-e' +0:vim-backward-incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eFa\C-e' +0:vim-backward-incarg works when the cursor is placed just to the right of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\e\C-e' +0:vim-backward-incarg works when the cursor is placed more than a single letter away to the right +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'10x9\eFx\C-e' +0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10x9\e\C-e' +0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on the right of x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10b1\e0\C-e' +0:vim-backward-incarg will turn [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b +>BUFFER: 11b1 +>CURSOR: 1 + + zletest $'10b1\eFb\C-e' +0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10b1\e\C-e' +0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on the right of b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10o7\e0\C-e' +0:vim-backward-incarg will turn [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o +>BUFFER: 11o7 +>CURSOR: 1 + + zletest $'10o7\eFo\C-e' +0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is on o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'10o7\e\C-e' +0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is at the right of o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'0b0x9\eF0\C-e' +0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the binary 0b0 when the cursor is on the left of x +>BUFFER: 0b1x9 +>CURSOR: 2 + + zletest $'0b0x9\eFx\C-e' +0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is on x +>BUFFER: 0b0xa +>CURSOR: 4 + + zletest $'0b0x9\e\C-e' +0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is at the right of x +>BUFFER: 0b0xa +>CURSOR: 4 + +# Repeats + + zletest $'echo 0\e0\C-n.' +0:vim-incarg is compatible with the repeat command +>BUFFER: echo 2 +>CURSOR: 5 + + zletest $'echo 0\e0\C-p.' +0:vim-decarg is compatible with the repeat command +>BUFFER: echo -2 +>CURSOR: 6 + + zletest $'echo 0 foo\e\C-e.' +0:vim-backward-incarg is compatible with the repeat command +>BUFFER: echo 2 foo +>CURSOR: 5 + + zletest $'echo 0\e010\C-n.' +0:Repeats of vim-incarg takes the numeric argument into account +>BUFFER: echo 20 +>CURSOR: 6 + + zletest $'echo 0 foo\e10\C-e.' +0:Repeats of vim-backward-incarg takes the numeric argument into account +>BUFFER: echo 20 foo +>CURSOR: 6 + + zpty_run 'TMUX_PANE=0' + zletest $'echo 0\e0\C-f.' + zpty_run 'unset TMUX_PANE' +0:Repeats of vim-sync-incarg work in pane 0 +>BUFFER: echo 0 +>CURSOR: 5 + + zpty_run 'TMUX_PANE=1' + zletest $'echo 0\e0\C-f.' + zpty_run 'unset TMUX_PANE' +0:Repeats of vim-sync-incarg work in pane 1 +>BUFFER: echo 2 +>CURSOR: 5 + + zpty_run 'TMUX_PANE=2' + zletest $'echo 0\e0\C-f.' + zpty_run 'unset TMUX_PANE' +0:Repeats of vim-sync-incarg work in pane 2 +>BUFFER: echo 4 +>CURSOR: 5 + +%clean + + zmodload -ui zsh/zpty diff --git a/Test/Y01completion.ztst b/Test/Y01completion.ztst index 882a0adc4..fc18b19a4 100644 --- a/Test/Y01completion.ztst +++ b/Test/Y01completion.ztst @@ -1,16 +1,7 @@ # Tests for completion system. %prep - unset -m LC_\* - ZSH_TEST_LANG= - langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8 - $(locale -a 2>/dev/null | egrep 'utf8|UTF-8')) - for LANG in $langs; do - if [[ é = ? ]]; then - ZSH_TEST_LANG=$LANG - break; - fi - done + ZSH_TEST_LANG=$(ZTST_find_UTF8) if ( zmodload zsh/zpty 2>/dev/null ); then . $ZTST_srcdir/comptest mkdir comp.tmp @@ -44,6 +35,54 @@ >line: {: dir1/}{} >line: {: dir2/}{} + comptest $': d\t\t\t\t\t \t' +0:unambiguous prefix and autoremovable suffix +>line: {: dir}{} +>line: {: dir}{} +>DESCRIPTION:{file} +>DI:{dir1} +>DI:{dir2} +>line: {: dir1/}{} +>line: {: dir2/}{} +>line: {: dir1/}{} +>line: {: dir1 }{} +>DESCRIPTION:{file} +>DI:{dir1} +>DI:{dir2} +>FI:{file1} +>FI:{file2} + + comptest $': suf\ebd\t\t\t\t\t \t' +0:unambiguous prefix and autoremovable suffix with _prefix completer +>line: {: dir}{suf} +>line: {: dir}{suf} +>DESCRIPTION:{file} +>DI:{dir1} +>DI:{dir2} +>line: {: dir1/}{suf} +>line: {: dir2/}{suf} +>line: {: dir1/}{suf} +>line: {: dir1 }{suf} +>DESCRIPTION:{file} +>DI:{dir1} +>DI:{dir2} +>FI:{file1} +>FI:{file2} +F:regression test workers/51641 + + comptesteval 'comptest-postfunc() { compstate[insert]=1 compstate[list]= }' + comptest $': \t \t' +0:compstate[insert]=1 compstate[list]= +>line: {: dir1/}{} +>line: {: dir1 dir1/}{} + + comptest $': suf\eb\t \t' +0:compstate[insert]=1 compstate[list]= with _prefix completer +>line: {: dir1/}{suf} +>line: {: dir1 dir1/}{suf} +F:regression test workers/51641 + + comptesteval 'comptest-postfunc() {}' comptest $': *\t\t\t\t\t\t' 0:_expand shows file types >line: {: dir1/}{} @@ -62,6 +101,17 @@ >line: {: dir1 dir2 file1 file2 }{} >line: {: *}{} + comptesteval $'zstyle \'*\' glob no' + comptesteval $'typeset -g tst=(*)' + comptest $': $tst\C-D' +0:_expand preserves array form +>DESCRIPTION:{expansions} +>NO:{dir1} +>NO:{dir2} +>NO:{file1} +>NO:{file2} + + comptesteval $'zstyle -d \'*\' glob' comptesteval '_users () { compadd user1 user2 }' comptest $': ~\t\t\t\t\t' 0:tilde @@ -73,6 +123,27 @@ >line: {: ~user2}{} >line: {: ~user1}{} + comptesteval 'zsh_directory_name() { compadd "$expl[@]" -- name/1 name2 }' + comptest $': ~[\t\t\t\t' +0:dynamic directory names after ~[ +>line: {: ~[name}{} +>line: {: ~[name}{} +>DESCRIPTION:{dynamically named directory} +>NO:{name/1} +>NO:{name2} +>line: {: ~[name/1]}{} +>line: {: ~[name2]}{} + + comptest $': ~[]\C-b\t\t\t\t' +0:dynamic directory names inside ~[...] +>line: {: ~[name}{]} +>line: {: ~[name}{]} +>DESCRIPTION:{dynamically named directory} +>NO:{name/1} +>NO:{name2} +>line: {: ~[name/1}{]} +>line: {: ~[name2}{]} + comptest $'echo ;:\C-b\C-b\t' 0:directories and files before separator >line: {echo }{;:} @@ -258,10 +329,10 @@ F:regression test workers/31611 comptesteval "typeset -a bar=({$'\\0'..$'\\C-?'})" comptesteval 'typeset -A bat=( "$bar[@]" )' comptesteval 'typeset bay="$bar"' - comptesteval 'zstyle ":completion:*:parameters" extra-verbose yes' + comptesteval 'zstyle ":completion:*:parameters" verbose yes' comptesteval 'zstyle ":completion:*" fake-parameters bar bat bay' comptest $': $ba\t' -0:extra-verbose shows parameter values +0:verbose shows parameter values >line: {: $ba}{} >DESCRIPTION:{parameter} >NO:{bar -- ( '^@' '^A' '^B' '^C' '^D' '^E' '^F' '^G' '^H' '\t' '\n' '^K' '^L' '} @@ -271,15 +342,15 @@ F:regression test workers/31611 comptesteval "path=( $ZTST_srcdir:A )" comptesteval 'typeset -H paths=HIDDEN' comptest $': $path\t' -0:extra-verbose doesn't show special or hidden parameter values +0:verbose doesn't show special or hidden parameter values >line: {: $path}{} >DESCRIPTION:{parameter} >NO:{path} >NO:{paths} - comptesteval 'zstyle -d ":completion:*:parameters" extra-verbose' + comptesteval 'zstyle -d ":completion:*:parameters" verbose' comptest $': $ba\t' -0:parameter values not shown without extra-verbose +0:parameter values not shown without verbose >line: {: $ba}{} >DESCRIPTION:{parameter} >NO:{bar} diff --git a/Test/Y02compmatch.ztst b/Test/Y02compmatch.ztst index 621707482..f28913867 100644 --- a/Test/Y02compmatch.ztst +++ b/Test/Y02compmatch.ztst @@ -378,15 +378,26 @@ comp.graphics.rendering.misc comp.graphics.rendering.raytracing comp.graphics.rendering.renderman) test_code $example4_matcher example4_list - comptest $'tst c.s.u\t' -0:Documentation example using input c.s.u + comptest $'tst .s.u\t' +0:r:|.=* should complete .s.u +>line: {tst comp.sources.unix }{} +>COMPADD:{} +>INSERT_POSITIONS:{21} + + example4b_matcher='r:[^.]||.=* r:|=*' + test_code $example4b_matcher example4_list + comptest $'tst .s.u\t^[bc\t' +0f:r:[^.]||.=* should not complete .s.u, but should complete c.s.u +>line: {tst .s.u}{} +>COMPADD:{} +>INSERT_POSITIONS:{} >line: {tst comp.sources.unix }{} >COMPADD:{} >INSERT_POSITIONS:{21} test_code $example4_matcher example4_list - comptest $'tst c.g.\ta\t.\tp\ta\tg\t' -0:Documentation example using input c.g.\ta\t.\tp\ta\tg\t + comptest $'tst .g.\ta\t.\tp\ta\tg\t' +0:r:|.=* should complete .g. >line: {tst comp.graphics.}{} >COMPADD:{} >INSERT_POSITIONS:{18} @@ -424,9 +435,32 @@ >COMPADD:{} >INSERT_POSITIONS:{32} + test_code $example4b_matcher example4_list + comptest $'tst .g.\t^[bc\t' +0f:r:[^.]||.=* should not complete .g., but should complete c.g. +>line: {tst .g.}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst comp.graphics.}{} +>COMPADD:{} +>INSERT_POSITIONS:{18} + test_code $example4_matcher example4_list - comptest $'tst c...pag\t' -0:Documentation example using input c...pag\t + comptest $'tst ...pag\t' +0:r:|.=* should complete ...pag +>line: {tst comp.graphics.apps.pagemaker }{} +>COMPADD:{} +>INSERT_POSITIONS:{32} + + test_code $example4b_matcher example4_list + comptest $'tst ...pag\t^[bc\t^Fg^F^Fa\t' +0f:r:[^.]||.=* should not complete ...pag or c...pag, but should complete c.g.a.p +>line: {tst ...pag}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst c...pag}{} +>COMPADD:{} +>INSERT_POSITIONS:{} >line: {tst comp.graphics.apps.pagemaker }{} >COMPADD:{} >INSERT_POSITIONS:{32} @@ -444,8 +478,8 @@ example5_matcher='r:|[.,_-]=* r:|=*' example5_list=(veryverylongfile.c veryverylongheader.h) test_code $example5_matcher example5_list - comptest $'tst v.c\tv.h\t' -0:Documentation example using input v.c\t + comptest $'tst .c\t.h\t' +0:r:|[.,_-]=* should complete .c and .h >line: {tst veryverylongfile.c }{} >COMPADD:{} >INSERT_POSITIONS:{23} @@ -453,6 +487,23 @@ >COMPADD:{} >INSERT_POSITIONS:{44} + example5b_matcher='r:[^.,_-]||[.,_-]=* r:|=*' + test_code $example5b_matcher example5_list + comptest $'tst .c\t^[bv\t.h\t^[bv\t' +0f:r:[^.,_-]||[.,_-]=* should not complete .c or .h, but should complete v.c and v.h +>line: {tst .c}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst veryverylongfile.c }{} +>COMPADD:{} +>INSERT_POSITIONS:{23} +>line: {tst veryverylongfile.c .h}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst veryverylongfile.c veryverylongheader.h }{} +>COMPADD:{} +>INSERT_POSITIONS:{44} + example6_list=(LikeTHIS FooHoo 5foo123 5bar234) test_code 'r:|[A-Z0-9]=* r:|=*' example6_list @@ -493,16 +544,57 @@ example7_matcher="r:[^A-Z0-9]||[A-Z0-9]=** r:|=*" example7_list=($example6_list) test_code $example7_matcher example7_list - comptest $'tst H\t2\t' -0:Documentation example using "r:[^A-Z0-9]||[A-Z0-9]=** r:|=*" + comptest $'tst H\t^BF\to\t2\t^B5\tb\t' +0f:r:[^A-Z0-9]||[A-Z0-9]=** should not complete H, FH, 2 or 52, but should complete FoH and 5b2. +>line: {tst H}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst F}{H} +>COMPADD:{} +>INSERT_POSITIONS:{} >line: {tst FooHoo }{} >COMPADD:{} >INSERT_POSITIONS:{10} +>line: {tst FooHoo 2}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst FooHoo 5}{2} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst FooHoo 5bar234 }{} +>COMPADD:{} +>INSERT_POSITIONS:{18} + + example7b_matcher="r:?||[A-Z0-9]=* r:|=*" + test_code $example7b_matcher example7_list + comptest $'tst H\t^BF2\t^B5\t' +0f:r:?||[A-Z0-9]=* r:|=* should not complete H or 2, but should complete FH and 52. +>line: {tst H}{} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst FooHoo }{} +>COMPADD:{} +>INSERT_POSITIONS:{10} +>line: {tst FooHoo 2}{} +>COMPADD:{} +>INSERT_POSITIONS:{} >line: {tst FooHoo 5bar234 }{} >COMPADD:{} >INSERT_POSITIONS:{18} + example8_list=(passwd.byname) + test_code 'r:[^.]||.=* l:.||[^.]=*' + comptest $'tst .^B\tpass^Fname\t' +0f:r:[^.]||.=* and l:.||[^.]=* should work symmetrically. +>line: {tst }{.} +>COMPADD:{} +>INSERT_POSITIONS:{} +>line: {tst passwd.byname }{} +>COMPADD:{} +>INSERT_POSITIONS:{17} + + workers_7311_matcher="m:{a-z}={A-Z} r:|[.,_-]=* r:|=*" workers_7311_list=(Abc-Def-Ghij.txt Abc-def.ghi.jkl_mno.pqr.txt Abc_def_ghi_jkl_mno_pqr.txt) test_code $workers_7311_matcher workers_7311_list @@ -537,11 +629,11 @@ >COMPADD:{} >INSERT_POSITIONS:{5} - workers_11081_matcher='m:{a-zA-Z}={A-Za-z} r:|[.,_-]=* r:[^A-Z0-9]||[A-Z0-9]=* r:[A-Z0-9]||[^A-Z0-9]=* r:[^0-9]||[0-9]=* r:|=*' + workers_11081_matcher='m:{a-zA-Z}={A-Za-z} r:|[.,_-]=* r:|=*' workers_11081_list=(build.out build.out1 build.out2) test_code $workers_11081_matcher workers_11081_list comptest $'tst bui\t\t\t' -0:Bug from workers 11081 +0:Erratic completion bug from workers 11081: bui > build.out[] > build[.]out > build.out[] > build.out1[] > build.out2[] >line: {tst build.out}{} >COMPADD:{} >INSERT_POSITIONS:{13} @@ -578,7 +670,7 @@ workers_11586_list=(c00.abc c01.abc.def.00.0) test_code $workers_11586_matcher workers_11586_list comptest $'tst c00\t.\ta\t' -0:Bug from workers 11586 +0:Disappearing characters bug from workers 11586: c00\t -> c0[], c00\t -> c0.abc[], c00.\t -> c0.abc[] >line: {tst c00}{} >COMPADD:{} >INSERT_POSITIONS:{6} @@ -611,12 +703,12 @@ >COMPADD:{} >INSERT_POSITIONS:{22} - workers_13320_matcher='r:|[.,_-]=** r:[^0-9]||[0-9]=**' + workers_13320_matcher='r:|[.,_-]=**' workers_13320_list=(glibc-2.1.94-3.i386.rpm glibc-devel-2.1.94-3.i386.rpm) workers_13320_list=($workers_13320_list glibc-profile-2.1.94-3.i386.rpm) test_code $workers_13320_matcher workers_13320_list comptest $'tst glibc-2.1\t' -0:Test from workers 13320 +0:Incorrect cursor position bug from workers 13320: glibc-2.1\t -> glibc-2[.]1.94-3.i386.rpm >line: {tst glibc}{-2.1.94-3.i386.rpm} >COMPADD:{} >INSERT_POSITIONS:{9:27} @@ -641,11 +733,11 @@ >NO:{A.C} - workers_13345b_matcher='r:|[.,_-]=** r:[^0-9]||[0-9]=**' + workers_13345b_matcher='r:|[.,_-]=** r:|[0-9]=**' workers_13345b_list=(a-b_1_2_2 a-b_2_0.gz a-b_2_0.zip) test_code $workers_13345b_matcher workers_13345b_list comptest $'tst a-b_2\t' -0:Second test from workers 13345 +0:Disappearing character bug from workers 13345: a-b_2\t -> a-b__ >line: {tst a-b_2_}{} >COMPADD:{} >INSERT_POSITIONS:{8:10} diff --git a/Test/Y03arguments.ztst b/Test/Y03arguments.ztst index bf41aead5..200c83e8c 100644 --- a/Test/Y03arguments.ztst +++ b/Test/Y03arguments.ztst @@ -102,6 +102,28 @@ >NO:{+o} >NO:{-o} + tst_arguments -s -{a,b,c} \!-{d,e,f} \!+{d,e,f} + comptest $'tst -ad\t\024\t\bef\t' +0:mix of + and - and exclusion of stacked options +>line: {tst -ad}{} +>DESCRIPTION:{option} +>NO:{-b} +>NO:{-c} +>line: {tst -da}{} +>DESCRIPTION:{option} +>NO:{-b} +>NO:{-c} +>line: {tst -def}{} +>DESCRIPTION:{option} +>NO:{-a} +>NO:{-b} +>NO:{-c} + + tst_arguments -s -{a,b,c} +{a,b,c} + comptest $'tst -a +b +c\t' +0:mix of + and - and exclusion of stacked options +>line: {tst -a +b +ca}{} + tst_arguments '-o:1:(a):2:(b)' comptest $'tst \t\t\t' 0:two option arguments @@ -359,6 +381,12 @@ 0:allowed option before -- >line: {tst -x }{ --} + tst_arguments -S '1:one' '2:two' + comptest $'tst -- -- \t' +0:only first of duplicate -- is ignored +>line: {tst -- -- }{} +>DESCRIPTION:{two} + tst_arguments -x :word comptest $'tst word -\t' 0:option after a word @@ -390,6 +418,25 @@ 0:continue completion after rest argument that looks like an option >line: {tst -a -x more }{} + tst_arguments -A '-*' -a -b '*: :(words)' + comptest $'tst -x -\t' +0:word matching -A pattern doesn't exclude options +>line: {tst -x -}{} +>DESCRIPTION:{option} +>NO:{-a} +>NO:{-b} + + tst_arguments -A '-*' -a -b '1:word:(word)' + comptest $'tst -x \t' +0:unrecognised word matching -A pattern not treated as a rest argument +>line: {tst -x word }{} + + tst_arguments -A "-*" '(3)-a' '1:one' '2:two' '3:three' '4:four' '*:extra' + comptest $'tst x -a \t' +0:exclusion from option following word matching -A pattern should not apply +>line: {tst x -a }{} +>DESCRIPTION:{three} + tst_arguments '*-v' comptest $'tst -v -\t' 0:repeatable options @@ -478,6 +525,16 @@ >NO:{-b} >NO:{-v} + tst_arguments -a -b -c '(-a)1:one' '(-b)2:two' '(-c)*:extra' + comptest $'tst x y z\e6\C-b-\t' +0:exclude option from normal argument to the right of the cursor +>line: {tst -}{ x y z} +>DESCRIPTION:{one} +>DESCRIPTION:{option} +>NO:{-a} +>NO:{-b} +>NO:{-c} + tst_arguments -a - set1 -d - set2 '(set2)-m' -n -o ':arg:(x)' - set2 -x comptest $'tst -m \t' 0:exclude own set from an option diff --git a/Test/comptest b/Test/comptest index 79c69979a..39ad14768 100644 --- a/Test/comptest +++ b/Test/comptest @@ -40,7 +40,7 @@ KEYTIMEOUT=1 setopt zle autoload -U compinit compinit -u -zstyle ":completion:*" completer _expand _complete _ignored +zstyle ":completion:*" completer _expand _complete _prefix _ignored zstyle ":completion:*:default" list-colors "no=<NO>" "fi=<FI>" "di=<DI>" "ln=<LN>" "pi=<PI>" "so=<SO>" "bd=<BD>" "cd=<CD>" "ex=<EX>" "mi=<MI>" "tc=<TC>" "sp=<SP>" "lc=<LC>" "ec=<EC>\n" "rc=<RC>" zstyle ":completion:*" group-name "" zstyle ":completion:*:messages" format "<MESSAGE>%d</MESSAGE> @@ -51,6 +51,12 @@ zstyle ":completion:*:options" verbose yes zstyle ":completion:*:values" verbose yes setopt noalwayslastprompt listrowsfirst completeinword zmodload zsh/complist +zle -C complete-word complete-word complete-word-with-postfunc +complete-word-with-postfunc() { + local +h -a comppostfuncs=( comptest-postfunc ) + _main_complete "$@" +} +comptest-postfunc() {} complete-word-with-report () { print -lr "<WIDGET><complete-word>" zle complete-word diff --git a/Test/runtests.zsh b/Test/runtests.zsh index b66d579b6..538663f50 100644 --- a/Test/runtests.zsh +++ b/Test/runtests.zsh @@ -15,6 +15,7 @@ for file in "${(f)ZTST_testlist}"; do (( skipped++ )) elif (( $retval )); then (( failure++ )) + (( $retval > 128 )) && print "$file: failed: SIG$signals[$retval - 127]." else (( success++ )) fi diff --git a/Test/ztst.zsh b/Test/ztst.zsh index a59c06dcf..1d05baddf 100755 --- a/Test/ztst.zsh +++ b/Test/ztst.zsh @@ -17,18 +17,30 @@ # Defined in such a way that any value from the environment is used. : ${ZTST_verbose:=0} +# If non-zero, continue the tests even after a test fails. +: ${ZTST_continue:=0} + # We require all options to be reset, not just emulation options. # Unfortunately, due to the crud which may be in /etc/zshenv this might # still not be good enough. Maybe we should trick it somehow. emulate -R zsh -# Ensure the locale does not screw up sorting. Don't supply a locale -# unless there's one set, to minimise problems. -[[ -n $LC_ALL ]] && LC_ALL=C -[[ -n $LC_COLLATE ]] && LC_COLLATE=C -[[ -n $LC_NUMERIC ]] && LC_NUMERIC=C -[[ -n $LC_MESSAGES ]] && LC_MESSAGES=C -[[ -n $LANG ]] && LANG=C +# By default tests are run in C locale. LANG must be passed to child zsh. +unset -m LC_\* +export LANG=C + +# find UTF-8 locale +ZTST_find_UTF8 () { + setopt multibyte + local langs=(en_{US,GB}.{UTF-,utf}8 en.UTF-8 + ${(M)$(locale -a 2>/dev/null):#*.(utf8|UTF-8)}) + for LANG in $langs; do + if [[ é = ? ]]; then + echo $LANG + return + fi + done +} # Don't propagate variables that are set by default in the shell. typeset +x WORDCHARS @@ -36,8 +48,6 @@ typeset +x WORDCHARS # Set the module load path to correspond to this build of zsh. # This Modules directory should have been created by "make check". [[ -d Modules/zsh ]] && module_path=( $PWD/Modules ) -# Allow this to be passed down. -export MODULE_PATH # We need to be able to save and restore the options used in the test. # We use the $options variable of the parameter module for this. @@ -144,6 +154,10 @@ ZTST_testfailed() { $ZTST_failmsg" fi ZTST_testfailed=1 + # if called from within ZTST_Test() this will increment ZTST_Test's local + # ZTST_failures. Otherwise global ZTST_failures will be incremented + # (but currently its value is not used). + (( ++ZTST_failures )) return 1 } ZTST_testxpassed() { @@ -157,6 +171,7 @@ ZTST_testxpassed() { $ZTST_failmsg" fi ZTST_testfailed=1 + (( ++ZTST_failures )) return 1 } @@ -292,16 +307,18 @@ ZTST_execchunk() { } # Functions for preparation and cleaning. -# When cleaning up (non-zero string argument), we ignore status. -ZTST_prepclean() { - # Execute indented code chunks. - while ZTST_getchunk; do - ZTST_execchunk >/dev/null || [[ -n $1 ]] || { - [[ -n "$ZTST_unimplemented" ]] || +ZTST_prep ZTST_clean () { + # Execute indented code chunks. If ZTST_unimplemented is set + # in any chunk then we will skip the remaining chunks. + # We ignore return status of chunks when cleaning up. + while [[ -z "$ZTST_unimplemented" ]] && ZTST_getchunk; do + ZTST_execchunk >/dev/null || [[ $0 = ZTST_clean ]] || { ZTST_testfailed "non-zero status from preparation code: -$ZTST_code" && return 0 +$ZTST_code" + return 1 } done + return 0 } # diff wrapper @@ -309,6 +326,7 @@ ZTST_diff() { emulate -L zsh setopt extendedglob + local -a diff_arg local diff_out integer diff_pat diff_ret @@ -325,6 +343,7 @@ ZTST_diff() { ;; esac shift + [[ $OSTYPE != solaris* ]] && diff_arg=( -a ) if (( diff_pat )); then local -a diff_lines1 diff_lines2 @@ -365,7 +384,7 @@ ZTST_diff() { diff_ret=1 fi else - diff_out=$(diff -a "$@") + diff_out=$(diff $diff_arg "$@") diff_ret="$?" if [[ "$diff_ret" != "0" ]]; then print -r -- "$diff_out" @@ -374,12 +393,12 @@ ZTST_diff() { return "$diff_ret" } - + ZTST_test() { local last match mbegin mend found substlines local diff_out diff_err local ZTST_skip - integer expected_to_fail + integer expected_to_fail ZTST_failures while true; do rm -f $ZTST_in $ZTST_out $ZTST_err @@ -493,7 +512,7 @@ $ZTST_curline" $ZTST_code${$(<$ZTST_terr):+ Error output: $(<$ZTST_terr)}" - return 1 + if (( ZTST_continue ));then continue; else return 1; fi fi ZTST_verbose 2 "ZTST_test: test produced standard output: @@ -516,7 +535,7 @@ $(<$ZTST_terr)" $ZTST_code${$(<$ZTST_terr):+ Error output: $(<$ZTST_terr)}" - return 1 + if (( ZTST_continue ));then continue; else return 1; fi fi if [[ $ZTST_flags = *q* && -s $ZTST_err ]]; then substlines="$(<$ZTST_err)" @@ -530,21 +549,27 @@ $(<$ZTST_terr)}" fi ZTST_testfailed "error output differs from expected as shown above for: $ZTST_code" - return 1 + if (( ZTST_continue ));then continue; else return 1; fi fi if (( expected_to_fail )); then ZTST_testxpassed - return 1 + if (( ZTST_continue ));then continue; else return 1; fi fi fi ZTST_verbose 1 "Test successful." [[ -n $last ]] && break done - ZTST_verbose 2 "ZTST_test: all tests successful" + if (( ZTST_failures )); then + ZTST_verbose 1 "ZTST_test: $ZTST_failures test(s) failed" + else + ZTST_verbose 2 "ZTST_test: all tests successful" + fi # reset message to keep ZTST_testfailed output correct ZTST_message='' + + return ZTST_failures } @@ -564,27 +589,29 @@ while [[ -z "$ZTST_unimplemented" ]] && ZTST_getsect $ZTST_skipok; do (prep) if (( ${ZTST_sects[prep]} + ${ZTST_sects[test]} + \ ${ZTST_sects[clean]} )); then ZTST_testfailed "\`prep' section must come first" - exit 1 + break # skip %test and %clean sections, but run ZTST_cleanup fi - ZTST_prepclean + ZTST_prep || ZTST_skipok=1 ZTST_sects[prep]=1 ;; (test) if (( ${ZTST_sects[test]} + ${ZTST_sects[clean]} )); then ZTST_testfailed "bad placement of \`test' section" - exit 1 + break # skip %clean section, but run ZTST_cleanup fi - # careful here: we can't execute ZTST_test before || or && - # because that affects the behaviour of traps in the tests. - ZTST_test - (( $? )) && ZTST_skipok=1 + if [[ -z "$ZTST_skipok" ]]; then # if no error in %prep + # careful here: we can't execute ZTST_test before || or && + # because that affects the behaviour of traps in the tests. + ZTST_test + (( $? )) && ZTST_skipok=1 + fi ZTST_sects[test]=1 ;; (clean) if (( ${ZTST_sects[test]} == 0 || ${ZTST_sects[clean]} )); then ZTST_testfailed "bad use of \`clean' section" else - ZTST_prepclean 1 + ZTST_clean ZTST_sects[clean]=1 fi ZTST_skipok= |