| #ifndef RAPID_CXX_TEST_HPP |
| #define RAPID_CXX_TEST_HPP |
| |
| # 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) |
| # |
| |
| #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) |
| |
| #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().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_HPP */ |