]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/atf/atf-sh/libatf-sh.subr
MFC r273929:
[FreeBSD/stable/10.git] / contrib / atf / atf-sh / libatf-sh.subr
1 # Copyright (c) 2007 The NetBSD Foundation, Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 # 1. Redistributions of source code must retain the above copyright
8 #    notice, this list of conditions and the following disclaimer.
9 # 2. Redistributions in binary form must reproduce the above copyright
10 #    notice, this list of conditions and the following disclaimer in the
11 #    documentation and/or other materials provided with the distribution.
12 #
13 # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14 # CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 # IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20 # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 # ------------------------------------------------------------------------
27 # GLOBAL VARIABLES
28 # ------------------------------------------------------------------------
29
30 # Values for the expect property.
31 Expect=pass
32 Expect_Reason=
33
34 # A boolean variable that indicates whether we are parsing a test case's
35 # head or not.
36 Parsing_Head=false
37
38 # The program name.
39 Prog_Name=${0##*/}
40
41 # The file to which the test case will print its result.
42 Results_File=
43
44 # The test program's source directory: i.e. where its auxiliary data files
45 # and helper utilities can be found.  Can be overriden through the '-s' flag.
46 Source_Dir="$(dirname ${0})"
47
48 # Indicates the test case we are currently processing.
49 Test_Case=
50
51 # List of meta-data variables for the current test case.
52 Test_Case_Vars=
53
54 # The list of all test cases provided by the test program.
55 Test_Cases=
56
57 # ------------------------------------------------------------------------
58 # PUBLIC INTERFACE
59 # ------------------------------------------------------------------------
60
61 #
62 # atf_add_test_case tc-name
63 #
64 #   Adds the given test case to the list of test cases that form the test
65 #   program.  The name provided here must be accompanied by two functions
66 #   named after it: <tc-name>_head and <tc-name>_body, and optionally by
67 #   a <tc-name>_cleanup function.
68 #
69 atf_add_test_case()
70 {
71     Test_Cases="${Test_Cases} ${1}"
72 }
73
74 #
75 # atf_check cmd expcode expout experr
76 #
77 #   Executes atf-check with given arguments and automatically calls
78 #   atf_fail in case of failure.
79 #
80 atf_check()
81 {
82     ${Atf_Check} "${@}" || \
83         atf_fail "atf-check failed; see the output of the test for details"
84 }
85
86 #
87 # atf_check_equal expected_expression actual_expression
88 #
89 #   Checks that expected_expression's value matches actual_expression's
90 #   and, if not, raises an error.  Ideally expected_expression and
91 #   actual_expression should be provided quoted (not expanded) so that
92 #   the error message is helpful; otherwise it will only show the values,
93 #   not the expressions themselves.
94 #
95 atf_check_equal()
96 {
97     eval _val1=\"${1}\"
98     eval _val2=\"${2}\"
99     test "${_val1}" = "${_val2}" || \
100         atf_fail "${1} != ${2} (${_val1} != ${_val2})"
101 }
102
103 #
104 # atf_config_get varname [defvalue]
105 #
106 #   Prints the value of a configuration variable.  If it is not
107 #   defined, prints the given default value.
108 #
109 atf_config_get()
110 {
111     _varname="__tc_config_var_$(_atf_normalize ${1})"
112     if [ ${#} -eq 1 ]; then
113         eval _value=\"\${${_varname}-__unset__}\"
114         [ "${_value}" = __unset__ ] && \
115             _atf_error 1 "Could not find configuration variable \`${1}'"
116         echo ${_value}
117     elif [ ${#} -eq 2 ]; then
118         eval echo \${${_varname}-${2}}
119     else
120         _atf_error 1 "Incorrect number of parameters for atf_config_get"
121     fi
122 }
123
124 #
125 # atf_config_has varname
126 #
127 #   Returns a boolean indicating if the given configuration variable is
128 #   defined or not.
129 #
130 atf_config_has()
131 {
132     _varname="__tc_config_var_$(_atf_normalize ${1})"
133     eval _value=\"\${${_varname}-__unset__}\"
134     [ "${_value}" != __unset__ ]
135 }
136
137 #
138 # atf_expect_death reason
139 #
140 #   Sets the expectations to 'death'.
141 #
142 atf_expect_death()
143 {
144     _atf_validate_expect
145
146     Expect=death
147     _atf_create_resfile "expected_death: ${*}"
148 }
149
150 #
151 # atf_expect_timeout reason
152 #
153 #   Sets the expectations to 'timeout'.
154 #
155 atf_expect_timeout()
156 {
157     _atf_validate_expect
158
159     Expect=timeout
160     _atf_create_resfile "expected_timeout: ${*}"
161 }
162
163 #
164 # atf_expect_exit exitcode reason
165 #
166 #   Sets the expectations to 'exit'.
167 #
168 atf_expect_exit()
169 {
170     _exitcode="${1}"; shift
171
172     _atf_validate_expect
173
174     Expect=exit
175     if [ "${_exitcode}" = "-1" ]; then
176         _atf_create_resfile "expected_exit: ${*}"
177     else
178         _atf_create_resfile "expected_exit(${_exitcode}): ${*}"
179     fi
180 }
181
182 #
183 # atf_expect_fail reason
184 #
185 #   Sets the expectations to 'fail'.
186 #
187 atf_expect_fail()
188 {
189     _atf_validate_expect
190
191     Expect=fail
192     Expect_Reason="${*}"
193 }
194
195 #
196 # atf_expect_pass
197 #
198 #   Sets the expectations to 'pass'.
199 #
200 atf_expect_pass()
201 {
202     _atf_validate_expect
203
204     Expect=pass
205     Expect_Reason=
206 }
207
208 #
209 # atf_expect_signal signo reason
210 #
211 #   Sets the expectations to 'signal'.
212 #
213 atf_expect_signal()
214 {
215     _signo="${1}"; shift
216
217     _atf_validate_expect
218
219     Expect=signal
220     if [ "${_signo}" = "-1" ]; then
221         _atf_create_resfile "expected_signal: ${*}"
222     else
223         _atf_create_resfile "expected_signal(${_signo}): ${*}"
224     fi
225 }
226
227 #
228 # atf_expected_failure msg1 [.. msgN]
229 #
230 #   Makes the test case report an expected failure with the given error
231 #   message.  Multiple words can be provided, which are concatenated with
232 #   a single blank space.
233 #
234 atf_expected_failure()
235 {
236     _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}"
237     exit 0
238 }
239
240 #
241 # atf_fail msg1 [.. msgN]
242 #
243 #   Makes the test case fail with the given error message.  Multiple
244 #   words can be provided, in which case they are joined by a single
245 #   blank space.
246 #
247 atf_fail()
248 {
249     case "${Expect}" in
250         fail)
251             atf_expected_failure "${@}"
252             ;;
253         pass)
254             _atf_create_resfile "failed: ${*}"
255             exit 1
256             ;;
257         *)
258             _atf_error 128 "Unreachable"
259             ;;
260     esac
261 }
262
263 #
264 # atf_get varname
265 #
266 #   Prints the value of a test case-specific variable.  Given that one
267 #   should not get the value of non-existent variables, it is fine to
268 #   always use this function as 'val=$(atf_get var)'.
269 #
270 atf_get()
271 {
272     eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})}
273 }
274
275 #
276 # atf_get_srcdir
277 #
278 #   Prints the value of the test case's source directory.
279 #
280 atf_get_srcdir()
281 {
282     echo ${Source_Dir}
283 }
284
285 #
286 # atf_pass
287 #
288 #   Makes the test case pass.  Shouldn't be used in general, as a test
289 #   case that does not explicitly fail is assumed to pass.
290 #
291 atf_pass()
292 {
293     case "${Expect}" in
294         fail)
295             Expect=pass
296             atf_fail "Test case was expecting a failure but got a pass instead"
297             ;;
298         pass)
299             _atf_create_resfile passed
300             exit 0
301             ;;
302         *)
303             _atf_error 128 "Unreachable"
304             ;;
305     esac
306 }
307
308 #
309 # atf_require_prog prog
310 #
311 #   Checks that the given program name (either provided as an absolute
312 #   path or as a plain file name) can be found.  If it is not available,
313 #   automatically skips the test case with an appropriate message.
314 #
315 #   Relative paths are not allowed because the test case cannot predict
316 #   where it will be executed from.
317 #
318 atf_require_prog()
319 {
320     _prog=
321     case ${1} in
322     /*)
323         _prog="${1}"
324         [ -x ${_prog} ] || \
325             atf_skip "The required program ${1} could not be found"
326         ;;
327     */*)
328         atf_fail "atf_require_prog does not accept relative path names \`${1}'"
329         ;;
330     *)
331         _prog=$(_atf_find_in_path "${1}")
332         [ -n "${_prog}" ] || \
333             atf_skip "The required program ${1} could not be found" \
334                      "in the PATH"
335         ;;
336     esac
337 }
338
339 #
340 # atf_set varname val1 [.. valN]
341 #
342 #   Sets the test case's variable 'varname' to the specified values
343 #   which are concatenated using a single blank space.  This function
344 #   is supposed to be called form the test case's head only.
345 #
346 atf_set()
347 {
348     ${Parsing_Head} || \
349         _atf_error 128 "atf_set called from the test case's body"
350
351     Test_Case_Vars="${Test_Case_Vars} ${1}"
352     _var=$(_atf_normalize ${1}); shift
353     eval __tc_var_${Test_Case}_${_var}=\"\${*}\"
354 }
355
356 #
357 # atf_skip msg1 [.. msgN]
358 #
359 #   Skips the test case because of the reason provided.  Multiple words
360 #   can be given, in which case they are joined by a single blank space.
361 #
362 atf_skip()
363 {
364     _atf_create_resfile "skipped: ${*}"
365     exit 0
366 }
367
368 #
369 # atf_test_case tc-name cleanup
370 #
371 #   Defines a new test case named tc-name.  The name provided here must be
372 #   accompanied by two functions named after it: <tc-name>_head and
373 #   <tc-name>_body.  If cleanup is set to 'cleanup', then this also expects
374 #   a <tc-name>_cleanup function to be defined.
375 #
376 atf_test_case()
377 {
378     eval "${1}_head() { :; }"
379     eval "${1}_body() { atf_fail 'Test case not implemented'; }"
380     if [ "${2}" = cleanup ]; then
381         eval __has_cleanup_${1}=true
382         eval "${1}_cleanup() { :; }"
383     else
384         eval "${1}_cleanup() {
385             _atf_error 1 'Test case ${1} declared without a cleanup routine'; }"
386     fi
387 }
388
389 # ------------------------------------------------------------------------
390 # PRIVATE INTERFACE
391 # ------------------------------------------------------------------------
392
393 #
394 # _atf_config_set varname val1 [.. valN]
395 #
396 #   Sets the test case's private variable 'varname' to the specified
397 #   values which are concatenated using a single blank space.
398 #
399 _atf_config_set()
400 {
401     _var=$(_atf_normalize ${1}); shift
402     eval __tc_config_var_${_var}=\"\${*}\"
403     Config_Vars="${Config_Vars} __tc_config_var_${_var}"
404 }
405
406 #
407 # _atf_config_set_str varname=val
408 #
409 #   Sets the test case's private variable 'varname' to the specified
410 #   value.  The parameter is of the form 'varname=val'.
411 #
412 _atf_config_set_from_str()
413 {
414     _oldifs=${IFS}
415     IFS='='
416     set -- ${*}
417     _var=${1}
418     shift
419     _val="${@}"
420     IFS=${_oldifs}
421     _atf_config_set "${_var}" "${_val}"
422 }
423
424 #
425 # _atf_create_resfile contents
426 #
427 #   Creates the results file.
428 #
429 _atf_create_resfile()
430 {
431     if [ -n "${Results_File}" ]; then
432         echo "${*}" >"${Results_File}" || \
433             _atf_error 128 "Cannot create results file '${Results_File}'"
434     else
435         echo "${*}"
436     fi
437 }
438
439 #
440 # _atf_error error_code [msg1 [.. msgN]]
441 #
442 #   Prints the given error message (which can be composed of multiple
443 #   arguments, in which case are joined by a single space) and exits
444 #   with the specified error code.
445 #
446 #   This must not be used by test programs themselves (hence making
447 #   the function private) to indicate a test case's failure.  They
448 #   have to use the atf_fail function.
449 #
450 _atf_error()
451 {
452     _error_code="${1}"; shift
453
454     echo "${Prog_Name}: ERROR:" "$@" 1>&2
455     exit ${_error_code}
456 }
457
458 #
459 # _atf_warning msg1 [.. msgN]
460 #
461 #   Prints the given warning message (which can be composed of multiple
462 #   arguments, in which case are joined by a single space).
463 #
464 _atf_warning()
465 {
466     echo "${Prog_Name}: WARNING:" "$@" 1>&2
467 }
468
469 #
470 # _atf_find_in_path program
471 #
472 #   Looks for a program in the path and prints the full path to it or
473 #   nothing if it could not be found.  It also returns true in case of
474 #   success.
475 #
476 _atf_find_in_path()
477 {
478     _prog="${1}"
479
480     _oldifs=${IFS}
481     IFS=:
482     for _dir in ${PATH}
483     do
484         if [ -x ${_dir}/${_prog} ]; then
485             IFS=${_oldifs}
486             echo ${_dir}/${_prog}
487             return 0
488         fi
489     done
490     IFS=${_oldifs}
491
492     return 1
493 }
494
495 #
496 # _atf_has_tc name
497 #
498 #   Returns true if the given test case exists.
499 #
500 _atf_has_tc()
501 {
502     for _tc in ${Test_Cases}; do
503         [ "${_tc}" != "${1}" ] || return 0
504     done
505     return 1
506 }
507
508 #
509 # _atf_list_tcs
510 #
511 #   Describes all test cases and prints the list to the standard output.
512 #
513 _atf_list_tcs()
514 {
515     echo 'Content-Type: application/X-atf-tp; version="1"'
516     echo
517
518     set -- ${Test_Cases}
519     while [ ${#} -gt 0 ]; do
520         _atf_parse_head ${1}
521
522         echo "ident: $(atf_get ident)"
523         for _var in ${Test_Case_Vars}; do
524             [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})"
525         done
526
527         [ ${#} -gt 1 ] && echo
528         shift
529     done
530 }
531
532 #
533 # _atf_normalize str
534 #
535 #   Normalizes a string so that it is a valid shell variable name.
536 #
537 _atf_normalize()
538 {
539     echo ${1} | tr .- __
540 }
541
542 #
543 # _atf_parse_head tcname
544 #
545 #   Evaluates a test case's head to gather its variables and prepares the
546 #   test program to run it.
547 #
548 _atf_parse_head()
549 {
550     Parsing_Head=true
551
552     Test_Case="${1}"
553     Test_Case_Vars=
554
555     if _atf_has_cleanup "${1}"; then
556         atf_set has.cleanup "true"
557     fi
558
559     ${1}_head
560     atf_set ident "${1}"
561
562     Parsing_Head=false
563 }
564
565 #
566 # _atf_run_tc tc
567 #
568 #   Runs the specified test case.  Prints its exit status to the
569 #   standard output and returns a boolean indicating if the test was
570 #   successful or not.
571 #
572 _atf_run_tc()
573 {
574     case ${1} in
575     *:*)
576         _tcname=${1%%:*}
577         _tcpart=${1#*:}
578
579         if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then
580             _atf_syntax_error "Unknown test case part \`${_tcpart}'"
581         fi
582         ;;
583
584     *)
585         _tcname=${1}
586         _tcpart=body
587         ;;
588     esac
589
590     _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'"
591
592     if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then
593         _atf_warning "Running test cases outside of kyua(1) is unsupported"
594         _atf_warning "No isolation nor timeout control is being applied;" \
595             "you may get unexpected failures; see atf-test-case(4)"
596     fi
597
598     _atf_parse_head ${_tcname}
599
600     case ${_tcpart} in
601     body)
602         if ${_tcname}_body; then
603             _atf_validate_expect
604             _atf_create_resfile passed
605         else
606             Expect=pass
607             atf_fail "Test case body returned a non-ok exit code, but" \
608                 "this is not allowed"
609         fi
610         ;;
611     cleanup)
612         if _atf_has_cleanup "${_tcname}"; then
613             ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \
614                 "returned a non-ok exit code, but this is not allowed"
615         fi
616         ;;
617     *)
618         _atf_error 128 "Unknown test case part"
619         ;;
620     esac
621 }
622
623 #
624 # _atf_syntax_error msg1 [.. msgN]
625 #
626 #   Formats and prints a syntax error message and terminates the
627 #   program prematurely.
628 #
629 _atf_syntax_error()
630 {
631     echo "${Prog_Name}: ERROR: ${@}" 1>&2
632     echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2
633     exit 1
634 }
635
636 #
637 # _atf_has_cleanup tc-name
638 #
639 #   Returns a boolean indicating if the given test case has a cleanup
640 #   routine or not.
641 #
642 _atf_has_cleanup()
643 {
644     _found=true
645     eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false"
646     [ "${_found}" = true ]
647 }
648
649 #
650 # _atf_validate_expect
651 #
652 #   Ensures that the current test case state is correct regarding the expect
653 #   status.
654 #
655 _atf_validate_expect()
656 {
657     case "${Expect}" in
658         death)
659             Expect=pass
660             atf_fail "Test case was expected to terminate abruptly but it" \
661                 "continued execution"
662             ;;
663         exit)
664             Expect=pass
665             atf_fail "Test case was expected to exit cleanly but it continued" \
666                 "execution"
667             ;;
668         fail)
669             Expect=pass
670             atf_fail "Test case was expecting a failure but none were raised"
671             ;;
672         pass)
673             ;;
674         signal)
675             Expect=pass
676             atf_fail "Test case was expected to receive a termination signal" \
677                 "but it continued execution"
678             ;;
679         timeout)
680             Expect=pass
681             atf_fail "Test case was expected to hang but it continued execution"
682             ;;
683         *)
684             _atf_error 128 "Unreachable"
685             ;;
686     esac
687 }
688
689 #
690 # _atf_warning [msg1 [.. msgN]]
691 #
692 #   Prints the given warning message (which can be composed of multiple
693 #   arguments, in which case are joined by a single space).
694 #
695 #   This must not be used by test programs themselves (hence making
696 #   the function private).
697 #
698 _atf_warning()
699 {
700     echo "${Prog_Name}: WARNING:" "$@" 1>&2
701 }
702
703 #
704 # main [options] test_case
705 #
706 #   Test program's entry point.
707 #
708 main()
709 {
710     # Process command-line options first.
711     _numargs=${#}
712     _lflag=false
713     while getopts :lr:s:v: arg; do
714         case ${arg} in
715         l)
716             _lflag=true
717             ;;
718
719         r)
720             Results_File=${OPTARG}
721             ;;
722
723         s)
724             Source_Dir=${OPTARG}
725             ;;
726
727         v)
728             _atf_config_set_from_str "${OPTARG}"
729             ;;
730
731         \?)
732             _atf_syntax_error "Unknown option -${OPTARG}."
733             # NOTREACHED
734             ;;
735         esac
736     done
737     shift `expr ${OPTIND} - 1`
738
739     case ${Source_Dir} in
740         /*)
741             ;;
742         *)
743             Source_Dir=$(pwd)/${Source_Dir}
744             ;;
745     esac
746     [ -f ${Source_Dir}/${Prog_Name} ] || \
747         _atf_error 1 "Cannot find the test program in the source" \
748                      "directory \`${Source_Dir}'"
749
750     # Call the test program's hook to register all available test cases.
751     atf_init_test_cases
752
753     # Run or list test cases.
754     if `${_lflag}`; then
755         if [ ${#} -gt 0 ]; then
756             _atf_syntax_error "Cannot provide test case names with -l"
757         fi
758         _atf_list_tcs
759     else
760         if [ ${#} -eq 0 ]; then
761             _atf_syntax_error "Must provide a test case name"
762         elif [ ${#} -gt 1 ]; then
763             _atf_syntax_error "Cannot provide more than one test case name"
764         else
765             _atf_run_tc "${1}"
766         fi
767     fi
768 }
769
770 # vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4