xref: /freebsd/contrib/atf/atf-sh/libatf-sh.subr (revision 9286d46a794f25482880d29864a8901ef6666fae)
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_set varname val1 [.. valN]
358#
359#   Sets the test case's variable 'varname' to the specified values
360#   which are concatenated using a single blank space.  This function
361#   is supposed to be called from the test case's head only.
362#
363atf_set()
364{
365    ${Parsing_Head} || \
366        _atf_error 128 "atf_set called from the test case's body"
367
368    Test_Case_Vars="${Test_Case_Vars} ${1}"
369    _var=$(_atf_normalize ${1}); shift
370    eval __tc_var_${Test_Case}_${_var}=\"\${*}\"
371}
372
373#
374# atf_skip msg1 [.. msgN]
375#
376#   Skips the test case because of the reason provided.  Multiple words
377#   can be given, in which case they are joined by a single blank space.
378#
379atf_skip()
380{
381    _atf_create_resfile "skipped: ${*}"
382    exit 0
383}
384
385#
386# atf_test_case tc-name cleanup
387#
388#   Defines a new test case named tc-name.  The name provided here must be
389#   accompanied by two functions named after it: <tc-name>_head and
390#   <tc-name>_body.  If cleanup is set to 'cleanup', then this also expects
391#   a <tc-name>_cleanup function to be defined.
392#
393atf_test_case()
394{
395    eval "${1}_head() { :; }"
396    eval "${1}_body() { atf_fail 'Test case not implemented'; }"
397    if [ "${2}" = cleanup ]; then
398        eval __has_cleanup_${1}=true
399        eval "${1}_cleanup() { :; }"
400    else
401        eval "${1}_cleanup() {
402            _atf_error 1 'Test case ${1} declared without a cleanup routine'; }"
403    fi
404}
405
406# ------------------------------------------------------------------------
407# PRIVATE INTERFACE
408# ------------------------------------------------------------------------
409
410#
411# _atf_config_set varname val1 [.. valN]
412#
413#   Sets the test case's private variable 'varname' to the specified
414#   values which are concatenated using a single blank space.
415#
416_atf_config_set()
417{
418    _var=$(_atf_normalize ${1}); shift
419    eval __tc_config_var_${_var}=\"\${*}\"
420    Config_Vars="${Config_Vars} __tc_config_var_${_var}"
421}
422
423#
424# _atf_config_set_str varname=val
425#
426#   Sets the test case's private variable 'varname' to the specified
427#   value.  The parameter is of the form 'varname=val'.
428#
429_atf_config_set_from_str()
430{
431    _oldifs=${IFS}
432    IFS='='
433    set -- ${*}
434    _var=${1}
435    shift
436    _val="${@}"
437    IFS=${_oldifs}
438    _atf_config_set "${_var}" "${_val}"
439}
440
441#
442# _atf_create_resfile contents
443#
444#   Creates the results file.
445#
446_atf_create_resfile()
447{
448    if [ -n "${Results_File}" ]; then
449        echo "${*}" >"${Results_File}" || \
450            _atf_error 128 "Cannot create results file '${Results_File}'"
451    else
452        echo "${*}"
453    fi
454}
455
456#
457# _atf_error error_code [msg1 [.. msgN]]
458#
459#   Prints the given error message (which can be composed of multiple
460#   arguments, in which case are joined by a single space) and exits
461#   with the specified error code.
462#
463#   This must not be used by test programs themselves (hence making
464#   the function private) to indicate a test case's failure.  They
465#   have to use the atf_fail function.
466#
467_atf_error()
468{
469    _error_code="${1}"; shift
470
471    echo "${Prog_Name}: ERROR:" "$@" 1>&2
472    exit ${_error_code}
473}
474
475#
476# _atf_warning msg1 [.. msgN]
477#
478#   Prints the given warning message (which can be composed of multiple
479#   arguments, in which case are joined by a single space).
480#
481_atf_warning()
482{
483    echo "${Prog_Name}: WARNING:" "$@" 1>&2
484}
485
486#
487# _atf_find_in_path program
488#
489#   Looks for a program in the path and prints the full path to it or
490#   nothing if it could not be found.  It also returns true in case of
491#   success.
492#
493_atf_find_in_path()
494{
495    _prog="${1}"
496
497    _oldifs=${IFS}
498    IFS=:
499    for _dir in ${PATH}
500    do
501        if [ -x ${_dir}/${_prog} ]; then
502            IFS=${_oldifs}
503            echo ${_dir}/${_prog}
504            return 0
505        fi
506    done
507    IFS=${_oldifs}
508
509    return 1
510}
511
512#
513# _atf_has_tc name
514#
515#   Returns true if the given test case exists.
516#
517_atf_has_tc()
518{
519    for _tc in ${Test_Cases}; do
520        [ "${_tc}" != "${1}" ] || return 0
521    done
522    return 1
523}
524
525#
526# _atf_list_tcs
527#
528#   Describes all test cases and prints the list to the standard output.
529#
530_atf_list_tcs()
531{
532    echo 'Content-Type: application/X-atf-tp; version="1"'
533    echo
534
535    set -- ${Test_Cases}
536    while [ ${#} -gt 0 ]; do
537        _atf_parse_head ${1}
538
539        echo "ident: $(atf_get ident)"
540        for _var in ${Test_Case_Vars}; do
541            [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})"
542        done
543
544        [ ${#} -gt 1 ] && echo
545        shift
546    done
547}
548
549#
550# _atf_normalize str
551#
552#   Normalizes a string so that it is a valid shell variable name.
553#
554_atf_normalize()
555{
556    # Check if the string contains any of the forbidden characters using
557    # POSIX parameter expansion (the ${var//} string substitution is
558    # unfortunately not supported in POSIX sh) and only use tr(1) then.
559    # tr(1) is generally not a builtin, so doing the substring check first
560    # avoids unnecessary fork()+execve() calls. As this function is called
561    # many times in each test script startup, those overheads add up
562    # (especially when running on emulated platforms such as QEMU).
563    if [ "${1#*[.-]}" != "$1" ]; then
564        echo "$1" | tr .- __
565    else
566        echo "$1"
567    fi
568}
569
570#
571# _atf_parse_head tcname
572#
573#   Evaluates a test case's head to gather its variables and prepares the
574#   test program to run it.
575#
576_atf_parse_head()
577{
578    Parsing_Head=true
579
580    Test_Case="${1}"
581    Test_Case_Vars=
582
583    if _atf_has_cleanup "${1}"; then
584        atf_set has.cleanup "true"
585    fi
586
587    ${1}_head
588    atf_set ident "${1}"
589
590    Parsing_Head=false
591}
592
593#
594# _atf_run_tc tc
595#
596#   Runs the specified test case.  Prints its exit status to the
597#   standard output and returns a boolean indicating if the test was
598#   successful or not.
599#
600_atf_run_tc()
601{
602    case ${1} in
603    *:*)
604        _tcname=${1%%:*}
605        _tcpart=${1#*:}
606
607        if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then
608            _atf_syntax_error "Unknown test case part \`${_tcpart}'"
609        fi
610        ;;
611
612    *)
613        _tcname=${1}
614        _tcpart=body
615        ;;
616    esac
617
618    _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'"
619
620    if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then
621        _atf_warning "Running test cases outside of kyua(1) is unsupported"
622        _atf_warning "No isolation nor timeout control is being applied;" \
623            "you may get unexpected failures; see atf-test-case(4)"
624    fi
625
626    _atf_parse_head ${_tcname}
627
628    case ${_tcpart} in
629    body)
630        if ${_tcname}_body; then
631            _atf_validate_expect
632            _atf_create_resfile passed
633        else
634            Expect=pass
635            atf_fail "Test case body returned a non-ok exit code, but" \
636                "this is not allowed"
637        fi
638        ;;
639    cleanup)
640        if _atf_has_cleanup "${_tcname}"; then
641            ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \
642                "returned a non-ok exit code, but this is not allowed"
643        fi
644        ;;
645    *)
646        _atf_error 128 "Unknown test case part"
647        ;;
648    esac
649}
650
651#
652# _atf_syntax_error msg1 [.. msgN]
653#
654#   Formats and prints a syntax error message and terminates the
655#   program prematurely.
656#
657_atf_syntax_error()
658{
659    echo "${Prog_Name}: ERROR: ${@}" 1>&2
660    echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2
661    exit 1
662}
663
664#
665# _atf_has_cleanup tc-name
666#
667#   Returns a boolean indicating if the given test case has a cleanup
668#   routine or not.
669#
670_atf_has_cleanup()
671{
672    _found=true
673    eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false"
674    [ "${_found}" = true ]
675}
676
677#
678# _atf_validate_expect
679#
680#   Ensures that the current test case state is correct regarding the expect
681#   status.
682#
683_atf_validate_expect()
684{
685    case "${Expect}" in
686        death)
687            Expect=pass
688            atf_fail "Test case was expected to terminate abruptly but it" \
689                "continued execution"
690            ;;
691        exit)
692            Expect=pass
693            atf_fail "Test case was expected to exit cleanly but it continued" \
694                "execution"
695            ;;
696        fail)
697            Expect=pass
698            atf_fail "Test case was expecting a failure but none were raised"
699            ;;
700        pass)
701            ;;
702        signal)
703            Expect=pass
704            atf_fail "Test case was expected to receive a termination signal" \
705                "but it continued execution"
706            ;;
707        timeout)
708            Expect=pass
709            atf_fail "Test case was expected to hang but it continued execution"
710            ;;
711        *)
712            _atf_error 128 "Unreachable"
713            ;;
714    esac
715}
716
717#
718# _atf_warning [msg1 [.. msgN]]
719#
720#   Prints the given warning message (which can be composed of multiple
721#   arguments, in which case are joined by a single space).
722#
723#   This must not be used by test programs themselves (hence making
724#   the function private).
725#
726_atf_warning()
727{
728    echo "${Prog_Name}: WARNING:" "$@" 1>&2
729}
730
731#
732# main [options] test_case
733#
734#   Test program's entry point.
735#
736main()
737{
738    # Process command-line options first.
739    _numargs=${#}
740    _lflag=false
741    while getopts :lr:s:v: arg; do
742        case ${arg} in
743        l)
744            _lflag=true
745            ;;
746
747        r)
748            Results_File=${OPTARG}
749            ;;
750
751        s)
752            Source_Dir=${OPTARG}
753            ;;
754
755        v)
756            _atf_config_set_from_str "${OPTARG}"
757            ;;
758
759        \?)
760            _atf_syntax_error "Unknown option -${OPTARG}."
761            # NOTREACHED
762            ;;
763        esac
764    done
765    shift $((OPTIND - 1))
766
767    case ${Source_Dir} in
768        /*)
769            ;;
770        *)
771            Source_Dir=$(pwd)/${Source_Dir}
772            ;;
773    esac
774    [ -f ${Source_Dir}/${Prog_Name} ] || \
775        _atf_error 1 "Cannot find the test program in the source" \
776                     "directory \`${Source_Dir}'"
777
778    # Call the test program's hook to register all available test cases.
779    atf_init_test_cases
780
781    # Run or list test cases.
782    if `${_lflag}`; then
783        if [ ${#} -gt 0 ]; then
784            _atf_syntax_error "Cannot provide test case names with -l"
785        fi
786        _atf_list_tcs
787    else
788        if [ ${#} -eq 0 ]; then
789            _atf_syntax_error "Must provide a test case name"
790        elif [ ${#} -gt 1 ]; then
791            _atf_syntax_error "Cannot provide more than one test case name"
792        else
793            _atf_run_tc "${1}"
794        fi
795    fi
796}
797
798# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4
799