867 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			867 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
| #ifndef RAPID_CXX_TEST_H
 | |
| #define RAPID_CXX_TEST_H
 | |
| 
 | |
| # include <cstddef>
 | |
| # include <cstdlib>
 | |
| # include <cstdio>
 | |
| # include <cstring>
 | |
| # include <cassert>
 | |
| 
 | |
| #include "test_macros.h"
 | |
| 
 | |
| #if !defined(RAPID_CXX_TEST_NO_SYSTEM_HEADER) || !defined(__GNUC__)
 | |
| #pragma GCC system_header
 | |
| #endif
 | |
| 
 | |
| # define RAPID_CXX_TEST_PP_CAT(x, y) RAPID_CXX_TEST_PP_CAT_2(x, y)
 | |
| # define RAPID_CXX_TEST_PP_CAT_2(x, y) x##y
 | |
| 
 | |
| # define RAPID_CXX_TEST_PP_STR(...) RAPID_CXX_TEST_PP_STR_2(__VA_ARGS__)
 | |
| # define RAPID_CXX_TEST_PP_STR_2(...) #__VA_ARGS__
 | |
| 
 | |
| # if defined(__GNUC__)
 | |
| #   define TEST_FUNC_NAME() __PRETTY_FUNCTION__
 | |
| #   define RAPID_CXX_TEST_UNUSED __attribute__((unused))
 | |
| # else
 | |
| #   define TEST_FUNC_NAME() __func__
 | |
| #   define RAPID_CXX_TEST_UNUSED
 | |
| # endif
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                          TEST_SUITE
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| # define TEST_SUITE(Name)                                           \
 | |
| namespace Name                                                      \
 | |
