# $Id: shunit2 71 2007-06-02 18:42:46Z sfsetse $ # vim:syntax=sh:sts=2 # vim:foldmethod=marker:foldmarker=/**,*/ # #/** # # # # shUnit 2.1.0 # Shell Unit Test Framework # # http://shunit2.sourceforge.net/ # # written by Kate Ward <kate.ward@forestent.com> # released under the LGPL # # this module implements a xUnit based unit test framework similar to JUnit # #*/ # shell flags for shunit: # u - treat unset variables as an error when performing parameter expansion __SHUNIT_SHELL_FLAGS='u' # save the current set of shell flags, and then set some for ourselves __shunit_oldShellFlags="$-" for _shunit_shellFlag in `echo "${__SHUNIT_SHELL_FLAGS}" |sed 's/\(.\)/\1 /g'` do set -${_shunit_shellFlag} done # constants __SHUNIT_VERSION='2.1.1pre' SHUNIT_TRUE=0 SHUNIT_FALSE=1 __SHUNIT_ASSERT_MSG_PREFIX='ASSERT:' for _su_const in `set |grep "^__SHUNIT_" |cut -d= -f1`; do readonly ${_su_const} done unset _su_const # variables __shunit_skip=${SHUNIT_FALSE} __shunit_suite='' __shunit_testsPassed=0 __shunit_testsFailed=0 __shunit_testsSkipped=0 __shunit_testsTotal=0 #----------------------------------------------------------------------------- # assert functions # #/** # # # void # # # # # assertEquals # string [message] # string expected # string actual # # # Asserts that expected and # actual are equal to one another. The message is # optional. # # #*/ assertEquals() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _su_message='' if [ $# -eq 3 ]; then _su_message=$1 shift fi _su_expected=$1 _su_actual=$2 if [ "${_su_expected}" = "${_su_actual}" ]; then _shunit_testPassed return ${SHUNIT_TRUE} else _shunit_testFailed "${_su_message}" return ${SHUNIT_FALSE} fi unset _su_message _su_expected _su_actual } #/** # # # void # # # # # assertNull # string [message] # string value # # # Asserts that value is null, # or in shell terms a zero-length string. The message is optional. # # #*/ assertNull() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _su_message='' if [ $# -eq 2 ]; then _su_message=$1 shift fi _su_value=$1 _su_value2=`eval echo "${_su_value}"` if [ -z "${_su_value2}" ]; then _shunit_testPassed return ${SHUNIT_TRUE} else _shunit_testFailed "${_su_message}" return ${SHUNIT_FALSE} fi unset _su_message _su_value _su_value2 } #/** # # # void # # # # # assertNotNull # string [message] # string value # # # Asserts that value is not null, or in shell terms not # a zero-length string. The message is optional. # # #*/ assertNotNull() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _su_message='' if [ $# -eq 2 ]; then _su_message=$1 shift fi _su_value=$1 _su_value2=`eval echo "${_su_value}"` if [ -n "${_su_value2}" ]; then _shunit_testPassed return ${SHUNIT_TRUE} else _shunit_testFailed "${_su_message}" return ${SHUNIT_FALSE} fi unset _su_message _su_value _su_value2 } #/** # # # void # # # # # assertSame # string [message] # string expected # string actual # # # This function is functionally equivalent to # assertEquals. # # #*/ assertSame() { assertEquals "$@" } #/** # # # void # # # # # assertNotSame # string [message] # string expected # string actual # # # Asserts that expected and # actual are not equal to one another. The message is optional. # # #*/ assertNotSame() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _su_message='' if [ $# -eq 3 ]; then _su_message=$1 shift fi _su_expected=$1 _su_actual=$2 if [ "${_su_expected}" != "${_su_actual}" ]; then _shunit_testPassed return ${SHUNIT_TRUE} else _shunit_testFailed "${_su_message}" return ${SHUNIT_FALSE} fi unset _su_message _su_expected _su_actual } #/** # # # void # # # # # assertTrue # string [message] # string condition # # # Asserts that a given shell test condition is true. The message is # optional. # Testing whether something is true or false is easy enough by using # the assertEquals/assertNotSame functions. Shell supports much more # complicated tests though, and a means to support them was needed. As such, # this function tests that conditions are true or false through evaluation # rather than just looking for a true or false. # # The following test will succeed: assertTrue "[ 34 -gt 23 ]" # The folloing test will fail with a message: assertTrue "test failed" "[ -r '/non/existant/file' ]" # # # #*/ assertTrue() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _su_message='' if [ $# -eq 2 ]; then _su_message=$1 shift fi _su_condition=$1 ( eval ${_su_condition} ) >/dev/null 2>&1 if [ $? -eq ${SHUNIT_TRUE} ]; then _shunit_testPassed return ${SHUNIT_TRUE} else _shunit_testFailed "${_su_message}" return ${SHUNIT_FALSE} fi unset _su_message _su_condition } #/** # # # void # # # # # assertFalse # string [message] # string condition # # # Asserts that a given shell test condition is false. The message is # optional. # Testing whether something is true or false is easy enough by using # the assertEquals/assertNotSame functions. Shell supports much more # complicated tests though, and a means to support them was needed. As such, # this function tests that conditions are true or false through evaluation # rather than just looking for a true or false. # # The following test will succeed: assertFalse "[ 'apples' = 'oranges' ]" # The folloing test will fail with a message: assertFalse "test failed" "[ 1 -eq 1 -a 2 -eq 2 ]" # # # #*/ assertFalse() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _su_message='' if [ $# -eq 2 ]; then _su_message=$1 shift fi _su_condition=$1 ( eval ${_su_condition} ) >/dev/null 2>&1 if [ $? -eq ${SHUNIT_FALSE} ]; then _shunit_testPassed return ${SHUNIT_TRUE} else _shunit_testFailed "${_su_message}" return ${SHUNIT_FALSE} fi unset _su_message _su_condition } #----------------------------------------------------------------------------- # failure functions # #/** # # # void # # # # # fail # string [message] # # # Fails the test immediately, with the optional message. # # #*/ fail() { _shunit_shouldSkip && return ${SHUNIT_TRUE} _shunit_testFailed "${@:-}" } #/** # # # void # # # # # failNotEquals # string [message] # string expected # string actual # # # Fails the test if expected and # actual are not # equal to one another. The message is optional. # # #*/ failNotEquals() { assertEquals "$@" } #/** # # # void # # # # # failSame # string [message] # string expected # string actual # # # Fails the test if expected and # actual are equal to one another. The message is # optional. # # #*/ failSame() { assertNotSame "$@" } #/** # # # void # # # # # failNotSame # string [message] # string expected # string actual # # # Fails the test if expected and # actual are equal to one another. The message is # optional. # # #*/ failNotSame() { assertEquals "$@" } #----------------------------------------------------------------------------- # skipping functions # #/** # # # void # # # # # startSkipping # # # # This function forces the remaining assert and fail functions to be # "skipped", i.e. they will have no effect. Each function skipped will be # recorded so that the total of asserts and fails will not be altered. # # #*/ startSkipping() { __shunit_skip=${SHUNIT_TRUE} } #/** # # # void # # # # # endSkipping # # # # This function returns calls to the assert and fail functions to their # default behavior, i.e. they will be called. # # #*/ endSkipping() { __shunit_skip=${SHUNIT_FALSE} } #/** # # # boolean # # # # # isSkipping # # # # This function returns the state of skipping. # # #*/ isSkipping() { return ${__shunit_skip} } #----------------------------------------------------------------------------- # suite functions # #/** # # # void # # # # # suite # # # # This function can be optionally overridden by the user in their test # suite. # If this function exists, it will be called when # shunit2 is sourced. If it does not exist, # shunit2 will search the parent script for all functions # beginning with the word test, and they will be added # dynamically to the test suite. # # #*/ # Note: see _shunit_mktempFunc() for actual implementation # suite() { :; } #/** # # # void # # # # # suite_addTest # string function # # # This function adds a function name to the list of tests scheduled for # execution as part of this test suite. This function should only be called # from within the suite() function. # # #*/ suite_addTest() { _su_func=$1 __shunit_suite="${__shunit_suite:+${__shunit_suite} }${_su_func}" unset _su_func } #/** # # # void # # # # # oneTimeSetUp # # # # This function can be be optionally overridden by the user in their # test suite. # If this function exists, it will be called once before any tests are # run. It is useful to prepare a common environment for all tests. # # #*/ # Note: see _shunit_mktempFunc() for actual implementation # oneTimeSetUp() { :; } #/** # # # void # # # # # oneTimeTearDown # # # # This function can be be optionally overridden by the user in their # test suite. # If this function exists, it will be called once after all tests are # completed. It is useful to clean up the environment after all tests. # # #*/ # Note: see _shunit_mktempFunc() for actual implementation # oneTimeTearDown() { :; } #/** # # # void # # # # # setUp # # # # This function can be be optionally overridden by the user in their # test suite. # If this function exists, it will be called before each test is run. # It is useful to reset the environment before each test. # # #*/ # Note: see _shunit_mktempFunc() for actual implementation # setUp() { :; } #/** # # # void # # # # # tearDown # # # # This function can be be optionally overridden by the user in their # test suite. # If this function exists, it will be called after each test completes. # It is useful to clean up the environment after each test. # # #*/ # Note: see _shunit_mktempFunc() for actual implementation # tearDown() { :; } #------------------------------------------------------------------------------ # internal shUnit functions # _shunit_cleanup() { name=$1 case ${name} in EXIT) signal=0 ;; INT) signal=2 ;; TERM) signal=15 ;; esac # do our work rm -fr "${__shunit_tmpDir}" # exit for all non-EXIT signals if [ ${name} != 'EXIT' ]; then echo "trapped and now handling the ${name} signal" >&2 _shunit_generateReport # disable EXIT trap trap 0 # add 127 to signal and exit signal=`expr ${signal} + 127` exit ${signal} fi } _shunit_execSuite() { echo '#' echo '# Performing tests' echo '#' for _su_func in ${__shunit_suite}; do # disable skipping endSkipping # execute the per-test setup function setUp # execute the test echo "${_su_func}" eval ${_su_func} # execute the per-test tear-down function tearDown done unset _su_func } _shunit_functionExists() { _su__func=$1 type ${_su__func} 2>/dev/null |grep "is a function$" >/dev/null _su__return=$? unset _su__func return ${_su__return} } _shunit_generateReport() { _su__awk='{printf("%4d %3.0f%%", $1, $1*100/$2)}' if [ ${__shunit_testsTotal} -gt 0 ]; then _su__passed=`echo ${__shunit_testsPassed} ${__shunit_testsTotal} |\ awk "${_su__awk}"` _su__failed=`echo ${__shunit_testsFailed} ${__shunit_testsTotal} |\ awk "${_su__awk}"` _su__skipped=`echo ${__shunit_testsSkipped} ${__shunit_testsTotal} |\ awk "${_su__awk}"` _su__total=`echo ${__shunit_testsTotal} 100 |\ awk '{printf("%4d %3d%%", $1, $2)}'` else _su__passed=`echo 0 0 |awk '{printf("%4d %3d%%", $1, $2)}'` _su__failed=${_su__passed} _su__skipped=${_su__passed} _su__total=${_su__passed} fi cat </dev/null ) && return # the standard mktemp didn't work. doing our own. if [ -r '/dev/urandom' ]; then _su__random=`od -vAn -N4 -tx4 &2 exit 1 } echo ${_su__tmpDir} unset _su__date _su__random _su__tmpDir } # this function is here to work around issues in Cygwin _shunit_mktempFunc() { for _su__func in oneTimeSetUp oneTimeTearDown setUp tearDown suite; do _su__file="${__shunit_tmpDir}/${_su__func}" cat <"${_su__file}" #! /bin/sh exit 0 EOF chmod +x "${_su__file}" done unset _su__file } _shunit_shouldSkip() { [ ${__shunit_skip} -eq ${SHUNIT_FALSE} ] && return ${SHUNIT_FALSE} _shunit_testSkipped } _shunit_testPassed() { __shunit_testsPassed=`expr ${__shunit_testsPassed} + 1` __shunit_testsTotal=`expr ${__shunit_testsTotal} + 1` } _shunit_testFailed() { [ $# -eq 1 ] && _su__msg=$1 __shunit_testsFailed=`expr ${__shunit_testsFailed} + 1` __shunit_testsTotal=`expr ${__shunit_testsTotal} + 1` [ -z "${_su__msg:-}" ] && _su__msg='failed' echo "${__SHUNIT_ASSERT_MSG_PREFIX} ${_su__msg}" >&2 unset _su__msg } _shunit_testSkipped() { __shunit_testsSkipped=`expr ${__shunit_testsSkipped} + 1` __shunit_testsTotal=`expr ${__shunit_testsTotal} + 1` } #------------------------------------------------------------------------------ # main # # create a temporary storage location __shunit_tmpDir=`_shunit_mktempDir` # setup traps to clean up after ourselves trap '_shunit_cleanup EXIT' 0 trap '_shunit_cleanup INT' 2 trap '_shunit_cleanup TERM' 15 # create phantom functions to work around issues with Cygwin _shunit_mktempFunc PATH="${__shunit_tmpDir}:${PATH}" # execute the oneTimeSetUp function (if it exists) #_shunit_functionExists oneTimeSetUp && oneTimeSetUp oneTimeSetUp # deprecated: execute the suite function defined in the parent test script suite # if no suite function was defined, dynamically build a list of functions if [ -z "${__shunit_suite}" ]; then funcs=`grep "^[ \t]*test[A-Za-z0-9_]* *()" $0 |sed 's/[^A-Za-z0-9_]//g'` for func in ${funcs}; do suite_addTest ${func} done fi # execute the tests _shunit_execSuite # execute the oneTimeTearDown function (if it exists) oneTimeTearDown # generate report _shunit_generateReport # restore the previous set of shell flags for _shunit_shellFlag in ${__SHUNIT_SHELL_FLAGS}; do echo ${__shunit_oldShellFlags} |grep ${_shunit_shellFlag} >/dev/null \ || set +${_shunit_shellFlag} done unset _shunit_shellFlag #/** # #*/