Skip to content

Commit 60f2ec6

Browse files
authored
Merge pull request #133 from mbland/plugin-scope-tests
Add plugin scope tests, new test helper, other related improvements
2 parents 790d07b + 37b3af4 commit 60f2ec6

File tree

9 files changed

+333
-29
lines changed

9 files changed

+333
-29
lines changed

go-core.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ declare _GO_INJECT_MODULE_PATH="$_GO_INJECT_MODULE_PATH"
259259
return 1
260260
fi
261261

262-
if [[ "${__go_cmd_path#$_GO_SCRIPTS_DIR}" =~ /plugins/ ]]; then
262+
if [[ "${__go_cmd_path#$_GO_SCRIPTS_DIR}" =~ /plugins/[^/]+/bin/ ]]; then
263263
_@go.run_plugin_command_script "$__go_cmd_path" "${__go_argv[@]}"
264264
else
265265
_@go.run_command_script "$__go_cmd_path" "${__go_argv[@]}"

lib/bats/assertions

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,21 @@ fail_if() {
149149
local constraints=()
150150
local constraint
151151
local i
152-
local bats_fail_no_output='true'
152+
local bats_fail_no_output=''
153153

154154
if [[ "$assertion" =~ _match ]]; then
155155
operation='match'
156156
fi
157157

158158
case "$assertion" in
159159
assert_equal|assert_matches)
160+
bats_fail_no_output='true'
160161
label="${3:-value}"
161162
constraints=("$1")
162163
value="$2"
163164
;;
164165
assert_output*|assert_status)
166+
bats_fail_no_output='true'
165167
label="${assertion#assert_}"
166168
label="${label%_*}"
167169
constraints=("$@")
@@ -173,12 +175,10 @@ fail_if() {
173175
value="${lines[$1]}"
174176
;;
175177
assert_lines_*)
176-
bats_fail_no_output=
177178
label="lines"
178179
constraints=("${@}")
179180
;;
180181
assert_file_*)
181-
bats_fail_no_output=
182182
label="'$1'"
183183
constraints=("${@:2}")
184184
;;

