about summary refs log tree commit diff
path: root/Test
diff options
context:
space:
mode:
Diffstat (limited to 'Test')
-rw-r--r--Test/A01grammar.ztst76
-rw-r--r--Test/A02alias.ztst7
-rw-r--r--Test/A04redirect.ztst7
-rw-r--r--Test/A05execution.ztst25
-rw-r--r--Test/A06assign.ztst21
-rw-r--r--Test/B02typeset.ztst28
-rw-r--r--Test/B03print.ztst5
-rw-r--r--Test/B04read.ztst10
-rw-r--r--Test/B12limit.ztst2
-rw-r--r--Test/C01arith.ztst8
-rw-r--r--Test/C02cond.ztst8
-rw-r--r--Test/C03traps.ztst203
-rw-r--r--Test/C04funcdef.ztst22
-rw-r--r--Test/D01prompt.ztst19
-rw-r--r--Test/D02glob.ztst38
-rw-r--r--Test/D03procsubst.ztst20
-rw-r--r--Test/D04parameter.ztst139
-rw-r--r--Test/D06subscript.ztst14
-rw-r--r--Test/D07multibyte.ztst56
-rw-r--r--Test/D08cmdsubst.ztst8
-rw-r--r--Test/D09brace.ztst7
-rw-r--r--Test/D10nofork.ztst515
-rw-r--r--Test/E01options.ztst72
-rw-r--r--Test/E03posix.ztst9
-rw-r--r--Test/K01nameref.ztst893
-rw-r--r--Test/K02parameter.ztst154
-rw-r--r--Test/P01privileged.ztst23
-rw-r--r--Test/README8
-rw-r--r--Test/V07pcre.ztst62
-rw-r--r--Test/V10private.ztst266
-rw-r--r--Test/V13zformat.ztst24
-rw-r--r--Test/V14system.ztst68
-rw-r--r--Test/W01history.ztst22
-rw-r--r--Test/W02jobs.ztst2
-rw-r--r--Test/W03jobparameters.ztst78
-rw-r--r--Test/X02zlevi.ztst18
-rw-r--r--Test/X03zlebindkey.ztst32
-rw-r--r--Test/X04zlehighlight.ztst18
-rw-r--r--Test/X05zleincarg.ztst562
-rw-r--r--Test/Y01completion.ztst101
-rw-r--r--Test/Y02compmatch.ztst126
-rw-r--r--Test/Y03arguments.ztst57
-rw-r--r--Test/comptest8
-rw-r--r--Test/runtests.zsh1
-rwxr-xr-xTest/ztst.zsh91
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=