forked from OSchip/llvm-project
867 lines
41 KiB
C++
867 lines
41 KiB
C++
#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)
|
|
#
|
|
|
|
#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_HPP */
|