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

814 lines, 440 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 with && || !
127set -o errexit
128shopt -s strict_errexit || true
129
130if true && true; then
131 echo A
132fi
133
134if true || false; then
135 echo B
136fi
137
138if ! false && ! false; then
139 echo C
140fi
141
142## STDOUT:
143A
144B
145C
146## END
147
148#### strict_errexit detects proc in && || !
149set -o errexit
150shopt -s strict_errexit || true
151
152myfunc() {
153 echo 'failing'
154 false
155 echo 'should not get here'
156}
157
158if true && ! myfunc; then
159 echo B
160fi
161
162if ! myfunc; then
163 echo A
164fi
165
166## status: 1
167## STDOUT:
168## END
169
170# POSIX shell behavior:
171
172## OK bash/dash/mksh/ash status: 0
173## OK bash/dash/mksh/ash STDOUT:
174failing
175should not get here
176failing
177should not get here
178## END
179
180
181
182#### strict_errexit without errexit proc
183myproc() {
184 echo myproc
185}
186myproc || true
187
188# This should be a no-op I guess
189shopt -s strict_errexit || true
190myproc || true
191
192## status: 1
193## STDOUT:
194myproc
195## END
196## N-I dash/bash/mksh/ash status: 0
197## N-I dash/bash/mksh/ash STDOUT:
198myproc
199myproc
200## END
201
202#### strict_errexit without errexit proc / command sub
203
204# Implementation quirk:
205# - The proc check happens only if errexit WAS on and is disabled
206# - But 'shopt --unset allow_csub_psub' happens if it was never on
207
208shopt -s strict_errexit || true
209
210p() {
211 echo before
212 local x
213 # This line fails, which is a bit weird, but errexit
214 x=$(false)
215 echo x=$x
216}
217
218if p; then
219 echo ok
220fi
221
222## N-I dash/bash/mksh/ash status: 0
223## N-I dash/bash/mksh/ash STDOUT:
224before
225x=
226ok
227## END
228## status: 1
229## STDOUT:
230## END
231
232#### strict_errexit and errexit disabled
233case $SH in (dash|bash|mksh|ash) exit ;; esac
234
235shopt -s parse_brace strict_errexit || true
236
237p() {
238 echo before
239 local x
240 # This line fails, which is a bit weird, but errexit
241 x=$(false)
242 echo x=$x
243}
244
245set -o errexit
246shopt --unset errexit {
247 # It runs normally here, because errexit was disabled (just not by a
248 # conditional)
249 p
250}
251## N-I dash/bash/mksh/ash STDOUT:
252## END
253## STDOUT:
254before
255x=
256## END
257
258
259#### command sub with command_sub_errexit only
260set -o errexit
261shopt -s command_sub_errexit || true
262echo zero
263echo $(echo one; false; echo two) # bash/ash keep going
264echo parent status=$?
265## STDOUT:
266zero
267one two
268parent status=0
269## END
270## N-I dash/mksh STDOUT:
271zero
272one
273parent status=0
274## END
275
276#### command_sub_errexit stops at first error
277case $SH in (dash|bash|mksh|ash) exit ;; esac
278
279set -o errexit
280shopt --set parse_brace command_sub_errexit verbose_errexit || true
281
282rm -f BAD
283
284try {
285 echo $(date %d) $(touch BAD)
286}
287if ! test -f BAD; then # should not exist
288 echo OK
289fi
290
291## STDOUT:
292OK
293## END
294## N-I dash/bash/mksh/ash STDOUT:
295## END
296
297#### command sub with inherit_errexit and command_sub_errexit
298set -o errexit
299
300# bash implements inherit_errexit, but it's not as strict as OSH.
301shopt -s inherit_errexit || true
302shopt -s command_sub_errexit || true
303echo zero
304echo $(echo one; false; echo two) # bash/ash keep going
305echo parent status=$?
306## STDOUT:
307zero
308## END
309## status: 1
310## N-I dash/mksh/bash status: 0
311## N-I dash/mksh/bash STDOUT:
312zero
313one
314parent status=0
315## END
316## N-I ash status: 0
317## N-I ash STDOUT:
318zero
319one two
320parent status=0
321## END
322
323#### command sub: last command fails but keeps going and exit code is 0
324set -o errexit
325echo $(echo one; false) # we lost the exit code
326echo status=$?
327## STDOUT:
328one
329status=0
330## END
331
332#### global assignment with command sub: middle command fails
333set -o errexit
334s=$(echo one; false; echo two;)
335echo "$s"
336## status: 0
337## STDOUT:
338one
339two
340## END
341# dash and mksh: whole thing aborts!
342## OK dash/mksh stdout-json: ""
343## OK dash/mksh status: 1
344
345#### global assignment with command sub: last command fails and it aborts
346set -o errexit
347s=$(echo one; false)
348echo status=$?
349## stdout-json: ""
350## status: 1
351
352#### local: middle command fails and keeps going
353set -o errexit
354f() {
355 echo good
356 local x=$(echo one; false; echo two)
357 echo status=$?
358 echo $x
359}
360f
361## STDOUT:
362good
363status=0
364one two
365## END
366# for dash and mksh, the INNER shell aborts, but the outer one keeps going!
367## OK dash/mksh STDOUT:
368good
369status=0
370one
371## END
372
373#### local: last command fails and also keeps going
374set -o errexit
375f() {
376 echo good
377 local x=$(echo one; false)
378 echo status=$?
379 echo $x
380}
381f
382## STDOUT:
383good
384status=0
385one
386## END
387
388#### local and inherit_errexit / command_sub_errexit
389# I've run into this problem a lot.
390set -o errexit
391shopt -s inherit_errexit || true # bash option
392shopt -s command_sub_errexit || true # oil option
393f() {
394 echo good
395 local x=$(echo one; false; echo two)
396 echo status=$?
397 echo $x
398}
399f
400## status: 1
401## STDOUT:
402good
403## END
404## N-I ash status: 0
405## N-I ash STDOUT:
406good
407status=0
408one two
409## END
410## N-I bash/dash/mksh status: 0
411## N-I bash/dash/mksh STDOUT:
412good
413status=0
414one
415## END
416
417#### global assignment when last status is failure
418# this is a bug I introduced
419set -o errexit
420x=$(false) || true # from abuild
421[ -n "$APORTSDIR" ] && true
422BUILDDIR=${_BUILDDIR-$BUILDDIR}
423echo status=$?
424## STDOUT:
425status=0
426## END
427
428#### strict_errexit prevents errexit from being disabled in function
429set -o errexit
430fun() { echo fun; }
431
432fun || true # this is OK
433
434shopt -s strict_errexit || true
435
436echo 'builtin ok' || true
437env echo 'external ok' || true
438
439fun || true # this fails
440
441## status: 1
442## STDOUT:
443fun
444builtin ok
445external ok
446## END
447## N-I dash/bash/mksh/ash status: 0
448## N-I dash/bash/mksh/ash STDOUT:
449fun
450builtin ok
451external ok
452fun
453## END
454
455#### strict_errexit prevents errexit from being disabled in brace group
456set -o errexit
457# false failure is NOT respected either way
458{ echo foo; false; echo bar; } || echo "failed"
459
460shopt -s strict_errexit || true
461{ echo foo; false; echo bar; } || echo "failed"
462## status: 1
463## STDOUT:
464foo
465bar
466## END
467
468## N-I dash/bash/mksh/ash status: 0
469## N-I dash/bash/mksh/ash STDOUT:
470foo
471bar
472foo
473bar
474## END
475
476#### strict_errexit prevents errexit from being disabled in subshell
477set -o errexit
478shopt -s inherit_errexit || true
479
480# false failure is NOT respected either way
481( echo foo; false; echo bar; ) || echo "failed"
482
483shopt -s strict_errexit || true
484( echo foo; false; echo bar; ) || echo "failed"
485## status: 1
486## STDOUT:
487foo
488bar
489## END
490
491## N-I dash/bash/mksh/ash status: 0
492## N-I dash/bash/mksh/ash STDOUT:
493foo
494bar
495foo
496bar
497## END
498
499#### strict_errexit and ! && || if while until
500prelude='set -o errexit
501shopt -s strict_errexit || true
502fun() { echo fun; }'
503
504$SH -c "$prelude; ! fun; echo 'should not get here'"
505echo bang=$?
506echo --
507
508$SH -c "$prelude; fun || true"
509echo or=$?
510echo --
511
512$SH -c "$prelude; fun && true"
513echo and=$?
514echo --
515
516$SH -c "$prelude; if fun; then true; fi"
517echo if=$?
518echo --
519
520$SH -c "$prelude; while fun; do echo while; exit; done"
521echo while=$?
522echo --
523
524$SH -c "$prelude; until fun; do echo until; exit; done"
525echo until=$?
526echo --
527
528
529## STDOUT:
530bang=1
531--
532or=1
533--
534and=1
535--
536if=1
537--
538while=1
539--
540until=1
541--
542## END
543## N-I dash/bash/mksh/ash STDOUT:
544fun
545should not get here
546bang=0
547--
548fun
549or=0
550--
551fun
552and=0
553--
554fun
555if=0
556--
557fun
558while
559while=0
560--
561fun
562until=0
563--
564## END
565
566#### if pipeline doesn't fail fatally
567set -o errexit
568set -o pipefail
569
570f() {
571 local dir=$1
572 if ls $dir | grep ''; then
573 echo foo
574 echo ${PIPESTATUS[@]}
575 fi
576}
577rmdir $TMP/_tmp || true
578rm -f $TMP/*
579f $TMP
580f /nonexistent # should fail
581echo done
582
583## N-I dash status: 2
584## N-I dash stdout-json: ""
585## STDOUT:
586done
587## END
588
589#### errexit is silent (verbose_errexit for Oil)
590shopt -u verbose_errexit 2>/dev/null || true
591set -e
592false
593## stderr-json: ""
594## status: 1
595
596#### command sub errexit preserves exit code
597set -e
598shopt -s command_sub_errexit || true
599
600echo before
601echo $(exit 42)
602echo after
603## STDOUT:
604before
605## END
606## status: 42
607## N-I dash/bash/mksh/ash STDOUT:
608before
609
610after
611## N-I dash/bash/mksh/ash status: 0
612
613#### What's in strict:all?
614
615# inherit_errexit, strict_errexit, but not command_sub_errexit!
616# for that you need oil:upgrade!
617
618set -o errexit
619shopt -s strict:all || true
620
621# inherit_errexit is bash compatible, so we have it
622#echo $(date %x)
623
624# command_sub_errexit would hide errors!
625f() {
626 local d=$(date %x)
627}
628f
629
630deploy_func() {
631 echo one
632 false
633 echo two
634}
635
636if ! deploy_func; then
637 echo failed
638fi
639
640echo 'should not get here'
641
642## status: 1
643## STDOUT:
644## END
645## N-I dash/bash/mksh/ash status: 0
646## N-I dash/bash/mksh/ash STDOUT:
647one
648two
649should not get here
650## END
651
652#### command_sub_errexit causes local d=$(date %x) to fail
653set -o errexit
654shopt -s inherit_errexit || true
655#shopt -s strict_errexit || true
656shopt -s command_sub_errexit || true
657
658myproc() {
659 # this is disallowed because we want a runtime error 100% of the time
660 local x=$(true)
661
662 # Realistic example. Should fail here but shells don't!
663 local d=$(date %x)
664 echo hi
665}
666myproc
667
668## status: 1
669## STDOUT:
670## END
671## N-I dash/bash/mksh/ash status: 0
672## N-I dash/bash/mksh/ash STDOUT:
673hi
674## END
675
676#### command_sub_errexit and command sub in array
677case $SH in (dash|ash|mksh) exit ;; esac
678
679set -o errexit
680shopt -s inherit_errexit || true
681#shopt -s strict_errexit || true
682shopt -s command_sub_errexit || true
683
684# We don't want silent failure here
685readonly -a myarray=( one "$(date %x)" two )
686
687#echo len=${#myarray[@]}
688argv.py "${myarray[@]}"
689## status: 1
690## STDOUT:
691## END
692## N-I bash status: 0
693## N-I bash STDOUT:
694['one', '', 'two']
695## END
696## N-I dash/ash/mksh status: 0
697
698#### OLD: command sub in conditional, with inherit_errexit
699set -o errexit
700shopt -s inherit_errexit || true
701if echo $(echo 1; false; echo 2); then
702 echo A
703fi
704echo done
705
706## STDOUT:
7071 2
708A
709done
710## END
711## N-I dash/mksh STDOUT:
7121
713A
714done
715## END
716
717#### OLD: command sub in redirect in conditional
718set -o errexit
719
720if echo tmp_contents > $(echo tmp); then
721 echo 2
722fi
723cat tmp
724## STDOUT:
7252
726tmp_contents
727## END
728
729#### Regression
730case $SH in (bash|dash|ash|mksh) exit ;; esac
731
732shopt --set oil:upgrade
733
734shopt --unset errexit {
735 echo hi
736}
737
738proc p {
739 echo p
740}
741
742shopt --unset errexit {
743 p
744}
745## STDOUT:
746hi
747p
748## END
749## N-I bash/dash/ash/mksh stdout-json: ""
750
751#### ShAssignment used as conditional
752
753while x=$(false)
754do
755 echo while
756done
757
758if x=$(false)
759then
760 echo if
761fi
762
763if x=$(true)
764then
765 echo yes
766fi
767
768# Same thing with errexit -- NOT affected
769set -o errexit
770
771while x=$(false)
772do
773 echo while
774done
775
776if x=$(false)
777then
778 echo if
779fi
780
781if x=$(true)
782then
783 echo yes
784fi
785
786# Same thing with strict_errexit -- NOT affected
787shopt -s strict_errexit || true
788
789while x=$(false)
790do
791 echo while
792done
793
794if x=$(false)
795then
796 echo if
797fi
798
799if x=$(true)
800then
801 echo yes
802fi
803
804## status: 1
805## STDOUT:
806yes
807yes
808## END
809## N-I dash/bash/mksh/ash status: 0
810## N-I dash/bash/mksh/ash STDOUT:
811yes
812yes
813yes
814## END