1 | #!/usr/bin/env bash
|
2 | #
|
3 | # Show problems with errexit.
|
4 | #
|
5 | # Usage:
|
6 | # ./errexit-confusion.sh <function name>
|
7 |
|
8 | set -o nounset
|
9 | set -o pipefail
|
10 | set -o errexit
|
11 |
|
12 | log() {
|
13 | echo "$@" 1>&2
|
14 | }
|
15 |
|
16 | die() {
|
17 | log "$@"
|
18 | exit 1
|
19 | }
|
20 |
|
21 | all-passing() {
|
22 | bin/osh --parse-and-print-arena foo # This fails
|
23 | echo status=$? # succeeds
|
24 | }
|
25 |
|
26 | # Copied from test/common.sh, to show the bug.
|
27 |
|
28 | # The bug is that errexit is disabled within if. Fixes tried:
|
29 | #
|
30 | # 1) Putting it on its own line: Then errexit triggers and you don't get the
|
31 | # failure message.
|
32 | #
|
33 | # $func_name 2>&1
|
34 | #
|
35 | # 2) Disabling with
|
36 | #
|
37 | # set +o errexit
|
38 | # $func_name 2>&1
|
39 | # status=$?
|
40 | # set -o errexit
|
41 | #
|
42 | # The problem is that the LAST line of all-passing succeeds. We want it to
|
43 | # fail in the middle.
|
44 |
|
45 | run-other-suite-for-release-OLD() {
|
46 | local suite_name=$1
|
47 | local func_name=$2
|
48 |
|
49 | local out=_tmp/other/${suite_name}.txt
|
50 | mkdir -p $(dirname $out)
|
51 |
|
52 | echo
|
53 | echo "*** Running test suite '$suite_name' ***"
|
54 | echo
|
55 |
|
56 | if $func_name 2>&1; then
|
57 | echo
|
58 | log "Test suite '$suite_name' ran without errors. Wrote $out"
|
59 | else
|
60 | echo
|
61 | die "Test suite '$suite_name' failed (running $func_name)"
|
62 | fi
|
63 | }
|
64 |
|
65 | # This is an awkward rewrite that works.
|
66 | #
|
67 | # Again the argv dispatch pattern saves the day! You can test if a function
|
68 | # failed while preserving its own errexit semantics!
|
69 | #
|
70 | # This composes!
|
71 |
|
72 | run-other-suite-for-release-FIXED() {
|
73 | local suite_name=$1
|
74 | local func_name=$2
|
75 |
|
76 | local out=_tmp/other/${suite_name}.txt
|
77 | mkdir -p $(dirname $out)
|
78 |
|
79 | echo
|
80 | echo "*** Running test suite '$suite_name' ***"
|
81 | echo
|
82 |
|
83 | local status=0
|
84 |
|
85 | # Run in a separate SHELL, not just in a separate process. ( $func_name )
|
86 | # doesn't work.
|
87 | $0 $func_name 2>&1 | tee $out || status=$?
|
88 |
|
89 | if test $status -eq 0; then
|
90 | echo
|
91 | log "Test suite '$suite_name' ran without errors. Wrote '$out'"
|
92 | else
|
93 | echo
|
94 | die "Test suite '$suite_name' failed with status $status (running '$func_name', wrote '$out')"
|
95 | fi
|
96 | }
|
97 |
|
98 | run-for-release-OLD() {
|
99 | run-other-suite-for-release-OLD example-failure all-passing
|
100 | }
|
101 |
|
102 | run-for-release-FIXED() {
|
103 | run-other-suite-for-release-FIXED example-failure all-passing
|
104 | }
|
105 |
|
106 | # This could be a blog post:
|
107 | #
|
108 | # Conditions for the problem:
|
109 | # - using errexit (pipefail)
|
110 | # - but you want to test if a FUNCTION failed. If you disable errexit, you are
|
111 | # changing the semantics of the function!
|
112 | #
|
113 | # Solution:
|
114 | #
|
115 | # 1. Use the argv dispatch pattern
|
116 | # 2. Use $0 $func_name || status=$?
|
117 |
|
118 | # -----------------------------------------------------------------------------
|
119 |
|
120 | # Another different that went away with the FIXED: A case where the stricter
|
121 | # behavior of OSH's errexit was triggered.
|
122 |
|
123 | # Can't set 'errexit' in a context where it's disabled (if, !, && ||,
|
124 | # while/until conditions)
|
125 | #
|
126 | # Arguably this is exposing a bug? errexit is already disabled? May have to
|
127 | # revisit this.
|
128 |
|
129 | test-case-that-sets-errexit() {
|
130 | set +o errexit
|
131 | echo hi
|
132 | }
|
133 |
|
134 | osh-stricter() {
|
135 | run-other-suite-for-release-OLD osh-stricter test-case-that-sets-errexit
|
136 | }
|
137 |
|
138 | "$@"
|
139 |
|