/* * (c) Copyright, Real-Time Innovations, 2014-2016. * All rights reserved. * * No duplications, whole or partial, manual or electronic, may be made * without express written permission. Any such copies, or * revisions thereof, must display this notice unaltered. * This code contains trade secrets of Real-Time Innovations, Inc. */ #ifndef RTI_TEST_ASSERTIONS_HPP_ #define RTI_TEST_ASSERTIONS_HPP_ #include #include #include #include #include #include #include #include #include "rtitest/test_log.h" #include "rtitest/test_time.h" namespace rti { namespace test { /** * @defgroup RTITestUtilsCPP C++ Utilities * @brief A set of definitions to help writing unit tests for Modern C++ code. */ // --- TestFailure exception: ------------------------------------------------- /** * @class TestFailure * @brief Represents a test failure exception. * * This class is derived from std::logic_error and is used to indicate a failure in a test case. * It provides information about the file, line, and message associated with the failure. */ class TestFailure : public std::logic_error { public: /** @internal * @brief Constructs a TestFailure object with the specified file, line, and message. * @param the_file \b In. The file where the failure occurred. * @param the_line \b In. The line number where the failure occurred. * @param the_msg \b In. The message describing the failure. */ TestFailure( const std::string& the_file, const std::string& the_line, const std::string& the_msg) : std::logic_error(the_file + ":" + the_line + ": " + the_msg), _file(the_file), _line(the_line), _msg(the_msg) { } /** @internal * @brief Constructs a TestFailure object with the specified file, line, message, and nested exception. * @param the_file \b In. The file where the failure occurred. * @param the_line \b In. The line number where the failure occurred. * @param the_msg \b In. The message describing the failure. * @param nested_ex \b In. The nested exception that caused the failure. */ TestFailure( const std::string& the_file, const std::string& the_line, const std::string& the_msg, const std::exception& nested_ex) : std::logic_error( the_file + ":" + the_line + ": " + the_msg + " - caused by:\n " + nested_ex.what()), _file(the_file), _line(the_line), _msg(the_msg) { } /** @internal * @brief Destructor for TestFailure. * * This destructor is declared with a no-throw exception specification, because * the base class std::logic_error has a no-throw destructor. Compilation will fail otherwise. */ ~TestFailure() throw() { } /** @internal * @brief Get the file where the failure occurred. * @return The file path as a const reference to std::string. */ const std::string& file() const { return _file; } /** @internal * @brief Get the line number where the failure occurred. * @return The line number as a const reference to std::string. */ const std::string& line() const { return _line; } /** @internal * @brief Get the message describing the failure. * @return The failure message as a const reference to std::string. */ const std::string& msg() const { return _msg; } private: std::string _file; /**< The file where the failure occurred. */ std::string _line; /**< The line number where the failure occurred. */ std::string _msg; /**< The message describing the failure. */ }; /** * @class TestIncomplete * @brief Exception class for indicating that a test is unsupported. * * This exception is thrown when a test is marked as unsupported. It inherits * from std::logic_error. */ class TestIncomplete : public std::logic_error { public: /** @internal * @brief Constructor for TestIncomplete. * @param retcode \b In. The return code associated with the incomplete test. * @param reason \b In. The reason for the test being incomplete. */ TestIncomplete(int retcode, const std::string& reason) : std::logic_error(reason), retcode_(retcode) { } /** @internal * @brief Destructor for TestIncomplete. * * This destructor is defined with a no-throw exception specification, because * the base class std::logic_error has a no-throw destructor. Compilation will * fail if this destructor is not defined with a no-throw exception specification. */ ~TestIncomplete() throw() { } /** @internal * @brief Get the return code associated with the incomplete test. * @return The return code. */ int retcode() const { return retcode_; } private: int retcode_; /**< The return code associated with the incomplete test. */ }; }} // namespace rti::test /** * @defgroup RTITestAssertionsCPP Assertions * @brief A set of definitions that implements assertions for Modern C++ code. */ // --- Assertion macros: ------------------------------------------------------ /** @internal * @ingroup RTITestAssertionsCPP * @brief Throw an exception * @param expression__ \b In. The expression that raises the exception. */ #define RTI_TEST_THROW(expression__) \ throw rti::test::TestFailure( \ "" __FILE__, \ "" RTI_TEST_TOSTRING(__LINE__), \ "assertion failed: " #expression__) /** * @ingroup RTITestAssertionsCPP * @brief Asserts that the given expression is true. Otherwise, throw a TestFailure exception. * @param expression__ \b In. The expression to be evaluated. */ #define RTI_TEST_ASSERT(expression__) \ if (!(expression__)) { \ RTI_TEST_THROW(expression__); \ } /** * @ingroup RTITestAssertionsCPP * @brief Throw an exception with a given message. * @param msg__ \b In. The message to be included in the exception. */ #define RTI_TEST_FAIL(msg__) \ throw rti::test::TestFailure( \ "" __FILE__, \ "" RTI_TEST_TOSTRING(__LINE__), \ msg__); /** * @ingroup RTITestAssertionsCPP * @brief Throw a TestIncomplete exception with the unsupported return code. * @param reason__ \b In. The reason for the test being incomplete. */ #define RTI_TEST_THROW_UNSUPPORTED(reason__) \ throw rti::test::TestIncomplete(RTI_TEST_RETCODE_UNSUPPORTED, reason__) /** @internal * @ingroup RTITestAssertionsCPP * @brief Throw a TestIncomplete exception with the unimplemented return code. * @param reason__ \b In. The reason for the test being incomplete. */ #define RTI_TEST_THROW_UNIMPLEMENTED(reason__) \ throw rti::test::TestIncomplete(RTI_TEST_RETCODE_UNIMPLEMENTED, reason__) template /** @internal * @ingroup RTITestAssertionsCPP * @brief Prints a failure message saying the assertion `a == b` failed. * @tparam T \b In. The type of the first value. * @tparam U \b In. The type of the second value. * @param a \b In. The first value to compare. * @param b \b In. The second value to compare. */ void print_assert_equals_failure(const T& a, const U& b) { std::ostringstream os; os << "expected: " << a << ", actual: " << b << "\n"; RTITestLog_logParamString( RTI_TEST_LOG_BIT_EXCEPTION, "%s", os.str().c_str()); } template < typename T, typename U, typename std::enable_if< !std::is_floating_point::value && !std::is_floating_point::value, bool>::type = true> /** @internal * @ingroup RTITestUtilsCPP * @brief Checks if two values are equal. * @tparam T \b In. The type of the first value. * @tparam U \b In. The type of the second value. * @param a \b In. The first value to compare. * @param b \b In. The second value to compare. * @return true if `a` is equal to `b`, false otherwise. */ bool check_equals(const T& a, const U& b) { if (a != b) { print_assert_equals_failure(a, b); return false; } return true; } template < typename T, typename U, typename std::enable_if< std::is_floating_point::value || std::is_floating_point::value, bool>::type = true> /** @internal * @ingroup RTITestUtilsCPP * @brief Checks if two floating point values are equal. * @tparam T \b In. The type of the first value. * @tparam U \b In. The type of the second value. * @param a \b In. The first value to compare. * @param b \b In. The second value to compare. * @return true if `a` is equal to `b`, false otherwise. */ bool check_equals(const T& a, const U& b) { long double diff = std::fabs(static_cast(a) - static_cast(b)); bool condition_check = diff > std::numeric_limits::epsilon(); if (condition_check) { print_assert_equals_failure(a, b); return false; } return true; } /** * @ingroup RTITestAssertionsCPP * @brief Assert that two given values are equal. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. * @note This macro requires that the type provide the != and << operators. */ #define RTI_TEST_ASSERT_EQUALS(expected__, actual__) \ if (!check_equals(expected__, actual__)) { \ RTI_TEST_THROW(expected__ == actual__); \ } /** * @ingroup RTITestAssertionsCPP * @brief Assert that two given strings are equal. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. */ #define RTI_TEST_ASSERT_EQUALS_STRING RTI_TEST_ASSERT_EQUALS /** * @ingroup RTITestAssertionsCPP * @brief Assert that two given integers are equal. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. */ #define RTI_TEST_ASSERT_EQUALS_INT(expected__, actual__) \ RTI_TEST_ASSERT_EQUALS((int) (expected__), (int) (actual__)) /** * @ingroup RTITestAssertionsCPP * @brief Assert that two given long long values are equal. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. */ #define RTI_TEST_ASSERT_EQUALS_LONG_LONG(expected__, actual__) \ RTI_TEST_ASSERT_EQUALS((long long) (expected__), (long long) (actual__)) /** * @ingroup RTITestAssertionsCPP * @brief Assert that two given doubles are equal. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. */ #define RTI_TEST_ASSERT_EQUALS_DOUBLE RTI_TEST_ASSERT_EQUALS /** * @ingroup RTITestUtilsCPP * @brief Sleep for a given number of seconds * @param max_seconds__ \b In. The number of seconds to sleep. */ #define RTI_TEST_WAIT(max_seconds__) \ { \ const struct RTITestTime sleepTime__ = { 0, 429496730 }; \ int sleeps__ = 0; \ while (sleeps__ < max_seconds__ * 10) { \ RTITest_sleep(&sleepTime__); \ ++sleeps__; \ } \ } /** * @ingroup RTITestUtilsCPP * @brief Wait for an expression to become true. Otherwise, throw a TestFailure exception. * @param expression__ \b In. The expression to be evaluated. * @param max_seconds__ \b In. The maximum number of seconds to wait for. */ #define RTI_TEST_WAIT_FOR(expression__, max_seconds__) \ { \ /* 100ms */ \ const struct RTITestTime sleepTime__ = { 0, 429496730 }; \ int sleeps__ = 0; \ bool result__ = (expression__); \ while (!result__ && sleeps__ < max_seconds__ * 10) { \ RTITest_sleep(&sleepTime__); \ ++sleeps__; \ result__ = (expression__); \ } \ if (!result__) { \ throw rti::test::TestFailure( \ "" __FILE__, \ "" RTI_TEST_TOSTRING(__LINE__), \ "wait for condition failed: " #expression__); \ } \ } /** * @ingroup RTITestUtilsCPP * @brief Wait for two values to become equal. Otherwise, throw a TestFailure exception. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. * @param max_seconds__ \b In. The maximum number of seconds to wait for. */ #define RTI_TEST_WAIT_FOR_EQUALS(expected__, actual__, max_seconds__) \ { \ try { \ RTI_TEST_WAIT_FOR((expected__) == (actual__), (max_seconds__)); \ } catch (const rti::test::TestFailure&) { \ print_assert_equals_failure(expected__, actual__); \ throw; \ } \ } /** * @ingroup RTITestUtilsCPP * @brief Wait for a value to become equal to or greater than another one. * Otherwise, throw a TestFailure exception. * @param actual__ \b In. The actual value. * @param limit__ \b In. The limit value. * @param max_seconds__ \b In. The maximum number of seconds to wait for. */ #define RTI_TEST_WAIT_FOR_GREATER_OR_EQUALS(actual__, limit__, max_seconds__) \ { \ try { \ RTI_TEST_WAIT_FOR((actual__) >= (limit__), (max_seconds__)); \ } catch (const rti::test::TestFailure&) { \ print_assert_equals_failure(limit__, actual__); \ throw; \ } \ } /** * @ingroup RTITestUtilsCPP * @brief Wait for an integer to become equal to or greater than another one. * Otherwise, throw a TestFailure exception. * @param actual__ \b In. The actual value. * @param limit__ \b In. The limit value. * @param max_seconds__ \b In. The maximum number of seconds to wait for. */ #define RTI_TEST_WAIT_FOR_GREATER_OR_EQUALS_INT \ RTI_TEST_WAIT_FOR_GREATER_OR_EQUALS /** * @ingroup RTITestUtilsCPP * @brief Wait for two given integers to become equal. * @param expected__ \b In. The expected value. * @param actual__ \b In. The actual value. * @param max_seconds__ \b In. The maximum number of seconds to wait for. */ #define RTI_TEST_WAIT_FOR_EQUALS_INT RTI_TEST_WAIT_FOR_EQUALS /** * @ingroup RTITestAssertionsCPP * @brief Assert that a given statement__ throws an exception of type exception_class__. * Otherwise, throw a TestFailure exception. * @param statement__ \b In. The statement to be evaluated. * @param exception_class__ \b In. The type of exception to be thrown. * @details Prints expected errors start and end log messages. */ #define RTI_TEST_ASSERT_EXCEPTION(statement__, exception_class__) \ try { \ RTITestLog_printExpectedErrorsStart(); \ statement__; \ RTI_TEST_FAIL("Expected exception not thrown"); \ } catch (const exception_class__&) { \ RTITestLog_printExpectedErrorsEnd(); \ } /** * @ingroup RTITestAssertionsCPP * @brief Assert that a given statement__ throws an exception of type exception_class__. * Otherwise, throw a TestFailure exception. * @param statement__ \b In. The statement to be evaluated. * @param exception_class__ \b In. The type of exception to be thrown. * @details This macro doesn't print the expected error's start and end log messages. */ #define RTI_TEST_ASSERT_EXCEPTION_NO_WARN(statement__, exception_class__) \ try { \ statement__; \ RTI_TEST_FAIL("Expected exception not thrown"); \ } catch (const exception_class__&) { } /** @internal * @ingroup RTITestUtilsCPP * @brief Throws a TestFailure exception if the given statement throws an exception. * @param statement__ \b In. The statement to be evaluated. * @param msg__ \b In. The message to be included in the exception. */ #define RTI_TEST_TRACE_W_MSG(statement__, msg__) \ try { \ (statement__); \ } catch (const std::exception& ex) { \ throw rti::test::TestFailure( \ "" __FILE__, \ "" RTI_TEST_TOSTRING(__LINE__), \ msg__, \ ex); \ } /** * @ingroup RTITestAssertionsCPP * @brief Assert that a given value is within a given tolerance. * @param expected__ \b In. The expected value. * @param count__ \b In. The actual value. * @param tolerance_percentage__ \b In. The tolerance percentage. */ #define RTI_TEST_ASSERT_RANGE(expected__, count__, tolerance_percentage__) \ { \ double tolerance__ = (double) (tolerance_percentage__) / 100; \ double max_count__ = (expected__) * (1 + tolerance__); \ double min_count__ = (expected__) * (1 - tolerance__); \ try { \ RTI_TEST_ASSERT((count__) >= min_count__ && (count__) <= max_count__); \ } catch (std::exception&) { \ RTITestLog_freeForm( \ RTI_LOG_BIT_EXCEPTION, \ "%s:%d: value out of range: [%f, %f], count: %f\n", \ (__FILE__), \ (__LINE__), \ min_count__, \ max_count__, \ (double) (count__)); \ throw; \ } \ } /** @internal * @ingroup RTITestUtilsCPP * @brief Throws a TestFailure exception if the given statement throws an exception. * @param statement__ \b In. The statement to be evaluated. * @param msg__ \b In. The message to be included in the exception. * @details If an assertion fails within a function call in statement__, * this macro adds this statement__ as a reason for the failure. * This is a way to build an exception stack to log more than * just the line where the exception originated. */ #define RTI_TEST_TRACE(statement__) \ RTI_TEST_TRACE_W_MSG(statement__, "test failure: " #statement__) #endif // RTI_TEST_ASSERTIONS_HPP_