1# Tests for both trap builtin and TRAP* functions.
2
3%prep
4
5  setopt localtraps
6  mkdir traps.tmp && cd traps.tmp
7
8%test
9
10  fn1() {
11    trap 'print EXIT1' EXIT
12    fn2() { trap 'print EXIT2' EXIT; }
13    fn2
14  }
15  fn1
160:Nested `trap ... EXIT'
17>EXIT2
18>EXIT1
19
20  fn1() {
21    TRAPEXIT() { print EXIT1; }
22    fn2() { TRAPEXIT() { print EXIT2; }; }
23    fn2
24  }
25  fn1
260: Nested TRAPEXIT
27>EXIT2
28>EXIT1
29
30  fn1() {
31    trap 'print EXIT1' EXIT
32    fn2() { trap - EXIT; }
33    fn2
34  }
35  fn1
360:Nested `trap - EXIT' on `trap ... EXIT'
37>EXIT1
38
39  fn1() {
40    TRAPEXIT() { print EXIT1; }
41    fn2() { trap - EXIT; }
42    fn2
43  }
44  fn1
450:Nested `trap - EXIT' on `TRAPEXIT'
46>EXIT1
47
48# We can't test an EXIT trap for the shell as a whole, because
49# we're inside a function scope which we don't leave when the
50# subshell exits.  Not sure if that's the correct behaviour, but
51# it's sort of consistent.
52  ( fn1() { trap 'print Function 1 going' EXIT; exit; print Not reached; }
53    fn2() { trap 'print Function 2 going' EXIT; fn1; print Not reached; }
54    fn2
55  )
560:EXIT traps on functions when exiting from function
57>Function 1 going
58>Function 2 going
59
60# $ZTST_exe is relative to the parent directory.
61# We ought to fix this in ztst.zsh...
62  (cd ..
63  $ZTST_exe -fc 'TRAPEXIT() { print Exited.; }')
640:EXIT traps on a script
65>Exited.
66
67  fn1() {
68    trap -
69    trap
70    trap 'print INT1' INT
71    fn2() { trap 'print INT2' INT; trap; }
72    trap
73    fn2
74    trap
75  }
76  fn1
770: Nested `trap ... INT', not triggered
78>trap -- 'print INT1' INT
79>trap -- 'print INT2' INT
80>trap -- 'print INT1' INT
81
82   fn1() {
83    trap -
84    trap
85    TRAPINT() { print INT1; }
86    fn2() { TRAPINT() { print INT2; }; trap; }
87    trap
88    fn2
89    trap
90  }
91  fn1
920: Nested TRAPINT, not triggered
93>TRAPINT () {
94>	print INT1
95>}
96>TRAPINT () {
97>	print INT2
98>}
99>TRAPINT () {
100>	print INT1
101>}
102
103  fn1() {
104    trap -
105    trap 'print INT1' INT
106    fn2() { trap - INT; trap; }
107    trap
108    fn2
109    trap
110  }
111  fn1
1120: Nested `trap - INT' on untriggered `trap ... INT'
113>trap -- 'print INT1' INT
114>trap -- 'print INT1' INT
115
116# Testing the triggering of traps here is very unpleasant.
117# The delays are attempts to avoid race conditions, though there is
118# no guarantee that they will work.  Note the subtlety that the
119# `sleep' in the function which receives the trap does *not* get the
120# signal, only the parent shell, which is waiting for a SIGCHILD.
121# (At least, that's what I think is happening.) Thus we have to wait at
122# least the full two seconds to make sure we have got the output from the
123# execution of the trap.
124
125  print -u $ZTST_fd 'This test takes at least three seconds...'
126  fn1() {
127    trap 'print TERM1' TERM
128    fn2() { trap 'print TERM2; return 1' TERM; sleep 2; }
129    fn2 &
130    sleep 1
131    kill -TERM $!
132    sleep 2
133  }
134  fn1
1350: Nested `trap ... TERM', triggered on inner loop
136>TERM2
137
138  print -u $ZTST_fd 'This test, too, takes at least three seconds...'
139  fn1() {
140    trap 'print TERM1; return 1' TERM
141    fn2() { trap 'print TERM2; return 1' TERM; }
142    fn2
143    sleep 2
144  }
145  fn1 &
146  sleep 1
147  kill -TERM $!
148  sleep 2
1490: Nested `trap ... TERM', triggered on outer loop
150>TERM1
151
152  TRAPZERR() { print error activated; }
153  fn() { print start of fn; false; print end of fn; }
154  fn
155  fn() {
156    setopt localoptions localtraps
157    unfunction TRAPZERR
158    print start of fn
159    false
160    print end of fn
161  }
162  fn
163  unfunction TRAPZERR
164  print finish
1650: basic localtraps handling
166>start of fn
167>error activated
168>end of fn
169>start of fn
170>end of fn
171>finish
172
173  TRAPZERR() { print 'ERR-or!'; }
174  f() { print f; false; }
175  t() { print t; }
176  f
177  f && t
178  t && f && true
179  t && f
180  testunset() {
181    setopt localtraps
182    unset -f TRAPZERR
183    print testunset
184    false
185    true
186  }
187  testunset
188  f
189  print status $?
190  unfunction TRAPZERR
1910: more sophisticated error trapping
192>f
193>ERR-or!
194>f
195>t
196>f
197>t
198>f
199>ERR-or!
200>testunset
201>f
202>ERR-or!
203>status 1
204
205  f() {
206    setopt localtraps
207    TRAPWINCH() { print "Window changed.  That wrecked the test."; }
208  }
209  f
210  f
211  functions TRAPWINCH
2121:Unsetting ordinary traps with localtraps.
213
214#
215# Returns from within traps are a perennial problem.
216# The following two apply to returns in and around standard
217# ksh-style traps.  The intention is that a return value from
218# within the function is preserved (i.e. statuses set by the trap
219# are ignored) unless the trap explicitly executes `return', which makes
220# it return from the enclosing function.
221#
222  fn() { trap 'true' EXIT; return 1; }
223  fn
2241: ksh-style EXIT traps preserve return value
225
226  inner() { trap 'return 3' EXIT; return 2; }
227  outer() { inner; return 1; }
228  outer
2293: ksh-style EXIT traps can force return status of enclosing function
230
231# Autoloaded traps are horrid, but unfortunately people expect
232# them to work if we support them.
233  echo "print Running exit trap" >TRAPEXIT
234  ${${ZTST_exe##[^/]*}:-$ZTST_testdir/$ZTST_exe} -fc '
235    fpath=(. $fpath)
236    autoload TRAPEXIT
237    print "Exiting, attempt 1"
238    exit
239    print "What?"
240  '
241  ${${ZTST_exe##[^/]*}:-$ZTST_testdir/$ZTST_exe} -fc '
242    fpath=(. $fpath)
243    autoload TRAPEXIT;
244    fn() { print Some function }
245    fn
246    print "Exiting, attempt 2"
247    exit
248  '
2490: autoloaded TRAPEXIT (exit status > 128 indicates an old bug is back)
250>Exiting, attempt 1
251>Running exit trap
252>Some function
253>Exiting, attempt 2
254>Running exit trap
255
256  print -u $ZTST_fd Another test that takes three seconds
257  gotsig=0
258  signal_handler() {
259   echo "parent received signal"
260   gotsig=1
261  }
262  child() {
263   sleep 1
264   echo "child sending signal"
265   kill -15 $parentpid
266   sleep 2
267   echo "child exiting" 
268   exit 33
269  } 
270  parentpid=$$
271  child &
272  childpid=$!
273  trap signal_handler 15
274  echo "parent waiting"
275  wait $childpid
276  cstatus=$?
277  echo "wait #1 finished, gotsig=$gotsig, status=$cstatus"
278  gotsig=0
279  wait $childpid
280  cstatus=$?
281  echo "wait #2 finished, gotsig=$gotsig, status=$cstatus"
2820:waiting for trapped signal
283>parent waiting
284>child sending signal
285>parent received signal
286>wait #1 finished, gotsig=1, status=143
287>child exiting
288>wait #2 finished, gotsig=0, status=33
289
290  fn1() {
291    setopt errexit
292    trap 'echo error1' ZERR
293    false
294    print Shouldn\'t get here 1a
295  }
296  fn2() {
297    setopt errexit
298    trap 'echo error2' ZERR
299    return 1
300    print Shouldn\'t get here 2a
301  }
302  fn3() {
303    setopt errexit
304    TRAPZERR() { echo error3; }
305    false
306    print Shouldn\'t get here 3a
307  }
308  fn4() {
309    setopt errexit
310    TRAPZERR() { echo error4; }
311    return 1
312    print Shouldn\'t get here 4a
313  }
314  (fn1; print Shouldn\'t get here 1b)
315  (fn2; print Shouldn\'t get here 2b)
316  (fn3; print Shouldn\'t get here 3b)
317  (fn4; print Shouldn\'t get here 4b)
3181: Combination of ERR_EXIT and ZERR trap
319>error1
320>error2
321>error3
322>error4
323
324   fn1() { TRAPZERR() { print trap; return 42; }; false; print Broken; }
325   (fn1)
326   print Working $?
3270: Force return of containing function from TRAPZERR.
328>trap
329>Working 42
330
331   fn2() { trap 'print trap; return 42' ZERR; false; print Broken }
332   (fn2)
333   print Working $?
3340: Return with non-zero status triggered from within trap '...' ZERR.
335>trap
336>Working 42
337
338   fn3() { TRAPZERR() { print trap; return 0; }; false; print OK this time; }
339   (fn3)
340   print Working $?
3410: Normal return from TRAPZERR.
342>trap
343>OK this time
344>Working 0
345
346   fn4() { trap 'print trap; return 0' ZERR; false; print Broken; }
347   (fn4)
348   print Working $?
3490: Return with zero status triggered from within trap '...' ZERR.
350>trap
351>Working 0
352
353   { trap 'echo This subshell is exiting' EXIT; } | cat
3540: EXIT trap set in current shell at left of pipeline
355>This subshell is exiting
356
357   ( trap 'echo This subshell is also exiting' EXIT; ) | cat
3580: EXIT trap set in subshell at left of pipeline
359>This subshell is also exiting
360
361   ( trap 'echo Should only appear once at the end' EXIT
362     ( : trap reset here ) | cat
363     : trap not reset but not part of shell command list | cat
364     echo nothing after this should appear $( : trap reset here too)
365   )
3660: EXIT trap set in subshell reset in subsubshell
367>nothing after this should appear
368>Should only appear once at the end
369
370   echo $( trap 'echo command substitution exited' EXIT )
3710: EXIT trap set in command substitution
372>command substitution exited
373
374   (cd ..; $ZTST_exe -fc 'setopt posixtraps;
375   TRAPEXIT() { print Exited; }
376   fn1() { trap; }
377   setopt localtraps # should be ignored by EXIT
378   fn2() { TRAPEXIT() { print No, really exited; } }
379   fn1
380   fn2
381   fn1')
3820:POSIX_TRAPS option
383>TRAPEXIT () {
384>	print Exited
385>}
386>TRAPEXIT () {
387>	print No, really exited
388>}
389>No, really exited
390
391   (set -e
392    printf "a\nb\n" | while read line
393    do
394       [[ $line = a* ]] || continue
395       ((ctr++))
396       [[ $line = foo ]]
397    done
398    echo "ctr = $ctr"
399   )
4001:ERREXIT in loop with simple commands
401
402%clean
403
404  rm -f TRAPEXIT
405