From 3eb019377b9d33742789869be5e9871cc7059c82 Mon Sep 17 00:00:00 2001 From: AJIOB Date: Tue, 12 Dec 2023 13:28:54 +0300 Subject: [PATCH] * Implement time calculation * Add timestamp tag to the testcase * Time tag is not added if calculation generates an error * Add new functionality tests --- shunit2 | 153 +++++++++++++++++++++++- shunit2_tools_test.sh | 113 ++++++++++++++++++ shunit2_xml_test.sh | 29 +++++ shunit2_xml_time_test.sh | 252 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 545 insertions(+), 2 deletions(-) create mode 100755 shunit2_tools_test.sh create mode 100755 shunit2_xml_time_test.sh diff --git a/shunit2 b/shunit2 index 1a54ea6..07148f4 100755 --- a/shunit2 +++ b/shunit2 @@ -43,6 +43,129 @@ if ${__SHUNIT_BUILTIN} [ "`echo -e test`" = '-e test' ]; then __SHUNIT_CMD_ECHO_ESC='echo' fi +# Determine if `date` supports nanosecond resolution. +__SHUNIT_CMD_DATE_SECONDS='date +%s.%N' +if ${__SHUNIT_BUILTIN} [ "`date +%N`" = '%N' ]; then + __SHUNIT_CMD_DATE_SECONDS='date +%s' +fi + +# Determine `bc` command. +__SHUNIT_CMD_BC='bc' +if ! (${__SHUNIT_CMD_BC} --help >/dev/null 2>&1); then + __SHUNIT_CMD_BC='busybox bc' +fi +if ! (${__SHUNIT_CMD_BC} --help >/dev/null 2>&1); then + __SHUNIT_CMD_BC='' +fi + +# Determine `dc` command. +__SHUNIT_CMD_DC='dc' +if ! (${__SHUNIT_CMD_DC} --help >/dev/null 2>&1); then + __SHUNIT_CMD_DC='busybox dc' +fi +if ! (${__SHUNIT_CMD_DC} --help >/dev/null 2>&1); then + __SHUNIT_CMD_DC='' +fi + +# Format float numbers to the single style from different tools. +# Args: +# num: string: float number to format +# Returns: +# string: formatted number. Empty string if error occurs. +_shunit_float_format() { + # Double-dot number is an error. + # No need to format if the number is an integer. + case "${1}" in + *.*.*) + return + ;; + *.*) + ;; + *) + echo "${1}" + return + ;; + esac + + _shunit_format_result_="$1" + + # Add leading zero if needed. + _shunit_format_result_="$(echo "${_shunit_format_result_}" \ + |command sed 's/^\./0./g')" + + # Remove trailing zeros. + _shunit_format_result_="$(echo "${_shunit_format_result_}" \ + |command sed 's/0\+$//g')" + + # Remove trailing dot. + _shunit_format_result_="$(echo "${_shunit_format_result_}" \ + |command sed 's/\.$//g')" + + # Print the result. + echo "${_shunit_format_result_}" + unset _shunit_format_result_ +} + +# Calculate numbers using bc. +# Args: +# left: string: left operand (may be float point) +# operation: string: operation (+ - * /) +# right: string: right operand (may be float point) +# Returns: +# string: result +_shunit_calc_bc() { + _shunit_output_="$(echo "$@" \ + |command ${__SHUNIT_CMD_BC:?})" + shunit_return=$? + if ${__SHUNIT_BUILTIN} [ ${shunit_return} -eq ${SHUNIT_TRUE} ]; then + _shunit_float_format "${_shunit_output_}" + shunit_return=$? + fi + + unset _shunit_output_ + return ${shunit_return} +} + +# Calculate numbers using dc. +# Args: +# left: string: left operand (may be float point) +# operation: string: operation (+ - * /) +# right: string: right operand (may be float point) +# Returns: +# string: result +_shunit_calc_dc() { + _shunit_output_="$(echo "$1" "$3" "$2" "p" \ + |command ${__SHUNIT_CMD_DC:?})" + shunit_return=$? + if ${__SHUNIT_BUILTIN} [ ${shunit_return} -eq ${SHUNIT_TRUE} ]; then + _shunit_float_format "${_shunit_output_}" + shunit_return=$? + fi + + unset _shunit_output_ + return ${shunit_return} +} + +# Calculate numbers using expr. +# Args: +# left: string: left integer number operand +# operation: string: operation (+ - * /) +# right: string: right integer number operand +# Returns: +# string: result. Empty string if error occurs. +_shunit_calc_expr() { + expr "$@" 2>/dev/null || ${__SHUNIT_BUILTIN} true +} + +# Determine what command to use for calculating numbers. +__SHUNIT_CMD_CALC='_shunit_calc_bc' +if ! ("${__SHUNIT_CMD_CALC}" 1 + 2 >/dev/null 2>&1); then + __SHUNIT_CMD_CALC=_shunit_calc_dc +fi +if ! ("${__SHUNIT_CMD_CALC}" 1 + 2 >/dev/null 2>&1); then + __SHUNIT_CMD_CALC=_shunit_calc_expr +fi + # Commands a user can override if needed. __SHUNIT_CMD_TPUT='tput' SHUNIT_CMD_TPUT=${SHUNIT_CMD_TPUT:-${__SHUNIT_CMD_TPUT}} @@ -89,6 +212,12 @@ __shunit_junitXmlOutputFile='' # File to use for JUnit XML output in addition t __shunit_junitXmlTestCases='' # Test cases info in the JUnit XML format for output __shunit_junitXmlCurrentTestCaseErrors='' # Current test case error info in the JUnit XML format for output +# Time variables +__shunit_startSuiteTime='' # When the suite execution was started +__shunit_endSuiteTime='' # When the suite execution ended +__shunit_startCaseTime='' # When the case execution was started +__shunit_endCaseTime='' # When the case execution ended + # ANSI colors (populated by _shunit_configureColor()). __shunit_ansi_none='' __shunit_ansi_red='' @@ -1032,8 +1161,10 @@ _shunit_cleanup() { __shunit_clean=${SHUNIT_TRUE} tearDown || _shunit_warn 'tearDown() returned non-zero return code.' + __shunit_endCaseTime=`${__SHUNIT_CMD_DATE_SECONDS}` oneTimeTearDown || \ _shunit_warn 'oneTimeTearDown() returned non-zero return code.' + __shunit_endSuiteTime=`${__SHUNIT_CMD_DATE_SECONDS}` command rm -fr "${__shunit_tmpDir}" fi @@ -1114,6 +1245,8 @@ _shunit_execSuite() { # Disable skipping. endSkipping + __shunit_startCaseTime=`${__SHUNIT_CMD_DATE_SECONDS}` + # Execute the per-test setUp() function. if ! setUp; then _shunit_fatal "setUp() returned non-zero return code." @@ -1131,12 +1264,16 @@ _shunit_execSuite() { if ! tearDown; then _shunit_fatal "tearDown() returned non-zero return code." fi + __shunit_endCaseTime=`${__SHUNIT_CMD_DATE_SECONDS}` + + _shunit_test_execution_time_=`"${__SHUNIT_CMD_CALC}" "${__shunit_endCaseTime}" - "${__shunit_startCaseTime}"` # Store current test case info in JUnit XML. __shunit_junitXmlTestCases="${__shunit_junitXmlTestCases} ${__shunit_junitXmlCurrentTestCaseErrors} " @@ -1149,7 +1286,7 @@ _shunit_execSuite() { fi done - unset _shunit_test_ + unset _shunit_test_ _shunit_test_execution_time_ } # Generates the user friendly report with appropriate OK/FAILED message. @@ -1179,11 +1316,19 @@ _shunit_generateReport() { fi if ${__SHUNIT_BUILTIN} [ -n "${__shunit_junitXmlOutputFile}" ]; then + # Calculate total execution time in seconds. + _shunit_suite_execution_time_=`"${__SHUNIT_CMD_CALC}" "${__shunit_endSuiteTime}" - "${__shunit_startSuiteTime}"` + + # Generate a ISO-8601 compliant date. + _shunit_suite_start_time_preformatted_=`date -u '+%Y-%m-%dT%H:%M:%S%z' -d "@${__shunit_startSuiteTime}"` + echo " ${__shunit_junitXmlTestCases} " > "${__shunit_junitXmlOutputFile}" @@ -1209,7 +1354,7 @@ _shunit_generateReport() { ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_}" __shunit_reportGenerated=${SHUNIT_TRUE} - unset _shunit_msg_ _shunit_ok_ + unset _shunit_msg_ _shunit_ok_ _shunit_suite_execution_time_ _shunit_suite_start_time_preformatted_ } # Test for whether a function should be skipped. @@ -1390,6 +1535,8 @@ fi # Configure default output coloring behavior. _shunit_configureColor "${SHUNIT_COLOR}" +__shunit_startSuiteTime=`${__SHUNIT_CMD_DATE_SECONDS}` + # Execute the oneTimeSetUp function (if it exists). if ! oneTimeSetUp; then _shunit_fatal "oneTimeSetUp() returned non-zero return code." @@ -1455,6 +1602,8 @@ if ! oneTimeTearDown; then _shunit_fatal "oneTimeTearDown() returned non-zero return code." fi +__shunit_endSuiteTime=`${__SHUNIT_CMD_DATE_SECONDS}` + # Generate a report summary. _shunit_generateReport diff --git a/shunit2_tools_test.sh b/shunit2_tools_test.sh new file mode 100755 index 0000000..5051de4 --- /dev/null +++ b/shunit2_tools_test.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# vim:et:ft=sh:sts=2:sw=2 +# +# shunit2 unit test for tools testing. +# +# Copyright 2023 AxxonSoft. All Rights Reserved. +# Released under the Apache 2.0 license. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# https://github.com/kward/shunit2 +# +# Disable source following. +# shellcheck disable=SC1090,SC1091 + +# Load test helpers. +. ./shunit2_test_helpers + +# Run integer calculation checks. +# Arguments: +# funcName: string: name of function to call as a calculator. +commonCalcInteger() { + _common_function="$1" + + assertEquals "3" "$("${_common_function}" 1 + 2)" + assertEquals "42" "$("${_common_function}" 0 + 42)" + assertEquals "-42" "$("${_common_function}" 0 - 42)" + assertEquals "78" "$("${_common_function}" 123 - 45)" + assertEquals "0" "$("${_common_function}" 1 - 1)" +} + +# Run float calculation checks. +# Arguments: +# funcName: string: name of function to call as a calculator. +commonCalcFloat() { + _common_function="$1" + + assertEquals "3" "$("${_common_function}" 1.0 + 2.0)" + assertEquals "42" "$("${_common_function}" 0.000 + 42.0)" + assertEquals "-42" "$("${_common_function}" 0 - 42.0)" + assertEquals "78" "$("${_common_function}" 123 - 45.00)" + assertEquals "0" "$("${_common_function}" 1.0 - 1.0)" + assertEquals "0" "$("${_common_function}" 1.0 - 1.00)" + + assertEquals "4.6" "$("${_common_function}" 1.2 + 3.4)" + assertEquals "5.1" "$("${_common_function}" 1.2 + 3.9)" + assertEquals "-2.9005" "$("${_common_function}" 1 - 3.9005)" + assertEquals "7.905" "$("${_common_function}" 11.005 - 3.1)" + assertEquals "0.01" "$("${_common_function}" 0.085 - 0.075)" +} + +testFloatFormat() { + # Bad values. + assertEquals '' "$(_shunit_float_format ..)" + assertEquals '' "$(_shunit_float_format 0.1.2)" + assertEquals '' "$(_shunit_float_format 0...)" + assertEquals '' "$(_shunit_float_format 123.123.123)" + assertEquals '' "$(_shunit_float_format 123.123.123.)" + + # Good values (unusual cases). + assertEquals '0' "$(_shunit_float_format .)" + + # Good values (integer). + assertEquals '1' "$(_shunit_float_format 1)" + assertEquals '10' "$(_shunit_float_format 10)" + assertEquals '2300' "$(_shunit_float_format 2300)" + + # Good values (float). + assertEquals '1' "$(_shunit_float_format 1.)" + assertEquals '10' "$(_shunit_float_format 10.)" + assertEquals '2300' "$(_shunit_float_format 2300.)" + assertEquals '1' "$(_shunit_float_format 1.0)" + assertEquals '10' "$(_shunit_float_format 10.0)" + assertEquals '2300' "$(_shunit_float_format 2300.000)" + assertEquals '0' "$(_shunit_float_format .000)" + assertEquals '1.2' "$(_shunit_float_format 1.2)" + assertEquals '4.3' "$(_shunit_float_format 4.30)" + assertEquals '0.3' "$(_shunit_float_format .30)" + assertEquals '0.7' "$(_shunit_float_format .7)" + assertEquals '1.08' "$(_shunit_float_format 1.080)" +} + +testCalcDc() { + if [ -z "${__SHUNIT_CMD_DC}" ]; then + # shellcheck disable=SC2016 + startSkipping '`dc` not found' + fi + + commonCalcInteger "_shunit_calc_dc" + commonCalcFloat "_shunit_calc_dc" +} + +testCalcBc() { + if [ -z "${__SHUNIT_CMD_BC}" ]; then + # shellcheck disable=SC2016 + startSkipping '`bc` not found' + fi + + commonCalcInteger "_shunit_calc_bc" + commonCalcFloat "_shunit_calc_bc" +} + +testCalcExpr() { + commonCalcInteger "_shunit_calc_expr" +} + +oneTimeSetUp() { + th_oneTimeSetUp +} + +# Load and run shunit2. +# shellcheck disable=SC2034 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. "${TH_SHUNIT}" diff --git a/shunit2_xml_test.sh b/shunit2_xml_test.sh index 4866dfc..f841fab 100755 --- a/shunit2_xml_test.sh +++ b/shunit2_xml_test.sh @@ -46,6 +46,12 @@ commonRunAndCheck() { th_showOutput fi + # Patch time & timestamp attribute to the magic number constant. + sed -i \ + -e 's/time="[0-9]*\(.[0-9]*\)\?"/time="42.25"/g' \ + -e 's/timestamp="[-0-9+T:]*"/timestamp="1983-10-27T03:36:45+0000"/g' \ + "${currentXmlF}" + if ! diff "${idealXmlF}" "${currentXmlF}" >/dev/null; then fail 'XML output is not equal' echo '>>> Ideal' >&2 @@ -106,11 +112,14 @@ EOF failures="0" name="unittest" tests="1" + timestamp="1983-10-27T03:36:45+0000" + time="42.25" assertions="1" > @@ -141,23 +150,28 @@ EOF failures="0" name="unittest" tests="3" + timestamp="1983-10-27T03:36:45+0000" + time="42.25" assertions="3" > @@ -188,17 +202,21 @@ EOF failures="0" name="unittest" tests="2" + timestamp="1983-10-27T03:36:45+0000" + time="42.25" assertions="5" > @@ -232,11 +250,14 @@ EOF failures="2" name="unittest" tests="3" + timestamp="1983-10-27T03:36:45+0000" + time="42.25" assertions="6" > @@ -317,11 +343,14 @@ EOF failures="0" name="Custom name with spaces & some special chars!" tests="1" + timestamp="1983-10-27T03:36:45+0000" + time="42.25" assertions="1" > diff --git a/shunit2_xml_time_test.sh b/shunit2_xml_time_test.sh new file mode 100755 index 0000000..fb40714 --- /dev/null +++ b/shunit2_xml_time_test.sh @@ -0,0 +1,252 @@ +#!/bin/sh +# vim:et:ft=sh:sts=2:sw=2 +# +# shunit2 unit test for running subset(s) of tests based upon junit XML time-specific fields. +# +# Copyright 2023 AxxonSoft. All Rights Reserved. +# Released under the Apache 2.0 license. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# https://github.com/kward/shunit2 +# +# Disable source following. +# shellcheck disable=SC1090,SC1091 + +# These variables will be overridden by the test helpers. +stdoutF="" +stderrF="" + +# Load test helpers. +. ./shunit2_test_helpers + +# Run test and check test finished correctly. +commonRunAndCheck() { + _common_test_should_succeed=true + _common_test_succeed=true + + _common_testStartSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -u)" + + ( exec "${SHELL:-sh}" "${unittestF}" -- "--output-junit-xml=${currentXmlF}" >"${stdoutF}" 2>"${stderrF}" ) || { + _common_test_succeed=false + } + + _common_testEndSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -u)" + _common_testDuration="$(${__SHUNIT_CMD_CALC} "${_common_testEndSeconds}" - "${_common_testStartSeconds}")" + + assertEquals "Test exit status" "${_common_test_should_succeed}" "${_common_test_succeed}" + + if ! grep '^Ran [0-9]* test' "${stdoutF}" >/dev/null; then + fail 'test count message was not generated' + th_showOutput + fi +} + +commonCompareTimes() { + _common_testDurationPatched="${_common_testDuration}" + + # Busybox'es ash (date with 1 second precision) test with GNU sh (date with 1 nanosecond precision) conflicts with time precision. + # That is why we need to add 1 second to the test duration in this case. + if [ "$(date '+%N')" = '%N' ] && [ "$("${SHELL:-sh}" -c "date '+%N'")" != '%N' ]; then + _common_testDurationPatched="$(${__SHUNIT_CMD_CALC} "${_common_testDurationPatched}" + 1)" + fi + + _common_executionTimeChecker="$(awk -v externTestDuration="${_common_testDurationPatched}" ' + BEGIN { + sumStamps = 0 + isTotalGiven = 0 + } + /^(.*[[:blank:]])?time="/ { + # Extract the time value. + sub(/.*time="/, "") + sub(/"$/, "") + sub(/" .*/, "") + + if (isTotalGiven == 1) { + sumStamps += $0 + } else { + isTotalGiven = 1 + total = $0 + } + } + END { + error = 0 + if (isTotalGiven == 0) { + print "No time=\"XXX\" given" + error = 1 + } + if (externTestDuration <= 0) { + print "0 >= externTestDuration =", externTestDuration + error = 1 + } + if (total <= 0) { + print "0 >= total =", total + error = 1 + } + if (sumStamps <= 0) { + print "0 >= sumStamps =", sumStamps + error = 1 + } + if (sumStamps > total) { + print "Sum is larger than total. Sum:", sumStamps, "Total:", total + error = 1 + } + if (externTestDuration < total) { + print "externTestDuration is smaller than total. externTestDuration:", externTestDuration, "Total:", total + error = 1 + } + + if (error == 0) { + print "OK" + } else { + print "ERROR" + } + } +' "${currentXmlF}")" +} + +testTimeStamp() { + sed 's/^#//' >"${unittestF}" < /dev/null 2>&1; then + # BusyBox date tool does not support the ISO 8601 input, but support the input format. + _test_xmlStartSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -D "${_test_timestampFormat}" -d "${_test_timestamp}" -u)" + else + _test_xmlStartSeconds="$(${__SHUNIT_CMD_DATE_SECONDS} -d "${_test_timestamp}" -u)" + fi + + # XML timestamp has no sub-second precision. + _test_startComparison="$(${__SHUNIT_CMD_CALC} "${_test_xmlStartSeconds}" - "${_common_testStartSeconds%.*}")" + _test_endComparison="$(${__SHUNIT_CMD_CALC} "${_common_testEndSeconds}" - "${_test_xmlStartSeconds}")" + + _test_startComparison="$(${__SHUNIT_CMD_CALC} "${_test_startComparison}" ">=" "0")" + _test_endComparison="$(${__SHUNIT_CMD_CALC} "${_test_endComparison}" ">=" "0")" + + # shellcheck disable=SC2016 + assertTrue 'XML timestamp is so early' '[ "${_test_startComparison}" -gt 0 ]' + # shellcheck disable=SC2016 + assertTrue 'XML timestamp is so late' '[ "${_test_endComparison}" -gt 0 ]' +} + +testSingleTimePeriod() { + sed 's/^#//' >"${unittestF}" <&2 + echo "<<"${unittestF}" <&2 + echo "<<"${unittestF}" <&2 + echo "<<= 5)}"; then + fail "Test duration should be at least 5 seconds" + fi + + if [ "$(grep -c 'time="2\(\.[0-9]\+\)\?"' "${currentXmlF}")" -ne 1 ]; then + fail "Must have a test with 2 seconds duration" + fi + + if [ "$(grep -c 'time="3\(\.[0-9]\+\)\?"' "${currentXmlF}")" -ne 1 ]; then + fail "Must have a test with 3 seconds duration" + fi +} + +oneTimeSetUp() { + th_oneTimeSetUp + unittestF="${SHUNIT_TMPDIR}/unittest" + currentXmlF="${SHUNIT_TMPDIR}/current.xml" +} + +# Load and run shunit2. +# shellcheck disable=SC2034 +[ -n "${ZSH_VERSION:-}" ] && SHUNIT_PARENT=$0 +. "${TH_SHUNIT}"