OILS / spec / errexit-osh.test.sh View on Github | oilshell.org

758 lines, 409 significant
1
2## compare_shells: bash dash mksh ash
3
4# OSH mechanisms:
5#
6# - shopt -s strict_errexit
7# - shopt -s command_sub_errexit
8# - inherit_errexit (bash)
9#
10# Summary:
11# - local assignment is different than global! The exit code and errexit
12# behavior are different because the concept of the "last command" is
13# different.
14# - ash has copied bash behavior!
15
16#### command sub: errexit is NOT inherited and outer shell keeps going
17
18# This is the bash-specific bug here:
19# https://blogs.janestreet.com/when-bash-scripts-bite/
20# See inherit_errexit below.
21#
22# I remember finding a script that relies on bash's bad behavior, so OSH copies
23# it. But you can opt in to better behavior.
24
25set -o errexit
26echo $(echo one; false; echo two) # bash/ash keep going
27echo parent status=$?
28## STDOUT:
29one two
30parent status=0
31## END
32# dash and mksh: inner shell aborts, but outer one keeps going!
33## OK dash/mksh STDOUT:
34one
35parent status=0
36## END
37
38#### command sub with inherit_errexit only
39set -o errexit
40shopt -s inherit_errexit || true
41echo zero
42echo $(echo one; false; echo two) # bash/ash keep going
43echo parent status=$?
44## STDOUT:
45zero
46one
47parent status=0
48## END
49## N-I ash STDOUT:
50zero
51one two
52parent status=0
53## END
54
55#### strict_errexit and assignment builtins (local, export, readonly ...)
56set -o errexit
57shopt -s strict_errexit || true
58#shopt -s command_sub_errexit || true
59
60f() {
61 local x=$(echo hi; false)
62 echo x=$x
63}
64
65eval 'f'
66echo ---
67
68## status: 1
69## STDOUT:
70## END
71## N-I dash/bash/mksh/ash status: 0
72## N-I dash/bash/mksh/ash STDOUT:
73x=hi
74---
75## END
76
77#### strict_errexit and command sub in export / readonly
78case $SH in (dash|bash|mksh|ash) exit ;; esac
79
80$SH -o errexit -O strict_errexit -c 'echo a; export x=$(might-fail); echo b'
81echo status=$?
82$SH -o errexit -O strict_errexit -c 'echo a; readonly x=$(might-fail); echo b'
83echo status=$?
84$SH -o errexit -O strict_errexit -c 'echo a; x=$(true); echo b'
85echo status=$?
86
87## STDOUT:
88a
89status=1
90a
91status=1
92a
93b
94status=0
95## END
96## N-I dash/bash/mksh/ash stdout-json: ""
97
98
99#### strict_errexit disallows pipeline
100set -o errexit
101shopt -s strict_errexit || true
102
103if echo 1 | grep 1; then
104 echo one
105fi
106
107## status: 1
108## N-I dash/bash/mksh/ash status: 0
109## N-I dash/bash/mksh/ash STDOUT:
1101
111one
112## END
113
114#### strict_errexit allows singleton pipeline
115set -o errexit
116shopt -s strict_errexit || true
117
118if ! false; then
119 echo yes
120fi
121
122## STDOUT:
123yes
124## END
125
126#### strict_errexit without errexit proc
127myproc() {
128 echo myproc
129}
130myproc || true
131
132# This should be a no-op I guess
133shopt -s strict_errexit || true
134myproc || true
135
136## status: 1
137## STDOUT:
138myproc
139## END
140## N-I dash/bash/mksh/ash status: 0
141## N-I dash/bash/mksh/ash STDOUT:
142myproc
143myproc
144## END
145
146#### strict_errexit without errexit proc / command sub
147
148# Implementation quirk:
149# - The proc check happens only if errexit WAS on and is disabled
150# - But 'shopt --unset allow_csub_psub' happens if it was never on
151
152shopt -s strict_errexit || true
153
154p() {
155 echo before
156 local x
157 # This line fails, which is a bit weird, but errexit
158 x=$(false)
159 echo x=$x
160}
161
162if p; then
163 echo ok
164fi
165
166## N-I dash/bash/mksh/ash status: 0
167## N-I dash/bash/mksh/ash STDOUT:
168before
169x=
170ok
171## END
172## status: 1
173## STDOUT:
174## END
175
176#### strict_errexit and errexit disabled
177case $SH in (dash|bash|mksh|ash) exit ;; esac
178
179shopt -s parse_brace strict_errexit || true
180
181p() {
182 echo before
183 local x
184 # This line fails, which is a bit weird, but errexit
185 x=$(false)
186 echo x=$x
187}
188
189set -o errexit
190shopt --unset errexit {
191 # It runs normally here, because errexit was disabled (just not by a
192 # conditional)
193 p
194}
195## N-I dash/bash/mksh/ash STDOUT:
196## END
197## STDOUT:
198before
199x=
200## END
201
202
203#### command sub with command_sub_errexit only
204set -o errexit
205shopt -s command_sub_errexit || true
206echo zero
207echo $(echo one; false; echo two) # bash/ash keep going
208echo parent status=$?
209## STDOUT:
210zero
211one two
212parent status=0
213## END
214## N-I dash/mksh STDOUT:
215zero
216one
217parent status=0
218## END
219
220#### command_sub_errexit stops at first error
221case $SH in (dash|bash|mksh|ash) exit ;; esac
222
223set -o errexit
224shopt --set parse_brace command_sub_errexit verbose_errexit || true
225
226rm -f BAD
227
228try {
229 echo $(date %d) $(touch BAD)
230}
231if ! test -f BAD; then # should not exist
232 echo OK
233fi
234
235## STDOUT:
236OK
237## END
238## N-I dash/bash/mksh/ash STDOUT:
239## END
240
241#### command sub with inherit_errexit and command_sub_errexit
242set -o errexit
243
244# bash implements inherit_errexit, but it's not as strict as OSH.
245shopt -s inherit_errexit || true
246shopt -s command_sub_errexit || true
247echo zero
248echo $(echo one; false; echo two) # bash/ash keep going
249echo parent status=$?
250## STDOUT:
251zero
252## END
253## status: 1
254## N-I dash/mksh/bash status: 0
255## N-I dash/mksh/bash STDOUT:
256zero
257one
258parent status=0
259## END
260## N-I ash status: 0
261## N-I ash STDOUT:
262zero
263one two
264parent status=0
265## END
266
267#### command sub: last command fails but keeps going and exit code is 0
268set -o errexit
269echo $(echo one; false) # we lost the exit code
270echo status=$?
271## STDOUT:
272one
273status=0
274## END
275
276#### global assignment with command sub: middle command fails
277set -o errexit
278s=$(echo one; false; echo two;)
279echo "$s"
280## status: 0
281## STDOUT:
282one
283two
284## END
285# dash and mksh: whole thing aborts!
286## OK dash/mksh stdout-json: ""
287## OK dash/mksh status: 1
288
289#### global assignment with command sub: last command fails and it aborts
290set -o errexit
291s=$(echo one; false)
292echo status=$?
293## stdout-json: ""
294## status: 1
295
296#### local: middle command fails and keeps going
297set -o errexit
298f() {
299 echo good
300 local x=$(echo one; false; echo two)
301 echo status=$?
302 echo $x
303}
304f
305## STDOUT:
306good
307status=0
308one two
309## END
310# for dash and mksh, the INNER shell aborts, but the outer one keeps going!
311## OK dash/mksh STDOUT:
312good
313status=0
314one
315## END
316
317#### local: last command fails and also keeps going
318set -o errexit
319f() {
320 echo good
321 local x=$(echo one; false)
322 echo status=$?
323 echo $x
324}
325f
326## STDOUT:
327good
328status=0
329one
330## END
331
332#### local and inherit_errexit / command_sub_errexit
333# I've run into this problem a lot.
334set -o errexit
335shopt -s inherit_errexit || true # bash option
336shopt -s command_sub_errexit || true # oil option
337f() {
338 echo good
339 local x=$(echo one; false; echo two)
340 echo status=$?
341 echo $x
342}
343f
344## status: 1
345## STDOUT:
346good
347## END
348## N-I ash status: 0
349## N-I ash STDOUT:
350good
351status=0
352one two
353## END
354## N-I bash/dash/mksh status: 0
355## N-I bash/dash/mksh STDOUT:
356good
357status=0
358one
359## END
360
361#### global assignment when last status is failure
362# this is a bug I introduced
363set -o errexit
364x=$(false) || true # from abuild
365[ -n "$APORTSDIR" ] && true
366BUILDDIR=${_BUILDDIR-$BUILDDIR}
367echo status=$?
368## STDOUT:
369status=0
370## END
371
372#### strict_errexit prevents errexit from being disabled in function
373set -o errexit
374fun() { echo fun; }
375
376fun || true # this is OK
377
378shopt -s strict_errexit || true
379
380echo 'builtin ok' || true
381env echo 'external ok' || true
382
383fun || true # this fails
384
385## status: 1
386## STDOUT:
387fun
388builtin ok
389external ok
390## END
391## N-I dash/bash/mksh/ash status: 0
392## N-I dash/bash/mksh/ash STDOUT:
393fun
394builtin ok
395external ok
396fun
397## END
398
399#### strict_errexit prevents errexit from being disabled in brace group
400set -o errexit
401# false failure is NOT respected either way
402{ echo foo; false; echo bar; } || echo "failed"
403
404shopt -s strict_errexit || true
405{ echo foo; false; echo bar; } || echo "failed"
406## status: 1
407## STDOUT:
408foo
409bar
410## END
411
412## N-I dash/bash/mksh/ash status: 0
413## N-I dash/bash/mksh/ash STDOUT:
414foo
415bar
416foo
417bar
418## END
419
420#### strict_errexit prevents errexit from being disabled in subshell
421set -o errexit
422shopt -s inherit_errexit || true
423
424# false failure is NOT respected either way
425( echo foo; false; echo bar; ) || echo "failed"
426
427shopt -s strict_errexit || true
428( echo foo; false; echo bar; ) || echo "failed"
429## status: 1
430## STDOUT:
431foo
432bar
433## END
434
435## N-I dash/bash/mksh/ash status: 0
436## N-I dash/bash/mksh/ash STDOUT:
437foo
438bar
439foo
440bar
441## END
442
443#### strict_errexit and ! && || if while until
444prelude='set -o errexit
445shopt -s strict_errexit || true
446fun() { echo fun; }'
447
448$SH -c "$prelude; ! fun; echo 'should not get here'"
449echo bang=$?
450echo --
451
452$SH -c "$prelude; fun || true"
453echo or=$?
454echo --
455
456$SH -c "$prelude; fun && true"
457echo and=$?
458echo --
459
460$SH -c "$prelude; if fun; then true; fi"
461echo if=$?
462echo --
463
464$SH -c "$prelude; while fun; do echo while; exit; done"
465echo while=$?
466echo --
467
468$SH -c "$prelude; until fun; do echo until; exit; done"
469echo until=$?
470echo --
471
472
473## STDOUT:
474bang=1
475--
476or=1
477--
478and=1
479--
480if=1
481--
482while=1
483--
484until=1
485--
486## END
487## N-I dash/bash/mksh/ash STDOUT:
488fun
489should not get here
490bang=0
491--
492fun
493or=0
494--
495fun
496and=0
497--
498fun
499if=0
500--
501fun
502while
503while=0
504--
505fun
506until=0
507--
508## END
509
510#### if pipeline doesn't fail fatally
511set -o errexit
512set -o pipefail
513
514f() {
515 local dir=$1
516 if ls $dir | grep ''; then
517 echo foo
518 echo ${PIPESTATUS[@]}
519 fi
520}
521rmdir $TMP/_tmp || true
522rm -f $TMP/*
523f $TMP
524f /nonexistent # should fail
525echo done
526
527## N-I dash status: 2
528## N-I dash stdout-json: ""
529## STDOUT:
530done
531## END
532
533#### errexit is silent (verbose_errexit for Oil)
534shopt -u verbose_errexit 2>/dev/null || true
535set -e
536false
537## stderr-json: ""
538## status: 1
539
540#### command sub errexit preserves exit code
541set -e
542shopt -s command_sub_errexit || true
543
544echo before
545echo $(exit 42)
546echo after
547## STDOUT:
548before
549## END
550## status: 42
551## N-I dash/bash/mksh/ash STDOUT:
552before
553
554after
555## N-I dash/bash/mksh/ash status: 0
556
557#### What's in strict:all?
558
559# inherit_errexit, strict_errexit, but not command_sub_errexit!
560# for that you need oil:upgrade!
561
562set -o errexit
563shopt -s strict:all || true
564
565# inherit_errexit is bash compatible, so we have it
566#echo $(date %x)
567
568# command_sub_errexit would hide errors!
569f() {
570 local d=$(date %x)
571}
572f
573
574deploy_func() {
575 echo one
576 false
577 echo two
578}
579
580if ! deploy_func; then
581 echo failed
582fi
583
584echo 'should not get here'
585
586## status: 1
587## STDOUT:
588## END
589## N-I dash/bash/mksh/ash status: 0
590## N-I dash/bash/mksh/ash STDOUT:
591one
592two
593should not get here
594## END
595
596#### command_sub_errexit causes local d=$(date %x) to fail
597set -o errexit
598shopt -s inherit_errexit || true
599#shopt -s strict_errexit || true
600shopt -s command_sub_errexit || true
601
602myproc() {
603 # this is disallowed because we want a runtime error 100% of the time
604 local x=$(true)
605
606 # Realistic example. Should fail here but shells don't!
607 local d=$(date %x)
608 echo hi
609}
610myproc
611
612## status: 1
613## STDOUT:
614## END
615## N-I dash/bash/mksh/ash status: 0
616## N-I dash/bash/mksh/ash STDOUT:
617hi
618## END
619
620#### command_sub_errexit and command sub in array
621case $SH in (dash|ash|mksh) exit ;; esac
622
623set -o errexit
624shopt -s inherit_errexit || true
625#shopt -s strict_errexit || true
626shopt -s command_sub_errexit || true
627
628# We don't want silent failure here
629readonly -a myarray=( one "$(date %x)" two )
630
631#echo len=${#myarray[@]}
632argv.py "${myarray[@]}"
633## status: 1
634## STDOUT:
635## END
636## N-I bash status: 0
637## N-I bash STDOUT:
638['one', '', 'two']
639## END
640## N-I dash/ash/mksh status: 0
641
642#### OLD: command sub in conditional, with inherit_errexit
643set -o errexit
644shopt -s inherit_errexit || true
645if echo $(echo 1; false; echo 2); then
646 echo A
647fi
648echo done
649
650## STDOUT:
6511 2
652A
653done
654## END
655## N-I dash/mksh STDOUT:
6561
657A
658done
659## END
660
661#### OLD: command sub in redirect in conditional
662set -o errexit
663
664if echo tmp_contents > $(echo tmp); then
665 echo 2
666fi
667cat tmp
668## STDOUT:
6692
670tmp_contents
671## END
672
673#### Regression
674case $SH in (bash|dash|ash|mksh) exit ;; esac
675
676shopt --set oil:upgrade
677
678shopt --unset errexit {
679 echo hi
680}
681
682proc p {
683 echo p
684}
685
686shopt --unset errexit {
687 p
688}
689## STDOUT:
690hi
691p
692## END
693## N-I bash/dash/ash/mksh stdout-json: ""
694
695#### ShAssignment used as conditional
696
697while x=$(false)
698do
699 echo while
700done
701
702if x=$(false)
703then
704 echo if
705fi
706
707if x=$(true)
708then
709 echo yes
710fi
711
712# Same thing with errexit -- NOT affected
713set -o errexit
714
715while x=$(false)
716do
717 echo while
718done
719
720if x=$(false)
721then
722 echo if
723fi
724
725if x=$(true)
726then
727 echo yes
728fi
729
730# Same thing with strict_errexit -- NOT affected
731shopt -s strict_errexit || true
732
733while x=$(false)
734do
735 echo while
736done
737
738if x=$(false)
739then
740 echo if
741fi
742
743if x=$(true)
744then
745 echo yes
746fi
747
748## status: 1
749## STDOUT:
750yes
751yes
752## END
753## N-I dash/bash/mksh/ash status: 0
754## N-I dash/bash/mksh/ash STDOUT:
755yes
756yes
757yes
758## END