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