shtap.sh
Go to the documentation of this file.
00001 ############################################################################## 00002 # @file shtap.sh 00003 # @brief Unit testing framework for BASH based on the Test Anything Protocol. 00004 # 00005 # @author Patrick LeBoutillier <patl at cpan.org> 00006 # 00007 # @note This file is a copy of the tap-functions file which is part of the 00008 # JTap project (http://svn.solucorp.qc.ca/repos/solucorp/JTap/trunk/). 00009 # The original implementation has been modified by Andreas Schuh as 00010 # part of the BASIS project at SBIA. 00011 # 00012 # Plan: 00013 # @code 00014 # plan_no_plan 00015 # plan_skip_all [REASON] 00016 # plan_tests NB_TESTS 00017 # @endcode 00018 # 00019 # Test: 00020 # @code 00021 # ok RESULT [NAME] 00022 # okx COMMAND 00023 # is RESULT EXPECTED [NAME] 00024 # isnt RESULT EXPECTED [NAME] 00025 # like RESULT PATTERN [NAME] 00026 # unlike RESULT PATTERN [NAME] 00027 # pass [NAME] 00028 # fail [NAME] 00029 # @endcode 00030 # 00031 # Skip: 00032 # @code 00033 # skip [CONDITION] [REASON] [NB_TESTS=1] 00034 # 00035 # skip $feature_not_present "feature not present" 2 || 00036 # { 00037 # is $a "a" 00038 # is $b "b" 00039 # } 00040 # @endcode 00041 # 00042 # Specify TODO mode by setting the TODO variable: 00043 # @code 00044 # TODO="not implemented yet" 00045 # ok $result "some not implemented test" 00046 # unset TODO 00047 # @endcode 00048 # 00049 # Other: 00050 # @code 00051 # diag MSG 00052 # @endcode 00053 # 00054 # Example: 00055 # @code 00056 # #! /usr/bin/env bash 00057 # 00058 # source shtap.sh 00059 # 00060 # plan_tests 7 00061 # 00062 # # test identity 00063 # { 00064 # me=${USER} 00065 # is ${USER} ${me} "I am myself" 00066 # like ${HOME} ${me} "My home is mine" 00067 # like "`id`" ${me} "My id matches myself" 00068 # } 00069 # 00070 # # test ls 00071 # { 00072 # ls ${HOME} 1>&2 00073 # ok $? "ls ${HOME}" 00074 # # same thing using okx shortcut 00075 # okx ls ${HOME} 00076 # } 00077 # 00078 # # test only for root 00079 # { 00080 # [[ "`id -u`" != "0" ]] 00081 # i_am_not_root=$? 00082 # skip ${i_am_not_root} "Must be root" || 00083 # { 00084 # okx ls /root 00085 # } 00086 # } 00087 # 00088 # # test TODO 00089 # { 00090 # TODO="figure out how to become root..." 00091 # okx [ "$HOME" == "/root" ] 00092 # unset TODO 00093 # } 00094 # @endcode 00095 # 00096 # Copyright (c) Patrick LeBoutillier.<br /> 00097 # Copyright (c) 2011, University of Pennsylvania.<br /> 00098 # All rights reserved. 00099 # 00100 # This program is free software; you can redistribute it and/or modify 00101 # it under the terms of the GNU General Public License as published by 00102 # the Free Software Foundation; either version 2 of the License, or 00103 # any later version. 00104 # 00105 # This program is distributed in the hope that it will be useful, 00106 # but WITHOUT ANY WARRANTY; without even the implied warranty of 00107 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00108 # GNU General Public License for more details. 00109 # 00110 # @sa http://testanything.org/wiki/index.php/Tap-functions 00111 # 00112 # Contact: SBIA Group <sbia-software at uphs.upenn.edu> 00113 ############################################################################## 00114 00115 # return if already loaded 00116 [ -n "${SHTAP_VERSION:-}" ] && return 0 00117 readonly SHTAP_VERSION='1.02-basis' 00118 00119 00120 . "`cd -P -- \`dirname -- "${BASH_SOURCE}"\` && pwd`/core.sh" || exit 1 # match() 00121 00122 readonly _SHTAP_FILENAME='' 00123 00124 TODO= 00125 00126 _shtap_plan_set=0 00127 _shtap_no_plan=0 00128 _shtap_skip_all=0 00129 _shtap_test_died=0 00130 _shtap_expected_tests=0 00131 _shtap_executed_tests=0 00132 _shtap_failed_tests=0 00133 00134 # used to call _cleanup() on shell exit 00135 trap _shtap_exit EXIT 00136 trap _shtap_exit INT 00137 trap _shtap_exit TERM 00138 00139 # ============================================================================ 00140 # plan 00141 # ============================================================================ 00142 00143 # ---------------------------------------------------------------------------- 00144 ## @brief Choose not to plan number of tests in advance. 00145 function plan_no_plan 00146 { 00147 [ ${_shtap_plan_set} -ne 0 ] && _shtap_die "You tried to plan twice!" 00148 00149 _shtap_plan_set=1 00150 _shtap_no_plan=1 00151 00152 return 0 00153 } 00154 00155 # ---------------------------------------------------------------------------- 00156 ## @brief Plan to skip all tests. 00157 function plan_skip_all 00158 { 00159 local reason=${1:-''} 00160 00161 [ ${_shtap_plan_set} -ne 0 ] && _shtap_die "You tried to plan twice!" 00162 00163 _shtap_print_plan 0 "Skip ${reason}" 00164 00165 _shtap_skip_all=1 00166 _shtap_plan_set=1 00167 _shtap_exit 0 00168 00169 return 0 00170 } 00171 00172 # ---------------------------------------------------------------------------- 00173 ## @brief Plan a certain number of tests and stick to it. 00174 function plan_tests 00175 { 00176 local tests=${1:?} 00177 00178 [ ${_shtap_plan_set} -ne 0 ] && _shtap_die "You tried to plan twice!" 00179 [ ${tests} -eq 0 ] && _shtap_die "You said to run 0 tests! You've got to run something." 00180 00181 _shtap_print_plan ${tests} 00182 _shtap_expected_tests=${tests} 00183 _shtap_plan_set=1 00184 00185 return ${tests} 00186 } 00187 00188 # ---------------------------------------------------------------------------- 00189 ## @brief Print plan. 00190 function _shtap_print_plan 00191 { 00192 local tests=${1:?} 00193 local directive=${2:-''} 00194 00195 echo -n "1..${tests}" 00196 [[ -n "${directive}" ]] && echo -n " # ${directive}" 00197 echo 00198 } 00199 00200 # ============================================================================ 00201 # pass / fail 00202 # ============================================================================ 00203 00204 # ---------------------------------------------------------------------------- 00205 ## @brief Pass in any case and print reason. 00206 function pass 00207 { 00208 local name=$1 00209 ok 0 "${name}" 00210 } 00211 00212 # ---------------------------------------------------------------------------- 00213 ## @brief Fail in any case and print reason. 00214 function fail 00215 { 00216 local name=$1 00217 ok 1 "${name}" 00218 } 00219 00220 # ============================================================================ 00221 # test 00222 # ============================================================================ 00223 00224 # ---------------------------------------------------------------------------- 00225 ## @brief Evaluate test expression and fail if it does not evaluate to 0 or 00226 # check return value of function. 00227 # 00228 # This is the workhorse method that actually prints the tests result. 00229 # 00230 # @param [in] expression Test expression or return value. 00231 # @param [in] name Name of test. 00232 function ok 00233 { 00234 local expression=${1:?} 00235 local name=${2:-''} 00236 00237 [ ${_shtap_plan_set} -eq 0 ] && _shtap_die "You tried to run a test without a plan! Gotta have a plan." 00238 00239 (( _shtap_executed_tests++ )) 00240 00241 if [ -n "${name}" ]; then 00242 if match "${name}" "^[0-9]+$"; then 00243 diag " You named your test '${name}'. You shouldn't use numbers for your test names." 00244 diag " Very confusing." 00245 fi 00246 fi 00247 00248 local match=`expr "${expression}" : '\([0-9]*\)'` 00249 local result=0 00250 if [ -z "${expression}" ]; then 00251 result=1 00252 elif [ -n "${match}" -a "${expression}" = "${match}" ]; then 00253 [ ${expression} -ne 0 ] && result=1 00254 else 00255 ( eval ${expression} ) >/dev/null 2>&1 00256 [ $? -ne 0 ] && result=1 00257 fi 00258 00259 if [ ${result} -ne 0 ]; then 00260 echo -n "not " 00261 (( _shtap_failed_tests++ )) 00262 fi 00263 echo -n "ok ${_shtap_executed_tests}" 00264 00265 if [ -n "${name}" ]; then 00266 local ename=${name//\#/\\#} 00267 echo -n " - ${ename}" 00268 fi 00269 00270 if [ -n "${TODO}" ]; then 00271 echo -n " # TODO ${TODO}" ; 00272 if [ ${result} -ne 0 ]; then 00273 (( _shtap_failed_tests-- )) 00274 fi 00275 fi 00276 00277 echo 00278 if [ ${result} -ne 0 ]; then 00279 local file='_SHTAP_FILENAME' 00280 local func= 00281 local line= 00282 00283 local i=0 00284 local bt=$(caller ${i}) 00285 while match "${bt}" "${_SHTAP_FILENAME}$"; do 00286 (( i++ )) 00287 bt=$(caller ${i}) 00288 done 00289 local backtrace= 00290 eval $(caller ${i} | (read line func file; echo "backtrace=\"${file}:${func}() at line ${line}.\"")) 00291 00292 local t= 00293 [ -n "${TODO}" ] && t="(TODO) " 00294 00295 if [ -n "${name}" ]; then 00296 diag " Failed ${t}test '${name}'" 00297 diag " in ${backtrace}" 00298 else 00299 diag " Failed ${t}test in ${backtrace}" 00300 fi 00301 fi 00302 00303 return ${result} 00304 } 00305 00306 # ---------------------------------------------------------------------------- 00307 ## @brief Execute command and check return value. 00308 # 00309 # @param [in] command Command to run. 00310 function okx 00311 { 00312 local command="$@" 00313 00314 local line= 00315 diag "Output of '${command}':" 00316 ${command} | while read line; do 00317 diag "${line}" 00318 done 00319 ok ${PIPESTATUS[0]} "${command}" 00320 } 00321 00322 # ---------------------------------------------------------------------------- 00323 ## @brief Compare actual and expected result. 00324 # 00325 # @param [in] result Actual result. 00326 # @param [in] expected Expected result. 00327 function _shtap_equals 00328 { 00329 local result=$1 00330 local expected=$2 00331 00332 if [[ "${result}" == "${expected}" ]] ; then 00333 return 0 00334 else 00335 return 1 00336 fi 00337 } 00338 00339 # ---------------------------------------------------------------------------- 00340 ## @brief Diagnostic message for is(). 00341 # 00342 # @param [in] result Actual result. 00343 # @param [in] expected Expected result. 00344 function _shtap_is_diag 00345 { 00346 local result=$1 00347 local expected=$2 00348 00349 diag " got: '${result}'" 00350 diag " expected: '${expected}'" 00351 } 00352 00353 # ---------------------------------------------------------------------------- 00354 ## @brief Test whether a given result is equal to the expected result. 00355 # 00356 # @param [in] result Actual result. 00357 # @param [in] expected Expected result. 00358 # @param [in] name Optional name of test. 00359 # 00360 # @returns Whether the results are equal. 00361 # 00362 # @retval 0 On equality. 00363 # @retval 1 Otherwise. 00364 function is 00365 { 00366 local result=$1 00367 local expected=$2 00368 local name=${3:-''} 00369 00370 _shtap_equals "${result}" "${expected}" 00371 [ $? -eq 0 ] 00372 ok $? "${name}" 00373 local r=$? 00374 [ ${r} -ne 0 ] && _shtap_is_diag "${result}" "${expected}" 00375 return ${r} 00376 } 00377 00378 # ---------------------------------------------------------------------------- 00379 ## @brief Test whether a given result is not equal the expected result. 00380 # 00381 # @param [in] result Actual result. 00382 # @param [in] expected Expected result. 00383 # @param [in] name Optional name of test. 00384 # 00385 # @returns Whether the results were not equal. 00386 # 00387 # @retval 0 Otherwise. 00388 # @retval 1 On equality. 00389 function isnt 00390 { 00391 local result=$1 00392 local expected=$2 00393 local name=${3:-''} 00394 00395 _shtap_equals "${result}" "${expected}" 00396 (( $? != 0 )) 00397 ok $? "${name}" 00398 local r=$? 00399 [ ${r} -ne 0 ] && _shtap_is_diag "${result}" "${expected}" 00400 return ${r} 00401 } 00402 00403 # ---------------------------------------------------------------------------- 00404 ## @brief Test whether a given result matches an expected pattern. 00405 # 00406 # @param [in] result Actual result. 00407 # @param [in] pattern Expected pattern. 00408 # @param [in] name Optional name of test. 00409 # 00410 # @returns Whether the result matched the pattern. 00411 # 00412 # @retval 0 On match. 00413 # @retval 1 Otherwise. 00414 function like 00415 { 00416 local result=$1 00417 local pattern=$2 00418 local name=${3:-''} 00419 00420 match "${result}" "${pattern}" 00421 [ $? -eq 0 ] 00422 ok $? "${name}" 00423 local r=$? 00424 [ ${r} -ne 0 ] && diag " '${result}' doesn't match '${pattern}'" 00425 return ${r} 00426 } 00427 00428 # ---------------------------------------------------------------------------- 00429 ## @brief Test whether a given result does not match an expected pattern. 00430 # 00431 # @param [in] result Actual result. 00432 # @param [in] pattern Expected pattern. 00433 # @param [in] name Optional name of test. 00434 # 00435 # @returns Whether the result did not match the pattern. 00436 # 00437 # @retval 0 Otherwise. 00438 # @retval 1 On match. 00439 function unlike 00440 { 00441 local result=$1 00442 local pattern=$2 00443 local name=${3:-''} 00444 00445 match "${result}" "${pattern}" 00446 [ $? -ne 0 ] 00447 ok $? "${name}" 00448 local r=$? 00449 [ ${r} -ne 0 ] && diag " '${result}' matches '${pattern}'" 00450 return ${r} 00451 } 00452 00453 # ============================================================================ 00454 # skip 00455 # ============================================================================ 00456 00457 # ---------------------------------------------------------------------------- 00458 ## @brief Skip tests under a certain condition. 00459 # 00460 # @param [in] condition The condition for skipping the tests. 00461 # If 0, the tests are skipped, otherwise not. 00462 # @param [in] reason An explanation for why skipping the tests. 00463 # @param [in] n The number of tests which will be skipped. 00464 # 00465 # @returns Whether the tests were skipped. 00466 # 00467 # @retval 0 If tests are to be skipped. 00468 # @retval 1 Otherwise. 00469 function skip 00470 { 00471 local condition=${1:?} 00472 local reason=${2:-''} 00473 local n=${3:-1} 00474 00475 if [ ${condition} -eq 0 ]; then 00476 local i= 00477 for (( i=0 ; i<$n ; i++ )); do 00478 (( _shtap_executed_tests++ )) 00479 echo "ok ${_shtap_executed_tests} # skip: ${reason}" 00480 done 00481 return 0 00482 else 00483 return 1 00484 fi 00485 } 00486 00487 # ============================================================================ 00488 # diagnostics 00489 # ============================================================================ 00490 00491 # ---------------------------------------------------------------------------- 00492 ## @brief Print diagnostic message. 00493 # 00494 # @param [in] msg Diagnostic message. 00495 # 00496 # @returns Always 1. 00497 function diag 00498 { 00499 local msg=${1:?} 00500 00501 if [ -n "${msg}" ]; then 00502 echo "# ${msg}" 00503 fi 00504 00505 return 1 00506 } 00507 00508 # ============================================================================ 00509 # termination 00510 # ============================================================================ 00511 00512 # ---------------------------------------------------------------------------- 00513 ## @brief Bail out. 00514 # 00515 # @param [in] reason Reason for bailing out. 00516 # 00517 # @returns Nothing. Instead, exits the process with error code 255. 00518 function SHTAP_BAIL_OUT 00519 { 00520 local reason=${1:-''} 00521 00522 echo "Bail out! ${reason}" >&2 00523 _shtap_exit 255 00524 } 00525 00526 # ---------------------------------------------------------------------------- 00527 ## @brief Abort test execution. 00528 # 00529 # @param [in] reason Reason for aborting the test execution. 00530 # 00531 # @returns Nothing. Instead, exits the process with error code 255. 00532 function _shtap_die 00533 { 00534 local reason=${1:-'<unspecified error>'} 00535 00536 echo "${reason}" >&2 00537 _shtap_test_died=1 00538 _shtap_exit 255 00539 } 00540 00541 # ---------------------------------------------------------------------------- 00542 ## @brief Cleaning up after execution of tests and see if plan was fulfilled. 00543 function _shtap_cleanup 00544 { 00545 local rc=0 # return code 00546 00547 if [ ${_shtap_plan_set} -eq 0 ]; then 00548 diag "Looks like your test died before it could output anything." 00549 return ${rc} 00550 fi 00551 00552 if [ ${_shtap_test_died} -ne 0 ]; then 00553 diag "Looks like your test died just after ${_shtap_executed_tests}." 00554 return ${rc} 00555 fi 00556 00557 if [ ${_shtap_skip_all} -eq 0 -a ${_shtap_no_plan} -ne 0 ]; then 00558 _shtap_print_plan ${_shtap_executed_tests} 00559 fi 00560 00561 local s= 00562 if [ ${_shtap_no_plan} -eq 0 -a ${_shtap_expected_tests} -lt ${_shtap_executed_tests} ]; then 00563 s='' 00564 [ ${_shtap_expected_tests} -gt 1 ] && s='s' 00565 local extra=$(( _shtap_executed_tests - _shtap_expected_tests )) 00566 diag "Looks like you planned ${_shtap_expected_tests} test${s} but ran ${extra} extra." 00567 rc=-1 ; 00568 fi 00569 00570 if [ ${_shtap_no_plan} -eq 0 -a ${_shtap_expected_tests} -gt ${_shtap_executed_tests} ]; then 00571 s='' 00572 [ ${_shtap_expected_tests} -gt 1 ] && s='s' 00573 diag "Looks like you planned ${_shtap_expected_tests} test${s} but only ran ${_shtap_executed_tests}." 00574 fi 00575 00576 if [ ${_shtap_failed_tests} -gt 0 ]; then 00577 s='' 00578 [ ${_shtap_failed_tests} -gt 1 ] && s='s' 00579 diag "Looks like you failed ${_shtap_failed_tests} test${s} of ${_shtap_executed_tests}." 00580 fi 00581 00582 return ${rc} 00583 } 00584 00585 # ---------------------------------------------------------------------------- 00586 ## @brief Calculate exit status indicating number of failed or extra tests. 00587 function _shtap_exit_status 00588 { 00589 if [ ${_shtap_no_plan} -ne 0 -o ${_shtap_plan_set} -eq 0 ]; then 00590 return ${_shtap_failed_tests} 00591 fi 00592 00593 if [ ${_shtap_expected_tests} -lt ${_shtap_executed_tests} ]; then 00594 return $(( _shtap_executed_tests - _shtap_expected_tests )) 00595 fi 00596 00597 return $(( _shtap_failed_tests + ( _shtap_expected_tests - _shtap_executed_tests ))) 00598 } 00599 00600 # ---------------------------------------------------------------------------- 00601 ## @brief Terminate execution of tests. 00602 function _shtap_exit 00603 { 00604 local rc=${1:-''} 00605 if [ -z "${rc}" ]; then 00606 _shtap_exit_status 00607 rc=$? 00608 fi 00609 00610 _shtap_cleanup 00611 local alt_rc=$? 00612 [ ${alt_rc} -ne 0 ] && rc=${alt_rc} 00613 trap - EXIT 00614 exit ${rc} 00615 }