xref: /freebsd/contrib/atf/atf-sh/libatf-sh.subr (revision 1fb62fb074788ca4713551be09d6569966a3abee)
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_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#
109atf_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#
130atf_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#
142atf_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#
155atf_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#
168atf_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#
187atf_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#
200atf_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#
213atf_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#
234atf_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#
247atf_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#
270atf_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#
280atf_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#
291atf_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#
318atf_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#
346atf_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#
362atf_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#
376atf_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#
708main()
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
771