lib/bats/helpers

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ test_join() {
223223
# When debugging a piece of code, you may wish to source this file and
224224
# temporarily include `test_printf` to trace values in your program.
225225
#
226+
# Globals:
227+
# TEST_DEBUG: prints to stderr when not null, disables printing otherwise
228+
#
226229
# Arguments:
227230
# ...: Arguments to `printf`
228231
test_printf() {
@@ -232,6 +235,66 @@ test_printf() {
232235
}
233236
export -f test_printf
234237

238+
# Breaks execution after a number of iterations and prints context to stderr
239+
#
240+
# After reaching the number of iterations, this function prints to standard
241+
# error the caller's stack trace, prints the names and values of any variables
242+
# specified in the argument list (one per line), and exits the program with an
243+
# error.
244+
#
245+
# Add this to a point in your program to determine how a program reached a
246+
# certain line of execution and to examine its context.
247+
#
248+
# Globals:
249+
# TEST_DEBUG: enables breaking when not null, disables breaking otherwise
250+
#
251+
# Arguments:
252+
# num_iterations: The number of iterations after which execution will break
253+
# ...: Names of variables to print to standard error on break
254+
#
255+
# Returns:
256+
# Nonzero if `TEST_DEBUG` is set and `num_iterations` has been reached,
257+
# zero otherwise
258+
test_break_after_num_iterations() {
259+
local __tbani_num_iterations="$1"
260+
261+
# In Bash versions earlier than 4.4, `BASH_SOURCE` isn't set properly for the
262+
# main script, so it will be empty. So we use $0 in that case instead.
263+
local __tbani_id="${BASH_SOURCE[1]:-$0}_${BASH_LINENO[0]}_${FUNCNAME[1]}"
264+
265+
local __tbani_var="__tbani_current_iteration_${__tbani_id//[^[:alnum:]]/_}"
266+
local __tbani_caller_var
267+
local __tbani_do_exit
268+
local __tbani_i
269+
270+
if [[ ! "$__tbani_num_iterations" =~ ^[1-9][0-9]*$ ]]; then
271+
printf 'The argument to %s must be a positive integer at:\n' "$FUNCNAME" >&2
272+
__tbani_do_exit='true'
273+
elif [[ -z "$TEST_DEBUG" ]]; then
274+
return
275+
else
276+
printf -v "$__tbani_var" -- '%d' "$((${!__tbani_var} + 1))"
277+
if [[ "${!__tbani_var}" -eq "$__tbani_num_iterations" ]]; then
278+
printf 'Breaking after iteration %d at:\n' "$__tbani_num_iterations" >&2
279+
__tbani_do_exit='true'
280+
fi
281+
fi
282+
283+
if [[ -n "$__tbani_do_exit" ]]; then
284+
for ((__tbani_i=1; __tbani_i != "${#FUNCNAME[@]}"; ++__tbani_i)); do
285+
# Again notice the use of $0 to cover the case when BASH_SOURCE isn't set.
286+
printf ' %s:%s %s\n' "${BASH_SOURCE[$__tbani_i]:-$0}" \
287+
"${BASH_LINENO[$((__tbani_i-1))]}" "${FUNCNAME[$__tbani_i]}" >&2
288+
done
289+
290+
for __tbani_caller_var in "${@:2}"; do
291+
printf -- '%s: %s\n' "$__tbani_caller_var" "${!__tbani_caller_var}" >&2
292+
done
293+
exit 1
294+
fi
295+
}
296+
export -f test_break_after_num_iterations
297+
235298
# Skips a test if `TEST_FILTER` is set but doesn't match `BATS_TEST_DESCRIPTION`
236299
#
237300
# Call this from the `setup` function of your test suite if you'd like to

lib/internal/commands

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ _@go.merge_scripts_into_list() {
2323
rhs_name="${rhs_script##*/}"
2424

2525
if [[ "$lhs_name" == "$rhs_name" ]]; then
26-
printf "ERROR: duplicate command $lhs_name:\n\n %s\n %s\n" \
27-
"$lhs_script" "$rhs_script" >&2
28-
return 1
26+
result+=("$lhs_script")
27+
((++i))
28+
((++j))
2929
elif [[ "$lhs_name" < "$rhs_name" ]]; then
3030
result+=("$lhs_script")
3131
((++i))
@@ -56,10 +56,7 @@ _@go.find_commands() {
5656
scripts+=("$script")
5757
fi
5858
done
59-
60-
if ! _@go.merge_scripts_into_list "${scripts[@]#$_GO_ROOTDIR/}"; then
61-
return 1
62-
fi
59+
_@go.merge_scripts_into_list "${scripts[@]#$_GO_ROOTDIR/}"
6360
done
6461

6562
if [[ "${#__go_command_scripts[@]}" -eq '0' ]]; then

lib/internal/path

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ _@go.set_search_paths() {
1313
# A plugin's own local plugin paths will appear before inherited ones. If
1414
# there is a version incompatibility issue with other installed plugins, this
1515
# allows a plugin's preferred version to take precedence.
16-
while true; do
16+
while [[ "$plugins_dir" =~ ^$_GO_PLUGINS_DIR ]]; do
1717
plugin_paths=("$plugins_dir"/*/bin)
1818
if [[ "${plugin_paths[0]}" != "$plugins_dir/*/bin" ]]; then
1919
_GO_PLUGINS_PATHS+=("${plugin_paths[@]}")

tests/assertions.bats

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,10 @@ teardown() {
613613
expect_assertion_failure "echo 'Hello, world!'" \
614614
"fail_if line_equals '0' 'Hello, world!'" \
615615
'Expected line 0 not to equal:' \
616-
" 'Hello, world!'"
616+
" 'Hello, world!'" \
617+
'STATUS: 0' \
618+
'OUTPUT:' \
619+
'Hello, world!'
617620
}
618621

619622
@test "$SUITE: fail_if succeeds when assert_line_matches fails" {
@@ -627,7 +630,10 @@ teardown() {
627630
'Expected line 0 not to match:' \
628631
" 'Hello'" \
629632
'Value:' \
630-
" 'Hello, world!'"
633+
" 'Hello, world!'" \
634+
'STATUS: 0' \
635+
'OUTPUT:' \
636+
'Hello, world!'
631637
}
632638

633639
@test "$SUITE: fail_if succeeds when assert_lines_match fails" {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#! /usr/bin/env bats
2+
3+
load ../environment
4+
5+
setup() {
6+
test_filter
7+
}
8+
9+
teardown() {
10+
remove_bats_test_dirs
11+
}
12+
13+
@test "$SUITE: error if argument not a positive integer" {
14+
local err_msg='The argument to test_break_after_num_iterations '
15+
err_msg+='must be a positive integer at:'
16+
17+
local top_stack_line="/bats-exec-test:[0-9]+ run"
18+
19+
run test_break_after_num_iterations
20+
assert_failure
21+
assert_line_equals 0 "$err_msg"
22+
assert_line_matches 1 "$top_stack_line"
23+
24+
run test_break_after_num_iterations 0
25+
assert_failure
26+
assert_line_equals 0 "$err_msg"
27+
assert_line_matches 1 "$top_stack_line"
28+
29+
run test_break_after_num_iterations -1
30+
assert_failure
31+
assert_line_equals 0 "$err_msg"
32+
assert_line_matches 1 "$top_stack_line"
33+
34+
run test_break_after_num_iterations 2foobar7
35+
assert_failure
36+
assert_line_equals 0 "$err_msg"
37+
assert_line_matches 1 "$top_stack_line"
38+
}
39+
40+
@test "$SUITE: break after specified number of iterations" {
41+
create_bats_test_script 'test-break-after-n' \
42+
'for ((i=0; i != 5; ++i)); do' \
43+
' test_break_after_num_iterations "$1"' \
44+
'done'
45+
TEST_DEBUG='true' run "$BATS_TEST_ROOTDIR/test-break-after-n" 5
46+
assert_failure 'Breaking after iteration 5 at:' \
47+
" $BATS_TEST_ROOTDIR/test-break-after-n:3 main"
48+
}
49+
50+
@test "$SUITE: does nothing if count not reached" {
51+
create_bats_test_script 'test-break-after-n' \
52+
'for ((i=0; i != 5; ++i)); do' \
53+
' test_break_after_num_iterations "$1"' \
54+
'done'
55+
TEST_DEBUG='true' run "$BATS_TEST_ROOTDIR/test-break-after-n" 6
56+
assert_success ''
57+
}
58+
59+
@test "$SUITE: does nothing if TEST_DEBUG is null" {
60+
create_bats_test_script 'test-break-after-n' \
61+
'for ((i=0; i != 5; ++i)); do' \
62+
' test_break_after_num_iterations "$1"' \
63+
'done'
64+
TEST_DEBUG= run "$BATS_TEST_ROOTDIR/test-break-after-n" 5
65+
assert_success ''
66+
}
67+
68+
@test "$SUITE: break after recursive calls" {
69+
create_bats_test_script 'test-break-after-n' \
70+
'recursive_func() {' \
71+
' test_break_after_num_iterations "$N"' \
72+
' recursive_func' \
73+
'}' \
74+
'N="$1"' \
75+
'recursive_func'
76+
TEST_DEBUG='true' run "$BATS_TEST_ROOTDIR/test-break-after-n" 3
77+
assert_failure 'Breaking after iteration 3 at:' \
78+
" $BATS_TEST_ROOTDIR/test-break-after-n:3 recursive_func" \
79+
" $BATS_TEST_ROOTDIR/test-break-after-n:4 recursive_func" \
80+
" $BATS_TEST_ROOTDIR/test-break-after-n:4 recursive_func" \
81+
" $BATS_TEST_ROOTDIR/test-break-after-n:7 main"
82+
}
83+
84+
@test "$SUITE: counts isolated between lines in same file" {
85+
create_bats_test_script 'test-break-after-n' \
86+
'for ((i=0; i != 5; ++i)); do' \
87+
' test_break_after_num_iterations "$(($1+1))"' \
88+
' test_break_after_num_iterations "$1"' \
89+
'done'
90+
TEST_DEBUG='true' run "$BATS_TEST_ROOTDIR/test-break-after-n" 5
91+
assert_failure 'Breaking after iteration 5 at:' \
92+
" $BATS_TEST_ROOTDIR/test-break-after-n:4 main"
93+
}
94+
95+
@test "$SUITE: counts isolated across files" {
96+
# Note we're using `$0` instead of `BASH_SOURCE[0]` because of a bug in Bash
97+
# versions before 4.4 wherein `BASH_SOURCE` isn't set for the main script.
98+
create_bats_test_script 'test-break-after-n' \
99+
'for ((i=0; i != 5; ++i)); do' \
100+
' . "${0%/*}/foo"' \
101+
' . "${0%/*}/bar"' \
102+
'done'
103+
create_bats_test_script 'foo' \
104+
' test_break_after_num_iterations "$(($1+1))"'
105+
create_bats_test_script 'bar' \
106+
' test_break_after_num_iterations "$1"'
107+
108+
TEST_DEBUG='true' run "$BATS_TEST_ROOTDIR/test-break-after-n" 5
109+
assert_failure 'Breaking after iteration 5 at:' \
110+
" $BATS_TEST_ROOTDIR/bar:2 source" \
111+
" $BATS_TEST_ROOTDIR/test-break-after-n:4 main"
112+
}
113+
114+
@test "$SUITE: prints values of variables on break" {
115+
create_bats_test_script 'test-break-after-n' \
116+
'declare stuff=("foo" "bar" "baz")' \
117+
'for ((i=0; i != 5; ++i)); do' \
118+
' test_break_after_num_iterations "$@" "stuff[*]" i' \
119+
'done'
120+
121+
TEST_DEBUG='true' run "$BATS_TEST_ROOTDIR/test-break-after-n" 5
122+
assert_failure 'Breaking after iteration 5 at:' \
123+
" $BATS_TEST_ROOTDIR/test-break-after-n:4 main" \
124+
'stuff[*]: foo bar baz' \
125+
'i: 4'
126+
}

tests/commands/find.bats

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,19 @@ assert_command_scripts_equal() {
9494
assert_command_scripts_equal "${__all_scripts[@]}"
9595
}
9696

97-
@test "$SUITE: return error if duplicates exists" {
97+
@test "$SUITE: commands from earlier paths precede duplicates in later paths" {
98+
# In this case, we're trying to duplicate a builtin. Since
99+
# `_GO_CORE_DIR/libexec` comes first in `_GO_SEARCH_PATHS`, it takes
100+
# precedence over the duplicate we add.
98101
local duplicate_cmd="${BUILTIN_SCRIPTS[0]##*/}"
99102
local __all_scripts=("${BUILTIN_SCRIPTS[@]}")
100103

101104
add_scripts "$duplicate_cmd"
102105
run "$TEST_GO_SCRIPT"
103-
assert_failure
104-
105-
assert_line_equals 0 "ERROR: duplicate command $duplicate_cmd:"
106-
107-
# Because the go-core.bash file is in the test's $_GO_ROOTDIR, and the test
108-
# script has a different $_GO_ROOTDIR, the builtin scripts will retain their
109-
# absolute path, whereas user scripts will be relative.
110-
assert_line_equals 1 " $_GO_ROOTDIR/${BUILTIN_SCRIPTS[0]}"
111-
assert_line_equals 2 " scripts/$duplicate_cmd"
106+
assert_success
107+
assert_line_equals 0 "LONGEST NAME LEN: ${#LONGEST_BUILTIN_NAME}"
108+
assert_line_equals 1 "COMMAND_NAMES: ${__all_scripts[*]##*/}"
109+
assert_command_scripts_equal "${__all_scripts[@]}"
112110
}
113111

114112
@test "$SUITE: return subcommands only" {

0 commit comments

Comments
 (0)