# # Automated Testing Framework (atf) # # Copyright (c) 2007, 2008 The NetBSD Foundation, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND # CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # # File: atf.footer.subr # # This file provides the test program's entry point and auxiliary # functions used during testing. # # ------------------------------------------------------------------------ # GLOBAL VARIABLES # ------------------------------------------------------------------------ # Values of configuration variables obtained from atf-config. Atf_Arch=$(atf-config -t atf_arch) Atf_Cleanup=$(atf-config -t atf_libexecdir)/atf-cleanup Atf_Exec=$(atf-config -t atf_libexecdir)/atf-exec Atf_Format=$(atf-config -t atf_libexecdir)/atf-format Atf_Killpg=$(atf-config -t atf_libexecdir)/atf-killpg Atf_Machine=$(atf-config -t atf_machine) # List of configuration variables set through the command line. Needed # during shortcut execution. Config_Vars= # List of blocked signals, to be processed when unblocked. Held_Signals= # A boolean variable that indicates whether we are parsing a test case's # head or not. Parsing_Head=false # The file descriptor on which the test program will print the results of # the test cases. Results_Fd=1 # The file to which the test case will print its result. Results_File= # The test program's source directory: i.e. where its auxiliary data files # and helper utilities can be found. Defaults to the current directory # but can be overriden through the '-s' flag. Source_Dir=. # Indicates the test case we are currently processing. Test_Case= # The list of all test cases provided by the test program. # Subset of ${Defined_Test_Cases}. Test_Cases= # The test case's work directory. The semantics of this variable are a # bit screwed. When running the parent program (not the test case's body), # this points to the test _program_'s directory. When we have reexecuted # the code through _atf_shortcut_exec, this points to the test _case_'s # work directory, which is a subdirectory of the other one. Work_Dir= # ------------------------------------------------------------------------ # PUBLIC INTERFACE # ------------------------------------------------------------------------ # # atf_add_test_case tc-name # # Adds the given test case to the list of test cases that form the test # program. The name provided here must be accompanied by two functions # named after it: <tc-name>_head and <tc-name>_body, and optionally by # a <tc-name>_cleanup function. # atf_add_test_case() { _atf_is_tc_defined "${1}" || \ _atf_error 128 "Test case ${1} was not correctly defined by" \ "this test program" Test_Cases="${Test_Cases} ${1}" } # # atf_check cmd expcode expout experr # # Executes a command and checks its error code, stdout and stderr against # the expected values for each. # # 'expcode' specifies the numeric error code the program is supposed to # return. # # 'expout' is one of 'expout', 'ignore', 'null' or 'stdout'. The meaning # of these parameters is as follows: # expout - What the command writes to the stdout channel must match # exactly what is found in the 'expout' file. # ignore - The test does not check what the command writes to the # stdout channel. # null - The command must not write anything to the stdout channel. # stdout - What the command writes to the stdout channel is written # to a 'stdout' file, available for further inspection. # # 'experr' is one of 'experr', 'ignore', 'null' or 'stderr'. The meaning # of these parameters is the same as their corresponding ones in the # stdout case. # atf_check() { test ${#} -eq 4 || atf_fail "Incorrect number of parameters" _cmd="${1}" _expcode="${2}" _expout="${3}" _experr="${4}" echo "Checking command [${_cmd}]" # Sanity-check the expout parameter and prepare the work directory for # the test. case ${_expout} in expout) test -f ${Work_Dir}/expout || atf_fail "No expout file found" ;; ignore) ;; null) ;; stdout) ;; *) atf_fail "Invalid value in atf_check's expout parameter" ;; esac # Sanity-check the experr parameter and prepare the work directory for # the test. case ${_experr} in experr) test -f ${Work_Dir}/experr || atf_fail "No experr file found" ;; ignore) ;; null) ;; stderr) ;; *) atf_fail "Invalid value in atf_check's experr parameter" ;; esac # Run the command and capture its error code, output and error # channels. ( eval ${_cmd} >${Work_Dir}/aux-stdout 2>${Work_Dir}/aux-stderr ) _code=${?} # Check the command's error code. if [ ${_code} -ne ${_expcode} ]; then echo "stdout:" cat aux-stdout echo "stderr:" cat aux-stderr atf_fail "Exit code ${_code} does not match the expected" \ "code ${_expcode}" fi # Check what the command wrote to stdout. case ${_expout} in expout) if cmp -s ${Work_Dir}/expout ${Work_Dir}/aux-stdout; then : else echo "stdout:" diff -u ${Work_Dir}/expout ${Work_Dir}/aux-stdout atf_fail "stdout does not match" fi ;; ignore) ;; null) touch ${Work_Dir}/empty-file if cmp -s ${Work_Dir}/empty-file ${Work_Dir}/aux-stdout; then : else echo "stdout:" cat ${Work_Dir}/aux-stdout atf_fail "stdout was not silent" fi ;; stdout) test -f ${Work_Dir}/stdout && rm -f ${Work_Dir}/stdout test -f ${Work_Dir}/stdout && \ atf_fail "Could not delete stale stdout file" mv ${Work_Dir}/aux-stdout ${Work_Dir}/stdout ;; *) _atf_error 128 "Internal error in the atf_check function" ;; esac # Check what the command wrote to stderr. case ${_experr} in experr) if cmp -s ${Work_Dir}/experr ${Work_Dir}/aux-stderr; then : else echo "stderr:" diff -u ${Work_Dir}/experr ${Work_Dir}/aux-stderr atf_fail "stderr does not match" fi ;; ignore) ;; null) touch ${Work_Dir}/empty-file if cmp -s ${Work_Dir}/empty-file ${Work_Dir}/aux-stderr; then : else echo "stderr:" cat ${Work_Dir}/aux-stderr atf_fail "stderr was not silent" fi ;; stderr) test -f ${Work_Dir}/stderr && rm -f ${Work_Dir}/stderr test -f ${Work_Dir}/stderr && \ atf_fail "Could not delete stale stderr file" mv ${Work_Dir}/aux-stderr ${Work_Dir}/stderr ;; *) _atf_error 128 "Internal error in the atf_check function" ;; esac } # # atf_check_equal expr1 expr2 # # Checks that expr1's value matches expr2's and, if not, raises an # error. Ideally expr1 and expr2 should be provided quoted (not # expanded) so that the error message is helpful; otherwise it will # only show the values, not the expressions themselves. # atf_check_equal() { eval _val1=\"${1}\" eval _val2=\"${2}\" test "${_val1}" = "${_val2}" || \ atf_fail "${1} != ${2} (${_val1} != ${_val2})" } # # atf_config_get varname [defvalue] # # Prints the value of a configuration variable. If it is not # defined, prints the given default value. # atf_config_get() { _varname="__tc_config_var_$(_atf_normalize ${1})" if [ ${#} -eq 1 ]; then eval _value=\"\${${_varname}-__unset__}\" [ "${_value}" = __unset__ ] && \ _atf_error 1 "Could not find configuration variable \`${1}'" echo ${_value} elif [ ${#} -eq 2 ]; then eval echo \${${_varname}-${2}} else _atf_error 1 "Incorrect number of parameters for atf_config_get" fi } # # atf_config_has varname # # Returns a boolean indicating if the given configuration variable is # defined or not. # atf_config_has() { _varname="__tc_config_var_$(_atf_normalize ${1})" eval _value=\"\${${_varname}-__unset__}\" [ "${_value}" != __unset__ ] } # # atf_fail msg1 [.. msgN] # # Makes the test case fail with the given error message. Multiple # words can be provided, in which case they are joined by a single # blank space. # atf_fail() { echo "failed, ${*}" >>${Results_File} exit 1 } # # atf_get varname # # Prints the value of a test case-specific variable. Given that one # should not get the value of non-existent variables, it is fine to # always use this function as 'val=$(atf_get var)'. # atf_get() { eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})} } # # atf_get_srcdir # # Prints the value of the test case's source directory. # atf_get_srcdir() { _atf_internal_get srcdir } # # atf_pass msg1 [.. msgN] # # Makes the test case pass. Shouldn't be used in general, as a test # case that does not explicitly fail is assumed to pass. # atf_pass() { echo "passed" >>${Results_File} exit 0 } # # atf_require_prog prog # # Checks that the given program name (either provided as an absolute # path or as a plain file name) can be found. If it is not available, # automatically skips the test case with an appropriate message. # # Relative paths are not allowed because the test case cannot predict # where it will be executed from. # atf_require_prog() { _prog= case ${1} in /*) _prog="${1}" [ -x ${_prog} ] || \ atf_skip "The required program ${1} could not be found" ;; */*) _atf_error 128 "atf_require_prog does not accept relative" \ "path names \`${1}'" ;; *) _prog=$(_atf_find_in_path "${1}") [ -n "${_prog}" ] || \ atf_skip "The required program ${1} could not be found" \ "in the PATH" ;; esac } # # atf_set varname val1 [.. valN] # # Sets the test case's variable 'varname' to the specified values # which are concatenated using a single blank space. This function # is supposed to be called form the test case's head only. # atf_set() { ${Parsing_Head} || \ _atf_error 128 "atf_set called from the test case's body" _var=$(_atf_normalize ${1}); shift eval __tc_var_${Test_Case}_${_var}=\"\${*}\" } # # atf_skip msg1 [.. msgN] # # Skips the test case because of the reason provided. Multiple words # can be given, in which case they are joined by a single blank space. # atf_skip() { echo "skipped, ${*}" >>${Results_File} exit 0 } # ------------------------------------------------------------------------ # PRIVATE INTERFACE # ------------------------------------------------------------------------ # # _atf_config_set varname val1 [.. valN] # # Sets the test case's private variable 'varname' to the specified # values which are concatenated using a single blank space. # _atf_config_set() { _var=$(_atf_normalize ${1}); shift eval __tc_config_var_${_var}=\"\${*}\" Config_Vars="${Config_Vars} __tc_config_var_${_var}" } # # _atf_config_set_str varname=val # # Sets the test case's private variable 'varname' to the specified # value. The parameter is of the form 'varname=val'. # _atf_config_set_from_str() { _oldifs=${IFS} IFS='=' set -- ${*} _var=${1} shift _val="${@}" IFS=${_oldifs} _atf_config_set "${_var}" "${_val}" } # # _atf_echo [-l indent] [-t tag] [msg1 [.. msgN]] # # Prints a formatted message using atf-format(1). See its manual # page for details on the syntax of this function. # _atf_echo() { ${Atf_Format} "${@}" } # # _atf_ensure_boolean var # # Ensures that the test case defined the variable 'var' to a boolean # value. # _atf_ensure_boolean() { _atf_ensure_not_empty ${1} case $(atf_get ${1}) in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) atf_set ${1} true ;; [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]) atf_set ${1} false ;; *) _atf_error 128 "Invalid value for boolean variable \`${1}'" ;; esac } # # _atf_ensure_integral var # # Ensures that the test case defined the variable 'var' to an integral # value. # _atf_ensure_integral() { _atf_ensure_not_empty ${1} case $(atf_get ${1}) in [0-9]*) ;; *) _atf_error 128 "Invalid value for integral variable \`${1}'" ;; esac } # # _atf_ensure_not_empty var # # Ensures that the test case defined the variable 'var' to a non-empty # value. # _atf_ensure_not_empty() { [ -n "$(atf_get ${1})" ] || \ _atf_error 128 "Undefined or empty variable \`${1}'" } # # _atf_error error_code [msg1 [.. msgN]] # # Prints the given error message (which can be composed of multiple # arguments, in which case are joined by a single space) and exits # with the specified error code. # # This must not be used by test programs themselves (hence making # the function private) to indicate a test case's failure. They # have to use the atf_fail function. # _atf_error() { _error_code="${1}"; shift _atf_echo -r -t "${Prog_Name}: " "ERROR:" "$@" 1>&2 exit ${_error_code} } # # _atf_expand_glob glob # # Prints all test case identifiers that match the provided glob # pattern. # _atf_expand_glob() { _glob="${1}" _matched="" set -- ${Test_Cases} while [ ${#} -gt 0 ]; do case "${1}" in ${_glob}) _matched="${_matched} ${1}" ;; *) ;; esac shift done set -- ${_matched} while [ ${#} -gt 0 ]; do echo ${1} shift done } # # _atf_get_bool varname # # Evaluates a test case-specific variable as a boolean and returns its # value. # _atf_get_bool() { eval $(atf_get ${1}) } # # _atf_init_output [tc1 .. tcN] # # Initializes the descriptor where we will send the test case's # results. # _atf_init_output() { echo "Content-Type: application/X-atf-tcs; version=\"1\"" \ >&${Results_Fd} echo "" >&${Results_Fd} echo "tcs-count: ${#}" >&${Results_Fd} } # # _atf_internal_get varname # # Prints the value of a test case-specific internal variable. Given # that one should not get the value of non-existent variables, it is # fine to always use this function as 'val=$(_atf_internal_get var)'. # _atf_internal_get() { eval echo \${__tc_internal_var_${Test_Case}_${1}} } # # _atf_internal_set varname val1 [.. valN] # # Sets the test case's private variable 'varname' to the specified # values which are concatenated using a single blank space. # _atf_internal_set() { _var=${1}; shift eval __tc_internal_var_${Test_Case}_${_var}=\"\${*}\" } # # _atf_list_tcs [tc1 .. tcN] # # Describes all given test cases and prints the list to the standard # output. # _atf_list_tcs() { # Calculate the length of the longest test case name. Needed for # correct indentation later on. _maxlen=0 for _tc in ${*}; do if [ ${#_tc} -gt ${_maxlen} ]; then _maxlen=${#_tc} fi done # Print the list of test cases. _maxlen=$((${_maxlen} + 4)) for _tc in ${*}; do _atf_parse_head ${_tc} _atf_echo -t ${_tc} -l ${_maxlen} $(atf_get descr) done } # # _atf_normalize str # # Normalizes a string so that it is a valid shell variable name. # _atf_normalize() { echo ${1} | tr .- __ } # # _atf_parse_head tcname # # Evaluates a test case's head to gather its variables and prepares the # test program to run it. # _atf_parse_head() { ${Parsing_Head} && _atf_error 128 "_atf_parse_head called recursively" Parsing_Head=true Test_Case="${1}" atf_set ident "${1}" atf_set timeout 300 ${1}_head _atf_ensure_not_empty descr _atf_ensure_not_empty ident _atf_ensure_integral timeout test $(atf_get ident) = "${1}" || \ _atf_error 128 "Test case redefined ident" Parsing_Head=false } # # _atf_parse_props tc # # Runs a test case's body but, before that, handles the default # properties. This function must be run in a subshell because the # test case is designed to abruptly exit the shell when any of # atf_pass, atf_skip or atf_fail are executed. # _atf_run_body() { HOME=$(pwd) export HOME unset LANG unset LC_ALL unset LC_COLLATE unset LC_CTYPE unset LC_MESSAGES unset LC_MONETARY unset LC_NUMERIC unset LC_TIME unset TZ umask 0022 _arches=$(atf_get require.arch) if [ -n "${_arches}" ]; then found=no for _a in ${_arches}; do if [ ${_a} = ${Atf_Arch} ]; then found=yes break fi done [ ${found} = yes ] || \ atf_skip "Requires one of the '${_arches}' architectures" fi _machines=$(atf_get require.machine) if [ -n "${_machines}" ]; then found=no for _m in ${_machines}; do if [ ${_m} = ${Atf_Machine} ]; then found=yes break fi done [ ${found} = yes ] || \ atf_skip "Requires one of the '${_machines}' machine types" fi _vars="$(atf_get require.config)" if [ -n "${_vars}" ]; then for _v in ${_vars}; do if ! atf_config_has ${_v}; then atf_skip "Required configuration variable ${_v} not defined" fi done fi _progs="$(atf_get require.progs)" if [ -n "${_progs}" ]; then for _p in ${_progs}; do atf_require_prog ${_p} done fi case $(atf_get require.user) in root) [ $(id -u) -eq 0 ] || \ atf_skip "Requires root privileges" ;; unprivileged) [ $(id -u) -ne 0 ] || \ atf_skip "Requires an unprivileged user" ;; "") ;; *) atf_fail "Invalid value in the require.user property" ;; esac # Previous versions of this code reverted all signal handlers to their # default behavior at this point. We do not need to do this any more # because this piece of code is run in a clean sub-shell (through the # _atf_shortcut_exec call), i.e. a completely re-executed shell, and # we have not messed with signal handlers at all until this point. ${1}_body } # # _atf_program_timeout pid workdir # # Prepares and launches in the background a script that will kill the # current test case's body (specified by the pid) after it times out. # _atf_program_timeout() { _timeout=$(atf_get timeout) if [ ${_timeout} -gt 0 ]; then cat >${2}/timeout.sh <<EOF #! /bin/sh sleep ${_timeout} touch ${2}/atf.timed.out ${Atf_Killpg} ${1} >/dev/null 2>&1 EOF ${Atf_Exec} -g /bin/sh ${2}/timeout.sh >/dev/null 2>&1 & echo $! else echo none fi } # # _atf_run_tc tc nrest # # Runs the specified test case. Prints its exit status to the # standard output and returns a boolean indicating if the test was # successful or not. The 'nrest' parameter indicates how many test # cases are left for execution. # _atf_run_tc() { _atf_parse_head ${1} # Block some signals while we mess with temporary files so that we can # clean them up later on. Held_Signals= trap _atf_sighup_handler SIGHUP trap _atf_sigint_handler SIGINT trap _atf_sigterm_handler SIGTERM echo "tc-start: ${Test_Case}" >&${Results_Fd} _atf_internal_set srcdir "${Source_Dir}" Results_File=$(mktemp ${Work_Dir}/atf.XXXXXX) _workdir=$(mktemp -d ${Work_Dir}/atf.XXXXXX) if [ ${?} -eq 0 ]; then _atf_shortcut_exec ${1} ${_workdir}; _body_pid=$! _timeout_pid=$(_atf_program_timeout ${_body_pid} ${_workdir}) wait ${_body_pid} _ret=$? if [ -f ${_workdir}/atf.timed.out ]; then [ ${_timeout_pid} != none ] || \ _atf_error 128 "Inconsistent status in timeout handling" ( atf_fail "Test case timed out after $(atf_get timeout) seconds" ) _ret=${?} else [ ${_timeout_pid} != none ] && \ ${Atf_Killpg} ${_timeout_pid} >/dev/null 2>&1 fi ( cd ${_workdir} ; ${1}_cleanup ) if [ ${2} -gt 1 ]; then echo __atf_tc_separator__ echo __atf_tc_separator__ 1>&2 fi ${Atf_Cleanup} ${_workdir} else ( atf_fail "Could not create the work directory" ) _ret=${?} fi # Set a default exit status if the test case did not report any. if [ -z "$(cat ${Results_File})" ]; then if [ -n "${Held_Signals}" ]; then ( atf_fail "Test case was interrupted by${Held_Signals}" ) else ( atf_fail "Test case did not report any status; bogus test" ) fi fi # Print the result of the test case and clean up the temporary file. echo "tc-end: ${Test_Case}, $(cat ${Results_File})" >&${Results_Fd} rm -f ${Results_File} Results_File= Test_Case= # Restore blocked signals and process them. trap - SIGHUP SIGINT SIGTERM for s in ${Held_Signals}; do kill -s ${s} $$ done return ${_ret} } # # _atf_run_tcs [tc1 .. tcN] # # Executes all the given test cases. Returns 0 if all tests were # successful, or 1 otherwise. # _atf_run_tcs() { # Now check that the base work directory exists. We do not want to # bother creating it. [ -d "${Work_Dir}" ] || \ _atf_error 1 "Cannot find the work directory \`${Work_Dir}'" _atf_init_output "${@}" _ok=true while [ ${#} -gt 0 ]; do _atf_run_tc ${1} ${#} || _ok=false shift done ${_ok} } # # _atf_shortcut_entry # # Secondary entry point for the program. This is only called internally # to process a test case's body. We must do a full re-exec of the script # in order to change its process group by means of an external tool. # Yes, this is ugly, but there is no other way to do it -- unless we # modified the shell interpreter to provide a built-in for changing the # process group of the current process... # # Keep in sync with _atf_shortcut_exec. # _atf_shortcut_entry() { # Set global program status. _config_file=${_ATF_CONFIG_FILE}; unset _ATF_CONFIG_FILE Results_Fd=${_ATF_RESULTS_FD}; unset _ATF_RESULTS_FD Results_File=${_ATF_RESULTS_FILE}; unset _ATF_RESULTS_FILE Source_Dir=${_ATF_SOURCE_DIR}; unset _ATF_SOURCE_DIR Work_Dir=${_ATF_WORK_DIR}; unset _ATF_WORK_DIR # Gather specific details of this re-exec. _shortcut_tc=${_ATF_SHORTCUT}; unset _ATF_SHORTCUT # Global initialization, as found in main. if [ -n "${_config_file}" -a -f "${_config_file}" ]; then . ${_config_file} rm ${_config_file} fi _atf_internal_set srcdir "${Source_Dir}" atf_init_test_cases # Test-case specific initialization, as found in _atf_run_tc. _atf_parse_head ${_shortcut_tc} _atf_internal_set srcdir "${Source_Dir}" # Really run the test case's body. This is the only part that # should remain if we were really able to change the process group # of a sub-shell. cd ${Work_Dir} _atf_run_body ${_shortcut_tc} atf_pass } # # _atf_shortcut_exec tc # # Re-executes the current script in a different process group in order # to process the given test case's body. $! is set to the child process # on return. # # Keep in sync with _atf_shortcut_entry. # _atf_shortcut_exec() { # Save the value of the configuration variables set through -v. # We must do this through a files as there is no other easy way to # preserve spaces in them. But this can bring raise problems... if [ -n "${Config_Vars}" ]; then _config_file=${Work_Dir}/atf.config.vars for _var in ${Config_Vars}; do _val=$(eval echo \${${_var}}) echo ${_var}=\'${_val}\' >>${_config_file} done fi # Now do the real re-execution. ${Atf_Exec} -g env \ _ATF_CONFIG_FILE=${_config_file} \ _ATF_RESULTS_FD=${Results_Fd} \ _ATF_RESULTS_FILE=${Results_File} \ _ATF_SHORTCUT=${1} \ _ATF_SOURCE_DIR=${Source_Dir} \ _ATF_WORK_DIR=${2} \ ${Source_Dir}/${Prog_Name} & } # # _atf_sighup_handler # # Handler for the SIGHUP signal that registers its occurrence so that # it can be processed at a later stage. # _atf_sighup_handler() { Held_Signals="${Held_Signals} SIGHUP" } # # _atf_sigint_handler # # Handler for the SIGINT signal that registers its occurrence so that # it can be processed at a later stage. # _atf_sigint_handler() { Held_Signals="${Held_Signals} SIGINT" } # # _atf_sigterm_handler # # Handler for the SIGTERM signal that registers its occurrence so that # it can be processed at a later stage. # _atf_sigterm_handler() { Held_Signals="${Held_Signals} SIGTERM" } # # _atf_syntax_error msg1 [.. msgN] # # Formats and prints a syntax error message and terminates the # program prematurely. # _atf_syntax_error() { _atf_echo -r -t "${Prog_Name}: " "ERROR: ${@}" 1>&2 _atf_echo -r -t "${Prog_Name}: " "Type \`${Prog_Name} -h' for more" \ "details." 1>&2 exit 1 } # # _atf_is_tc_defined tc-name # # Returns a boolean indicating if the given test case was defined by the # test program or not. # _atf_is_tc_defined() { for _tc in ${Defined_Test_Cases}; do [ ${_tc} = ${1} ] && return 0 done return 1 } # # _atf_usage # # Prints usage information and exits the program. # _atf_usage() { _atf_echo -t "Usage: " "${Prog_Name} [options] [test_case1" \ "[.. test_caseN]]" echo _atf_echo "This is an independent atf test program." echo _atf_echo "Available options:" _atf_echo -t " -h " "Shows this help message" _atf_echo -t " -l " "List test cases and their purpose" _atf_echo -t " -r fd " "The file descriptor to which the" \ "test program will send the results" \ "of the test cases" _atf_echo -t " -s srcdir " "Directory where the test's data" \ "files are located" _atf_echo -t " -v var=value " "Sets the configuration variable" \ "\`var' to \`value'" _atf_echo -t " -w workdir " "Directory where the test's" \ "temporary files are located" echo _atf_echo "For more details please see atf-test-program(1) and atf(7)." } # # _atf_warning [msg1 [.. msgN]] # # Prints the given warning message (which can be composed of multiple # arguments, in which case are joined by a single space). # # This must not be used by test programs themselves (hence making # the function private). # _atf_warning() { _atf_echo -r -t "${Prog_Name}: " "WARNING:" "$@" 1>&2 } # # main [options] [test_case1 [.. test_caseN]] # # Test program's entry point. # main() { # Handle shortcut execution path as early as possible. if [ ${_ATF_SHORTCUT-__unset__} != __unset__ ]; then _atf_shortcut_entry # NOTREACHED fi # The test program's base directory where it will put temporary files. Work_Dir=$(atf-config -t atf_workdir) # Process command-line options first. _numargs=${#} _hflag=false _lflag=false while getopts :hlr:s:v:w: arg; do case ${arg} in h) _hflag=true ;; l) _lflag=true ;; r) Results_Fd=${OPTARG} ;; s) Source_Dir=${OPTARG} ;; v) _atf_config_set_from_str "${OPTARG}" ;; w) Work_Dir=${OPTARG} ;; \?) _atf_syntax_error "Unknown option -${OPTARG}." # NOTREACHED ;; esac done shift `expr ${OPTIND} - 1` if [ ${_hflag} = true ]; then [ ${_numargs} -eq 1 ] || _atf_syntax_error "-h must be given alone." _atf_usage true return fi # First of all, make sure that the source directory is correct. It # doesn't matter if the user did not change it, because the default # value may not work. (TODO: It possibly should, even though it is # not a big deal because atf-run deals with this.) case ${Source_Dir} in /*) ;; *) Source_Dir=$(pwd)/${Source_Dir} ;; esac [ -f ${Source_Dir}/${Prog_Name} ] || \ _atf_error 1 "Cannot find the test program in the source" \ "directory \`${Source_Dir}'" # Set some global variables useful to the user. Not specific to the # test case because they may be needed during initialization too. # XXX I'm not too fond on this though. Sure, it is very useful in some # situations -- such as in NetBSD's fs/tmpfs/* tests where each test # program includes a helper subroutines file -- but there are also # other, maybe better ways to achieve the same. Because, for example, # at the moment it is not possible to detect failures in the inclusion # and report them nicely. Plus this change is difficult to implement # in the current C++ API. _atf_internal_set srcdir "${Source_Dir}" # Call the test program's hook to register all available test cases. atf_init_test_cases # Set _tcs to the test cases to run. if [ ${#} -gt 0 ]; then # Expand glob patterns and report erroneous test cases. _tcs= while [ ${#} -gt 0 ]; do _matches=$(_atf_expand_glob "${1}") [ ${#_matches} -eq 0 ] && _atf_error 1 "Unknown test case \`${1}'" _tcs="${_tcs} ${_matches}" shift done else _tcs=${Test_Cases} fi # Run or list test cases, restricting them to _tcs. if `${_lflag}`; then _atf_list_tcs ${_tcs} else _atf_run_tcs ${_tcs} fi } # vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4