Skip to content

Commit 69ad67f

Browse files
authored
Merge pull request #164 from mbland/bats-function
Complete application of `lib/bats/helper-function` utilities everywhere
2 parents af36aa4 + e3f8fbf commit 69ad67f

File tree

5 files changed

+419
-286
lines changed

5 files changed

+419
-286
lines changed

lib/bats/assertion-test-helpers

Lines changed: 141 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
# a Bats assertion function complies with the expectations outlined in
77
# `$_GO_CORE_DIR/lib/bats/assertions`, namely that it:
88
#
9-
# - begins with `set "$BATS_ASSERTION_DISABLE_SHELL_OPTIONS"`
10-
# - calls `return_from_bats_assertion` directly via every return path
9+
# - begins with `set "$DISABLE_BATS_SHELL_OPTIONS"`
10+
# - calls `restore_bats_shell_options` directly via every return path
1111
#
1212
# They also enforce that the assertion produces no output when successful, and
1313
# that it writes only to standard error (`>&2`) when it fails.
@@ -30,14 +30,19 @@ if [[ -z "$ASSERTION_SOURCE" ]]; then
3030
fi
3131

3232
. "$ASSERTION_SOURCE"
33-
. "${BASH_SOURCE%/*}/helpers"
33+
34+
# Temp directory for assertion test artifacts.
35+
#
36+
# Defined the same as `BATS_TEST_ROOTDIR` from `lib/bats/helpers`, but not
37+
# dependent on it, so this file remains self-contained.
38+
export ASSERTION_TEST_ROOTDIR="${BATS_TEST_ROOTDIR:-$BATS_TMPDIR/test rootdir}"
3439

3540
# This is the file written to by `printf_to_test_output_file`.
36-
export TEST_OUTPUT_FILE="$BATS_TEST_ROOTDIR/test-output.txt"
41+
export TEST_OUTPUT_FILE="$ASSERTION_TEST_ROOTDIR/test-output.txt"
3742

3843
# Path to the script generated by `expect_assertion_*` to test whether
39-
# `return_from_bats_assertion` was called or not.
40-
readonly ASSERTION_TEST_SCRIPT="$BATS_TEST_ROOTDIR/assertion-test.bats"
44+
# `restore_bats_shell_options` was called or not.
45+
readonly ASSERTION_TEST_SCRIPT="$ASSERTION_TEST_ROOTDIR/assertion-test.bats"
4146

