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-sbia'
00118
00119
00120 readonly _SHTAP_DIR=$(cd -P -- "$(dirname -- "${BASH_SOURCE}")" && pwd -P)
00121 source "${_SHTAP_DIR}/./core.sh" # match()
00122
00123 readonly _SHTAP_FILENAME='shtap.sh'
00124
00125 TODO=
00126
00127 _shtap_plan_set=0
00128 _shtap_no_plan=0
00129 _shtap_skip_all=0
00130 _shtap_test_died=0
00131 _shtap_expected_tests=0
00132 _shtap_executed_tests=0
00133 _shtap_failed_tests=0
00134
00135 # used to call _cleanup() on shell exit
00136 trap _shtap_exit EXIT
00137 trap _shtap_exit INT
00138 trap _shtap_exit TERM
00139
00140 # ============================================================================
00141 # plan
00142 # ============================================================================
00143
00144 # ----------------------------------------------------------------------------
00145 ## @brief Choose not to plan number of tests in advance.
00146 function plan_no_plan
00147 {
00148 [ ${_shtap_plan_set} -ne 0 ] && _shtap_die "You tried to plan twice!"
00149
00150 _shtap_plan_set=1
00151 _shtap_no_plan=1
00152
00153 return 0
00154 }
00155
00156 # ----------------------------------------------------------------------------
00157 ## @brief Plan to skip all tests.
00158 function plan_skip_all
00159 {
00160 local reason=${1:-''}
00161
00162 [ ${_shtap_plan_set} -ne 0 ] && _shtap_die "You tried to plan twice!"
00163
00164 _shtap_print_plan 0 "Skip ${reason}"
00165
00166 _shtap_skip_all=1
00167 _shtap_plan_set=1
00168 _shtap_exit 0
00169
00170 return 0
00171 }
00172
00173 # ----------------------------------------------------------------------------
00174 ## @brief Plan a certain number of tests and stick to it.
00175 function plan_tests
00176 {
00177 local tests=${1:?}
00178
00179 [ ${_shtap_plan_set} -ne 0 ] && _shtap_die "You tried to plan twice!"
00180 [ ${tests} -eq 0 ] && _shtap_die "You said to run 0 tests! You've got to run something."
00181
00182 _shtap_print_plan ${tests}
00183 _shtap_expected_tests=${tests}
00184 _shtap_plan_set=1
00185
00186 return ${tests}
00187 }
00188
00189 # ----------------------------------------------------------------------------
00190 ## @brief Print plan.
00191 function _shtap_print_plan
00192 {
00193 local tests=${1:?}
00194 local directive=${2:-''}
00195
00196 echo -n "1..${tests}"
00197 [[ -n "${directive}" ]] && echo -n " # ${directive}"
00198 echo
00199 }
00200
00201 # ============================================================================
00202 # pass / fail
00203 # ============================================================================
00204
00205 # ----------------------------------------------------------------------------
00206 ## @brief Pass in any case and print reason.
00207 function pass
00208 {
00209 local name=$1
00210 ok 0 "${name}"
00211 }
00212
00213 # ----------------------------------------------------------------------------
00214 ## @brief Fail in any case and print reason.
00215 function fail
00216 {
00217 local name=$1
00218 ok 1 "${name}"
00219 }
00220
00221 # ============================================================================
00222 # test
00223 # ============================================================================
00224
00225 # ----------------------------------------------------------------------------
00226 ## @brief Evaluate test expression and fail if it does not evaluate to 0 or
00227 # check return value of function.
00228 #
00229 # This is the workhorse method that actually prints the tests result.
00230 #
00231 # @param [in] expression Test expression or return value.
00232 # @param [in] name Name of test.
00233 function ok
00234 {
00235 local expression=${1:?}
00236 local name=${2:-''}
00237
00238 [ ${_shtap_plan_set} -eq 0 ] && _shtap_die "You tried to run a test without a plan! Gotta have a plan."
00239
00240 (( _shtap_executed_tests++ ))
00241
00242 if [ -n "${name}" ]; then
00243 if match "${name}" "^[0-9]+$"; then
00244 diag " You named your test '${name}'. You shouldn't use numbers for your test names."
00245 diag " Very confusing."
00246 fi
00247 fi
00248
00249 local match=`expr "${expression}" : '\([0-9]*\)'`
00250 local result=0
00251 if [ -z "${expression}" ]; then
00252 result=1
00253 elif [ -n "${match}" -a "${expression}" = "${match}" ]; then
00254 [ ${expression} -ne 0 ] && result=1
00255 else
00256 ( eval ${expression} ) >/dev/null 2>&1
00257 [ $? -ne 0 ] && result=1
00258 fi
00259
00260 if [ ${result} -ne 0 ]; then
00261 echo -n "not "
00262 (( _shtap_failed_tests++ ))
00263 fi
00264 echo -n "ok ${_shtap_executed_tests}"
00265
00266 if [ -n "${name}" ]; then
00267 local ename=${name//\#/\\#}
00268 echo -n " - ${ename}"
00269 fi
00270
00271 if [ -n "${TODO}" ]; then
00272 echo -n " # TODO ${TODO}" ;
00273 if [ ${result} -ne 0 ]; then
00274 (( _shtap_failed_tests-- ))
00275 fi
00276 fi
00277
00278 echo
00279 if [ ${result} -ne 0 ]; then
00280 local file='_SHTAP_FILENAME'
00281 local func=
00282 local line=
00283
00284 local i=0
00285 local bt=$(caller ${i})
00286 while match "${bt}" "${_SHTAP_FILENAME}$"; do
00287 (( i++ ))
00288 bt=$(caller ${i})
00289 done
00290 local backtrace=
00291 eval $(caller ${i} | (read line func file; echo "backtrace=\"${file}:${func}() at line ${line}.\""))
00292
00293 local t=
00294 [ -n "${TODO}" ] && t="(TODO) "
00295
00296 if [ -n "${name}" ]; then
00297 diag " Failed ${t}test '${name}'"
00298 diag " in ${backtrace}"
00299 else
00300 diag " Failed ${t}test in ${backtrace}"
00301 fi
00302 fi
00303
00304 return ${result}
00305 }
00306
00307 # ----------------------------------------------------------------------------
00308 ## @brief Execute command and check return value.
00309 #
00310 # @param [in] command Command to run.
00311 function okx
00312 {
00313 local command="$@"
00314
00315 local line=
00316 diag "Output of '${command}':"
00317 ${command} | while read line; do
00318 diag "${line}"
00319 done
00320 ok ${PIPESTATUS[0]} "${command}"
00321 }
00322
00323 # ----------------------------------------------------------------------------
00324 ## @brief Compare actual and expected result.
00325 #
00326 # @param [in] result Actual result.
00327 # @param [in] expected Expected result.
00328 function _shtap_equals
00329 {
00330 local result=$1
00331 local expected=$2
00332
00333 if [[ "${result}" == "${expected}" ]] ; then
00334 return 0
00335 else
00336 return 1
00337 fi
00338 }
00339
00340 # ----------------------------------------------------------------------------
00341 ## @brief Diagnostic message for is().
00342 #
00343 # @param [in] result Actual result.
00344 # @param [in] expected Expected result.
00345 function _shtap_is_diag
00346 {
00347 local result=$1
00348 local expected=$2
00349
00350 diag " got: '${result}'"
00351 diag " expected: '${expected}'"
00352 }
00353
00354 # ----------------------------------------------------------------------------
00355 ## @brief Test whether a given result is equal to the expected result.
00356 #
00357 # @param [in] result Actual result.
00358 # @param [in] expected Expected result.
00359 # @param [in] name Optional name of test.
00360 #
00361 # @returns Whether the results are equal.
00362 #
00363 # @retval 0 On equality.
00364 # @retval 1 Otherwise.
00365 function is
00366 {
00367 local result=$1
00368 local expected=$2
00369 local name=${3:-''}
00370
00371 _shtap_equals "${result}" "${expected}"
00372 [ $? -eq 0 ]
00373 ok $? "${name}"
00374 local r=$?
00375 [ ${r} -ne 0 ] && _shtap_is_diag "${result}" "${expected}"
00376 return ${r}
00377 }
00378
00379 # ----------------------------------------------------------------------------
00380 ## @brief Test whether a given result is not equal the expected result.
00381 #
00382 # @param [in] result Actual result.
00383 # @param [in] expected Expected result.
00384 # @param [in] name Optional name of test.
00385 #
00386 # @returns Whether the results were not equal.
00387 #
00388 # @retval 0 Otherwise.
00389 # @retval 1 On equality.
00390 function isnt
00391 {
00392 local result=$1
00393 local expected=$2
00394 local name=${3:-''}
00395
00396 _shtap_equals "${result}" "${expected}"
00397 (( $? != 0 ))
00398 ok $? "${name}"
00399 local r=$?
00400 [ ${r} -ne 0 ] && _shtap_is_diag "${result}" "${expected}"
00401 return ${r}
00402 }
00403
00404 # ----------------------------------------------------------------------------
00405 ## @brief Test whether a given result matches an expected pattern.
00406 #
00407 # @param [in] result Actual result.
00408 # @param [in] pattern Expected pattern.
00409 # @param [in] name Optional name of test.
00410 #
00411 # @returns Whether the result matched the pattern.
00412 #
00413 # @retval 0 On match.
00414 # @retval 1 Otherwise.
00415 function like
00416 {
00417 local result=$1
00418 local pattern=$2
00419 local name=${3:-''}
00420
00421 match "${result}" "${pattern}"
00422 [ $? -eq 0 ]
00423 ok $? "${name}"
00424 local r=$?
00425 [ ${r} -ne 0 ] && diag " '${result}' doesn't match '${pattern}'"
00426 return ${r}
00427 }
00428
00429 # ----------------------------------------------------------------------------
00430 ## @brief Test whether a given result does not match an expected pattern.
00431 #
00432 # @param [in] result Actual result.
00433 # @param [in] pattern Expected pattern.
00434 # @param [in] name Optional name of test.
00435 #
00436 # @returns Whether the result did not match the pattern.
00437 #
00438 # @retval 0 Otherwise.
00439 # @retval 1 On match.
00440 function unlike
00441 {
00442 local result=$1
00443 local pattern=$2
00444 local name=${3:-''}
00445
00446 match "${result}" "${pattern}"
00447 [ $? -ne 0 ]
00448 ok $? "${name}"
00449 local r=$?
00450 [ ${r} -ne 0 ] && diag " '${result}' matches '${pattern}'"
00451 return ${r}
00452 }
00453
00454 # ============================================================================
00455 # skip
00456 # ============================================================================
00457
00458 # ----------------------------------------------------------------------------
00459 ## @brief Skip tests under a certain condition.
00460 #
00461 # @param [in] condition The condition for skipping the tests.
00462 # If 0, the tests are skipped, otherwise not.
00463 # @param [in] reason An explanation for why skipping the tests.
00464 # @param [in] n The number of tests which will be skipped.
00465 #
00466 # @returns Whether the tests were skipped.
00467 #
00468 # @retval 0 If tests are to be skipped.
00469 # @retval 1 Otherwise.
00470 function skip
00471 {
00472 local condition=${1:?}
00473 local reason=${2:-''}
00474 local n=${3:-1}
00475
00476 if [ ${condition} -eq 0 ]; then
00477 local i=
00478 for (( i=0 ; i<$n ; i++ )); do
00479 (( _shtap_executed_tests++ ))
00480 echo "ok ${_shtap_executed_tests} # skip: ${reason}"
00481 done
00482 return 0
00483 else
00484 return 1
00485 fi
00486 }
00487
00488 # ============================================================================
00489 # diagnostics
00490 # ============================================================================
00491
00492 # ----------------------------------------------------------------------------
00493 ## @brief Print diagnostic message.
00494 #
00495 # @param [in] msg Diagnostic message.
00496 #
00497 # @returns Always 1.
00498 function diag
00499 {
00500 local msg=${1:?}
00501
00502 if [ -n "${msg}" ]; then
00503 echo "# ${msg}"
00504 fi
00505
00506 return 1
00507 }
00508
00509 # ============================================================================
00510 # termination
00511 # ============================================================================
00512
00513 # ----------------------------------------------------------------------------
00514 ## @brief Bail out.
00515 #
00516 # @param [in] reason Reason for bailing out.
00517 #
00518 # @returns Nothing. Instead, exits the process with error code 255.
00519 function SHTAP_BAIL_OUT
00520 {
00521 local reason=${1:-''}
00522
00523 echo "Bail out! ${reason}" >&2
00524 _shtap_exit 255
00525 }
00526
00527 # ----------------------------------------------------------------------------
00528 ## @brief Abort test execution.
00529 #
00530 # @param [in] reason Reason for aborting the test execution.
00531 #
00532 # @returns Nothing. Instead, exits the process with error code 255.
00533 function _shtap_die
00534 {
00535 local reason=${1:-'<unspecified error>'}
00536
00537 echo "${reason}" >&2
00538 _shtap_test_died=1
00539 _shtap_exit 255
00540 }
00541
00542 # ----------------------------------------------------------------------------
00543 ## @brief Cleaning up after execution of tests and see if plan was fulfilled.
00544 function _shtap_cleanup
00545 {
00546 local rc=0 # return code
00547
00548 if [ ${_shtap_plan_set} -eq 0 ]; then
00549 diag "Looks like your test died before it could output anything."
00550 return ${rc}
00551 fi
00552
00553 if [ ${_shtap_test_died} -ne 0 ]; then
00554 diag "Looks like your test died just after ${_shtap_executed_tests}."
00555 return ${rc}
00556 fi
00557
00558 if [ ${_shtap_skip_all} -eq 0 -a ${_shtap_no_plan} -ne 0 ]; then
00559 _shtap_print_plan ${_shtap_executed_tests}
00560 fi
00561
00562 local s=
00563 if [ ${_shtap_no_plan} -eq 0 -a ${_shtap_expected_tests} -lt ${_shtap_executed_tests} ]; then
00564 s=''
00565 [ ${_shtap_expected_tests} -gt 1 ] && s='s'
00566 local extra=$(( _shtap_executed_tests - _shtap_expected_tests ))
00567 diag "Looks like you planned ${_shtap_expected_tests} test${s} but ran ${extra} extra."
00568 rc=-1 ;
00569 fi
00570
00571 if [ ${_shtap_no_plan} -eq 0 -a ${_shtap_expected_tests} -gt ${_shtap_executed_tests} ]; then
00572 s=''
00573 [ ${_shtap_expected_tests} -gt 1 ] && s='s'
00574 diag "Looks like you planned ${_shtap_expected_tests} test${s} but only ran ${_shtap_executed_tests}."
00575 fi
00576
00577 if [ ${_shtap_failed_tests} -gt 0 ]; then
00578 s=''
00579 [ ${_shtap_failed_tests} -gt 1 ] && s='s'
00580 diag "Looks like you failed ${_shtap_failed_tests} test${s} of ${_shtap_executed_tests}."
00581 fi
00582
00583 return ${rc}
00584 }
00585
00586 # ----------------------------------------------------------------------------
00587 ## @brief Calculate exit status indicating number of failed or extra tests.
00588 function _shtap_exit_status
00589 {
00590 if [ ${_shtap_no_plan} -ne 0 -o ${_shtap_plan_set} -eq 0 ]; then
00591 return ${_shtap_failed_tests}
00592 fi
00593
00594 if [ ${_shtap_expected_tests} -lt ${_shtap_executed_tests} ]; then
00595 return $(( _shtap_executed_tests - _shtap_expected_tests ))
00596 fi
00597
00598 return $(( _shtap_failed_tests + ( _shtap_expected_tests - _shtap_executed_tests )))
00599 }
00600
00601 # ----------------------------------------------------------------------------
00602 ## @brief Terminate execution of tests.
00603 function _shtap_exit
00604 {
00605 local rc=${1:-''}
00606 if [ -z "${rc}" ]; then
00607 _shtap_exit_status
00608 rc=$?
00609 fi
00610
00611 _shtap_cleanup
00612 local alt_rc=$?
00613 [ ${alt_rc} -ne 0 ] && rc=${alt_rc}
00614 trap - EXIT
00615 exit ${rc}
00616 }