| {                                                                   \
 | |
|     inline ::rapid_cxx_test::test_suite & get_test_suite()          \
 | |
|     {                                                               \
 | |
|         static ::rapid_cxx_test::test_suite m_suite(#Name);         \
 | |
|         return m_suite;                                             \
 | |
|     }                                                               \
 | |
|                                                                     \
 | |
|     inline int unit_test_main(int, char**)                          \
 | |
|     {                                                               \
 | |
|         ::rapid_cxx_test::test_runner runner(get_test_suite());     \
 | |
|         return runner.run();                                        \
 | |
|     }                                                               \
 | |
| }                                                                   \
 | |
| int main(int argc, char **argv)                                     \
 | |
| {                                                                   \
 | |
|     return Name::unit_test_main(argc, argv);                        \
 | |
| }                                                                   \
 | |
| namespace Name                                                      \
 | |
| { /* namespace closed in TEST_SUITE_END */
 | |
| #
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                         TEST_SUITE_END
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| # define TEST_SUITE_END()                                       \
 | |
| } /* namespace opened in TEST_SUITE(...) */
 | |
| #
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                          TEST_CASE
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| # if !defined(__clang__)
 | |
| #
 | |
| # define TEST_CASE(Name)                                                                                \
 | |
|     void Name();                                                                                        \
 | |
|     static void RAPID_CXX_TEST_PP_CAT(Name, _invoker)()                                                 \
 | |
|     {                                                                                                   \
 | |
|         Name();                                                                                         \
 | |
|     }                                                                                                   \
 | |
|     static ::rapid_cxx_test::registrar                                                                  \
 | |
|     RAPID_CXX_TEST_PP_CAT(rapid_cxx_test_registrar_, Name)(                                         \
 | |
|         get_test_suite()                                                                                \
 | |
|       , ::rapid_cxx_test::test_case(__FILE__, #Name, __LINE__, & RAPID_CXX_TEST_PP_CAT(Name, _invoker)) \
 | |
|       );                                                                                                \
 | |
|     void Name()
 | |
| #
 | |
| # else /* __clang__ */
 | |
| #
 | |
| # define TEST_CASE(Name)                                                                                \
 | |
|     void Name();                                                                                        \
 | |
|     static void RAPID_CXX_TEST_PP_CAT(Name, _invoker)()                                                 \
 | |
|     {                                                                                                   \
 | |
|         Name();                                                                                         \
 | |
|     }                                                                                                   \
 | |
|     _Pragma("clang diagnostic push")                                                                    \
 | |
|     _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"")                                       \
 | |
|     static ::rapid_cxx_test::registrar                                                                  \
 | |
|     RAPID_CXX_TEST_PP_CAT(rapid_cxx_test_registrar_, Name)(                                         \
 | |
|         get_test_suite()                                                                                \
 | |
|       , ::rapid_cxx_test::test_case(__FILE__, #Name, __LINE__, & RAPID_CXX_TEST_PP_CAT(Name, _invoker)) \
 | |
|       );                                                                                                \
 | |
|     _Pragma("clang diagnostic pop")                                                                     \
 | |
|     void Name()
 | |
| #
 | |
| # endif /* !defined(__clang__) */
 | |
| 
 | |
| 
 | |
| # define TEST_SET_CHECKPOINT() ::rapid_cxx_test::set_checkpoint(__FILE__, TEST_FUNC_NAME(), __LINE__)
 | |
| 
 | |
| #define RAPID_CXX_TEST_OUTCOME()
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                              TEST_UNSUPPORTED
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| # define TEST_UNSUPPORTED()                                                                 \
 | |
|     do {                                                                                    \
 | |
|         TEST_SET_CHECKPOINT();                                                              \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                                 \
 | |
|           ::rapid_cxx_test::failure_type::unsupported, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|           , "", ""                                                                          \
 | |
|         );                                                                                  \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                       \
 | |
|         return;                                                                             \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                            BASIC ASSERTIONS
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| # define TEST_WARN(...)                                                                \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_WARN(" #__VA_ARGS__ ")", ""                                        \
 | |
|             );                                                                         \
 | |
|         if (not (__VA_ARGS__)) {                                                       \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::warn;                           \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_CHECK(...)                                                               \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_CHECK(" #__VA_ARGS__ ")", ""                                       \
 | |
|             );                                                                         \
 | |
|         if (not (__VA_ARGS__)) {                                                       \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::check;                          \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_REQUIRE(...)                                                             \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_REQUIRE(" #__VA_ARGS__ ")", ""                                     \
 | |
|             );                                                                         \
 | |
|         if (not (__VA_ARGS__)) {                                                       \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::require;                        \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             return;                                                                    \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_ASSERT(...)                                                              \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_ASSERT(" #__VA_ARGS__ ")", ""                                      \
 | |
|             );                                                                         \
 | |
|         if (not (__VA_ARGS__)) {                                                       \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::assert;                         \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             std::abort();                                                              \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                    TEST_CHECK_NO_THROW / TEST_CHECK_THROW
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| #ifndef TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| # define TEST_CHECK_NO_THROW(...)                                                      \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_CHECK_NO_THROW(" #__VA_ARGS__ ")", ""                              \
 | |
|             );                                                                         \
 | |
|         try {                                                                          \
 | |
|             (static_cast<void>(__VA_ARGS__));                                          \
 | |
|         } catch (...) {                                                                \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::check;                          \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_CHECK_THROW(Except, ...)                                                 \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_CHECK_THROW(" #Except "," #__VA_ARGS__ ")", ""                     \
 | |
|             );                                                                         \
 | |
|         try {                                                                          \
 | |
|             (static_cast<void>(__VA_ARGS__));                                          \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::check;                          \
 | |
|         } catch (Except const &) {}                                                    \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| #define TEST_CHECK_THROW_RESULT(Except, Checker, ...)                          \
 | |
|   do {                                                                         \
 | |
|     TEST_SET_CHECKPOINT();                                                     \
 | |
|     ::rapid_cxx_test::test_outcome m_f(::rapid_cxx_test::failure_type::none,   \
 | |
|                                        __FILE__, TEST_FUNC_NAME(), __LINE__,   \
 | |
|                                        "TEST_CHECK_THROW_RESULT(" #Except      \
 | |
|                                        "," #Checker "," #__VA_ARGS__ ")",      \
 | |
|                                        "");                                    \
 | |
|     try {                                                                      \
 | |
|       (static_cast<void>(__VA_ARGS__));                                        \
 | |
|       m_f.type = ::rapid_cxx_test::failure_type::check;                        \
 | |
|     } catch (Except const& Caught) {                                           \
 | |
|       Checker(Caught);                                                         \
 | |
|     }                                                                          \
 | |
|     ::rapid_cxx_test::get_reporter().report(m_f);                              \
 | |
|   } while (false)
 | |
| #
 | |
| 
 | |
| #else // TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| # define TEST_CHECK_NO_THROW(...)                                                      \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_CHECK_NO_THROW(" #__VA_ARGS__ ")", ""                              \
 | |
|             );                                                                         \
 | |