4247
# Format for the error message emitted when `ASSERTION_TEST_SCRIPT` fails.
4348
# Should be called as:
@@ -48,11 +53,11 @@ export ASSERTION_TEST_SCRIPT_FAILURE_MESSAGE=
4853
! read -rd '' ASSERTION_TEST_SCRIPT_FAILURE_MESSAGE <<END_OF_FAILURE_MESSAGE
4954
The very first line of '%s' must be
5055
51-
set "\$BATS_ASSERTION_DISABLE_SHELL_OPTIONS"
56+
set "\$DISABLE_BATS_SHELL_OPTIONS"
5257
53-
and it must call 'return_from_bats_assertion' directly from every return path.
58+
and it must call 'restore_bats_shell_options' directly from every return path.
5459
55-
For details, see the comments for 'return_from_bats_assertion' from:
60+
For details, see the comments for 'restore_bats_shell_options' from:
5661
5762
${BASH_SOURCE%/*}/assertions
5863
END_OF_FAILURE_MESSAGE
@@ -62,116 +67,32 @@ END_OF_FAILURE_MESSAGE
6267
# Will execute the command and assertion directly using the `run` command to
6368
# make sure the condition is satisfied as expected, then it will execute them
6469
# in a test script to make sure the assertion calls `set
65-
# "$BATS_ASSERTION_DISABLE_SHELL_OPTIONS` and `return_from_bats_assertion`
66-
# appropriately.
70+
# "$DISABLE_BATS_SHELL_OPTIONS` and `restore_bats_shell_options` appropriately.
6771
#
6872
# Arguments:
6973
# run_cmd: The full command to pass to `run` as a single string
7074
# assertion: The full assertion to evaluate as a single string
7175
expect_assertion_success() {
7276
set +eET
73-
local run_cmd="$1"
74-
local assertion="$2"
75-
local test_script="$ASSERTION_TEST_SCRIPT"
76-
local __assertion_output
77-
local __assertion_status
78-
local expected_output=()
79-
80-
if ! __run_command_and_assertion_in_subshell "$run_cmd" "$assertion"; then
81-
__return_from_expect_assertion '1'
82-
elif [[ "$__assertion_status" -ne '0' ]]; then
83-
printf "In subshell: expected passing status, actual %d\nOutput:\n%s\n" \
84-
"$__assertion_status" "$__assertion_output" >&2
85-
__return_from_expect_assertion '1'
86-
elif ! __check_expected_output "$__assertion_output"; then
87-
printf "'%s' should not produce output when successful.\n" \
88-
"${assertion%% *}" >&2
89-
__return_from_expect_assertion '1'
90-
else
91-
# Although we expect the assertion under test to pass, this script injects a
92-
# failing assertion after it to check that the assertion under test directly
93-
# calls `return_from_bats_assertion` upon returning. If it doesn't, `set
94-
# -eET` will not be in effect, so the failing assertion will not trigger the
95-
# ERR trap or fail the test case.
96-
#
97-
# In an earlier incarnation of `return_from_bats_assertion` that only
98-
# restored `set -o functrace` (and when tests only started with `set +o
99-
# functrace`, equivalent to `set +T`), the failing assertion would fire the
100-
# ERR trap and exit the test case, but Bats would show the passing
101-
# assertion's stack, per issue #48.
102-
__run_assertion_test_script \
103-
" run $run_cmd" \
104-
" failing_assertion() { [ 0 -eq 1 ]; }" \
105-
" $assertion" \
106-
" failing_assertion"
107-
108-
expected_output=('1..1'
109-
"not ok 1 $BATS_TEST_DESCRIPTION"
110-
"# (from function \`failing_assertion' in file $test_script, line 5,"
111-
"# in test file $test_script, line 7)"
112-
"# \`failing_assertion' failed")
113-
114-
if ! __check_expected_output "$output" "${expected_output[@]}"; then
115-
printf "$ASSERTION_TEST_SCRIPT_FAILURE_MESSAGE" "${assertion%% *}" >&2
116-
__return_from_expect_assertion '1'
117-
else
118-
__return_from_expect_assertion
119-
fi
120-
fi
77+
__expect_assertion_success "$@"
78+
__return_from_expect_assertion "$?"
12179
}
12280

12381
# Validates that an assertion fails for the provided inputs
12482
#
12583
# Will execute the command and assertion directly using the `run` command to
12684
# make sure the condition fails and the output is as expected, then it will
12785
# execute them in a test script to make sure the assertion calls `set
128-
# "$BATS_ASSERTION_DISABLE_SHELL_OPTIONS` and `return_from_bats_assertion`
129-
# appropriately.
86+
# "$DISABLE_BATS_SHELL_OPTIONS` and `restore_bats_shell_options` appropriately.
13087
#
13188
# Arguments:
13289
# run_cmd: The full command to pass to `run` as a single string
13390
# assertion: The full assertion to evaluate as a single string
13491
# expected: The expected output of the assertion failure
13592
expect_assertion_failure() {
13693
set +eET
137-
local run_cmd="$1"
138-
local assertion="$2"
139-
shift 2
140-
local __assertion_output
141-
local __assertion_status
142-
local with_status
143-
local expected_output=()
144-
145-
if ! __run_command_and_assertion_in_subshell "$run_cmd" "$assertion"; then
146-
__return_from_expect_assertion '1'
147-
elif [[ "$__assertion_status" -eq '0' ]]; then
148-
printf "In subshell: expected failure, but succeeded\nOutput:\n%s\n" \
149-
"$__assertion_output" >&2
150-
__return_from_expect_assertion '1'
151-
else
152-
if [[ "$__assertion_status" -ne '1' ]]; then
153-
with_status=" with status $__assertion_status"
154-
fi
155-
156-
if ! __check_expected_output "$__assertion_output" "$@"; then
157-
__return_from_expect_assertion '1'
158-
else
159-
__run_assertion_test_script " run $run_cmd" " $assertion"
160-
161-
expected_output=('1..1'
162-
"not ok 1 $BATS_TEST_DESCRIPTION"
163-
"# (in test file $ASSERTION_TEST_SCRIPT, line 5)"
164-
"# \`$assertion' failed${with_status}"
165-
"${@/#/# }")
166-
167-
if ! __check_expected_output "$output" "${expected_output[@]}"; then
168-
printf "$ASSERTION_TEST_SCRIPT_FAILURE_MESSAGE" "${assertion%% *}" >&2
169-
__return_from_expect_assertion '1'
170-
else
171-
__return_from_expect_assertion
172-
fi
173-
fi
174-
fi
94+
__expect_assertion_failure "$@"
95+
__return_from_expect_assertion "$?"
17596
}
17697

17798
# Calls `printf` on its arguments and returns an error
@@ -204,8 +125,8 @@ export -f printf_with_error
204125
# Arguments:
205126
# ...: Arguments to `printf`
206127
printf_to_test_output_file() {
207-
if [[ ! -d "$BATS_TEST_ROOTDIR" ]]; then
208-
mkdir "$BATS_TEST_ROOTDIR"
128+
if [[ ! -d "$ASSERTION_TEST_ROOTDIR" ]]; then
129+
mkdir "$ASSERTION_TEST_ROOTDIR"
209130
fi
210131
printf "$@" >"$TEST_OUTPUT_FILE"
211132
}
@@ -217,6 +138,103 @@ export -f printf_to_test_output_file
217138
# None of the functions below this line are part of the public interface.
218139
# --------------------------------
219140

141+
# Implementation for `expect_assertion_success`
142+
#
143+
# Arguments:
144+
# run_cmd: The full command to pass to `run` as a single string
145+
# assertion: The full assertion to evaluate as a single string
146+
__expect_assertion_success() {
147+
local run_cmd="$1"
148+
local assertion="$2"
149+
local test_script="$ASSERTION_TEST_SCRIPT"
150+
local __assertion_output
151+
local __assertion_status
152+
local expected_output=()
153+
154+
if ! __run_command_and_assertion_in_subshell "$run_cmd" "$assertion"; then
155+
return '1'
156+
elif [[ "$__assertion_status" -ne '0' ]]; then
157+
printf "In subshell: expected passing status, actual %d\nOutput:\n%s\n" \
158+
"$__assertion_status" "$__assertion_output" >&2
159+
return '1'
160+
elif ! __check_expected_output "$__assertion_output"; then
161+
printf "'%s' should not produce output when successful.\n" \
162+
"${assertion%% *}" >&2
163+
return '1'
164+
fi
165+
166+
# Although we expect the assertion under test to pass, this script injects a
167+
# failing assertion after it to check that the assertion under test directly
168+
# calls `restore_bats_shell_options` upon returning. If it doesn't, `set
169+
# -eET` will not be in effect, so the failing assertion will not trigger the
170+
# ERR trap or fail the test case.
171+
#
172+
# In an earlier incarnation of `restore_bats_shell_options` that only
173+
# restored `set -o functrace` (and when tests only started with `set +o
174+
# functrace`, equivalent to `set +T`), the failing assertion would fire the
175+
# ERR trap and exit the test case, but Bats would show the passing
176+
# assertion's stack, per issue #48.
177+
__run_assertion_test_script \
178+
" run $run_cmd" \
179+
" failing_assertion() { [ 0 -eq 1 ]; }" \
180+
" $assertion" \
181+
" failing_assertion"
182+
183+
expected_output=('1..1'
184+
"not ok 1 $BATS_TEST_DESCRIPTION"
185+
"# (from function \`failing_assertion' in file $test_script, line 5,"
186+
"# in test file $test_script, line 7)"
187+
"# \`failing_assertion' failed")
188+
189+
if ! __check_expected_output "$output" "${expected_output[@]}"; then
190+
printf "$ASSERTION_TEST_SCRIPT_FAILURE_MESSAGE" "${assertion%% *}" >&2
191+
return '1'
192+
fi
193+
}
194+
195+
# Implementation for `expect_assertion_failure`
196+
#
197+
# Arguments:
198+
# run_cmd: The full command to pass to `run` as a single string
199+
# assertion: The full assertion to evaluate as a single string
200+
# expected: The expected output of the assertion failure
201+
__expect_assertion_failure() {
202+
local run_cmd="$1"
203+
local assertion="$2"
204+
shift 2
205+
local __assertion_output
206+
local __assertion_status
207+
local with_status
208+
local expected_output=()
209+
210+
if ! __run_command_and_assertion_in_subshell "$run_cmd" "$assertion"; then
211+
return '1'
212+
elif [[ "$__assertion_status" -eq '0' ]]; then
213+
printf "In subshell: expected failure, but succeeded\nOutput:\n%s\n" \
214+
"$__assertion_output" >&2
215+
return '1'
216+
fi
217+
218+
if [[ "$__assertion_status" -ne '1' ]]; then
219+
with_status=" with status $__assertion_status"
220+
elif ! __check_expected_output "$__assertion_output" "$@"; then
221+
return '1'
222+
fi
223+
224+
__run_assertion_test_script " run $run_cmd" " $assertion"
225+
226+
expected_output=('1..1'
227+
"not ok 1 $BATS_TEST_DESCRIPTION"
228+
"# (in test file $ASSERTION_TEST_SCRIPT, line 5)"
229+
"# \`$assertion' failed${with_status}"
230+
"${@/#/# }")
231+
232+
if ! __check_expected_output "$output" "${expected_output[@]}"; then
233+
printf "$ASSERTION_TEST_SCRIPT_FAILURE_MESSAGE" "${assertion%% *}" >&2
234+
return '1'
235+
fi
236+
}
237+
220238
# Passes the target command to `run`, the executes the `assertion` in a subshell
221239
#
222240
# The `run_cmd` is executed in-process to set `output`, `status`, and `lines`.
@@ -259,7 +277,7 @@ __run_command_and_assertion_in_subshell() {
259277
elif [[ -z "$__assertion_status" ]]; then
260278
__assertion_output="${__assertion_output%exit:*}"
261279

262-
if [[ "${FUNCNAME[1]}" == 'expect_assertion_failure' ]]; then
280+
if [[ "${FUNCNAME[1]}" == '__expect_assertion_failure' ]]; then
263281
printf '"%s" output does not end with a newline character:\n%s\n' \
264282
"${assertion%% *}" "${__assertion_output%exit:*}" >&2
265283
return 1
@@ -272,12 +290,26 @@ __run_command_and_assertion_in_subshell() {
272290
# Arguments:
273291
# ...: Body of the test case to execute
274292
__run_assertion_test_script() {
275-
create_bats_test_script "${ASSERTION_TEST_SCRIPT#$BATS_TEST_ROOTDIR/}" \
276-
'#! /usr/bin/env bats' \
277-
"load '$ASSERTION_SOURCE'" \
278-
"@test \"$BATS_TEST_DESCRIPTION\" {" \
279-
"$@" \
280-
'}'
293+
local script_dir="${ASSERTION_TEST_SCRIPT%/*}"
294+
local script_impl=('#! /usr/bin/env bats'
295+
"load '$ASSERTION_SOURCE'"
296+
"@test \"$BATS_TEST_DESCRIPTION\" {"
297+
"$@"
298+
'}')
299+
300+
if [[ ! -d "$script_dir" ]] && ! mkdir -p "$script_dir"; then
301+
printf 'Failed to create parent directory for assertion test script: %s\n' \
302+
"$ASSERTION_TEST_SCRIPT" >&2
303+
return '1'
304+
elif ! printf '%s\n' "${script_impl[@]}" >"$ASSERTION_TEST_SCRIPT"; then
305+
printf 'Failed to create assertion test script: %s\n' \
306+
"$ASSERTION_TEST_SCRIPT" >&2
307+
return '1'
308+
elif ! chmod 755 "$ASSERTION_TEST_SCRIPT"; then
309+
printf 'Failed to set permissions for assertion test script: %s\n' \
310+
"$ASSERTION_TEST_SCRIPT" >&2
311+
return '1'
312+
fi
281313
run "$ASSERTION_TEST_SCRIPT"
282314
}
283315

@@ -300,7 +332,7 @@ __check_expected_output() {
300332
fi
301333
}
302334

303-
# Basically the same as `return_from_bats_assertion`, but specific to this file.
335+
# Basically the same as `restore_bats_shell_options`, but specific to this file.
304336
#
305337
# Arguments:
306338
# result: Return value of the calling assertion; defaults to 0

0 commit comments

Comments
 (0)