summaryrefslogtreecommitdiffstats
path: root/unit/atf-src/atf-sh/libatf-sh.subr
diff options
context:
space:
mode:
Diffstat (limited to 'unit/atf-src/atf-sh/libatf-sh.subr')
-rw-r--r--unit/atf-src/atf-sh/libatf-sh.subr770
1 files changed, 770 insertions, 0 deletions
diff --git a/unit/atf-src/atf-sh/libatf-sh.subr b/unit/atf-src/atf-sh/libatf-sh.subr
new file mode 100644
index 0000000..a078975
--- /dev/null
+++ b/unit/atf-src/atf-sh/libatf-sh.subr
@@ -0,0 +1,770 @@
+# Copyright (c) 2007 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.
+
+# ------------------------------------------------------------------------
+# GLOBAL VARIABLES
+# ------------------------------------------------------------------------
+
+# Values for the expect property.
+Expect=pass
+Expect_Reason=
+
+# A boolean variable that indicates whether we are parsing a test case's
+# head or not.
+Parsing_Head=false
+
+# The program name.
+Prog_Name=${0##*/}
+
+# 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. Can be overriden through the '-s' flag.
+Source_Dir="$(dirname ${0})"
+
+# Indicates the test case we are currently processing.
+Test_Case=
+
+# List of meta-data variables for the current test case.
+Test_Case_Vars=
+
+# The list of all test cases provided by the test program.
+Test_Cases=
+
+# ------------------------------------------------------------------------
+# 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()
+{
+ Test_Cases="${Test_Cases} ${1}"
+}
+
+#
+# atf_check cmd expcode expout experr
+#
+# Executes atf-check with given arguments and automatically calls
+# atf_fail in case of failure.
+#
+atf_check()
+{
+ ${Atf_Check} "${@}" || \
+ atf_fail "atf-check failed; see the output of the test for details"
+}
+
+#
+# atf_check_equal expected_expression actual_expression
+#
+# Checks that expected_expression's value matches actual_expression's
+# and, if not, raises an error. Ideally expected_expression and
+# actual_expression 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_expect_death reason
+#
+# Sets the expectations to 'death'.
+#
+atf_expect_death()
+{
+ _atf_validate_expect
+
+ Expect=death
+ _atf_create_resfile "expected_death: ${*}"
+}
+
+#
+# atf_expect_timeout reason
+#
+# Sets the expectations to 'timeout'.
+#
+atf_expect_timeout()
+{
+ _atf_validate_expect
+
+ Expect=timeout
+ _atf_create_resfile "expected_timeout: ${*}"
+}
+
+#
+# atf_expect_exit exitcode reason
+#
+# Sets the expectations to 'exit'.
+#
+atf_expect_exit()
+{
+ _exitcode="${1}"; shift
+
+ _atf_validate_expect
+
+ Expect=exit
+ if [ "${_exitcode}" = "-1" ]; then
+ _atf_create_resfile "expected_exit: ${*}"
+ else
+ _atf_create_resfile "expected_exit(${_exitcode}): ${*}"
+ fi
+}
+
+#
+# atf_expect_fail reason
+#
+# Sets the expectations to 'fail'.
+#
+atf_expect_fail()
+{
+ _atf_validate_expect
+
+ Expect=fail
+ Expect_Reason="${*}"
+}
+
+#
+# atf_expect_pass
+#
+# Sets the expectations to 'pass'.
+#
+atf_expect_pass()
+{
+ _atf_validate_expect
+
+ Expect=pass
+ Expect_Reason=
+}
+
+#
+# atf_expect_signal signo reason
+#
+# Sets the expectations to 'signal'.
+#
+atf_expect_signal()
+{
+ _signo="${1}"; shift
+
+ _atf_validate_expect
+
+ Expect=signal
+ if [ "${_signo}" = "-1" ]; then
+ _atf_create_resfile "expected_signal: ${*}"
+ else
+ _atf_create_resfile "expected_signal(${_signo}): ${*}"
+ fi
+}
+
+#
+# atf_expected_failure msg1 [.. msgN]
+#
+# Makes the test case report an expected failure with the given error
+# message. Multiple words can be provided, which are concatenated with
+# a single blank space.
+#
+atf_expected_failure()
+{
+ _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}"
+ exit 0
+}
+
+#
+# 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()
+{
+ case "${Expect}" in
+ fail)
+ atf_expected_failure "${@}"
+ ;;
+ pass)
+ _atf_create_resfile "failed: ${*}"
+ exit 1
+ ;;
+ *)
+ _atf_error 128 "Unreachable"
+ ;;
+ esac
+}
+
+#
+# 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()
+{
+ echo ${Source_Dir}
+}
+
+#
+# atf_pass
+#
+# 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()
+{
+ case "${Expect}" in
+ fail)
+ Expect=pass
+ atf_fail "Test case was expecting a failure but got a pass instead"
+ ;;
+ pass)
+ _atf_create_resfile passed
+ exit 0
+ ;;
+ *)
+ _atf_error 128 "Unreachable"
+ ;;
+ esac
+}
+
+#
+# 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_fail "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"
+
+ Test_Case_Vars="${Test_Case_Vars} ${1}"
+ _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()
+{
+ _atf_create_resfile "skipped: ${*}"
+ exit 0
+}
+
+#
+# atf_test_case tc-name cleanup
+#
+# Defines a new test case named tc-name. The name provided here must be
+# accompanied by two functions named after it: <tc-name>_head and
+# <tc-name>_body. If cleanup is set to 'cleanup', then this also expects
+# a <tc-name>_cleanup function to be defined.
+#
+atf_test_case()
+{
+ eval "${1}_head() { :; }"
+ eval "${1}_body() { atf_fail 'Test case not implemented'; }"
+ if [ "${2}" = cleanup ]; then
+ eval __has_cleanup_${1}=true
+ eval "${1}_cleanup() { :; }"
+ else
+ eval "${1}_cleanup() {
+ _atf_error 1 'Test case ${1} declared without a cleanup routine'; }"
+ fi
+}
+
+# ------------------------------------------------------------------------
+# 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_create_resfile contents
+#
+# Creates the results file.
+#
+_atf_create_resfile()
+{
+ if [ -n "${Results_File}" ]; then
+ echo "${*}" >"${Results_File}" || \
+ _atf_error 128 "Cannot create results file '${Results_File}'"
+ else
+ echo "${*}"
+ fi
+}
+
+#
+# _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
+
+ echo "${Prog_Name}: ERROR:" "$@" 1>&2
+ exit ${_error_code}
+}
+
+#
+# _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).
+#
+_atf_warning()
+{
+ echo "${Prog_Name}: WARNING:" "$@" 1>&2
+}
+
+#
+# _atf_find_in_path program
+#
+# Looks for a program in the path and prints the full path to it or
+# nothing if it could not be found. It also returns true in case of
+# success.
+#
+_atf_find_in_path()
+{
+ _prog="${1}"
+
+ _oldifs=${IFS}
+ IFS=:
+ for _dir in ${PATH}
+ do
+ if [ -x ${_dir}/${_prog} ]; then
+ IFS=${_oldifs}
+ echo ${_dir}/${_prog}
+ return 0
+ fi
+ done
+ IFS=${_oldifs}
+
+ return 1
+}
+
+#
+# _atf_has_tc name
+#
+# Returns true if the given test case exists.
+#
+_atf_has_tc()
+{
+ for _tc in ${Test_Cases}; do
+ [ "${_tc}" != "${1}" ] || return 0
+ done
+ return 1
+}
+
+#
+# _atf_list_tcs
+#
+# Describes all test cases and prints the list to the standard output.
+#
+_atf_list_tcs()
+{
+ echo 'Content-Type: application/X-atf-tp; version="1"'
+ echo
+
+ set -- ${Test_Cases}
+ while [ ${#} -gt 0 ]; do
+ _atf_parse_head ${1}
+
+ echo "ident: $(atf_get ident)"
+ for _var in ${Test_Case_Vars}; do
+ [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})"
+ done
+
+ [ ${#} -gt 1 ] && echo
+ shift
+ 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=true
+
+ Test_Case="${1}"
+ Test_Case_Vars=
+
+ if _atf_has_cleanup "${1}"; then
+ atf_set has.cleanup "true"
+ fi
+
+ ${1}_head
+ atf_set ident "${1}"
+
+ Parsing_Head=false
+}
+
+#
+# _atf_run_tc tc
+#
+# 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.
+#
+_atf_run_tc()
+{
+ case ${1} in
+ *:*)
+ _tcname=${1%%:*}
+ _tcpart=${1#*:}
+
+ if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then
+ _atf_syntax_error "Unknown test case part \`${_tcpart}'"
+ fi
+ ;;
+
+ *)
+ _tcname=${1}
+ _tcpart=body
+ ;;
+ esac
+
+ _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'"
+
+ if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then
+ _atf_warning "Running test cases outside of kyua(1) is unsupported"
+ _atf_warning "No isolation nor timeout control is being applied;" \
+ "you may get unexpected failures; see atf-test-case(4)"
+ fi
+
+ _atf_parse_head ${_tcname}
+
+ case ${_tcpart} in
+ body)
+ if ${_tcname}_body; then
+ _atf_validate_expect
+ _atf_create_resfile passed
+ else
+ Expect=pass
+ atf_fail "Test case body returned a non-ok exit code, but" \
+ "this is not allowed"
+ fi
+ ;;
+ cleanup)
+ if _atf_has_cleanup "${_tcname}"; then
+ ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \
+ "returned a non-ok exit code, but this is not allowed"
+ fi
+ ;;
+ *)
+ _atf_error 128 "Unknown test case part"
+ ;;
+ esac
+}
+
+#
+# _atf_syntax_error msg1 [.. msgN]
+#
+# Formats and prints a syntax error message and terminates the
+# program prematurely.
+#
+_atf_syntax_error()
+{
+ echo "${Prog_Name}: ERROR: ${@}" 1>&2
+ echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2
+ exit 1
+}
+
+#
+# _atf_has_cleanup tc-name
+#
+# Returns a boolean indicating if the given test case has a cleanup
+# routine or not.
+#
+_atf_has_cleanup()
+{
+ _found=true
+ eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false"
+ [ "${_found}" = true ]
+}
+
+#
+# _atf_validate_expect
+#
+# Ensures that the current test case state is correct regarding the expect
+# status.
+#
+_atf_validate_expect()
+{
+ case "${Expect}" in
+ death)
+ Expect=pass
+ atf_fail "Test case was expected to terminate abruptly but it" \
+ "continued execution"
+ ;;
+ exit)
+ Expect=pass
+ atf_fail "Test case was expected to exit cleanly but it continued" \
+ "execution"
+ ;;
+ fail)
+ Expect=pass
+ atf_fail "Test case was expecting a failure but none were raised"
+ ;;
+ pass)
+ ;;
+ signal)
+ Expect=pass
+ atf_fail "Test case was expected to receive a termination signal" \
+ "but it continued execution"
+ ;;
+ timeout)
+ Expect=pass
+ atf_fail "Test case was expected to hang but it continued execution"
+ ;;
+ *)
+ _atf_error 128 "Unreachable"
+ ;;
+ esac
+}
+
+#
+# _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()
+{
+ echo "${Prog_Name}: WARNING:" "$@" 1>&2
+}
+
+#
+# main [options] test_case
+#
+# Test program's entry point.
+#
+main()
+{
+ # Process command-line options first.
+ _numargs=${#}
+ _lflag=false
+ while getopts :lr:s:v: arg; do
+ case ${arg} in
+ l)
+ _lflag=true
+ ;;
+
+ r)
+ Results_File=${OPTARG}
+ ;;
+
+ s)
+ Source_Dir=${OPTARG}
+ ;;
+
+ v)
+ _atf_config_set_from_str "${OPTARG}"
+ ;;
+
+ \?)
+ _atf_syntax_error "Unknown option -${OPTARG}."
+ # NOTREACHED
+ ;;
+ esac
+ done
+ shift `expr ${OPTIND} - 1`
+
+ 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}'"
+
+ # Call the test program's hook to register all available test cases.
+ atf_init_test_cases
+
+ # Run or list test cases.
+ if `${_lflag}`; then
+ if [ ${#} -gt 0 ]; then
+ _atf_syntax_error "Cannot provide test case names with -l"
+ fi
+ _atf_list_tcs
+ else
+ if [ ${#} -eq 0 ]; then
+ _atf_syntax_error "Must provide a test case name"
+ elif [ ${#} -gt 1 ]; then
+ _atf_syntax_error "Cannot provide more than one test case name"
+ else
+ _atf_run_tc "${1}"
+ fi
+ fi
+}
+
+# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4