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_check_not_equal expected_expression actual_expression 105# 106# Checks that expected_expression's value does not match actual_expression's 107# and, if it does, raises an error. Ideally expected_expression and 108# actual_expression should be provided quoted (not expanded) so that 109# the error message is helpful; otherwise it will only show the values, 110# not the expressions themselves. 111# 112atf_check_not_equal() 113{ 114 eval _val1=\"${1}\" 115 eval _val2=\"${2}\" 116 test "${_val1}" != "${_val2}" || \ 117 atf_fail "${1} == ${2} (${_val1} == ${_val2})" 118} 119 120# 121# atf_config_get varname [defvalue] 122# 123# Prints the value of a configuration variable. If it is not 124# defined, prints the given default value. 125# 126atf_config_get() 127{ 128 _varname="__tc_config_var_$(_atf_normalize ${1})" 129 if [ ${#} -eq 1 ]; then 130 eval _value=\"\${${_varname}-__unset__}\" 131 [ "${_value}" = __unset__ ] && \ 132 _atf_error 1 "Could not find configuration variable \`${1}'" 133 echo ${_value} 134 elif [ ${#} -eq 2 ]; then 135 eval echo \${${_varname}-${2}} 136 else 137 _atf_error 1 "Incorrect number of parameters for atf_config_get" 138 fi 139} 140 141# 142# atf_config_has varname 143# 144# Returns a boolean indicating if the given configuration variable is 145# defined or not. 146# 147atf_config_has() 148{ 149 _varname="__tc_config_var_$(_atf_normalize ${1})" 150 eval _value=\"\${${_varname}-__unset__}\" 151 [ "${_value}" != __unset__ ] 152} 153 154# 155# atf_expect_death reason 156# 157# Sets the expectations to 'death'. 158# 159atf_expect_death() 160{ 161 _atf_validate_expect 162 163 Expect=death 164 _atf_create_resfile "expected_death: ${*}" 165} 166 167# 168# atf_expect_timeout reason 169# 170# Sets the expectations to 'timeout'. 171# 172atf_expect_timeout() 173{ 174 _atf_validate_expect 175 176 Expect=timeout 177 _atf_create_resfile "expected_timeout: ${*}" 178} 179 180# 181# atf_expect_exit exitcode reason 182# 183# Sets the expectations to 'exit'. 184# 185atf_expect_exit() 186{ 187 _exitcode="${1}"; shift 188 189 _atf_validate_expect 190 191 Expect=exit 192 if [ "${_exitcode}" = "-1" ]; then 193 _atf_create_resfile "expected_exit: ${*}" 194 else 195 _atf_create_resfile "expected_exit(${_exitcode}): ${*}" 196 fi 197} 198 199# 200# atf_expect_fail reason 201# 202# Sets the expectations to 'fail'. 203# 204atf_expect_fail() 205{ 206 _atf_validate_expect 207 208 Expect=fail 209 Expect_Reason="${*}" 210} 211 212# 213# atf_expect_pass 214# 215# Sets the expectations to 'pass'. 216# 217atf_expect_pass() 218{ 219 _atf_validate_expect 220 221 Expect=pass 222 Expect_Reason= 223} 224 225# 226# atf_expect_signal signo reason 227# 228# Sets the expectations to 'signal'. 229# 230atf_expect_signal() 231{ 232 _signo="${1}"; shift 233 234 _atf_validate_expect 235 236 Expect=signal 237 if [ "${_signo}" = "-1" ]; then 238 _atf_create_resfile "expected_signal: ${*}" 239 else 240 _atf_create_resfile "expected_signal(${_signo}): ${*}" 241 fi 242} 243 244# 245# atf_expected_failure msg1 [.. msgN] 246# 247# Makes the test case report an expected failure with the given error 248# message. Multiple words can be provided, which are concatenated with 249# a single blank space. 250# 251atf_expected_failure() 252{ 253 _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}" 254 exit 0 255} 256 257# 258# atf_fail msg1 [.. msgN] 259# 260# Makes the test case fail with the given error message. Multiple 261# words can be provided, in which case they are joined by a single 262# blank space. 263# 264atf_fail() 265{ 266 case "${Expect}" in 267 fail) 268 atf_expected_failure "${@}" 269 ;; 270 pass) 271 _atf_create_resfile "failed: ${*}" 272 exit 1 273 ;; 274 *) 275 _atf_error 128 "Unreachable" 276 ;; 277 esac 278} 279 280# 281# atf_get varname 282# 283# Prints the value of a test case-specific variable. Given that one 284# should not get the value of non-existent variables, it is fine to 285# always use this function as 'val=$(atf_get var)'. 286# 287atf_get() 288{ 289 eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})} 290} 291 292# 293# atf_get_srcdir 294# 295# Prints the value of the test case's source directory. 296# 297atf_get_srcdir() 298{ 299 echo ${Source_Dir} 300} 301 302# 303# atf_pass 304# 305# Makes the test case pass. Shouldn't be used in general, as a test 306# case that does not explicitly fail is assumed to pass. 307# 308atf_pass() 309{ 310 case "${Expect}" in 311 fail) 312 Expect=pass 313 atf_fail "Test case was expecting a failure but got a pass instead" 314 ;; 315 pass) 316 _atf_create_resfile passed 317 exit 0 318 ;; 319 *) 320 _atf_error 128 "Unreachable" 321 ;; 322 esac 323} 324 325# 326# atf_require_prog prog 327# 328# Checks that the given program name (either provided as an absolute 329# path or as a plain file name) can be found. If it is not available, 330# automatically skips the test case with an appropriate message. 331# 332# Relative paths are not allowed because the test case cannot predict 333# where it will be executed from. 334# 335atf_require_prog() 336{ 337 _prog= 338 case ${1} in 339 /*) 340 _prog="${1}" 341 [ -x ${_prog} ] || \ 342 atf_skip "The required program ${1} could not be found" 343 ;; 344 */*) 345 atf_fail "atf_require_prog does not accept relative path names \`${1}'" 346 ;; 347 *) 348 _prog=$(_atf_find_in_path "${1}") 349 [ -n "${_prog}" ] || \ 350 atf_skip "The required program ${1} could not be found" \ 351 "in the PATH" 352 ;; 353 esac 354} 355 356# 357# atf_set varname val1 [.. valN] 358# 359# Sets the test case's variable 'varname' to the specified values 360# which are concatenated using a single blank space. This function 361# is supposed to be called form the test case's head only. 362# 363atf_set() 364{ 365 ${Parsing_Head} || \ 366 _atf_error 128 "atf_set called from the test case's body" 367 368 Test_Case_Vars="${Test_Case_Vars} ${1}" 369 _var=$(_atf_normalize ${1}); shift 370 eval __tc_var_${Test_Case}_${_var}=\"\${*}\" 371} 372 373# 374# atf_skip msg1 [.. msgN] 375# 376# Skips the test case because of the reason provided. Multiple words 377# can be given, in which case they are joined by a single blank space. 378# 379atf_skip() 380{ 381 _atf_create_resfile "skipped: ${*}" 382 exit 0 383} 384 385# 386# atf_test_case tc-name cleanup 387# 388# Defines a new test case named tc-name. The name provided here must be 389# accompanied by two functions named after it: <tc-name>_head and 390# <tc-name>_body. If cleanup is set to 'cleanup', then this also expects 391# a <tc-name>_cleanup function to be defined. 392# 393atf_test_case() 394{ 395 eval "${1}_head() { :; }" 396 eval "${1}_body() { atf_fail 'Test case not implemented'; }" 397 if [ "${2}" = cleanup ]; then 398 eval __has_cleanup_${1}=true 399 eval "${1}_cleanup() { :; }" 400 else 401 eval "${1}_cleanup() { 402 _atf_error 1 'Test case ${1} declared without a cleanup routine'; }" 403 fi 404} 405 406# ------------------------------------------------------------------------ 407# PRIVATE INTERFACE 408# ------------------------------------------------------------------------ 409 410# 411# _atf_config_set varname val1 [.. valN] 412# 413# Sets the test case's private variable 'varname' to the specified 414# values which are concatenated using a single blank space. 415# 416_atf_config_set() 417{ 418 _var=$(_atf_normalize ${1}); shift 419 eval __tc_config_var_${_var}=\"\${*}\" 420 Config_Vars="${Config_Vars} __tc_config_var_${_var}" 421} 422 423# 424# _atf_config_set_str varname=val 425# 426# Sets the test case's private variable 'varname' to the specified 427# value. The parameter is of the form 'varname=val'. 428# 429_atf_config_set_from_str() 430{ 431 _oldifs=${IFS} 432 IFS='=' 433 set -- ${*} 434 _var=${1} 435 shift 436 _val="${@}" 437 IFS=${_oldifs} 438 _atf_config_set "${_var}" "${_val}" 439} 440 441# 442# _atf_create_resfile contents 443# 444# Creates the results file. 445# 446_atf_create_resfile() 447{ 448 if [ -n "${Results_File}" ]; then 449 echo "${*}" >"${Results_File}" || \ 450 _atf_error 128 "Cannot create results file '${Results_File}'" 451 else 452 echo "${*}" 453 fi 454} 455 456# 457# _atf_error error_code [msg1 [.. msgN]] 458# 459# Prints the given error message (which can be composed of multiple 460# arguments, in which case are joined by a single space) and exits 461# with the specified error code. 462# 463# This must not be used by test programs themselves (hence making 464# the function private) to indicate a test case's failure. They 465# have to use the atf_fail function. 466# 467_atf_error() 468{ 469 _error_code="${1}"; shift 470 471 echo "${Prog_Name}: ERROR:" "$@" 1>&2 472 exit ${_error_code} 473} 474 475# 476# _atf_warning msg1 [.. msgN] 477# 478# Prints the given warning message (which can be composed of multiple 479# arguments, in which case are joined by a single space). 480# 481_atf_warning() 482{ 483 echo "${Prog_Name}: WARNING:" "$@" 1>&2 484} 485 486# 487# _atf_find_in_path program 488# 489# Looks for a program in the path and prints the full path to it or 490# nothing if it could not be found. It also returns true in case of 491# success. 492# 493_atf_find_in_path() 494{ 495 _prog="${1}" 496 497 _oldifs=${IFS} 498 IFS=: 499 for _dir in ${PATH} 500 do 501 if [ -x ${_dir}/${_prog} ]; then 502 IFS=${_oldifs} 503 echo ${_dir}/${_prog} 504 return 0 505 fi 506 done 507 IFS=${_oldifs} 508 509 return 1 510} 511 512# 513# _atf_has_tc name 514# 515# Returns true if the given test case exists. 516# 517_atf_has_tc() 518{ 519 for _tc in ${Test_Cases}; do 520 [ "${_tc}" != "${1}" ] || return 0 521 done 522 return 1 523} 524 525# 526# _atf_list_tcs 527# 528# Describes all test cases and prints the list to the standard output. 529# 530_atf_list_tcs() 531{ 532 echo 'Content-Type: application/X-atf-tp; version="1"' 533 echo 534 535 set -- ${Test_Cases} 536 while [ ${#} -gt 0 ]; do 537 _atf_parse_head ${1} 538 539 echo "ident: $(atf_get ident)" 540 for _var in ${Test_Case_Vars}; do 541 [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})" 542 done 543 544 [ ${#} -gt 1 ] && echo 545 shift 546 done 547} 548 549# 550# _atf_normalize str 551# 552# Normalizes a string so that it is a valid shell variable name. 553# 554_atf_normalize() 555{ 556 # Check if the string contains any of the forbidden characters using 557 # POSIX parameter expansion (the ${var//} string substitution is 558 # unfortunately not supported in POSIX sh) and only use tr(1) then. 559 # tr(1) is generally not a builtin, so doing the substring check first 560 # avoids unnecessary fork()+execve() calls. As this function is called 561 # many times in each test script startup, those overheads add up 562 # (especially when running on emulated platforms such as QEMU). 563 if [ "${1#*[.-]}" != "$1" ]; then 564 echo "$1" | tr .- __ 565 else 566 echo "$1" 567 fi 568} 569 570# 571# _atf_parse_head tcname 572# 573# Evaluates a test case's head to gather its variables and prepares the 574# test program to run it. 575# 576_atf_parse_head() 577{ 578 Parsing_Head=true 579 580 Test_Case="${1}" 581 Test_Case_Vars= 582 583 if _atf_has_cleanup "${1}"; then 584 atf_set has.cleanup "true" 585 fi 586 587 ${1}_head 588 atf_set ident "${1}" 589 590 Parsing_Head=false 591} 592 593# 594# _atf_run_tc tc 595# 596# Runs the specified test case. Prints its exit status to the 597# standard output and returns a boolean indicating if the test was 598# successful or not. 599# 600_atf_run_tc() 601{ 602 case ${1} in 603 *:*) 604 _tcname=${1%%:*} 605 _tcpart=${1#*:} 606 607 if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then 608 _atf_syntax_error "Unknown test case part \`${_tcpart}'" 609 fi 610 ;; 611 612 *) 613 _tcname=${1} 614 _tcpart=body 615 ;; 616 esac 617 618 _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'" 619 620 if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then 621 _atf_warning "Running test cases outside of kyua(1) is unsupported" 622 _atf_warning "No isolation nor timeout control is being applied;" \ 623 "you may get unexpected failures; see atf-test-case(4)" 624 fi 625 626 _atf_parse_head ${_tcname} 627 628 case ${_tcpart} in 629 body) 630 if ${_tcname}_body; then 631 _atf_validate_expect 632 _atf_create_resfile passed 633 else 634 Expect=pass 635 atf_fail "Test case body returned a non-ok exit code, but" \ 636 "this is not allowed" 637 fi 638 ;; 639 cleanup) 640 if _atf_has_cleanup "${_tcname}"; then 641 ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \ 642 "returned a non-ok exit code, but this is not allowed" 643 fi 644 ;; 645 *) 646 _atf_error 128 "Unknown test case part" 647 ;; 648 esac 649} 650 651# 652# _atf_syntax_error msg1 [.. msgN] 653# 654# Formats and prints a syntax error message and terminates the 655# program prematurely. 656# 657_atf_syntax_error() 658{ 659 echo "${Prog_Name}: ERROR: ${@}" 1>&2 660 echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2 661 exit 1 662} 663 664# 665# _atf_has_cleanup tc-name 666# 667# Returns a boolean indicating if the given test case has a cleanup 668# routine or not. 669# 670_atf_has_cleanup() 671{ 672 _found=true 673 eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false" 674 [ "${_found}" = true ] 675} 676 677# 678# _atf_validate_expect 679# 680# Ensures that the current test case state is correct regarding the expect 681# status. 682# 683_atf_validate_expect() 684{ 685 case "${Expect}" in 686 death) 687 Expect=pass 688 atf_fail "Test case was expected to terminate abruptly but it" \ 689 "continued execution" 690 ;; 691 exit) 692 Expect=pass 693 atf_fail "Test case was expected to exit cleanly but it continued" \ 694 "execution" 695 ;; 696 fail) 697 Expect=pass 698 atf_fail "Test case was expecting a failure but none were raised" 699 ;; 700 pass) 701 ;; 702 signal) 703 Expect=pass 704 atf_fail "Test case was expected to receive a termination signal" \ 705 "but it continued execution" 706 ;; 707 timeout) 708 Expect=pass 709 atf_fail "Test case was expected to hang but it continued execution" 710 ;; 711 *) 712 _atf_error 128 "Unreachable" 713 ;; 714 esac 715} 716 717# 718# _atf_warning [msg1 [.. msgN]] 719# 720# Prints the given warning message (which can be composed of multiple 721# arguments, in which case are joined by a single space). 722# 723# This must not be used by test programs themselves (hence making 724# the function private). 725# 726_atf_warning() 727{ 728 echo "${Prog_Name}: WARNING:" "$@" 1>&2 729} 730 731# 732# main [options] test_case 733# 734# Test program's entry point. 735# 736main() 737{ 738 # Process command-line options first. 739 _numargs=${#} 740 _lflag=false 741 while getopts :lr:s:v: arg; do 742 case ${arg} in 743 l) 744 _lflag=true 745 ;; 746 747 r) 748 Results_File=${OPTARG} 749 ;; 750 751 s) 752 Source_Dir=${OPTARG} 753 ;; 754 755 v) 756 _atf_config_set_from_str "${OPTARG}" 757 ;; 758 759 \?) 760 _atf_syntax_error "Unknown option -${OPTARG}." 761 # NOTREACHED 762 ;; 763 esac 764 done 765 shift $((OPTIND - 1)) 766 767 case ${Source_Dir} in 768 /*) 769 ;; 770 *) 771 Source_Dir=$(pwd)/${Source_Dir} 772 ;; 773 esac 774 [ -f ${Source_Dir}/${Prog_Name} ] || \ 775 _atf_error 1 "Cannot find the test program in the source" \ 776 "directory \`${Source_Dir}'" 777 778 # Call the test program's hook to register all available test cases. 779 atf_init_test_cases 780 781 # Run or list test cases. 782 if `${_lflag}`; then 783 if [ ${#} -gt 0 ]; then 784 _atf_syntax_error "Cannot provide test case names with -l" 785 fi 786 _atf_list_tcs 787 else 788 if [ ${#} -eq 0 ]; then 789 _atf_syntax_error "Must provide a test case name" 790 elif [ ${#} -gt 1 ]; then 791 _atf_syntax_error "Cannot provide more than one test case name" 792 else 793 _atf_run_tc "${1}" 794 fi 795 fi 796} 797 798# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 799