|         (static_cast<void>(__VA_ARGS__));                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| #define TEST_CHECK_THROW(Except, ...) ((void)0)
 | |
| #define TEST_CHECK_THROW_RESULT(Except, Checker, ...) ((void)0)
 | |
| 
 | |
| #endif // TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                    TEST_REQUIRE_NO_THROW / TEST_REQUIRE_THROWs
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| #ifndef TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| # define TEST_REQUIRE_NO_THROW(...)                                                    \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_REQUIRE_NO_THROW(" #__VA_ARGS__ ")", ""                            \
 | |
|             );                                                                         \
 | |
|         try {                                                                          \
 | |
|             (static_cast<void>(__VA_ARGS__));                                          \
 | |
|         } catch (...) {                                                                \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::require;                        \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             return;                                                                    \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_REQUIRE_THROW(Except, ...)                                               \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_REQUIRE_THROW(" #Except "," #__VA_ARGS__ ")", ""                   \
 | |
|             );                                                                         \
 | |
|         try {                                                                          \
 | |
|             (static_cast<void>(__VA_ARGS__));                                          \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::require;                        \
 | |
|         } catch (Except const &) {}                                                    \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             return;                                                                    \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| #else // TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| # define TEST_REQUIRE_NO_THROW(...)                                                    \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_REQUIRE_NO_THROW(" #__VA_ARGS__ ")", ""                            \
 | |
|             );                                                                         \
 | |
|         (static_cast<void>(__VA_ARGS__));                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| #define TEST_REQUIRE_THROW(Except, ...) ((void)0)
 | |
| 
 | |
| #endif // TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //                    TEST_ASSERT_NO_THROW / TEST_ASSERT_THROW
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| #ifndef TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| # define TEST_ASSERT_NO_THROW(...)                                                     \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_ASSERT_NO_THROW(" #__VA_ARGS__ ")", ""                             \
 | |
|             );                                                                         \
 | |
|         try {                                                                          \
 | |
|             (static_cast<void>(__VA_ARGS__));                                          \
 | |
|         } catch (...) {                                                                \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::assert;                         \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             std::abort();                                                              \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_ASSERT_THROW(Except, ...)                                                \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_ASSERT_THROW(" #Except "," #__VA_ARGS__ ")", ""                    \
 | |
|             );                                                                         \
 | |
|         try {                                                                          \
 | |
|             (static_cast<void>(__VA_ARGS__));                                          \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::assert;                         \
 | |
|         } catch (Except const &) {}                                                    \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             std::abort();                                                              \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| #else // TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| # define TEST_ASSERT_NO_THROW(...)                                                     \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|             ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__ \
 | |
|             , "TEST_ASSERT_NO_THROW(" #__VA_ARGS__ ")", ""                             \
 | |
|             );                                                                         \
 | |
|         (static_cast<void>(__VA_ARGS__));                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| #define TEST_ASSERT_THROW(Except, ...) ((void)0)
 | |
| 
 | |
| #endif // TEST_HAS_NO_EXCEPTIONS
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| # define TEST_WARN_EQUAL_COLLECTIONS(...)                                              \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|           ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__   \
 | |
|           , "TEST_WARN_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")", ""                        \
 | |
|         );                                                                             \
 | |
|         if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) { \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::warn;                           \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_CHECK_EQUAL_COLLECTIONS(...)                                             \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|           ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__   \
 | |
|           , "TEST_CHECK_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")", ""                       \
 | |
