Commit | Line | Data |
---|---|---|
86a96e6c CB |
1 | #!/bin/bash |
2 | # | |
3 | # Copyright 2010 Patrick LeBoutillier <patrick.leboutillier@gmail.com> | |
4 | # | |
9d16b343 | 5 | # SPDX-License-Identifier: GPL-3.0-or-later |
86a96e6c | 6 | # |
86a96e6c CB |
7 | |
8 | _version='1.01' | |
9 | ||
10 | _plan_set=0 | |
11 | _no_plan=0 | |
12 | _skip_all=0 | |
13 | _test_died=0 | |
14 | _expected_tests=0 | |
15 | _executed_tests=0 | |
16 | _failed_tests=0 | |
2a69bf14 KS |
17 | _auto_timing="${TAP_AUTOTIME:-1}" |
18 | _last_time='' | |
86a96e6c | 19 | TODO= |
2a69bf14 KS |
20 | TIME_SCRIPT="$(realpath -e -- "$(dirname "${BASH_SOURCE[0]}")")/clock" |
21 | print "${TIME_SCRIPT}" | |
86a96e6c CB |
22 | |
23 | usage(){ | |
24 | cat <<'USAGE' | |
25 | tap-functions: A TAP-producing BASH library | |
26 | ||
27 | PLAN: | |
28 | plan_no_plan | |
29 | plan_skip_all [REASON] | |
30 | plan_tests NB_TESTS | |
31 | ||
32 | TEST: | |
33 | ok RESULT [NAME] | |
34 | okx COMMAND | |
35 | is RESULT EXPECTED [NAME] | |
36 | isnt RESULT EXPECTED [NAME] | |
37 | like RESULT PATTERN [NAME] | |
38 | unlike RESULT PATTERN [NAME] | |
39 | pass [NAME] | |
40 | fail [NAME] | |
41 | ||
42 | SKIP: | |
43 | skip [CONDITION] [REASON] [NB_TESTS=1] | |
44 | ||
45 | skip $feature_not_present "feature not present" 2 || { | |
46 | is $a "a" | |
47 | is $b "b" | |
48 | } | |
49 | ||
50 | TODO: | |
51 | Specify TODO mode by setting $TODO: | |
52 | TODO="not implemented yet" | |
53 | ok $result "some not implemented test" | |
54 | unset TODO | |
55 | ||
56 | OTHER: | |
57 | diag MSG | |
2a69bf14 | 58 | autotime 0|1 |
86a96e6c CB |
59 | |
60 | EXAMPLE: | |
61 | #!/bin/bash | |
62 | ||
63 | . tap-functions | |
64 | ||
65 | plan_tests 7 | |
66 | ||
67 | me=$USER | |
68 | is $USER $me "I am myself" | |
69 | like $HOME $me "My home is mine" | |
70 | like "`id`" $me "My id matches myself" | |
71 | ||
72 | /bin/ls $HOME 1>&2 | |
73 | ok $? "/bin/ls $HOME" | |
74 | # Same thing using okx shortcut | |
75 | okx /bin/ls $HOME | |
76 | ||
77 | [[ "`id -u`" != "0" ]] | |
78 | i_am_not_root=$? | |
79 | skip $i_am_not_root "Must be root" || { | |
80 | okx ls /root | |
81 | } | |
82 | ||
83 | TODO="figure out how to become root..." | |
84 | okx [ "$HOME" == "/root" ] | |
85 | unset TODO | |
86 | USAGE | |
87 | exit | |
88 | } | |
89 | ||
90 | opt= | |
91 | set_u= | |
92 | while getopts ":sx" opt ; do | |
93 | case $_opt in | |
94 | u) set_u=1 ;; | |
95 | *) usage ;; | |
96 | esac | |
97 | done | |
98 | shift $(( OPTIND - 1 )) | |
99 | # Don't allow uninitialized variables if requested | |
100 | [[ -n "$set_u" ]] && set -u | |
101 | unset opt set_u | |
102 | ||
103 | # Used to call _cleanup on shell exit | |
104 | trap _exit EXIT | |
105 | ||
106 | ||
107 | plan_no_plan(){ | |
108 | (( _plan_set != 0 )) && "You tried to plan twice!" | |
109 | ||
110 | _plan_set=1 | |
111 | _no_plan=1 | |
2a69bf14 | 112 | _last_time=$("${TIME_SCRIPT}") |
86a96e6c CB |
113 | |
114 | return 0 | |
115 | } | |
116 | ||
117 | ||
118 | plan_skip_all(){ | |
119 | local reason=${1:-''} | |
120 | ||
121 | (( _plan_set != 0 )) && _die "You tried to plan twice!" | |
122 | ||
123 | _print_plan 0 "Skip $reason" | |
124 | ||
125 | _skip_all=1 | |
126 | _plan_set=1 | |
2a69bf14 | 127 | _last_time=$("${TIME_SCRIPT}") |
86a96e6c CB |
128 | _exit 0 |
129 | ||
130 | return 0 | |
131 | } | |
132 | ||
133 | plan_tests(){ | |
134 | local tests=${1:?} | |
135 | ||
136 | (( _plan_set != 0 )) && _die "You tried to plan twice!" | |
137 | (( tests == 0 )) && _die "You said to run 0 tests! You've got to run something." | |
138 | ||
139 | _print_plan $tests | |
140 | _expected_tests=$tests | |
141 | _plan_set=1 | |
2a69bf14 | 142 | _last_time=$("${TIME_SCRIPT}") |
86a96e6c CB |
143 | |
144 | return $tests | |
145 | } | |
146 | ||
147 | ||
148 | _print_plan(){ | |
149 | local tests=${1:?} | |
150 | local directive=${2:-''} | |
151 | ||
152 | echo -n "1..$tests" | |
153 | [[ -n "$directive" ]] && echo -n " # $directive" | |
154 | echo | |
155 | } | |
156 | ||
157 | ||
158 | pass(){ | |
159 | local name=$1 | |
160 | ok 0 "$name" | |
161 | } | |
162 | ||
163 | ||
164 | fail(){ | |
165 | local name=$1 | |
166 | ok 1 "$name" | |
167 | } | |
168 | ||
169 | # This is the workhorse method that actually | |
170 | # prints the tests result. | |
171 | ok(){ | |
172 | local result=${1:?} | |
173 | local name=${2:-''} | |
174 | ||
175 | (( _plan_set == 0 )) && _die "You tried to run a test without a plan! Gotta have a plan." | |
176 | ||
177 | _executed_tests=$(( $_executed_tests + 1 )) | |
178 | ||
179 | if [[ -n "$name" ]] ; then | |
180 | if _matches "$name" "^[0-9]+$" ; then | |
181 | diag " You named your test '$name'. You shouldn't use numbers for your test names." | |
182 | diag " Very confusing." | |
183 | fi | |
184 | fi | |
185 | ||
186 | if (( result != 0 )) ; then | |
187 | echo -n "not " | |
188 | _failed_tests=$(( _failed_tests + 1 )) | |
189 | fi | |
190 | echo -n "ok $_executed_tests" | |
191 | ||
192 | if [[ -n "$name" ]] ; then | |
193 | local ename=${name//\#/\\#} | |
194 | echo -n " - $ename" | |
195 | fi | |
196 | ||
197 | if [[ -n "$TODO" ]] ; then | |
198 | echo -n " # TODO $TODO" ; | |
199 | if (( result != 0 )) ; then | |
200 | _failed_tests=$(( _failed_tests - 1 )) | |
201 | fi | |
202 | fi | |
203 | ||
204 | echo | |
2a69bf14 | 205 | _autotime |
86a96e6c CB |
206 | if (( result != 0 )) ; then |
207 | local file='tap-functions' | |
208 | local func= | |
209 | local line= | |
210 | ||
211 | local i=0 | |
212 | local bt=$(caller $i) | |
213 | while _matches "$bt" "tap-functions$" ; do | |
214 | i=$(( $i + 1 )) | |
215 | bt=$(caller $i) | |
216 | done | |
217 | local backtrace= | |
218 | eval $(caller $i | (read line func file ; echo "backtrace=\"$file:$func() at line $line.\"")) | |
219 | ||
220 | local t= | |
221 | [[ -n "$TODO" ]] && t="(TODO) " | |
222 | ||
223 | if [[ -n "$name" ]] ; then | |
224 | diag " Failed ${t}test '$name'" | |
225 | diag " in $backtrace" | |
226 | else | |
227 | diag " Failed ${t}test in $backtrace" | |
228 | fi | |
229 | fi | |
230 | ||
231 | return $result | |
232 | } | |
233 | ||
234 | ||
235 | okx(){ | |
236 | local command="$@" | |
237 | ||
238 | local line= | |
239 | diag "Output of '$command':" | |
240 | "$@" | while read line ; do | |
241 | diag "$line" | |
242 | done | |
243 | ok ${PIPESTATUS[0]} "$command" | |
2a69bf14 | 244 | _autotime |
86a96e6c CB |
245 | } |
246 | ||
247 | ||
248 | _equals(){ | |
249 | local result=${1:?} | |
250 | local expected=${2:?} | |
251 | ||
252 | if [[ "$result" == "$expected" ]] ; then | |
253 | return 0 | |
254 | else | |
255 | return 1 | |
256 | fi | |
257 | } | |
258 | ||
259 | ||
260 | # Thanks to Aaron Kangas for the patch to allow regexp matching | |
261 | # under bash < 3. | |
262 | _bash_major_version=${BASH_VERSION%%.*} | |
263 | _matches(){ | |
264 | local result=${1:?} | |
265 | local pattern=${2:?} | |
266 | ||
267 | if [[ -z "$result" || -z "$pattern" ]] ; then | |
268 | return 1 | |
269 | else | |
270 | if (( _bash_major_version >= 3 )) ; then | |
271 | [[ "$result" =~ "$pattern" ]] | |
272 | else | |
273 | echo "$result" | egrep -q "$pattern" | |
274 | fi | |
275 | fi | |
276 | } | |
277 | ||
278 | ||
279 | _is_diag(){ | |
280 | local result=${1:?} | |
281 | local expected=${2:?} | |
282 | ||
283 | diag " got: '$result'" | |
284 | diag " expected: '$expected'" | |
285 | } | |
286 | ||
287 | ||
288 | is(){ | |
289 | local result=${1:?} | |
290 | local expected=${2:?} | |
291 | local name=${3:-''} | |
292 | ||
293 | _equals "$result" "$expected" | |
294 | (( $? == 0 )) | |
295 | ok $? "$name" | |
296 | local r=$? | |
297 | (( r != 0 )) && _is_diag "$result" "$expected" | |
298 | return $r | |
299 | } | |
300 | ||
301 | ||
302 | isnt(){ | |
303 | local result=${1:?} | |
304 | local expected=${2:?} | |
305 | local name=${3:-''} | |
306 | ||
307 | _equals "$result" "$expected" | |
308 | (( $? != 0 )) | |
309 | ok $? "$name" | |
310 | local r=$? | |
311 | (( r != 0 )) && _is_diag "$result" "$expected" | |
312 | return $r | |
313 | } | |
314 | ||
315 | ||
316 | like(){ | |
317 | local result=${1:?} | |
318 | local pattern=${2:?} | |
319 | local name=${3:-''} | |
320 | ||
321 | _matches "$result" "$pattern" | |
322 | (( $? == 0 )) | |
323 | ok $? "$name" | |
324 | local r=$? | |
325 | (( r != 0 )) && diag " '$result' doesn't match '$pattern'" | |
326 | return $r | |
327 | } | |
328 | ||
329 | ||
330 | unlike(){ | |
331 | local result=${1:?} | |
332 | local pattern=${2:?} | |
333 | local name=${3:-''} | |
334 | ||
335 | _matches "$result" "$pattern" | |
336 | (( $? != 0 )) | |
337 | ok $? "$name" | |
338 | local r=$? | |
339 | (( r != 0 )) && diag " '$result' matches '$pattern'" | |
340 | return $r | |
341 | } | |
342 | ||
343 | ||
344 | skip(){ | |
345 | local condition=${1:?} | |
346 | local reason=${2:-''} | |
347 | local n=${3:-1} | |
348 | ||
349 | if (( condition == 0 )) ; then | |
350 | local i= | |
351 | for (( i=0 ; i<$n ; i++ )) ; do | |
352 | _executed_tests=$(( _executed_tests + 1 )) | |
353 | echo "ok $_executed_tests # skip: $reason" | |
2a69bf14 | 354 | _autotime |
86a96e6c CB |
355 | done |
356 | return 0 | |
357 | else | |
358 | return | |
359 | fi | |
360 | } | |
361 | ||
2a69bf14 KS |
362 | _autotime(){ |
363 | local new_time | |
364 | local duration | |
365 | ||
366 | if [ "${_auto_timing}" -eq "1" ] ; then | |
367 | new_time=$("${TIME_SCRIPT}") | |
368 | duration=$(awk "BEGIN { printf(\"%f\n\", ($new_time - $_last_time)*1000) }") | |
369 | echo " ---" | |
370 | echo " duration_ms: ${duration}" | |
371 | echo " ..." | |
372 | fi | |
373 | _last_time=$("${TIME_SCRIPT}") | |
374 | return 0 | |
375 | } | |
376 | ||
377 | ||
378 | autotime(){ | |
379 | local val=${1:?} | |
380 | ||
381 | if [[ "${val}" != "0" ]] ; then | |
382 | _auto_timing=1 | |
383 | else | |
384 | _auto_timing=0; | |
385 | fi | |
386 | return 0 | |
387 | } | |
86a96e6c CB |
388 | |
389 | diag(){ | |
390 | local msg=${1:?} | |
391 | ||
392 | if [[ -n "$msg" ]] ; then | |
393 | echo "# $msg" | |
394 | fi | |
395 | ||
396 | return 1 | |
397 | } | |
398 | ||
399 | ||
400 | _die(){ | |
401 | local reason=${1:-'<unspecified error>'} | |
402 | ||
403 | echo "$reason" >&2 | |
404 | _test_died=1 | |
405 | _exit 255 | |
406 | } | |
407 | ||
408 | ||
409 | BAIL_OUT(){ | |
410 | local reason=${1:-''} | |
411 | ||
412 | echo "Bail out! $reason" >&2 | |
413 | _exit 255 | |
414 | } | |
415 | ||
416 | ||
417 | _cleanup(){ | |
418 | local rc=0 | |
419 | ||
9f4a25d3 SM |
420 | if (( _plan_set == 0 )) ; then |
421 | diag "Looks like your test died before it could output anything." | |
422 | return $rc | |
423 | fi | |
424 | ||
86a96e6c CB |
425 | if (( _test_died != 0 )) ; then |
426 | diag "Looks like your test died just after $_executed_tests." | |
427 | return $rc | |
428 | fi | |
429 | ||
430 | if (( _skip_all == 0 && _no_plan != 0 )) ; then | |
431 | _print_plan $_executed_tests | |
432 | fi | |
433 | ||
434 | local s= | |
435 | if (( _no_plan == 0 && _expected_tests < _executed_tests )) ; then | |
436 | s= ; (( _expected_tests > 1 )) && s=s | |
437 | local extra=$(( _executed_tests - _expected_tests )) | |
438 | diag "Looks like you planned $_expected_tests test$s but ran $extra extra." | |
439 | rc=1 ; | |
440 | fi | |
441 | ||
442 | if (( _no_plan == 0 && _expected_tests > _executed_tests )) ; then | |
443 | s= ; (( _expected_tests > 1 )) && s=s | |
444 | diag "Looks like you planned $_expected_tests test$s but only ran $_executed_tests." | |
445 | fi | |
446 | ||
447 | if (( _failed_tests > 0 )) ; then | |
448 | s= ; (( _failed_tests > 1 )) && s=s | |
449 | diag "Looks like you failed $_failed_tests test$s of $_executed_tests." | |
450 | fi | |
451 | ||
452 | return $rc | |
453 | } | |
454 | ||
455 | ||
456 | _exit_status(){ | |
457 | if (( _no_plan != 0 || _plan_set == 0 )) ; then | |
458 | return $_failed_tests | |
459 | fi | |
460 | ||
461 | if (( _expected_tests < _executed_tests )) ; then | |
462 | return $(( _executed_tests - _expected_tests )) | |
463 | fi | |
464 | ||
465 | return $(( _failed_tests + ( _expected_tests - _executed_tests ))) | |
466 | } | |
467 | ||
468 | ||
469 | _exit(){ | |
470 | local rc=${1:-''} | |
471 | if [[ -z "$rc" ]] ; then | |
472 | _exit_status | |
473 | rc=$? | |
474 | fi | |
475 | ||
476 | _cleanup | |
477 | local alt_rc=$? | |
478 | (( alt_rc != 0 )) && rc=$alt_rc | |
479 | trap - EXIT | |
480 | exit $rc | |
481 | } |