xref: /freebsd/contrib/atf/atf-sh/libatf-sh.subr (revision 83a1ee578c9d1ab7013e997289c7cd470c0e6902)
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.
31Expect=pass
32Expect_Reason=
33
34# A boolean variable that indicates whether we are parsing a test case's
35# head or not.
36Parsing_Head=false
37
38# The program name.
39Prog_Name=${0##*/}
40
41# The file to which the test case will print its result.
42Results_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.
46Source_Dir="$(dirname ${0})"
47
48# Indicates the test case we are currently processing.
49Test_Case=
50
51# List of meta-data variables for the current test case.
52Test_Case_Vars=
53
54# The list of all test cases provided by the test program.
55Test_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#
69atf_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#
80atf_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#
95atf_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_check_not_equal expected_expression actual_expression
105#
106#   Checks that expected_expression's value does not match actual_expression's
107#   and, if it does, raises an error.  Ideally expected_expression and
108#   actual_expression should be provided quoted (not expanded) so that
109#   the error message is helpful; otherwise it will only show the values,
110#   not the expressions themselves.
111#
112atf_check_not_equal()
113{
114    eval _val1=\"${1}\"
115    eval _val2=\"${2}\"
116    test "${_val1}" != "${_val2}" || \
117        atf_fail "${1} == ${2} (${_val1} == ${_val2})"
118}
119
120#
121# atf_config_get varname [defvalue]
122#
123#   Prints the value of a configuration variable.  If it is not
124#   defined, prints the given default value.
125#
126atf_config_get()
127{
128    _varname="__tc_config_var_$(_atf_normalize ${1})"
129    if [ ${#} -eq 1 ]; then
130        eval _value=\"\${${_varname}-__unset__}\"
131        [ "${_value}" = __unset__ ] && \
132            _atf_error 1 "Could not find configuration variable \`${1}'"
133        echo ${_value}
134    elif [ ${#} -eq 2 ]; then
135        eval echo \${${_varname}-${2}}
136    else
137        _atf_error 1 "Incorrect number of parameters for atf_config_get"
138    fi
139}
140
141#
142# atf_config_has varname
143#
144#   Returns a boolean indicating if the given configuration variable is
145#   defined or not.
146#
147atf_config_has()
148{
149    _varname="__tc_config_var_$(_atf_normalize ${1})"
150    eval _value=\"\${${_varname}-__unset__}\"
151    [ "${_value}" != __unset__ ]
152}
153
154#
155# atf_expect_death reason
156#
157#   Sets the expectations to 'death'.
158#
159atf_expect_death()
160{
161    _atf_validate_expect
162
163    Expect=death
164    _atf_create_resfile "expected_death: ${*}"
165}
166
167#
168# atf_expect_timeout reason
169#
170#   Sets the expectations to 'timeout'.
171#
172atf_expect_timeout()
173{
174    _atf_validate_expect
175
176    Expect=timeout
177    _atf_create_resfile "expected_timeout: ${*}"
178}
179
180#
181# atf_expect_exit exitcode reason
182#
183#   Sets the expectations to 'exit'.
184#
185atf_expect_exit()
186{
187    _exitcode="${1}"; shift
188
189    _atf_validate_expect
190
191    Expect=exit
192    if [ "${_exitcode}" = "-1" ]; then
193        _atf_create_resfile "expected_exit: ${*}"
194    else
195        _atf_create_resfile "expected_exit(${_exitcode}): ${*}"
196    fi
197}
198
199#
200# atf_expect_fail reason
201#
202#   Sets the expectations to 'fail'.
203#
204atf_expect_fail()
205{
206    _atf_validate_expect
207
208    Expect=fail
209    Expect_Reason="${*}"
210}
211
212#
213# atf_expect_pass
214#
215#   Sets the expectations to 'pass'.
216#
217atf_expect_pass()
218{
219    _atf_validate_expect
220
221    Expect=pass
222    Expect_Reason=
223}
224
225#
226# atf_expect_signal signo reason
227#
228#   Sets the expectations to 'signal'.
229#
230atf_expect_signal()
231{
232    _signo="${1}"; shift
233
234    _atf_validate_expect
235
236    Expect=signal
237    if [ "${_signo}" = "-1" ]; then
238        _atf_create_resfile "expected_signal: ${*}"
239    else
240        _atf_create_resfile "expected_signal(${_signo}): ${*}"
241    fi
242}
243
244#
245# atf_expected_failure msg1 [.. msgN]
246#
247#   Makes the test case report an expected failure with the given error
248#   message.  Multiple words can be provided, which are concatenated with
249#   a single blank space.
250#
251atf_expected_failure()
252{
253    _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}"
254    exit 0
255}
256
257#
258# atf_fail msg1 [.. msgN]
259#
260#   Makes the test case fail with the given error message.  Multiple
261#   words can be provided, in which case they are joined by a single
262#   blank space.
263#
264atf_fail()
265{
266    case "${Expect}" in
267        fail)
268            atf_expected_failure "${@}"
269            ;;
270        pass)
271            _atf_create_resfile "failed: ${*}"
272            exit 1
273            ;;
274        *)
275            _atf_error 128 "Unreachable"
276            ;;
277    esac
278}
279
280#
281# atf_get varname
282#
283#   Prints the value of a test case-specific variable.  Given that one
284#   should not get the value of non-existent variables, it is fine to
285#   always use this function as 'val=$(atf_get var)'.
286#
287atf_get()
288{
289    eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})}
290}
291
292#
293# atf_get_srcdir
294#
295#   Prints the value of the test case's source directory.
296#
297atf_get_srcdir()
298{
299    echo ${Source_Dir}
300}
301
302#
303# atf_pass
304#
305#   Makes the test case pass.  Shouldn't be used in general, as a test
306#   case that does not explicitly fail is assumed to pass.
307#
308atf_pass()
309{
310    case "${Expect}" in
311        fail)
312            Expect=pass
313            atf_fail "Test case was expecting a failure but got a pass instead"
314            ;;
315        pass)
316            _atf_create_resfile passed
317            exit 0
318            ;;
319        *)
320            _atf_error 128 "Unreachable"
321            ;;
322    esac
323}
324
325#
326# atf_require_prog prog
327#
328#   Checks that the given program name (either provided as an absolute
329#   path or as a plain file name) can be found.  If it is not available,
330#   automatically skips the test case with an appropriate message.
331#
332#   Relative paths are not allowed because the test case cannot predict
333#   where it will be executed from.
334#
335atf_require_prog()
336{
337    _prog=
338    case ${1} in
339    /*)
340        _prog="${1}"
341        [ -x ${_prog} ] || \
342            atf_skip "The required program ${1} could not be found"
343        ;;
344    */*)
345        atf_fail "atf_require_prog does not accept relative path names \`${1}'"
346        ;;
347    *)
348        _prog=$(_atf_find_in_path "${1}")
349        [ -n "${_prog}" ] || \
350            atf_skip "The required program ${1} could not be found" \
351                     "in the PATH"
352        ;;
353    esac
354}
355
356#
357# atf_require_kmod kmod
358#
359#   Checks that the given kmod is loaded.  If it is not, automatically
360#   skips the test case with an appropriate message.
361#
362atf_require_kmod()
363{
364    kldstat -q "${1}" || \
365        atf_skip "The required kmod ${1} is not loaded"
366}
367
368#
369# atf_set varname val1 [.. valN]
370#
371#   Sets the test case's variable 'varname' to the specified values
372#   which are concatenated using a single blank space.  This function
373#   is supposed to be called from the test case's head only.
374#
375atf_set()
376{
377    ${Parsing_Head} || \
378        _atf_error 128 "atf_set called from the test case's body"
379
380    Test_Case_Vars="${Test_Case_Vars} ${1}"
381    _var=$(_atf_normalize ${1}); shift
382    eval __tc_var_${Test_Case}_${_var}=\"\${*}\"
383}
384
385#
386# atf_skip msg1 [.. msgN]
387#
388#   Skips the test case because of the reason provided.  Multiple words
389#   can be given, in which case they are joined by a single blank space.
390#
391atf_skip()
392{
393    _atf_create_resfile "skipped: ${*}"
394    exit 0
395}
396
397#
398# atf_test_case tc-name cleanup
399#
400#   Defines a new test case named tc-name.  The name provided here must be
401#   accompanied by two functions named after it: <tc-name>_head and
402#   <tc-name>_body.  If cleanup is set to 'cleanup', then this also expects
403#   a <tc-name>_cleanup function to be defined.
404#
405atf_test_case()
406{
407    eval "${1}_head() { :; }"
408    eval "${1}_body() { atf_fail 'Test case not implemented'; }"
409    if [ "${2}" = cleanup ]; then
410        eval __has_cleanup_${1}=true
411        eval "${1}_cleanup() { :; }"
412    else
413        eval "${1}_cleanup() {
414            _atf_error 1 'Test case ${1} declared without a cleanup routine'; }"
415    fi
416}
417
418# ------------------------------------------------------------------------
419# PRIVATE INTERFACE
420# ------------------------------------------------------------------------
421
422#
423# _atf_config_set varname val1 [.. valN]
424#
425#   Sets the test case's private variable 'varname' to the specified
426#   values which are concatenated using a single blank space.
427#
428_atf_config_set()
429{
430    _var=$(_atf_normalize ${1}); shift
431    eval __tc_config_var_${_var}=\"\${*}\"
432    Config_Vars="${Config_Vars} __tc_config_var_${_var}"
433}
434
435#
436# _atf_config_set_str varname=val
437#
438#   Sets the test case's private variable 'varname' to the specified
439#   value.  The parameter is of the form 'varname=val'.
440#
441_atf_config_set_from_str()
442{
443    _oldifs=${IFS}
444    IFS='='
445    set -- ${*}
446    _var=${1}
447    shift
448    _val="${@}"
449    IFS=${_oldifs}
450    _atf_config_set "${_var}" "${_val}"
451}
452
453#
454# _atf_create_resfile contents
455#
456#   Creates the results file.
457#
458_atf_create_resfile()
459{
460    if [ -n "${Results_File}" ]; then
461        echo "${*}" >"${Results_File}" || \
462            _atf_error 128 "Cannot create results file '${Results_File}'"
463    else
464        echo "${*}"
465    fi
466}
467
468#
469# _atf_error error_code [msg1 [.. msgN]]
470#
471#   Prints the given error message (which can be composed of multiple
472#   arguments, in which case are joined by a single space) and exits
473#   with the specified error code.
474#
475#   This must not be used by test programs themselves (hence making
476#   the function private) to indicate a test case's failure.  They
477#   have to use the atf_fail function.
478#
479_atf_error()
480{
481    _error_code="${1}"; shift
482
483    echo "${Prog_Name}: ERROR:" "$@" 1>&2
484    exit ${_error_code}
485}
486
487#
488# _atf_warning msg1 [.. msgN]
489#
490#   Prints the given warning message (which can be composed of multiple
491#   arguments, in which case are joined by a single space).
492#
493_atf_warning()
494{
495    echo "${Prog_Name}: WARNING:" "$@" 1>&2
496}
497
498#
499# _atf_find_in_path program
500#
501#   Looks for a program in the path and prints the full path to it or
502#   nothing if it could not be found.  It also returns true in case of
503#   success.
504#
505_atf_find_in_path()
506{
507    _prog="${1}"
508
509    _oldifs=${IFS}
510    IFS=:
511    for _dir in ${PATH}
512    do
513        if [ -x ${_dir}/${_prog} ]; then
514            IFS=${_oldifs}
515            echo ${_dir}/${_prog}
516            return 0
517        fi
518    done
519    IFS=${_oldifs}
520
521    return 1
522}
523
524#
525# _atf_has_tc name
526#
527#   Returns true if the given test case exists.
528#
529_atf_has_tc()
530{
531    for _tc in ${Test_Cases}; do
532        [ "${_tc}" != "${1}" ] || return 0
533    done
534    return 1
535}
536
537#
538# _atf_list_tcs
539#
540#   Describes all test cases and prints the list to the standard output.
541#
542_atf_list_tcs()
543{
544    echo 'Content-Type: application/X-atf-tp; version="1"'
545    echo
546
547    set -- ${Test_Cases}
548    while [ ${#} -gt 0 ]; do
549        _atf_parse_head ${1}
550
551        echo "ident: $(atf_get ident)"
552        for _var in ${Test_Case_Vars}; do
553            [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})"
554        done
555
556        [ ${#} -gt 1 ] && echo
557        shift
558    done
559}
560
561#
562# _atf_normalize str
563#
564#   Normalizes a string so that it is a valid shell variable name.
565#
566_atf_normalize()
567{
568    # Check if the string contains any of the forbidden characters using
569    # POSIX parameter expansion (the ${var//} string substitution is
570    # unfortunately not supported in POSIX sh) and only use tr(1) then.
571    # tr(1) is generally not a builtin, so doing the substring check first
572    # avoids unnecessary fork()+execve() calls. As this function is called
573    # many times in each test script startup, those overheads add up
574    # (especially when running on emulated platforms such as QEMU).
575    if [ "${1#*[.-]}" != "$1" ]; then
576        echo "$1" | tr .- __
577    else
578        echo "$1"
579    fi
580}
581
582#
583# _atf_parse_head tcname
584#
585#   Evaluates a test case's head to gather its variables and prepares the
586#   test program to run it.
587#
588_atf_parse_head()
589{
590    Parsing_Head=true
591
592    Test_Case="${1}"
593    Test_Case_Vars=
594
595    if _atf_has_cleanup "${1}"; then
596        atf_set has.cleanup "true"
597    fi
598
599    ${1}_head
600    atf_set ident "${1}"
601
602    Parsing_Head=false
603}
604
605#
606# _atf_run_tc tc
607#
608#   Runs the specified test case.  Prints its exit status to the
609#   standard output and returns a boolean indicating if the test was
610#   successful or not.
611#
612_atf_run_tc()
613{
614    case ${1} in
615    *:*)
616        _tcname=${1%%:*}
617        _tcpart=${1#*:}
618
619        if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then
620            _atf_syntax_error "Unknown test case part \`${_tcpart}'"
621        fi
622        ;;
623
624    *)
625        _tcname=${1}
626        _tcpart=body
627        ;;
628    esac
629
630    _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'"
631
632    if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then
633        _atf_warning "Running test cases outside of kyua(1) is unsupported"
634        _atf_warning "No isolation nor timeout control is being applied;" \
635            "you may get unexpected failures; see atf-test-case(4)"
636    fi
637
638    _atf_parse_head ${_tcname}
639
640    case ${_tcpart} in
641    body)
642        if ${_tcname}_body; then
643            _atf_validate_expect
644            _atf_create_resfile passed
645        else
646            Expect=pass
647            atf_fail "Test case body returned a non-ok exit code, but" \
648                "this is not allowed"
649        fi
650        ;;
651    cleanup)
652        if _atf_has_cleanup "${_tcname}"; then
653            ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \
654                "returned a non-ok exit code, but this is not allowed"
655        fi
656        ;;
657    *)
658        _atf_error 128 "Unknown test case part"
659        ;;
660    esac
661}
662
663#
664# _atf_syntax_error msg1 [.. msgN]
665#
666#   Formats and prints a syntax error message and terminates the
667#   program prematurely.
668#
669_atf_syntax_error()
670{
671    echo "${Prog_Name}: ERROR: ${@}" 1>&2
672    echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2
673    exit 1
674}
675
676#
677# _atf_has_cleanup tc-name
678#
679#   Returns a boolean indicating if the given test case has a cleanup
680#   routine or not.
681#
682_atf_has_cleanup()
683{
684    _found=true
685    eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false"
686    [ "${_found}" = true ]
687}
688
689#
690# _atf_validate_expect
691#
692#   Ensures that the current test case state is correct regarding the expect
693#   status.
694#
695_atf_validate_expect()
696{
697    case "${Expect}" in
698        death)
699            Expect=pass
700            atf_fail "Test case was expected to terminate abruptly but it" \
701                "continued execution"
702            ;;
703        exit)
704            Expect=pass
705            atf_fail "Test case was expected to exit cleanly but it continued" \
706                "execution"
707            ;;
708        fail)
709            Expect=pass
710            atf_fail "Test case was expecting a failure but none were raised"
711            ;;
712        pass)
713            ;;
714        signal)
715            Expect=pass
716            atf_fail "Test case was expected to receive a termination signal" \
717                "but it continued execution"
718            ;;
719        timeout)
720            Expect=pass
721            atf_fail "Test case was expected to hang but it continued execution"
722            ;;
723        *)
724            _atf_error 128 "Unreachable"
725            ;;
726    esac
727}
728
729#
730# _atf_warning [msg1 [.. msgN]]
731#
732#   Prints the given warning message (which can be composed of multiple
733#   arguments, in which case are joined by a single space).
734#
735#   This must not be used by test programs themselves (hence making
736#   the function private).
737#
738_atf_warning()
739{
740    echo "${Prog_Name}: WARNING:" "$@" 1>&2
741}
742
743#
744# main [options] test_case
745#
746#   Test program's entry point.
747#
748main()
749{
750    # Process command-line options first.
751    _numargs=${#}
752    _lflag=false
753    while getopts :lr:s:v: arg; do
754        case ${arg} in
755        l)
756            _lflag=true
757            ;;
758
759        r)
760            Results_File=${OPTARG}
761            ;;
762
763        s)
764            Source_Dir=${OPTARG}
765            ;;
766
767        v)
768            _atf_config_set_from_str "${OPTARG}"
769            ;;
770
771        \?)
772            _atf_syntax_error "Unknown option -${OPTARG}."
773            # NOTREACHED
774            ;;
775        esac
776    done
777    shift $((OPTIND - 1))
778
779    case ${Source_Dir} in
780        /*)
781            ;;
782        *)
783            Source_Dir=$(pwd)/${Source_Dir}
784            ;;
785    esac
786    [ -f ${Source_Dir}/${Prog_Name} ] || \
787        _atf_error 1 "Cannot find the test program in the source" \
788                     "directory \`${Source_Dir}'"
789
790    # Call the test program's hook to register all available test cases.
791    atf_init_test_cases
792
793    # Run or list test cases.
794    if `${_lflag}`; then
795        if [ ${#} -gt 0 ]; then
796            _atf_syntax_error "Cannot provide test case names with -l"
797        fi
798        _atf_list_tcs
799    else
800        if [ ${#} -eq 0 ]; then
801            _atf_syntax_error "Must provide a test case name"
802        elif [ ${#} -gt 1 ]; then
803            _atf_syntax_error "Cannot provide more than one test case name"
804        else
805            _atf_run_tc "${1}"
806        fi
807    fi
808}
809
810# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
811