|         );                                                                             \
 | |
|         if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) { \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::check;                          \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_REQUIRE_EQUAL_COLLECTIONS(...)                                           \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|           ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__   \
 | |
|           , "TEST_REQUIRE_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")", ""                     \
 | |
|         );                                                                             \
 | |
|         if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) { \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::require;                        \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|             return;                                                                    \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| # define TEST_ASSERT_EQUAL_COLLECTIONS(...)                                            \
 | |
|     do {                                                                               \
 | |
|         TEST_SET_CHECKPOINT();                                                         \
 | |
|         ::rapid_cxx_test::test_outcome m_f(                                            \
 | |
|           ::rapid_cxx_test::failure_type::none, __FILE__, TEST_FUNC_NAME(), __LINE__   \
 | |
|           , "TEST_ASSERT_EQUAL_COLLECTIONS(" #__VA_ARGS__ ")", ""                      \
 | |
|         );                                                                             \
 | |
|         if (not ::rapid_cxx_test::detail::check_equal_collections_impl(__VA_ARGS__)) { \
 | |
|             m_f.type = ::rapid_cxx_test::failure_type::assert;                         \
 | |
|         }                                                                              \
 | |
|         ::rapid_cxx_test::get_reporter().report(m_f);                                  \
 | |
|         if (m_f.type != ::rapid_cxx_test::failure_type::none) {                        \
 | |
|           ::std::abort();                                                              \
 | |
|         }                                                                              \
 | |
|     } while (false)
 | |
| #
 | |
| 
 | |
| namespace rapid_cxx_test
 | |
| {
 | |
|     typedef void (*invoker_t)();
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     struct test_case
 | |
|     {
 | |
|         test_case()
 | |
|             : file(""), func(""), line(0), invoke(NULL)
 | |
|         {}
 | |
| 
 | |
|         test_case(const char* file1, const char* func1, std::size_t line1,
 | |
|                   invoker_t invoke1)
 | |
|             : file(file1), func(func1), line(line1), invoke(invoke1)
 | |
|         {}
 | |
| 
 | |
|         const char *file;
 | |
|         const char *func;
 | |
|         std::size_t line;
 | |
|         invoker_t invoke;
 | |
|     };
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     struct failure_type
 | |
|     {
 | |
|         enum enum_type {
 | |
|             none,
 | |
|             unsupported,
 | |
|             warn,
 | |
|             check,
 | |
|             require,
 | |
|             assert,
 | |
|             uncaught_exception
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     typedef failure_type::enum_type failure_type_t;
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     struct test_outcome
 | |
|     {
 | |
|         test_outcome()
 | |
|             : type(failure_type::none),
 | |
|               file(""), func(""), line(0),
 | |
|               expression(""), message("")
 | |
|         {}
 | |
| 
 | |
|         test_outcome(failure_type_t type1, const char* file1, const char* func1,
 | |
|                      std::size_t line1, const char* expression1,
 | |
|                      const char* message1)
 | |
|             : type(type1), file(file1), func(func1), line(line1),
 | |
|               expression(expression1), message(message1)
 | |
|         {
 | |
|             trim_func_string();
 | |
|         }
 | |
| 
 | |
|         failure_type_t type;
 | |
|         const char *file;
 | |
|         const char *func;
 | |
|         std::size_t line;
 | |
|         const char *expression;
 | |
|         const char *message;
 | |
| 
 | |
|     private:
 | |
|         void trim_file_string() {
 | |
|             const char* f_start  = file;
 | |
|             const char* prev_start = f_start;
 | |
|             const char* last_start = f_start;
 | |
|             char last;
 | |
|             while ((last = *f_start) != '\0') {
 | |
|                 ++f_start;
 | |
|                 if (last == '/' && *f_start) {
 | |
|                     prev_start = last_start;
 | |
|                     last_start = f_start;
 | |
|                 }
 | |
|             }
 | |
|             file = prev_start;
 | |
|         }
 | |
|       void trim_func_string() {
 | |
|           const char* void_loc = ::strstr(func, "void ");
 | |
|           if (void_loc == func) {
 | |
|               func += strlen("void ");
 | |
|           }
 | |
|           const char* namespace_loc = ::strstr(func, "::");
 | |
|           if (namespace_loc) {
 | |
|               func = namespace_loc + 2;
 | |
|           }
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     struct checkpoint
 | |
|     {
 | |
|         const char *file;
 | |
|         const char *func;
 | |
|         std::size_t line;
 | |
|     };
 | |
| 
 | |
|     namespace detail
 | |
|     {
 | |
|         inline checkpoint & global_checkpoint()
 | |
|         {
 | |
|             static checkpoint cp = {"", "", 0};
 | |
|             return cp;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     inline void set_checkpoint(const char* file, const char* func, std::size_t line)
 | |
|     {
 | |
|         checkpoint& cp = detail::global_checkpoint();
 | |
|         cp.file = file;
 | |
|         cp.func = func;
 | |
|         cp.line = line;
 | |
|     }
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     inline checkpoint const & get_checkpoint()
 | |
|     {
 | |
|         return detail::global_checkpoint();
 | |
|     }
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     class test_suite
 | |
|     {
 | |
|     public:
 | |
|         typedef test_case const* iterator;
 | |
|         typedef iterator const_iterator;
 | |
| 
 | |
|     public:
 | |
|         test_suite(const char *xname)
 | |
|           : m_name(xname), m_tests(), m_size(0)
 | |
|         {
 | |
|             assert(xname);
 | |
|         }
 | |
| 
 | |
|     public:
 | |
|         const char *name() const { return m_name; }
 | |
| 
 | |
|         std::size_t size() const { return m_size; }
 | |
| 
 | |
|         test_case const & operator[](std::size_t i) const
 | |
|         {
 | |
|             assert(i < m_size);
 | |
|             return m_tests[i];
 | |
|         }
 | |
| 
 | |
|         const_iterator begin() const
 | |
|         { return m_tests; }
 | |
| 
 | |
|         const_iterator end() const
 | |
|         {
 | |
|             return m_tests + m_size;
 | |
|         }
 | |
| 
 | |
|     public:
 | |
|         std::size_t register_test(test_case tc)
 | |
|         {
 | |
|             static std::size_t test_case_max = sizeof(m_tests) / sizeof(test_case);
 | |
|             assert(m_size < test_case_max);
 | |
|             m_tests[m_size] = tc;
 | |
|             return m_size++;
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         test_suite(test_suite const &);
 | |
|         test_suite & operator=(test_suite const &);
 | |
| 
 | |
|     private:
 | |
|         const char* m_name;
 | |
|         // Since fast compile times in a priority, we use simple containers
 | |
|         // with hard limits.
 | |
|         test_case m_tests[256];
 | |
|         std::size_t m_size;
 | |
|     };
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     class registrar
 | |
|     {
 | |
|     public:
 | |
|         registrar(test_suite & st, test_case tc)
 | |
|         {
 | |
|             st.register_test(tc);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     class test_reporter
 | |
|     {
 | |
|     public:
 | |
|         test_reporter()
 | |
|             : m_testcases(0), m_testcase_failures(0), m_unsupported(0),
 | |
|               m_assertions(0), m_warning_failures(0), m_check_failures(0),
 | |
|               m_require_failures(0), m_uncaught_exceptions(0), m_failure()
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         void test_case_begin()
 | |
|         {
 | |
|             ++m_testcases;
 | |
|             clear_failure();
 | |
|         }
 | |
| 
 | |
|         void test_case_end()
 | |
|         {
 | |
|             if (m_failure.type != failure_type::none
 | |
|                 && m_failure.type !=  failure_type::unsupported) {
 | |
|                 ++m_testcase_failures;
 | |
|             }
 | |
|         }
 | |
| 
 | |
| # if defined(__GNUC__)
 | |
| #   pragma GCC diagnostic push
 | |
| #   pragma GCC diagnostic ignored "-Wswitch-default"
 | |
| # endif
 | |
|         // Each assertion and failure is reported through this function.
 | |
|         void report(test_outcome o)
 | |
|         {
 | |
|             ++m_assertions;
 | |
|             switch (o.type)
 | |
|             {
 | |
|             case failure_type::none:
 | |
|                 break;
 | |
|             case failure_type::unsupported:
 | |
|                 ++m_unsupported;
 | |
|                 m_failure = o;
 | |
|                 break;
 | |
|             case failure_type::warn:
 | |
|                 ++m_warning_failures;
 | |
|                 report_error(o);
 | |
|                 break;
 | |
|             case failure_type::check:
 | |
|                 ++m_check_failures;
 | |
|                 report_error(o);
 | |
|                 m_failure = o;
 | |
|                 break;
 | |
|             case failure_type::require:
 | |
|                 ++m_require_failures;
 | |
|                 report_error(o);
 | |
|                 m_failure = o;
 | |
|                 break;
 | |
|             case failure_type::assert:
 | |
|                 report_error(o);
 | |
|                 break;
 | |
|             case failure_type::uncaught_exception:
 | |
|                 ++m_uncaught_exceptions;
 | |
|                 std::fprintf(stderr
 | |
|                     , "Test case FAILED with uncaught exception:\n"
 | |
|                       "    last checkpoint near %s::%lu %s\n\n"
 | |
|                     , o.file, o.line, o.func
 | |
|                     );
 | |
|                 m_failure = o;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| # if defined(__GNUC__)
 | |
| #   pragma GCC diagnostic pop
 | |
| # endif
 | |
| 
 | |
|         test_outcome current_failure() const
 | |
|         {
 | |
|             return m_failure;
 | |
|         }
 | |
| 
 | |
|         void clear_failure()
 | |
|         {
 | |
|             m_failure.type = failure_type::none;
 | |
|             m_failure.file = "";
 | |
|             m_failure.func = "";
 | |
|             m_failure.line = 0;
 | |
|             m_failure.expression = "";
 | |
|             m_failure.message = "";
 | |
|         }
 | |
| 
 | |
|         std::size_t test_case_count() const
 | |
|         { return m_testcases; }
 | |
| 
 | |
|         std::size_t test_case_failure_count() const
 | |
|         { return m_testcase_failures; }
 | |
| 
 | |
|         std::size_t unsupported_count() const
 | |
|         { return m_unsupported; }
 | |
| 
 | |
|         std::size_t assertion_count() const
 | |
|         { return m_assertions; }
 | |
| 
 | |
|         std::size_t warning_failure_count() const
 | |
|         { return m_warning_failures; }
 | |
| 
 | |
|         std::size_t check_failure_count() const
 | |
|         { return m_check_failures; }
 | |
| 
 | |
|         std::size_t require_failure_count() const
 | |
|         { return m_require_failures; }
 | |
| 
 | |
|         std::size_t failure_count() const
 | |
|         { return m_check_failures + m_require_failures + m_uncaught_exceptions; }
 | |
| 
 | |
|         // Print a summary of what was run and the outcome.
 | |
|         void print_summary(const char* suitename) const
 | |
|         {
 | |
|             FILE* out = failure_count() ? stderr : stdout;
 | |
|             std::size_t testcases_run = m_testcases - m_unsupported;
 | |
|             std::fprintf(out, "Summary for testsuite %s:\n", suitename);
 | |
|             std::fprintf(out, "    %lu of %lu test cases passed.\n", testcases_run - m_testcase_failures, testcases_run);
 | |
|             std::fprintf(out, "    %lu of %lu assertions passed.\n", m_assertions - (m_warning_failures + m_check_failures + m_require_failures), m_assertions);
 | |
|             std::fprintf(out, "    %lu unsupported test case%s.\n", m_unsupported, (m_unsupported != 1 ? "s" : ""));
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         test_reporter(test_reporter const &);
 | |
|         test_reporter const & operator=(test_reporter const &);
 | |
| 
 | |
|         void report_error(test_outcome o) const
 | |
|         {
 | |
|             std::fprintf(stderr, "In %s:%lu Assertion %s failed.\n    in file: %s\n    %s\n"
 | |
|                 , o.func, o.line, o.expression, o.file,  o.message ? o.message : ""
 | |
|               );
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         // counts of testcases, failed testcases, and unsupported testcases.
 | |
|         std::size_t m_testcases;
 | |
|         std::size_t m_testcase_failures;
 | |
|         std::size_t m_unsupported;
 | |
| 
 | |
|         // counts of assertions and assertion failures.
 | |
|         std::size_t m_assertions;
 | |
|         std::size_t m_warning_failures;
 | |
|         std::size_t m_check_failures;
 | |
|         std::size_t m_require_failures;
 | |
|         std::size_t m_uncaught_exceptions;
 | |
| 
 | |
|         // The last failure. This is cleared between testcases.
 | |
|         test_outcome m_failure;
 | |
|     };
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     inline test_reporter & get_reporter()
 | |
|     {
 | |
|         static test_reporter o;
 | |
|         return o;
 | |
|     }
 | |
| 
 | |
|     ////////////////////////////////////////////////////////////////////////////
 | |
|     class test_runner
 | |
|     {
 | |
|     public:
 | |
|         test_runner(test_suite & ts)
 | |
|           : m_ts(ts)
 | |
|         {}
 | |
| 
 | |
|     public:
 | |
|         int run()
 | |
|         {
 | |
|             // for each testcase
 | |
|             for (test_suite::const_iterator b = m_ts.begin(), e = m_ts.end();
 | |
|                  b != e; ++b)
 | |
|             {
 | |
|                 test_case const& tc = *b;
 | |
|                 set_checkpoint(tc.file, tc.func, tc.line);
 | |
|                 get_reporter().test_case_begin();
 | |
| #ifndef TEST_HAS_NO_EXCEPTIONS
 | |
|                 try {
 | |
| #endif
 | |
|                     tc.invoke();
 | |
| #ifndef TEST_HAS_NO_EXCEPTIONS
 | |
|                 } catch (...) {
 | |
|                     test_outcome o;
 | |
|                     o.type = failure_type::uncaught_exception;
 | |
|                     o.file = get_checkpoint().file;
 | |
|                     o.func = get_checkpoint().func;
 | |
|                     o.line = get_checkpoint().line;
 | |
|                     o.expression = "";
 | |
|                     o.message = "";
 | |
|                     get_reporter().report(o);
 | |
|                 }
 | |
| #endif
 | |
|                 get_reporter().test_case_end();
 | |
|             }
 | |
|             auto exit_code = get_reporter().failure_count() ? EXIT_FAILURE : EXIT_SUCCESS;
 | |
|             if (exit_code == EXIT_FAILURE || get_reporter().unsupported_count())
 | |
|               get_reporter().print_summary(m_ts.name());
 | |
|             return exit_code;
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         test_runner(test_runner const &);
 | |
|         test_runner operator=(test_runner const &);
 | |
| 
 | |
|         test_suite & m_ts;
 | |
|     };
 | |
| 
 | |
|     namespace detail
 | |
|     {
 | |
|         template <class Iter1, class Iter2>
 | |
|         bool check_equal_collections_impl(
 | |
|             Iter1 start1, Iter1 const end1
 | |
|           , Iter2 start2, Iter2 const end2
 | |
|           )
 | |
|         {
 | |
|             while (start1 != end1 && start2 != end2) {
 | |
|                 if (*start1 != *start2) {
 | |
|                     return false;
 | |
|                 }
 | |
|                 ++start1; ++start2;
 | |
|             }
 | |
|             return (start1 == end1 && start2 == end2);
 | |
|         }
 | |
|     }                                                       // namespace detail
 | |
| 
 | |
| }                                                    // namespace rapid_cxx_test
 | |
| 
 | |
| 
 | |
| # if defined(__GNUC__)
 | |
| #   pragma GCC diagnostic pop
 | |
| # endif
 | |
| 
 | |
| #endif /* RAPID_CXX_TEST_H */
 |