libatf-sh.subr revision 1.4
1# 2# Automated Testing Framework (atf) 3# 4# Copyright (c) 2007 The NetBSD Foundation, Inc. 5# All rights reserved. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 16# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND 17# CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 18# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20# IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY 21# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28# 29 30set -e 31 32# ------------------------------------------------------------------------ 33# GLOBAL VARIABLES 34# ------------------------------------------------------------------------ 35 36# Values for the expect property. 37Expect=pass 38Expect_Reason= 39 40# A boolean variable that indicates whether we are parsing a test case's 41# head or not. 42Parsing_Head=false 43 44# The program name. 45Prog_Name=${0##*/} 46 47# The file to which the test case will print its result. 48Results_File= 49 50# The test program's source directory: i.e. where its auxiliary data files 51# and helper utilities can be found. Can be overriden through the '-s' flag. 52Source_Dir="$(dirname ${0})" 53 54# Indicates the test case we are currently processing. 55Test_Case= 56 57# List of meta-data variables for the current test case. 58Test_Case_Vars= 59 60# The list of all test cases provided by the test program. 61Test_Cases= 62 63# ------------------------------------------------------------------------ 64# PUBLIC INTERFACE 65# ------------------------------------------------------------------------ 66 67# 68# atf_add_test_case tc-name 69# 70# Adds the given test case to the list of test cases that form the test 71# program. The name provided here must be accompanied by two functions 72# named after it: <tc-name>_head and <tc-name>_body, and optionally by 73# a <tc-name>_cleanup function. 74# 75atf_add_test_case() 76{ 77 Test_Cases="${Test_Cases} ${1}" 78} 79 80# 81# atf_check cmd expcode expout experr 82# 83# Executes atf-check with given arguments and automatically calls 84# atf_fail in case of failure. 85# 86atf_check() 87{ 88 ${Atf_Check} "${@}" || \ 89 atf_fail "atf-check failed; see the output of the test for details" 90} 91 92# 93# atf_check_equal expr1 expr2 94# 95# Checks that expr1's value matches expr2's and, if not, raises an 96# error. Ideally expr1 and expr2 should be provided quoted (not 97# expanded) so that the error message is helpful; otherwise it will 98# only show the values, not the expressions themselves. 99# 100atf_check_equal() 101{ 102 eval _val1=\"${1}\" 103 eval _val2=\"${2}\" 104 test "${_val1}" = "${_val2}" || \ 105 atf_fail "${1} != ${2} (${_val1} != ${_val2})" 106} 107 108# 109# atf_config_get varname [defvalue] 110# 111# Prints the value of a configuration variable. If it is not 112# defined, prints the given default value. 113# 114atf_config_get() 115{ 116 _varname="__tc_config_var_$(_atf_normalize ${1})" 117 if [ ${#} -eq 1 ]; then 118 eval _value=\"\${${_varname}-__unset__}\" 119 [ "${_value}" = __unset__ ] && \ 120 _atf_error 1 "Could not find configuration variable \`${1}'" 121 echo ${_value} 122 elif [ ${#} -eq 2 ]; then 123 eval echo \${${_varname}-${2}} 124 else 125 _atf_error 1 "Incorrect number of parameters for atf_config_get" 126 fi 127} 128 129# 130# atf_config_has varname 131# 132# Returns a boolean indicating if the given configuration variable is 133# defined or not. 134# 135atf_config_has() 136{ 137 _varname="__tc_config_var_$(_atf_normalize ${1})" 138 eval _value=\"\${${_varname}-__unset__}\" 139 [ "${_value}" != __unset__ ] 140} 141 142# 143# atf_expect_death reason 144# 145# Sets the expectations to 'death'. 146# 147atf_expect_death() 148{ 149 _atf_validate_expect 150 151 Expect=death 152 _atf_create_resfile "expected_death: ${*}" 153} 154 155# 156# atf_expect_timeout reason 157# 158# Sets the expectations to 'timeout'. 159# 160atf_expect_timeout() 161{ 162 _atf_validate_expect 163 164 Expect=timeout 165 _atf_create_resfile "expected_timeout: ${*}" 166} 167 168# 169# atf_expect_exit exitcode reason 170# 171# Sets the expectations to 'exit'. 172# 173atf_expect_exit() 174{ 175 _exitcode="${1}"; shift 176 177 _atf_validate_expect 178 179 Expect=exit 180 if [ "${_exitcode}" = "-1" ]; then 181 _atf_create_resfile "expected_exit: ${*}" 182 else 183 _atf_create_resfile "expected_exit(${_exitcode}): ${*}" 184 fi 185} 186 187# 188# atf_expect_fail reason 189# 190# Sets the expectations to 'fail'. 191# 192atf_expect_fail() 193{ 194 _atf_validate_expect 195 196 Expect=fail 197 Expect_Reason="${*}" 198} 199 200# 201# atf_expect_pass 202# 203# Sets the expectations to 'pass'. 204# 205atf_expect_pass() 206{ 207 _atf_validate_expect 208 209 Expect=pass 210 Expect_Reason= 211} 212 213# 214# atf_expect_signal signo reason 215# 216# Sets the expectations to 'signal'. 217# 218atf_expect_signal() 219{ 220 _signo="${1}"; shift 221 222 _atf_validate_expect 223 224 Expect=signal 225 if [ "${_signo}" = "-1" ]; then 226 _atf_create_resfile "expected_signal: ${*}" 227 else 228 _atf_create_resfile "expected_signal(${_signo}): ${*}" 229 fi 230} 231 232# 233# atf_expected_failure msg1 [.. msgN] 234# 235# Makes the test case report an expected failure with the given error 236# message. Multiple words can be provided, which are concatenated with 237# a single blank space. 238# 239atf_expected_failure() 240{ 241 _atf_create_resfile "expected_failure: ${Expect_Reason}: ${*}" 242 exit 0 243} 244 245# 246# atf_fail msg1 [.. msgN] 247# 248# Makes the test case fail with the given error message. Multiple 249# words can be provided, in which case they are joined by a single 250# blank space. 251# 252atf_fail() 253{ 254 case "${Expect}" in 255 fail) 256 atf_expected_failure "${@}" 257 ;; 258 pass) 259 _atf_create_resfile "failed: ${*}" 260 exit 1 261 ;; 262 *) 263 _atf_error 128 "Unreachable" 264 ;; 265 esac 266} 267 268# 269# atf_get varname 270# 271# Prints the value of a test case-specific variable. Given that one 272# should not get the value of non-existent variables, it is fine to 273# always use this function as 'val=$(atf_get var)'. 274# 275atf_get() 276{ 277 eval echo \${__tc_var_${Test_Case}_$(_atf_normalize ${1})} 278} 279 280# 281# atf_get_srcdir 282# 283# Prints the value of the test case's source directory. 284# 285atf_get_srcdir() 286{ 287 echo ${Source_Dir} 288} 289 290# 291# atf_pass 292# 293# Makes the test case pass. Shouldn't be used in general, as a test 294# case that does not explicitly fail is assumed to pass. 295# 296atf_pass() 297{ 298 case "${Expect}" in 299 fail) 300 Expect=pass 301 atf_fail "Test case was expecting a failure but got a pass instead" 302 ;; 303 pass) 304 _atf_create_resfile passed 305 exit 0 306 ;; 307 *) 308 _atf_error 128 "Unreachable" 309 ;; 310 esac 311} 312 313# 314# atf_require_prog prog 315# 316# Checks that the given program name (either provided as an absolute 317# path or as a plain file name) can be found. If it is not available, 318# automatically skips the test case with an appropriate message. 319# 320# Relative paths are not allowed because the test case cannot predict 321# where it will be executed from. 322# 323atf_require_prog() 324{ 325 _prog= 326 case ${1} in 327 /*) 328 _prog="${1}" 329 [ -x ${_prog} ] || \ 330 atf_skip "The required program ${1} could not be found" 331 ;; 332 */*) 333 atf_fail "atf_require_prog does not accept relative path names \`${1}'" 334 ;; 335 *) 336 _prog=$(_atf_find_in_path "${1}") 337 [ -n "${_prog}" ] || \ 338 atf_skip "The required program ${1} could not be found" \ 339 "in the PATH" 340 ;; 341 esac 342} 343 344# 345# atf_set varname val1 [.. valN] 346# 347# Sets the test case's variable 'varname' to the specified values 348# which are concatenated using a single blank space. This function 349# is supposed to be called form the test case's head only. 350# 351atf_set() 352{ 353 ${Parsing_Head} || \ 354 _atf_error 128 "atf_set called from the test case's body" 355 356 Test_Case_Vars="${Test_Case_Vars} ${1}" 357 _var=$(_atf_normalize ${1}); shift 358 eval __tc_var_${Test_Case}_${_var}=\"\${*}\" 359} 360 361# 362# atf_skip msg1 [.. msgN] 363# 364# Skips the test case because of the reason provided. Multiple words 365# can be given, in which case they are joined by a single blank space. 366# 367atf_skip() 368{ 369 _atf_create_resfile "skipped: ${*}" 370 exit 0 371} 372 373# 374# atf_test_case tc-name cleanup 375# 376# Defines a new test case named tc-name. The name provided here must be 377# accompanied by two functions named after it: <tc-name>_head and 378# <tc-name>_body. If cleanup is set to 'cleanup', then this also expects 379# a <tc-name>_cleanup function to be defined. 380# 381atf_test_case() 382{ 383 eval "${1}_head() { :; }" 384 eval "${1}_body() { atf_fail 'Test case not implemented'; }" 385 if [ "${2}" = cleanup ]; then 386 eval __has_cleanup_${1}=true 387 eval "${1}_cleanup() { :; }" 388 else 389 eval "${1}_cleanup() { 390 _atf_error 1 'Test case ${1} declared without a cleanup routine'; }" 391 fi 392} 393 394# ------------------------------------------------------------------------ 395# PRIVATE INTERFACE 396# ------------------------------------------------------------------------ 397 398# 399# _atf_config_set varname val1 [.. valN] 400# 401# Sets the test case's private variable 'varname' to the specified 402# values which are concatenated using a single blank space. 403# 404_atf_config_set() 405{ 406 _var=$(_atf_normalize ${1}); shift 407 eval __tc_config_var_${_var}=\"\${*}\" 408 Config_Vars="${Config_Vars} __tc_config_var_${_var}" 409} 410 411# 412# _atf_config_set_str varname=val 413# 414# Sets the test case's private variable 'varname' to the specified 415# value. The parameter is of the form 'varname=val'. 416# 417_atf_config_set_from_str() 418{ 419 _oldifs=${IFS} 420 IFS='=' 421 set -- ${*} 422 _var=${1} 423 shift 424 _val="${@}" 425 IFS=${_oldifs} 426 _atf_config_set "${_var}" "${_val}" 427} 428 429# 430# _atf_create_resfile contents 431# 432# Creates the results file. 433# 434_atf_create_resfile() 435{ 436 if [ -n "${Results_File}" ]; then 437 echo "${*}" >"${Results_File}" || \ 438 _atf_error 128 "Cannot create results file '${Results_File}'" 439 else 440 echo "${*}" 441 fi 442} 443 444# 445# _atf_error error_code [msg1 [.. msgN]] 446# 447# Prints the given error message (which can be composed of multiple 448# arguments, in which case are joined by a single space) and exits 449# with the specified error code. 450# 451# This must not be used by test programs themselves (hence making 452# the function private) to indicate a test case's failure. They 453# have to use the atf_fail function. 454# 455_atf_error() 456{ 457 _error_code="${1}"; shift 458 459 echo "${Prog_Name}: ERROR:" "$@" 1>&2 460 exit ${_error_code} 461} 462 463# 464# _atf_warning msg1 [.. msgN] 465# 466# Prints the given warning message (which can be composed of multiple 467# arguments, in which case are joined by a single space). 468# 469_atf_warning() 470{ 471 echo "${Prog_Name}: WARNING:" "$@" 1>&2 472} 473 474# 475# _atf_find_in_path program 476# 477# Looks for a program in the path and prints the full path to it or 478# nothing if it could not be found. It also returns true in case of 479# success. 480# 481_atf_find_in_path() 482{ 483 _prog="${1}" 484 485 _oldifs=${IFS} 486 IFS=: 487 for _dir in ${PATH} 488 do 489 if [ -x ${_dir}/${_prog} ]; then 490 IFS=${_oldifs} 491 echo ${_dir}/${_prog} 492 return 0 493 fi 494 done 495 IFS=${_oldifs} 496 497 return 1 498} 499 500# 501# _atf_has_tc name 502# 503# Returns true if the given test case exists. 504# 505_atf_has_tc() 506{ 507 for _tc in ${Test_Cases}; do 508 [ "${_tc}" != "${1}" ] || return 0 509 done 510 return 1 511} 512 513# 514# _atf_list_tcs 515# 516# Describes all test cases and prints the list to the standard output. 517# 518_atf_list_tcs() 519{ 520 echo 'Content-Type: application/X-atf-tp; version="1"' 521 echo 522 523 set -- ${Test_Cases} 524 while [ ${#} -gt 0 ]; do 525 _atf_parse_head ${1} 526 527 echo "ident: $(atf_get ident)" 528 for _var in ${Test_Case_Vars}; do 529 # Elide ksh bug! 530 set +e 531 [ "${_var}" != "ident" ] && echo "${_var}: $(atf_get ${_var})" 532 set -e 533 done 534 535 [ ${#} -gt 1 ] && echo 536 shift 537 done 538} 539 540# 541# _atf_normalize str 542# 543# Normalizes a string so that it is a valid shell variable name. 544# 545_atf_normalize() 546{ 547 echo ${1} | tr .- __ 548} 549 550# 551# _atf_parse_head tcname 552# 553# Evaluates a test case's head to gather its variables and prepares the 554# test program to run it. 555# 556_atf_parse_head() 557{ 558 Parsing_Head=true 559 560 Test_Case="${1}" 561 Test_Case_Vars= 562 563 if _atf_has_cleanup "${1}"; then 564 atf_set has.cleanup "true" 565 fi 566 567 ${1}_head 568 atf_set ident "${1}" 569 570 Parsing_Head=false 571} 572 573# 574# _atf_run_tc tc 575# 576# Runs the specified test case. Prints its exit status to the 577# standard output and returns a boolean indicating if the test was 578# successful or not. 579# 580_atf_run_tc() 581{ 582 case ${1} in 583 *:*) 584 _tcname=${1%%:*} 585 _tcpart=${1#*:} 586 587 if [ "${_tcpart}" != body -a "${_tcpart}" != cleanup ]; then 588 _atf_syntax_error "Unknown test case part \`${_tcpart}'" 589 fi 590 ;; 591 592 *) 593 _tcname=${1} 594 _tcpart=body 595 ;; 596 esac 597 598 _atf_has_tc "${_tcname}" || _atf_syntax_error "Unknown test case \`${1}'" 599 600 if [ "${__RUNNING_INSIDE_ATF_RUN}" != "internal-yes-value" ]; then 601 _atf_warning "Running test cases without atf-run(1) is unsupported" 602 _atf_warning "No isolation nor timeout control is being applied;" \ 603 "you may get unexpected failures; see atf-test-case(4)" 604 fi 605 606 _atf_parse_head ${_tcname} 607 608 case ${_tcpart} in 609 body) 610 if ${_tcname}_body; then 611 _atf_validate_expect 612 _atf_create_resfile passed 613 else 614 Expect=pass 615 atf_fail "Test case body returned a non-ok exit code, but" \ 616 "this is not allowed" 617 fi 618 ;; 619 cleanup) 620 if _atf_has_cleanup "${_tcname}"; then 621 ${_tcname}_cleanup || _atf_error 128 "The test case cleanup" \ 622 "returned a non-ok exit code, but this is not allowed" 623 fi 624 ;; 625 *) 626 _atf_error 128 "Unknown test case part" 627 ;; 628 esac 629} 630 631# 632# _atf_syntax_error msg1 [.. msgN] 633# 634# Formats and prints a syntax error message and terminates the 635# program prematurely. 636# 637_atf_syntax_error() 638{ 639 echo "${Prog_Name}: ERROR: ${@}" 1>&2 640 echo "${Prog_Name}: See atf-test-program(1) for usage details." 1>&2 641 exit 1 642} 643 644# 645# _atf_has_cleanup tc-name 646# 647# Returns a boolean indicating if the given test case has a cleanup 648# routine or not. 649# 650_atf_has_cleanup() 651{ 652 _found=true 653 eval "[ x\"\${__has_cleanup_${1}}\" = xtrue ] || _found=false" 654 [ "${_found}" = true ] 655} 656 657# 658# _atf_validate_expect 659# 660# Ensures that the current test case state is correct regarding the expect 661# status. 662# 663_atf_validate_expect() 664{ 665 case "${Expect}" in 666 death) 667 Expect=pass 668 atf_fail "Test case was expected to terminate abruptly but it" \ 669 "continued execution" 670 ;; 671 exit) 672 Expect=pass 673 atf_fail "Test case was expected to exit cleanly but it continued" \ 674 "execution" 675 ;; 676 fail) 677 Expect=pass 678 atf_fail "Test case was expecting a failure but none were raised" 679 ;; 680 pass) 681 ;; 682 signal) 683 Expect=pass 684 atf_fail "Test case was expected to receive a termination signal" \ 685 "but it continued execution" 686 ;; 687 timeout) 688 Expect=pass 689 atf_fail "Test case was expected to hang but it continued execution" 690 ;; 691 *) 692 _atf_error 128 "Unreachable" 693 ;; 694 esac 695} 696 697# 698# _atf_warning [msg1 [.. msgN]] 699# 700# Prints the given warning message (which can be composed of multiple 701# arguments, in which case are joined by a single space). 702# 703# This must not be used by test programs themselves (hence making 704# the function private). 705# 706_atf_warning() 707{ 708 echo "${Prog_Name}: WARNING:" "$@" 1>&2 709} 710 711# 712# main [options] test_case 713# 714# Test program's entry point. 715# 716main() 717{ 718 # Process command-line options first. 719 _numargs=${#} 720 _lflag=false 721 while getopts :lr:s:v: arg; do 722 case ${arg} in 723 l) 724 _lflag=true 725 ;; 726 727 r) 728 Results_File=${OPTARG} 729 ;; 730 731 s) 732 Source_Dir=${OPTARG} 733 ;; 734 735 v) 736 _atf_config_set_from_str "${OPTARG}" 737 ;; 738 739 \?) 740 _atf_syntax_error "Unknown option -${OPTARG}." 741 # NOTREACHED 742 ;; 743 esac 744 done 745 shift `expr ${OPTIND} - 1` 746 747 # First of all, make sure that the source directory is correct. It 748 # doesn't matter if the user did not change it, because the default 749 # value may not work. (TODO: It possibly should, even though it is 750 # not a big deal because atf-run deals with this.) 751 case ${Source_Dir} in 752 /*) 753 ;; 754 *) 755 Source_Dir=$(pwd)/${Source_Dir} 756 ;; 757 esac 758 [ -f ${Source_Dir}/${Prog_Name} ] || \ 759 _atf_error 1 "Cannot find the test program in the source" \ 760 "directory \`${Source_Dir}'" 761 762 # Call the test program's hook to register all available test cases. 763 atf_init_test_cases 764 765 # Run or list test cases. 766 if `${_lflag}`; then 767 if [ ${#} -gt 0 ]; then 768 _atf_syntax_error "Cannot provide test case names with -l" 769 fi 770 _atf_list_tcs 771 else 772 if [ ${#} -eq 0 ]; then 773 _atf_syntax_error "Must provide a test case name" 774 elif [ ${#} -gt 1 ]; then 775 _atf_syntax_error "Cannot provide more than one test case name" 776 else 777 _atf_run_tc "${1}" 778 fi 779 fi 780} 781 782# vim: syntax=sh:expandtab:shiftwidth=4:softtabstop=4 783