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