David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

780 lines
18 KiB
Bash

#
# Automated Testing Framework (atf)
#
# 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.
#
set -e
# ------------------------------------------------------------------------
# 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 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_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 without atf-run(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`
# 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}'"
# 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