@bats-hardened/bats 1.14.1-alpha02
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @bats-hardened/bats might be problematic. Click here for more details.
- package/LICENSE.md +53 -0
- package/README.md +135 -0
- package/bin/bats +75 -0
- package/install.sh +29 -0
- package/lib/bats-core/common.bash +278 -0
- package/lib/bats-core/formatter.bash +151 -0
- package/lib/bats-core/preprocessing.bash +19 -0
- package/lib/bats-core/semaphore.bash +113 -0
- package/lib/bats-core/test_functions.bash +539 -0
- package/lib/bats-core/tracing.bash +425 -0
- package/lib/bats-core/validator.bash +37 -0
- package/lib/bats-core/warnings.bash +44 -0
- package/libexec/bats-core/bats +555 -0
- package/libexec/bats-core/bats-exec-file +402 -0
- package/libexec/bats-core/bats-exec-suite +346 -0
- package/libexec/bats-core/bats-exec-test +383 -0
- package/libexec/bats-core/bats-format-cat +6 -0
- package/libexec/bats-core/bats-format-junit +293 -0
- package/libexec/bats-core/bats-format-pretty +360 -0
- package/libexec/bats-core/bats-format-tap +55 -0
- package/libexec/bats-core/bats-format-tap13 +89 -0
- package/libexec/bats-core/bats-gather-tests +402 -0
- package/libexec/bats-core/bats-preprocess +118 -0
- package/man/Makefile +23 -0
- package/man/README.md +5 -0
- package/man/bats.1 +143 -0
- package/man/bats.1.ronn +201 -0
- package/man/bats.7 +300 -0
- package/man/bats.7.ronn +404 -0
- package/package.json +36 -0
- package/uninstall.sh +63 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -eET
|
|
3
|
+
|
|
4
|
+
args=("$@")
|
|
5
|
+
filter_tags_list=()
|
|
6
|
+
|
|
7
|
+
# shellcheck source=lib/bats-core/common.bash disable=SC2153
|
|
8
|
+
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
|
|
9
|
+
# shellcheck source=lib/bats-core/preprocessing.bash
|
|
10
|
+
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/preprocessing.bash"
|
|
11
|
+
|
|
12
|
+
abort() {
|
|
13
|
+
printf 'ERROR: '
|
|
14
|
+
# shellcheck disable=SC2059
|
|
15
|
+
printf "$@"
|
|
16
|
+
exit 1
|
|
17
|
+
} >&2
|
|
18
|
+
|
|
19
|
+
read_tags() {
|
|
20
|
+
local IFS=,
|
|
21
|
+
read -ra tags <<<"$1" || true
|
|
22
|
+
if ((${#tags[@]} > 0)); then
|
|
23
|
+
for ((i = 0; i < ${#tags[@]}; ++i)); do
|
|
24
|
+
bats_trim "tags[$i]" "${tags[$i]}"
|
|
25
|
+
done
|
|
26
|
+
bats_sort sorted_tags "${tags[@]}"
|
|
27
|
+
# shellcheck disable=SC2154
|
|
28
|
+
filter_tags_list+=("${sorted_tags[*]}")
|
|
29
|
+
else
|
|
30
|
+
filter_tags_list+=("")
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
while [[ "$#" -ne 0 ]]; do
|
|
35
|
+
case "$1" in
|
|
36
|
+
--dummy-flag)
|
|
37
|
+
;;
|
|
38
|
+
--filter-tags)
|
|
39
|
+
shift
|
|
40
|
+
read_tags "$1"
|
|
41
|
+
;;
|
|
42
|
+
--)
|
|
43
|
+
shift 1
|
|
44
|
+
break
|
|
45
|
+
;;
|
|
46
|
+
*)
|
|
47
|
+
abort "Unknown flag %s in command:\nbats-gather-tests %s" "$1" "${args[*]}"
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
50
|
+
shift 1
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# shellcheck source=lib/bats-core/test_functions.bash disable=SC2153
|
|
55
|
+
# required to provide e.g. `load` for free code (some users rely on this)
|
|
56
|
+
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/test_functions.bash"
|
|
57
|
+
# _bats_test_functions_setup will be called per file further down
|
|
58
|
+
|
|
59
|
+
# override test_functions.bash's version to use it for test registration
|
|
60
|
+
bats_test_function() {
|
|
61
|
+
local -a tags=() current_tags=()
|
|
62
|
+
local description=
|
|
63
|
+
|
|
64
|
+
while (( $# > 0 )); do
|
|
65
|
+
case "$1" in
|
|
66
|
+
--description)
|
|
67
|
+
description=$2
|
|
68
|
+
shift 2
|
|
69
|
+
;;
|
|
70
|
+
--tags)
|
|
71
|
+
if [[ "$2" != "" ]]; then # avoid unbound variable with set -u Bash 3
|
|
72
|
+
IFS=, read -ra current_tags <<<"$2" || true
|
|
73
|
+
tags+=("${current_tags[@]}")
|
|
74
|
+
fi
|
|
75
|
+
shift 2
|
|
76
|
+
;;
|
|
77
|
+
--)
|
|
78
|
+
shift
|
|
79
|
+
break
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
printf "ERROR: unknown option %s for bats_test_function" "$1" >&2
|
|
83
|
+
exit 1
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
done
|
|
87
|
+
|
|
88
|
+
if (( ${#tags[@]} > 1 )); then # avoid unbound variable with set -u Bash 3
|
|
89
|
+
bats_sort tags "${tags[@]}"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
line="$BATS_TEST_FILENAME"
|
|
93
|
+
line+=$'\t'
|
|
94
|
+
line+="$*"
|
|
95
|
+
# always execute should_skip_because_of_status for its sideeffects
|
|
96
|
+
if should_skip_because_of_status || should_skip_because_of_focus || should_skip_because_of_filter_tags || should_skip_because_of_filter || should_skip_because_of_negative_filter; then
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# dereferencing ${test_names[*]} fails on older bash versions when empty -> check before
|
|
101
|
+
if [[ ${#test_names[@]} -gt 0 && " ${test_names[*]} " == *" $line "* ]]; then
|
|
102
|
+
test_dupes+=("$line")
|
|
103
|
+
fi
|
|
104
|
+
test_names+=("$line")
|
|
105
|
+
|
|
106
|
+
local args
|
|
107
|
+
printf -v args '%q ' "$@"
|
|
108
|
+
#echo "Adding test $line as '$BATS_TEST_FILENAME$args'" >&2
|
|
109
|
+
printf "%s\t%s\n" "$BATS_TEST_FILENAME" "$args" >> "$TESTS_LIST_FILE"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function should_skip_because_of_focus() {
|
|
113
|
+
if bats_all_in tags 'bats:focus'; then
|
|
114
|
+
if [[ $focus_mode == 1 ]]; then
|
|
115
|
+
# focused tests in focus mode should just be registered
|
|
116
|
+
:
|
|
117
|
+
else
|
|
118
|
+
# the current test enables focus mode ...
|
|
119
|
+
focus_mode=1
|
|
120
|
+
# ... -> remove previously found, unfocused tests
|
|
121
|
+
included_tests=()
|
|
122
|
+
: >| "$TESTS_LIST_FILE"
|
|
123
|
+
fi
|
|
124
|
+
elif [[ $focus_mode == 1 ]]; then
|
|
125
|
+
# the current test is not focused but focus mode is enabled -> filter out
|
|
126
|
+
return 0
|
|
127
|
+
# no else -> unfocused tests outside focus mode should just be registered
|
|
128
|
+
fi
|
|
129
|
+
return 1
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function should_skip_because_of_filter_tags() {
|
|
133
|
+
if (( ${#filter_tags_list[@]} > 0 )); then
|
|
134
|
+
for filter_tags in "${filter_tags_list[@]}"; do
|
|
135
|
+
# empty search tags only match empty test tags!
|
|
136
|
+
if [[ -z "$filter_tags" ]]; then
|
|
137
|
+
if [[ ${#tags[@]} -eq 0 ]]; then
|
|
138
|
+
return 1
|
|
139
|
+
fi
|
|
140
|
+
continue
|
|
141
|
+
fi
|
|
142
|
+
# non empty filter tags must be processed
|
|
143
|
+
local -a positive_filter_tags=() negative_filter_tags=()
|
|
144
|
+
IFS=, read -ra filter_tags <<<"$filter_tags" || true
|
|
145
|
+
for filter_tag in "${filter_tags[@]}"; do
|
|
146
|
+
if [[ $filter_tag == !* ]]; then
|
|
147
|
+
bats_trim filter_tag "${filter_tag#!}"
|
|
148
|
+
negative_filter_tags+=("${filter_tag}")
|
|
149
|
+
else
|
|
150
|
+
positive_filter_tags+=("${filter_tag}")
|
|
151
|
+
fi
|
|
152
|
+
done
|
|
153
|
+
if bats_append_arrays_as_args positive_filter_tags -- bats_all_in tags &&
|
|
154
|
+
! bats_append_arrays_as_args negative_filter_tags -- bats_any_in tags; then
|
|
155
|
+
return 1
|
|
156
|
+
fi
|
|
157
|
+
done
|
|
158
|
+
return 0 # skip, because no match was found
|
|
159
|
+
fi
|
|
160
|
+
return 1 # no filter tags -> nothing to skip
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if [[ -n "${filter-}" ]]; then
|
|
164
|
+
function should_skip_because_of_filter() {
|
|
165
|
+
# shellcheck disable=SC2154 # filter should be inherited as env var
|
|
166
|
+
! [[ "$description" =~ $filter ]]
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
function should_skip_because_of_filter() {
|
|
170
|
+
# skip this when there is no $filter
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if [[ -n "${nfilter-}" ]]; then
|
|
176
|
+
function should_skip_because_of_negative_filter() {
|
|
177
|
+
# shellcheck disable=SC2154 # nfilter should be inherited as env var
|
|
178
|
+
[[ "$description" =~ $nfilter ]]
|
|
179
|
+
}
|
|
180
|
+
else
|
|
181
|
+
function should_skip_because_of_negative_filter() {
|
|
182
|
+
# skip this when there is no $nfilter
|
|
183
|
+
return 1
|
|
184
|
+
}
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
function should_skip_because_of_status() {
|
|
188
|
+
# disable this filter if not activated by $filter_status
|
|
189
|
+
return 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# shellcheck disable=SC2154 # filter_status is set in the environment
|
|
193
|
+
if [[ -n "${filter_status-}" ]]; then
|
|
194
|
+
case "$filter_status" in
|
|
195
|
+
failed)
|
|
196
|
+
bats_filter_test_by_status() { # <line>
|
|
197
|
+
! bats_binary_search "$1" "passed_tests"
|
|
198
|
+
}
|
|
199
|
+
;;
|
|
200
|
+
passed)
|
|
201
|
+
bats_filter_test_by_status() {
|
|
202
|
+
! bats_binary_search "$1" "failed_tests"
|
|
203
|
+
}
|
|
204
|
+
;;
|
|
205
|
+
missed)
|
|
206
|
+
bats_filter_test_by_status() {
|
|
207
|
+
! bats_binary_search "$1" "failed_tests" && ! bats_binary_search "$1" "passed_tests"
|
|
208
|
+
}
|
|
209
|
+
;;
|
|
210
|
+
*)
|
|
211
|
+
printf "Error: Unknown value '%s' for --filter-status. Valid values are 'failed' and 'missed'.\n" "$filter_status" >&2
|
|
212
|
+
exit 1
|
|
213
|
+
;;
|
|
214
|
+
esac
|
|
215
|
+
|
|
216
|
+
if IFS='' read -d $'\n' -r BATS_PREVIOUS_RUNLOG_FILE < <(ls -1r "$BATS_RUN_LOGS_DIRECTORY"); then
|
|
217
|
+
BATS_PREVIOUS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/$BATS_PREVIOUS_RUNLOG_FILE"
|
|
218
|
+
if [[ $BATS_PREVIOUS_RUNLOG_FILE == "$BATS_RUNLOG_FILE" ]]; then
|
|
219
|
+
count=$(find "$BATS_RUN_LOGS_DIRECTORY" -name "$BATS_RUNLOG_DATE*" | wc -l)
|
|
220
|
+
BATS_RUNLOG_FILE="$BATS_RUN_LOGS_DIRECTORY/${BATS_RUNLOG_DATE}-$count.log"
|
|
221
|
+
fi
|
|
222
|
+
failed_tests=()
|
|
223
|
+
passed_tests=()
|
|
224
|
+
# store tests that were already filtered out in the last run for the same filter reason
|
|
225
|
+
last_filtered_tests=()
|
|
226
|
+
i=0
|
|
227
|
+
while read -rd $'\n' line; do
|
|
228
|
+
((++i))
|
|
229
|
+
case "$line" in
|
|
230
|
+
"passed "*)
|
|
231
|
+
passed_tests+=("${line#passed }")
|
|
232
|
+
;;
|
|
233
|
+
"failed "*)
|
|
234
|
+
failed_tests+=("${line#failed }")
|
|
235
|
+
;;
|
|
236
|
+
"status-filtered $filter_status"*) # pick up tests that were filtered in the last round for the same status
|
|
237
|
+
last_filtered_tests+=("${line#status-filtered "$filter_status" }")
|
|
238
|
+
;;
|
|
239
|
+
"status-filtered "*) # ignore other status-filtered lines
|
|
240
|
+
;;
|
|
241
|
+
"#"*) # allow for comments
|
|
242
|
+
;;
|
|
243
|
+
*)
|
|
244
|
+
printf "Error: %s:%d: Invalid format: %s\n" "$BATS_PREVIOUS_RUNLOG_FILE" "$i" "$line" >&2
|
|
245
|
+
exit 1
|
|
246
|
+
;;
|
|
247
|
+
esac
|
|
248
|
+
done < <(sort "$BATS_PREVIOUS_RUNLOG_FILE")
|
|
249
|
+
|
|
250
|
+
# enable filter only, when there is something to filter
|
|
251
|
+
function should_skip_because_of_status() {
|
|
252
|
+
#echo "Match:"
|
|
253
|
+
#echo "$line"
|
|
254
|
+
#printf "%s\n" "${failed_tests[@]}"
|
|
255
|
+
#echo "or"
|
|
256
|
+
#printf "%s\n" "${last_filtered_tests[@]}"
|
|
257
|
+
if bats_filter_test_by_status "$line"; then
|
|
258
|
+
if ! bats_binary_search "$line" last_filtered_tests; then
|
|
259
|
+
included_tests+=("$line")
|
|
260
|
+
#echo "included"
|
|
261
|
+
return 1
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
#echo "excluded"
|
|
265
|
+
excluded_tests+=("$line")
|
|
266
|
+
return 0
|
|
267
|
+
} >&2
|
|
268
|
+
else
|
|
269
|
+
printf "No recording of previous runs found. Running all tests!\n" >&2
|
|
270
|
+
fi
|
|
271
|
+
else
|
|
272
|
+
: #printf "Not filtering by status!\n" >&2
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# shellcheck source=lib/bats-core/tracing.bash
|
|
276
|
+
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/tracing.bash"
|
|
277
|
+
|
|
278
|
+
BATS_OUT="$BATS_RUN_TMPDIR/gather-tests.out"
|
|
279
|
+
touch "$BATS_OUT"
|
|
280
|
+
|
|
281
|
+
bats_gather_tests_exit_trap() {
|
|
282
|
+
local bats_gather_tests_exit_status=$?
|
|
283
|
+
trap - ERR EXIT DEBUG
|
|
284
|
+
if (( bats_gather_tests_exit_status != 0)); then
|
|
285
|
+
printf "1..1\nnot ok 1 bats-gather-tests\n"
|
|
286
|
+
# play back traces from test file evaluation
|
|
287
|
+
bats_replace_filename < "$BATS_TRACE"
|
|
288
|
+
bats_replace_filename <"$BATS_OUT" | bats_prefix_lines_for_tap_output
|
|
289
|
+
fi >&2
|
|
290
|
+
exit "$bats_gather_tests_exit_status"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
trap bats_gather_tests_exit_trap EXIT
|
|
294
|
+
|
|
295
|
+
# prepare tracing for errors during test file evaluation
|
|
296
|
+
BATS_TRACE="$BATS_RUN_TMPDIR/bats-gather-tests.trace"
|
|
297
|
+
touch "$BATS_TRACE"
|
|
298
|
+
|
|
299
|
+
bats_gather_tests_source_exit_trap() {
|
|
300
|
+
local bats_gather_tests_source_exit_status=$?
|
|
301
|
+
trap - ERR EXIT DEBUG
|
|
302
|
+
if (( bats_gather_tests_source_exit_status != 0)); then
|
|
303
|
+
bats_get_failure_stack_trace stack_trace
|
|
304
|
+
# shellcheck disable=SC2154
|
|
305
|
+
bats_print_stack_trace "${stack_trace[@]}"
|
|
306
|
+
# TODO: why doesn't this work via ERR trap?
|
|
307
|
+
# shellcheck disable=SC2154,SC2034
|
|
308
|
+
BATS_ERROR_STATUS=$bats_gather_tests_source_exit_status
|
|
309
|
+
bats_print_failed_command "${stack_trace[@]}"
|
|
310
|
+
fi >>"$BATS_TRACE"
|
|
311
|
+
exit "$bats_gather_tests_source_exit_status"
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
bats_gather_tests_for_file() {
|
|
315
|
+
local test_names=() test_dupes=() included_tests=() excluded_tests=()
|
|
316
|
+
|
|
317
|
+
trap bats_gather_tests_source_exit_trap EXIT
|
|
318
|
+
bats_setup_tracing
|
|
319
|
+
bats_set_stacktrace_limit
|
|
320
|
+
|
|
321
|
+
# do the actual evaluation for gathering the tests
|
|
322
|
+
# shellcheck disable=SC1090
|
|
323
|
+
BATS_TEST_DIRNAME="${filename%/*}" source "$BATS_TEST_SOURCE" 1>>"$BATS_OUT" 2>&1
|
|
324
|
+
|
|
325
|
+
if [[ "${#test_dupes[@]}" -ne 0 ]]; then
|
|
326
|
+
printf 'file_duplicate_test_names="%q"\n' "${test_dupes[*]#$filename$'\t'}"
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
if [[ -n "$filter_status" ]]; then
|
|
330
|
+
# save filtered tests to exclude them again in next round
|
|
331
|
+
for test_line in "${excluded_tests[@]}"; do
|
|
332
|
+
printf "status-filtered %s %s\n" "$filter_status" "$test_line"
|
|
333
|
+
done >>"$BATS_RUNLOG_FILE"
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
printf "file_test_count=%d\n" "${#test_names[@]}"
|
|
338
|
+
printf "file_included_test_count=%d\n" "${#included_tests[@]}"
|
|
339
|
+
printf "focus_mode=%d\n" "$focus_mode"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
focus_mode=0
|
|
343
|
+
total_test_count=0
|
|
344
|
+
total_included_test_count=0
|
|
345
|
+
export BATS_TEST_FILE_NUMBER=0
|
|
346
|
+
|
|
347
|
+
for filename in "$@"; do
|
|
348
|
+
(( ++BATS_TEST_FILE_NUMBER ))
|
|
349
|
+
if [[ ! -f "$filename" ]]; then
|
|
350
|
+
abort 'Test file "%s" does not exist.\n' "${filename}"
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
BATS_TEST_FILENAME="$filename"
|
|
354
|
+
_bats_test_functions_setup -1 # invalid TEST_NUMBER, as this is not a test
|
|
355
|
+
|
|
356
|
+
# shellcheck disable=SC2034
|
|
357
|
+
BATS_TEST_NAME=source
|
|
358
|
+
bats_preprocess_source # uses BATS_TEST_FILENAME, BATS_TEST_FILE_NUMBER
|
|
359
|
+
|
|
360
|
+
file_duplicate_test_names=""
|
|
361
|
+
file_test_count=0
|
|
362
|
+
file_included_test_count=0
|
|
363
|
+
saved_focus_mode=$focus_mode
|
|
364
|
+
|
|
365
|
+
# get new values for the variables above
|
|
366
|
+
if [[ $BASH_VERSION == 4.3.* ]]; then
|
|
367
|
+
# Bash 4.3 has function scoping issues when this is run in $() -> work around via file
|
|
368
|
+
bats_gather_tests_var_transfer_file=$BATS_RUN_TMPDIR/gather-tests-var-transfer
|
|
369
|
+
(set -eET; bats_gather_tests_for_file >"$bats_gather_tests_var_transfer_file")
|
|
370
|
+
result=$(<"$bats_gather_tests_var_transfer_file")
|
|
371
|
+
else
|
|
372
|
+
# separate retrieval from eval to avoid hiding the exit code
|
|
373
|
+
result="$(set -eET; bats_gather_tests_for_file)"
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
eval "$result"
|
|
377
|
+
|
|
378
|
+
if [[ -n "$file_duplicate_test_names" ]]; then
|
|
379
|
+
trap - EXIT # prevent 1..1 from being printed
|
|
380
|
+
abort 'Duplicate test name(s) in file "%s": %s' "$filename" "$file_duplicate_test_names"
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
total_test_count=$((total_test_count + file_test_count))
|
|
384
|
+
|
|
385
|
+
# did focus mode turn on in this file? (cannot turn off afterwards)
|
|
386
|
+
if (( saved_focus_mode != focus_mode)); then # -> only count new tests
|
|
387
|
+
total_included_test_count=$file_included_test_count
|
|
388
|
+
else # -> count previous tests as well
|
|
389
|
+
total_included_test_count=$((total_included_test_count + file_included_test_count))
|
|
390
|
+
fi
|
|
391
|
+
done
|
|
392
|
+
|
|
393
|
+
if [[ -n "$filter_status" ]]; then
|
|
394
|
+
if (( total_test_count == 0 && total_included_test_count == 0 )); then
|
|
395
|
+
printf "There were no tests of status '%s' in the last recorded run.\n" "$filter_status" >&2
|
|
396
|
+
fi
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# communicate to the caller that we are running in focus mode
|
|
400
|
+
if (( focus_mode )); then
|
|
401
|
+
printf "focus_mode\n"
|
|
402
|
+
fi
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
bats_encode_test_name() {
|
|
5
|
+
local name="$1"
|
|
6
|
+
local result='test_'
|
|
7
|
+
local hex_code
|
|
8
|
+
|
|
9
|
+
if [[ ! "$name" =~ [^[:alnum:]\ _-] ]]; then
|
|
10
|
+
name="${name//_/-5f}"
|
|
11
|
+
name="${name//-/-2d}"
|
|
12
|
+
name="${name// /_}"
|
|
13
|
+
result+="$name"
|
|
14
|
+
else
|
|
15
|
+
local length="${#name}"
|
|
16
|
+
local char i
|
|
17
|
+
|
|
18
|
+
for ((i = 0; i < length; i++)); do
|
|
19
|
+
char="${name:$i:1}"
|
|
20
|
+
if [[ "$char" == ' ' ]]; then
|
|
21
|
+
result+='_'
|
|
22
|
+
elif [[ "$char" =~ [[:alnum:]] ]]; then
|
|
23
|
+
result+="$char"
|
|
24
|
+
else
|
|
25
|
+
printf -v 'hex_code' -- '-%02x' \'"$char"
|
|
26
|
+
result+="$hex_code"
|
|
27
|
+
fi
|
|
28
|
+
done
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
printf -v "$2" '%s' "$result"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
BATS_TEST_PATTERN="^[[:blank:]]*@test[[:blank:]]+(.*[^[:blank:]])[[:blank:]]+\{(.*)\$"
|
|
35
|
+
BATS_TEST_PATTERN_COMMENT="[[:blank:]]*([^[:blank:]()]+)[[:blank:]]*\(?\)?[[:blank:]]+\{[[:blank:]]+#[[:blank:]]*@test[[:blank:]]*\$"
|
|
36
|
+
BATS_COMMENT_COMMAND_PATTERN="^[[:blank:]]*#[[:blank:]]*bats[[:blank:]]+(.*)$"
|
|
37
|
+
BATS_VALID_TAG_PATTERN="[-_:[:alnum:]]+"
|
|
38
|
+
BATS_VALID_TAGS_PATTERN="^ *($BATS_VALID_TAG_PATTERN)?( *, *$BATS_VALID_TAG_PATTERN)* *$"
|
|
39
|
+
|
|
40
|
+
# shellcheck source=lib/bats-core/common.bash
|
|
41
|
+
source "$BATS_ROOT/$BATS_LIBDIR/bats-core/common.bash"
|
|
42
|
+
|
|
43
|
+
extract_tags() { # <tag_type/return_var> <tags-string>
|
|
44
|
+
local -r tag_type=$1 tags_string=$2
|
|
45
|
+
local -a tags=()
|
|
46
|
+
|
|
47
|
+
if [[ $tags_string =~ $BATS_VALID_TAGS_PATTERN ]]; then
|
|
48
|
+
IFS=, read -ra tags <<<"$tags_string"
|
|
49
|
+
local -ri length=${#tags[@]}
|
|
50
|
+
for ((i = 0; i < length; ++i)); do
|
|
51
|
+
local element="tags[$i]"
|
|
52
|
+
bats_trim "$element" "${!element}" 2>/dev/null # printf on bash 3 will complain but work anyways
|
|
53
|
+
if [[ -z "${!element}" && -n "${CHECK_BATS_COMMENT_COMMANDS:-}" ]]; then
|
|
54
|
+
printf "%s:%d: Error: Invalid %s: '%s'. " "$test_file" "$line_number" "$tag_type" "$tags_string"
|
|
55
|
+
printf "Tags must not be empty. Please remove redundant commas!\n"
|
|
56
|
+
exit_code=1
|
|
57
|
+
fi
|
|
58
|
+
done
|
|
59
|
+
elif [[ -n "${CHECK_BATS_COMMENT_COMMANDS:-}" ]]; then
|
|
60
|
+
printf "%s:%d: Error: Invalid %s: '%s'. " "$test_file" "$line_number" "$tag_type" "$tags_string"
|
|
61
|
+
printf "Valid tags must match %s and be separated with comma (and optional spaces)\n" "$BATS_VALID_TAG_PATTERN"
|
|
62
|
+
exit_code=1
|
|
63
|
+
fi >&2
|
|
64
|
+
if ((${#tags[@]} > 0)); then
|
|
65
|
+
eval "$tag_type=(\"\${tags[@]}\")"
|
|
66
|
+
else
|
|
67
|
+
eval "$tag_type=()"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
test_file="$1"
|
|
72
|
+
test_tags=()
|
|
73
|
+
file_tags=()
|
|
74
|
+
line_number=0
|
|
75
|
+
exit_code=0
|
|
76
|
+
EMPTY_BODY_REGEX='[[:space:]]*\}'
|
|
77
|
+
IFS=,
|
|
78
|
+
{
|
|
79
|
+
while IFS= read -r line; do
|
|
80
|
+
((++line_number))
|
|
81
|
+
line="${line//$'\r'/}"
|
|
82
|
+
if [[ "$line" =~ $BATS_TEST_PATTERN ]] || [[ "$line" =~ $BATS_TEST_PATTERN_COMMENT ]]; then
|
|
83
|
+
name="${BASH_REMATCH[1]#[\'\"]}"
|
|
84
|
+
name="${name%[\'\"]}"
|
|
85
|
+
body="${BASH_REMATCH[2]:-}"
|
|
86
|
+
bats_encode_test_name "$name" 'encoded_name'
|
|
87
|
+
|
|
88
|
+
if [[ "$body" =~ $EMPTY_BODY_REGEX ]]; then
|
|
89
|
+
# ":;" is needed for empty {} after test
|
|
90
|
+
lead=':; '
|
|
91
|
+
else
|
|
92
|
+
# avoid injecting non user code into tests
|
|
93
|
+
lead=''
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# shellcheck disable=SC2154 # encoded_name is declare via bats_encode_test_name
|
|
97
|
+
printf 'bats_test_function --description %q --tags "%s" --tags "%s" -- %s;' "$name" "${test_tags[*]-}" "${file_tags[*]-}" "$encoded_name"
|
|
98
|
+
printf '%s() { %s%s\n' "${encoded_name:?}" "$lead" "$body" || :
|
|
99
|
+
|
|
100
|
+
test_tags=() # reset test tags for next test
|
|
101
|
+
else
|
|
102
|
+
if [[ "$line" =~ $BATS_COMMENT_COMMAND_PATTERN ]]; then
|
|
103
|
+
command=${BASH_REMATCH[1]}
|
|
104
|
+
case $command in
|
|
105
|
+
'test_tags='*)
|
|
106
|
+
extract_tags test_tags "${command#test_tags=}"
|
|
107
|
+
;;
|
|
108
|
+
'file_tags='*)
|
|
109
|
+
extract_tags file_tags "${command#file_tags=}"
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
112
|
+
fi
|
|
113
|
+
printf '%s\n' "$line"
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
} <<<"$(<"$test_file")"$'\n'
|
|
117
|
+
|
|
118
|
+
exit $exit_code
|
package/man/Makefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Makefile
|
|
2
|
+
#
|
|
3
|
+
# bats-core manpages
|
|
4
|
+
#
|
|
5
|
+
RONN := ronn -W
|
|
6
|
+
PAGES := bats.1 bats.7
|
|
7
|
+
ORG := bats-core
|
|
8
|
+
MANUAL := 'Bash Automated Testing System'
|
|
9
|
+
ISOFMT := $(shell date -I)
|
|
10
|
+
RM := rm -f
|
|
11
|
+
|
|
12
|
+
.PHONY: all clean
|
|
13
|
+
|
|
14
|
+
all: $(PAGES)
|
|
15
|
+
|
|
16
|
+
bats.1: bats.1.ronn
|
|
17
|
+
$(RONN) --date=$(ISOFMT) --manual=$(MANUAL) --organization=$(ORG) --roff $<
|
|
18
|
+
|
|
19
|
+
bats.7: bats.7.ronn
|
|
20
|
+
$(RONN) --date=$(ISOFMT) --manual=$(MANUAL) --organization=$(ORG) --roff $<
|
|
21
|
+
|
|
22
|
+
clean:
|
|
23
|
+
$(RM) $(PAGES)
|
package/man/README.md
ADDED
package/man/bats.1
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
.\" generated with Ronn-NG/v0.10.1
|
|
2
|
+
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
|
|
3
|
+
.TH "BATS" "1" "July 2026" "bats-core" "Bash Automated Testing System"
|
|
4
|
+
.SH "NAME"
|
|
5
|
+
\fBbats\fR \- Bash Automated Testing System
|
|
6
|
+
.SH "SYNOPSIS"
|
|
7
|
+
Usage: bats [OPTIONS] \fItests\fR bats [\-h | \-v]
|
|
8
|
+
.P
|
|
9
|
+
\fItests\fR is the path to a Bats test file, or the path to a directory containing Bats test files (ending with "\.bats")
|
|
10
|
+
.SH "DESCRIPTION"
|
|
11
|
+
Bats is a TAP\-compliant testing framework for Bash\. It provides a simple way to verify that the UNIX programs you write behave as expected\.
|
|
12
|
+
.P
|
|
13
|
+
A Bats test file is a Bash script with special syntax for defining test cases\. Under the hood, each test case is just a function with a description\.
|
|
14
|
+
.P
|
|
15
|
+
Test cases consist of standard shell commands\. Bats makes use of Bash's \fBerrexit\fR (\fBset \-e\fR) option when running test cases\. If every command in the test case exits with a \fB0\fR status code (success), the test passes\. In this way, each line is an assertion of truth\.
|
|
16
|
+
.P
|
|
17
|
+
See \fBbats\fR(7) for more information on writing Bats tests\.
|
|
18
|
+
.SH "RUNNING TESTS"
|
|
19
|
+
To run your tests, invoke the \fBbats\fR interpreter with a path to a test file\. The file's test cases are run sequentially and in isolation\. If all the test cases pass, \fBbats\fR exits with a \fB0\fR status code\. If there are any failures, \fBbats\fR exits with a \fB1\fR status code\.
|
|
20
|
+
.P
|
|
21
|
+
You can invoke the \fBbats\fR interpreter with multiple test file arguments, or with a path to a directory containing multiple \fB\.bats\fR files\. Bats will run each test file individually and aggregate the results\. If any test case fails, \fBbats\fR exits with a \fB1\fR status code\.
|
|
22
|
+
.SH "FILTERING TESTS"
|
|
23
|
+
There are multiple mechanisms to filter which tests to execute:
|
|
24
|
+
.IP "\(bu" 4
|
|
25
|
+
\fB\-\-filter <regex>\fR to filter by test name
|
|
26
|
+
.IP "\(bu" 4
|
|
27
|
+
\fB\-\-filter\-status <status>\fR to filter by the test's status in the last run
|
|
28
|
+
.IP "\(bu" 4
|
|
29
|
+
\fB\-\-filter\-tags <tag\-list>\fR to filter by the tags of a test
|
|
30
|
+
.IP "" 0
|
|
31
|
+
.SH "\-\-FILTER\-TAGS <var>TAG\-LIST</var>"
|
|
32
|
+
Tags can be used for finegrained filtering of which tests to run via \fB\-\-filter\-tags\fR\. This accepts a comma separated list of tags\. Only tests that match all of these tags will be executed\. For example, \fBbats \-\-filter\-tags a,b,c\fR will pick up tests with tags \fBa,b,c\fR, but not tests that miss one or more of those tags\.
|
|
33
|
+
.P
|
|
34
|
+
Additionally, you can specify negative tags via \fBbats \-\-filter\-tags a,!b,c\fR, which now won't match tests with tags \fBa,b,c\fR, due to the \fBb\fR, but will select \fBa,c\fR\. To put it more formally, \fB\-\-filter\-tags\fR is a boolean conjunction\.
|
|
35
|
+
.P
|
|
36
|
+
To allow for more complex queries, you can specify multiple \fB\-\-filter\-tags\fR\. A test will be executed, if it matches at least one of them\. This means multiple \fB\-\-filter\-tags\fR form a boolean disjunction\.
|
|
37
|
+
.P
|
|
38
|
+
A query of \fB\-\-filter\-tags a,!b \-\-filter\-tags b,c\fR can be translated to: Execute only tests that (have tag a, but not tag b) or (have tag b and c)\.
|
|
39
|
+
.P
|
|
40
|
+
An empty tag list matches tests without tags\.
|
|
41
|
+
.SH "OPTIONS"
|
|
42
|
+
.IP "\(bu" 4
|
|
43
|
+
\fB\-\-abort\fR: Stop execution of suite on first failed test
|
|
44
|
+
.IP "\(bu" 4
|
|
45
|
+
\fB\-\-allow\-empty\-suite\fR: Exit with code 0 (instead of the default code 1) when no tests are found\. Can also be set via \fB$BATS_ALLOW_EMPTY_SUITE\fR (to any non\-empty value), which keeps compatibility with Bats versions that don't know this flag\.
|
|
46
|
+
.IP "\(bu" 4
|
|
47
|
+
\fB\-c\fR, \fB\-\-count\fR: Count the number of test cases without running any tests
|
|
48
|
+
.IP "\(bu" 4
|
|
49
|
+
\fB\-\-code\-quote\-style <style>\fR: A two character string of code quote delimiters or \fBcustom\fR which requires setting \fB$BATS_BEGIN_CODE_QUOTE\fR and \fB$BATS_END_CODE_QUOTE\fR\. Can also be set via \fB$BATS_CODE_QUOTE_STYLE\fR\.
|
|
50
|
+
.IP "\(bu" 4
|
|
51
|
+
\fB\-\-line\-reference\-format\fR Controls how file/line references e\.g\. in stack traces are printed:
|
|
52
|
+
.IP "\(bu" 4
|
|
53
|
+
comma_line (default): a\.bats, line 1
|
|
54
|
+
.IP "\(bu" 4
|
|
55
|
+
colon: a\.bats:1
|
|
56
|
+
.IP "\(bu" 4
|
|
57
|
+
uri: file:///tests/a\.bats:1
|
|
58
|
+
.IP "\(bu" 4
|
|
59
|
+
custom: provide your own via defining bats_format_file_line_reference_custom with parameters \fIfilename\fR \fIline\fR, store via \fBprintf \-v "$output"\fR
|
|
60
|
+
.IP "" 0
|
|
61
|
+
|
|
62
|
+
.IP "\(bu" 4
|
|
63
|
+
\fB\-\-errexit\fR: Enable errexit (\fBset \-e\fR) for commands run in \fBrun\fR
|
|
64
|
+
.IP "\(bu" 4
|
|
65
|
+
\fB\-f\fR, \fB\-\-filter <regex>\fR: Filter test cases by names matching the regular expression
|
|
66
|
+
.IP "\(bu" 4
|
|
67
|
+
\fB\-F\fR, \fB\-\-formatter <type>\fR: Switch between formatters: pretty (default), tap (default w/o term), tap13, junit, \fB/<absolute path to formatter>\fR
|
|
68
|
+
.IP "\(bu" 4
|
|
69
|
+
\fB\-\-filter\-status <status>\fR: Only run tests with the given status in the last completed (no CTRL+C/SIGINT) run\. Valid \fIstatus\fR values are: failed \- runs tests that failed or were not present in the last run missed \- runs tests that were not present in the last run
|
|
70
|
+
.IP "\(bu" 4
|
|
71
|
+
\fB\-\-filter\-tags <comma\-separated\-tag\-list>\fR: Only run tests that match all the tags in the list (\fB&&\fR)\. You can negate a tag via prepending \fB!\fR\. Specifying this flag multiple times allows for logical or (\fB||\fR): \fB\-\-filter\-tags A,B \-\-filter\-tags A,!C\fR matches tags \fB(A && B) || (A && !C)\fR
|
|
72
|
+
.IP "\(bu" 4
|
|
73
|
+
\fB\-\-gather\-test\-outputs\-in <directory>\fR: Gather the output of failing \fIand\fR passing tests as files in directory
|
|
74
|
+
.IP "\(bu" 4
|
|
75
|
+
\fB\-h\fR, \fB\-\-help\fR: Display this help message
|
|
76
|
+
.IP "\(bu" 4
|
|
77
|
+
\fB\-j\fR, \fB\-\-jobs <jobs>\fR: Number of parallel jobs (requires GNU parallel)
|
|
78
|
+
.IP "\(bu" 4
|
|
79
|
+
\fB\-\-negative\-filter <regex>\fR: Only run tests that do not match the regular expression
|
|
80
|
+
.IP "\(bu" 4
|
|
81
|
+
\fB\-\-no\-tempdir\-cleanup\fR: Preserve test output temporary directory
|
|
82
|
+
.IP "\(bu" 4
|
|
83
|
+
\fB\-\-no\-parallelize\-across\-files\fR: Serialize test file execution instead of running them in parallel (requires \-\-jobs >1)
|
|
84
|
+
.IP "\(bu" 4
|
|
85
|
+
\fB\-\-no\-parallelize\-within\-files\fR: Serialize test execution within files instead of running them in parallel (requires \-\-jobs >1)
|
|
86
|
+
.IP "\(bu" 4
|
|
87
|
+
\fB\-\-parallel\-binary\-name <name>\fR: Name of parallel binary
|
|
88
|
+
.IP "\(bu" 4
|
|
89
|
+
\fB\-\-report\-formatter <type>\fR: Switch between reporters (same options as \-\-formatter)
|
|
90
|
+
.IP "\(bu" 4
|
|
91
|
+
\fB\-o\fR, \fB\-\-output <dir>\fR: Directory to write report files
|
|
92
|
+
.IP "\(bu" 4
|
|
93
|
+
\fB\-p\fR, \fB\-\-pretty\fR: Shorthand for "\-\-formatter pretty"
|
|
94
|
+
.IP "\(bu" 4
|
|
95
|
+
\fB\-\-print\-output\-on\-failure\fR: Automatically print the value of \fB$output\fR on failed tests
|
|
96
|
+
.IP "\(bu" 4
|
|
97
|
+
\fB\-r\fR, \fB\-\-recursive\fR: Include tests in subdirectories
|
|
98
|
+
.IP "\(bu" 4
|
|
99
|
+
\fB\-\-show\-output\-of\-passing\-tests\fR: Print output of passing tests
|
|
100
|
+
.IP "\(bu" 4
|
|
101
|
+
\fB\-t\fR, \fB\-\-tap\fR: Shorthand for "\-\-formatter tap"
|
|
102
|
+
.IP "\(bu" 4
|
|
103
|
+
\fB\-T\fR, \fB\-\-timing\fR: Add timing information to tests
|
|
104
|
+
.IP "\(bu" 4
|
|
105
|
+
\fB\-x\fR, \fB\-\-trace\fR: Print test commands as they are executed (like \fBset \-x\fR)
|
|
106
|
+
.IP "\(bu" 4
|
|
107
|
+
\fB\-\-verbose\-run\fR: Make \fBrun\fR print \fB$output\fR by default
|
|
108
|
+
.IP "\(bu" 4
|
|
109
|
+
\fB\-v\fR, \fB\-\-version\fR: Display the version number
|
|
110
|
+
.IP "" 0
|
|
111
|
+
.SH "OUTPUT"
|
|
112
|
+
When you run Bats from a terminal, you'll see output as each test is performed, with a check\-mark next to the test's name if it passes or an "X" if it fails\.
|
|
113
|
+
.IP "" 4
|
|
114
|
+
.nf
|
|
115
|
+
$ bats addition\.bats
|
|
116
|
+
✓ addition using bc
|
|
117
|
+
✓ addition using dc
|
|
118
|
+
|
|
119
|
+
2 tests, 0 failures
|
|
120
|
+
.fi
|
|
121
|
+
.IP "" 0
|
|
122
|
+
.P
|
|
123
|
+
If Bats is not connected to a terminal\-\-in other words, if you run it from a continuous integration system or redirect its output to a file\-\-the results are displayed in human\-readable, machine\-parsable TAP format\. You can force TAP output from a terminal by invoking Bats with the \fB\-\-tap\fR option\.
|
|
124
|
+
.IP "" 4
|
|
125
|
+
.nf
|
|
126
|
+
$ bats \-\-tap addition\.bats
|
|
127
|
+
1\.\.2
|
|
128
|
+
ok 1 addition using bc
|
|
129
|
+
ok 2 addition using dc
|
|
130
|
+
.fi
|
|
131
|
+
.IP "" 0
|
|
132
|
+
.SH "EXIT STATUS"
|
|
133
|
+
The \fBbats\fR interpreter exits with a value of \fB0\fR if all test cases pass, or \fB1\fR if one or more test cases fail\.
|
|
134
|
+
.SH "SEE ALSO"
|
|
135
|
+
Bats wiki: \fIhttps://github\.com/bats\-core/bats\-core/wiki/\fR
|
|
136
|
+
.P
|
|
137
|
+
\fBbash\fR(1), \fBbats\fR(7)
|
|
138
|
+
.SH "COPYRIGHT"
|
|
139
|
+
(c) 2017\-2022 bats\-core organization
|
|
140
|
+
.br
|
|
141
|
+
(c) 2011\-2016 Sam Stephenson
|
|
142
|
+
.P
|
|
143
|
+
Bats is released under the terms of an MIT\-style license\.
|