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