blob: 5fa29ed54ff3ecf8990dfc084a405c29600002b5 [file] [log] [blame]
// Suppress this globally - there is no way to silence it in the expression decomposition macros
// _Pragma() in macros doesn't work for the c++ front-end of g++
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55578
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69543
// Also it is completely worthless nowadays - http://stackoverflow.com/questions/14016993
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic ignored "-Waggregate-return"
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
#endif // __clang__
#if defined(__GNUC__) && !defined(__clang__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6)
#pragma GCC diagnostic push
#endif // > gcc 4.6
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wstrict-overflow"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#pragma GCC diagnostic ignored "-Winline"
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6)
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif // > gcc 4.6
//#pragma GCC diagnostic ignored "-Wlong-long"
#endif // __GNUC__
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4996)
#pragma warning(disable : 4267)
#endif // _MSC_VER
#ifndef DOCTEST_LIBRARY_INCLUDED
#define DOCTEST_LIBRARY_INCLUDED
// internal macros for string concatenation and anonymous variable name generation
#define DOCTEST_STR_CONCAT_IMPL(s1, s2) s1##s2
#define DOCTEST_STR_CONCAT(s1, s2) DOCTEST_STR_CONCAT_IMPL(s1, s2)
#ifdef __COUNTER__ // not standard and may be missing for some compilers
#define DOCTEST_ANONYMOUS(x) DOCTEST_STR_CONCAT(x, __COUNTER__)
#else // __COUNTER__
#define DOCTEST_ANONYMOUS(x) DOCTEST_STR_CONCAT(x, __LINE__)
#endif // __COUNTER__
// internal macro for making a string
#define DOCTEST_TOSTR_IMPL(x) #x
#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x)
// internal macro for concatenating 2 literals and making the result a string
#define DOCTEST_STR_CONCAT_TOSTR(s1, s2) DOCTEST_TOSTR(DOCTEST_STR_CONCAT(s1, s2))
namespace doctest
{
// the function type this library works with
typedef void (*funcType)(void);
class String
{
char* m_str;
void copy(const String& other);
public:
String(const char* in = 0);
String(const String& other);
~String();
String& operator=(const String& other);
String operator+(const String& other) const;
String& operator+=(const String& other);
char& operator[](unsigned pos) { return m_str[pos]; }
const char& operator[](unsigned pos) const { return m_str[pos]; }
char* c_str() { return m_str; }
const char* c_str() const { return m_str; }
int compare(const char* other, bool no_case = false) const;
int compare(const String& other, bool no_case = false) const;
};
#if !defined(DOCTEST_DISABLE)
namespace detail
{
template <class T>
class Vector
{
unsigned m_size;
unsigned m_capacity;
T* m_buffer;
public:
Vector();
explicit Vector(unsigned num);
Vector(const Vector& other);
~Vector();
Vector& operator=(const Vector& other);
T* data() { return m_buffer; }
const T* data() const { return m_buffer; }
unsigned size() const { return m_size; }
T& operator[](unsigned index) { return m_buffer[index]; }
const T& operator[](unsigned index) const { return m_buffer[index]; }
void clear();
void pop_back();
void push_back(const T& item);
};
struct Subcase
{
String m_name;
const char* m_file;
int m_line;
bool m_entered;
Subcase(const char* name, const char* file, int line);
~Subcase();
Subcase(const Subcase& other);
Subcase& operator=(const Subcase& other);
bool operator==(const Subcase& other) const;
operator bool() const { return m_entered; }
};
String stringify(const char* in);
String stringify(bool in);
String stringify(char in);
String stringify(int in);
String stringify(long in);
//String stringify(long long in);
String stringify(unsigned in);
String stringify(unsigned long in);
//String stringify(unsigned long long in);
String stringify(float in);
String stringify(double in);
String stringify(long double in);
template <typename T>
String stringify(const T&) {
return "{?}";
}
// pointers???
//template <typename T>
//String stringify(const T*&) {
// return "{?}";
//}
template <typename L, typename R>
String stringify(const L& lhs, const char* op, const R& rhs) {
return stringify(lhs) + " " + op + " " + stringify(rhs);
}
struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
struct TROLOLO;
struct Result
{
bool m_passed;
String m_decomposition;
Result(bool passed = true, const String& decomposition = "")
: m_passed(passed)
, m_decomposition(decomposition) {}
operator bool() { return !m_passed; }
void invert() { m_passed = !m_passed; }
// clang-format off
//template <typename R> TROLOLO& operator+(const R&);
//template <typename R> TROLOLO& operator-(const R&);
//template <typename R> TROLOLO& operator/(const R&);
//template <typename R> TROLOLO& operator*(const R&);
//template <typename R> TROLOLO& operator&&(const R&);
//template <typename R> TROLOLO& operator||(const R&);
//
//template <typename R> TROLOLO& operator==(const R&);
//template <typename R> TROLOLO& operator!=(const R&);
//template <typename R> TROLOLO& operator<(const R&);
//template <typename R> TROLOLO& operator<=(const R&);
//template <typename R> TROLOLO& operator>(const R&);
//template <typename R> TROLOLO& operator>=(const R&);
// clang-format on
};
template <typename L>
struct Expression_lhs
{
const L lhs;
Expression_lhs(L in)
: lhs(in) {}
operator Result() { return Result(!!lhs, stringify(lhs)); }
// clang-format off
template <typename R> Result operator==(const R& rhs) { return Result(lhs == rhs, stringify(lhs, "==", rhs)); }
template <typename R> Result operator!=(const R& rhs) { return Result(lhs != rhs, stringify(lhs, "!=", rhs)); }
template <typename R> Result operator< (const R& rhs) { return Result(lhs < rhs, stringify(lhs, "<", rhs)); }
template <typename R> Result operator<=(const R& rhs) { return Result(lhs <= rhs, stringify(lhs, "<=", rhs)); }
template <typename R> Result operator> (const R& rhs) { return Result(lhs > rhs, stringify(lhs, ">", rhs)); }
template <typename R> Result operator>=(const R& rhs) { return Result(lhs >= rhs, stringify(lhs, ">=", rhs)); }
// clang-format on
};
struct ExpressionDecomposer
{
template <typename L>
Expression_lhs<const L&> operator<<(const L& operand) {
return Expression_lhs<const L&>(operand);
}
};
struct TestFailureException
{};
} // namespace detail
#endif // DOCTEST_DISABLE
class Context
{
#if !defined(DOCTEST_DISABLE)
detail::Vector<detail::Vector<String> > filters;
bool count; // if only the count of matching tests is to be retreived
bool case_sensitive; // if filtering should be case sensitive
bool separate_process; // if each test should be executed in a separate process
bool allow_overrides; // all but this can be overriden
bool exit_after_tests; // calls exit() after the tests are ran/counted
bool hash_table_histogram; // if the hash table should be printed as a histogram
bool no_run; // to not run the tests at all (can be done with an "*" exclude)
int first; // the first (matching) test to be executed
int last; // the last (matching) test to be executed
int (*testExecutionWrapper)(funcType); // wrapper for test execution
#endif // DOCTEST_DISABLE
public:
Context(int argc, char** argv);
void addFilter(const char* filter, const char* value);
void setOption(const char* option, int value);
void setTestExecutionWrapper(int (*f)(funcType));
int runTests();
};
} // namespace doctest
// if registering is not disabled
#if !defined(DOCTEST_DISABLE)
namespace doctest
{
namespace detail
{
// forward declarations of functions used by the macros
int regTest(void (*f)(void), unsigned line, const char* file, const char* name);
int setTestSuiteName(const char* name);
bool logAssert(const Result& res, bool threw, const char* expr, const char* assert_name,
bool is_check, const char* file, int line);
bool logAssertThrows(const char* expr, bool threw, bool is_check, const char* file, int line);
bool logAssertThrowsAs(const char* expr, const char* as, bool threw, bool threw_as,
bool is_check, const char* file, int line);
bool logAssertNothrow(const char* expr, bool threw, bool is_check, const char* file, int line);
} // namespace detail
} // namespace doctest
// registers the test by initializing a dummy var with a function
#if defined(__GNUC__) && !defined(__clang__)
#define DOCTEST_REGISTER_FUNCTION(f, name) \
static int DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) __attribute__((unused)) = \
doctest::detail::regTest(f, __LINE__, __FILE__, #name);
#elif defined(__clang__)
#define DOCTEST_REGISTER_FUNCTION(f, name) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") static int \
DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) = \
doctest::detail::regTest(f, __LINE__, __FILE__, #name); \
_Pragma("clang diagnostic pop")
#else // MSVC
#define DOCTEST_REGISTER_FUNCTION(f, name) \
static int DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) = \
doctest::detail::regTest(f, __LINE__, __FILE__, #name);
#endif // MSVC
#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \
namespace \
{ \
struct der : base \
{ void f(); }; \
static void func() { \
der v; \
v.f(); \
} \
DOCTEST_REGISTER_FUNCTION(func, name) \
} \
inline void der::f()
#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \
static void f(); \
DOCTEST_REGISTER_FUNCTION(f, name) \
inline void f()
// for registering tests
#define DOCTEST_TESTCASE(name) \
DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FUNC_), name)
// for registering tests with a fixture
#define DOCTEST_TESTCASE_FIXTURE(c, name) \
DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_CLASS_), c, \
DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FUNC_), name)
// for subcases
#if defined(__GNUC__)
#define DOCTEST_SUBCASE(name) \
if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_SUBCASE_) \
__attribute__((unused)) = \
doctest::detail::Subcase(#name, __FILE__, __LINE__))
#else // __GNUC__
#define DOCTEST_SUBCASE(name) \
if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_SUBCASE_) = \
doctest::detail::Subcase(#name, __FILE__, __LINE__))
#endif // __GNUC__
// for starting a testsuite block
#if defined(__GNUC__) && !defined(__clang__)
#define DOCTEST_TESTSUITE(name) \
static int DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) __attribute__((unused)) = \
doctest::detail::setTestSuiteName(#name); \
void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#elif defined(__clang__)
#define DOCTEST_TESTSUITE(name) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") static int \
DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) = \
doctest::detail::setTestSuiteName(#name); \
_Pragma("clang diagnostic pop") void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#else // MSVC
#define DOCTEST_TESTSUITE(name) \
static int DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) = doctest::detail::setTestSuiteName(#name); \
void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#endif // MSVC
// for ending a testsuite block
#if defined(__GNUC__) && !defined(__clang__)
#define DOCTEST_TESTSUITE_END \
static int DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) __attribute__((unused)) = \
doctest::detail::setTestSuiteName(""); \
void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#elif defined(__clang__)
#define DOCTEST_TESTSUITE_END \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wglobal-constructors\"") static int \
DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) = \
doctest::detail::setTestSuiteName(""); \
_Pragma("clang diagnostic pop") void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#else // MSVC
#define DOCTEST_TESTSUITE_END \
static int DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_VAR_) = doctest::detail::setTestSuiteName(""); \
void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#endif // MSVC
#define DOCTEST_ASSERT_IMPLEMENT(expr, assert_name, is_check, false_invert_op) \
doctest::detail::Result res; \
bool threw = false; \
try { \
res = doctest::detail::ExpressionDecomposer() << expr; \
} catch(...) { threw = true; } \
false_invert_op; \
if(res || threw) { \
} \
if(doctest::detail::logAssert(res, threw, #expr, assert_name, is_check, __FILE__, __LINE__)) \
throw doctest::detail::TestFailureException();
//if(doctest::detail::isDebuggerActive())
// doctest::detail::debugBreak();
#if defined(__clang__)
#define DOCTEST_ASSERT_PROXY(expr, assert_name, is_check, false_invert_op) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Woverloaded-shift-op-parentheses\"") \
DOCTEST_ASSERT_IMPLEMENT(expr, assert_name, is_check, false_invert_op) \
_Pragma("clang diagnostic pop") \
} while(false)
#else // __clang__
#define DOCTEST_ASSERT_PROXY(expr, assert_name, is_check, false_invert_op) \
do { \
DOCTEST_ASSERT_IMPLEMENT(expr, assert_name, is_check, false_invert_op) \
} while(false)
#endif // __clang__
#define DOCTEST_CHECK(expr) DOCTEST_ASSERT_PROXY(expr, "CHECK", true, ((void)0))
#define DOCTEST_REQUIRE(expr) DOCTEST_ASSERT_PROXY(expr, "REQUIRE", false, ((void)0))
#define DOCTEST_CHECK_FALSE(expr) DOCTEST_ASSERT_PROXY(expr, "CHECK_FALSE", true, res.invert())
#define DOCTEST_REQUIRE_FALSE(expr) DOCTEST_ASSERT_PROXY(expr, "REQUIRE", false, res.invert())
#define DOCTEST_ASSERT_THROWS(expr, is_check) \
do { \
bool threw = false; \
try { \
expr; \
} catch(...) { threw = true; } \
if(doctest::detail::logAssertThrows(#expr, threw, is_check, __FILE__, __LINE__)) \
throw doctest::detail::TestFailureException(); \
} while(false)
#define DOCTEST_ASSERT_THROWS_AS(expr, as, is_check) \
do { \
bool threw = false; \
bool threw_as = false; \
try { \
expr; \
} catch(as&) { \
threw = true; \
threw_as = true; \
} catch(...) { threw = true; } \
if(doctest::detail::logAssertThrowsAs(#expr, #as, threw, threw_as, is_check, __FILE__, \
__LINE__)) \
throw doctest::detail::TestFailureException(); \
} while(false)
#define DOCTEST_ASSERT_NOTHROW(expr, is_check) \
do { \
bool threw = false; \
try { \
expr; \
} catch(...) { threw = true; } \
if(doctest::detail::logAssertNothrow(#expr, threw, is_check, __FILE__, __LINE__)) \
throw doctest::detail::TestFailureException(); \
} while(false)
#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, true)
#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS(expr, false)
#define DOCTEST_CHECK_THROWS_AS(expr, ex) DOCTEST_ASSERT_THROWS_AS(expr, ex, true)
#define DOCTEST_REQUIRE_THROWS_AS(expr, ex) DOCTEST_ASSERT_THROWS_AS(expr, ex, false)
#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, true)
#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, false)
// =================================================================================================
// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! ==
// == THIS CAN BE ENABLED BY DEFINING DOCTEST_DISABLE GLOBALLY! ==
// =================================================================================================
#else // DOCTEST_DISABLE
namespace doctest
{
inline String::String(const char*) {}
inline String::String(const String&) {}
inline String::~String() {}
inline String& String::operator=(const String&) { return *this; }
inline String String::operator+(const String&) const { return String(); }
inline String& String::operator+=(const String&) { return *this; }
inline int String::compare(const char*, bool) const { return 0; }
inline int String::compare(const String&, bool) const { return 0; }
inline Context::Context(int, char**) {}
inline void Context::addFilter(const char*, const char*) {}
inline void Context::setOption(const char*, int) {}
inline void Context::setTestExecutionWrapper(int (*)(void (*)(void))) {}
inline int Context::runTests() { return 0; }
} // namespace doctest
#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \
namespace \
{ \
template <typename T> \
struct der : base \
{ void f(); }; \
} \
template <typename T> \
inline void der<T>::f()
#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \
template <typename T> \
static inline void f()
// for registering tests
#define DOCTEST_TESTCASE(name) \
DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FUNC_), name)
// for registering tests with a fixture
#define DOCTEST_TESTCASE_FIXTURE(x, name) \
DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_CLASS_), x, \
DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FUNC_), name)
// for subcases
#define DOCTEST_SUBCASE(name)
// for starting a testsuite block
#define DOCTEST_TESTSUITE(name) void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
// for ending a testsuite block
#define DOCTEST_TESTSUITE_END void DOCTEST_ANONYMOUS(DOCTEST_AUTOGEN_FOR_SEMICOLON_)()
#define DOCTEST_CHECK(expr) expr
#define DOCTEST_CHECK_FALSE(expr) expr
#define DOCTEST_CHECK_THROWS(expr) expr
#define DOCTEST_CHECK_THROWS_AS(expr, ex) expr
#define DOCTEST_CHECK_NOTHROW(expr) expr
#define DOCTEST_REQUIRE(expr) expr
#define DOCTEST_REQUIRE_FALSE(expr) expr
#define DOCTEST_REQUIRE_THROWS(expr) expr
#define DOCTEST_REQUIRE_THROWS_AS(expr, ex) expr
#define DOCTEST_REQUIRE_NOTHROW(expr) expr
#endif // DOCTEST_DISABLE
#define doctest_testcase DOCTEST_TESTCASE
#define doctest_testcase_fixture DOCTEST_TESTCASE_FIXTURE
#define doctest_subcase DOCTEST_SUBCASE
#define doctest_testsuite DOCTEST_TESTSUITE
#define doctest_testsuite_end DOCTEST_TESTSUITE_END
#define doctest_check DOCTEST_CHECK
#define doctest_check_false DOCTEST_CHECK_FALSE
#define doctest_check_throws DOCTEST_CHECK_THROWS
#define doctest_check_throws_as DOCTEST_CHECK_THROWS_AS
#define doctest_check_nothrow DOCTEST_CHECK_NOTHROW
#define doctest_require DOCTEST_REQUIRE
#define doctest_require_false DOCTEST_REQUIRE_FALSE
#define doctest_require_throws DOCTEST_REQUIRE_THROWS
#define doctest_require_throws_as DOCTEST_REQUIRE_THROWS_AS
#define doctest_require_nothrow DOCTEST_REQUIRE_NOTHROW
// == SHORT VERSIONS OF THE TEST/FIXTURE/TESTSUITE MACROS
#ifndef DOCTEST_NO_SHORT_MACRO_NAMES
#define TESTCASE DOCTEST_TESTCASE
#define TESTCASE_FIXTURE DOCTEST_TESTCASE_FIXTURE
#define SUBCASE DOCTEST_SUBCASE
#define TESTSUITE DOCTEST_TESTSUITE
#define TESTSUITE_END DOCTEST_TESTSUITE_END
#define CHECK DOCTEST_CHECK
#define CHECK_FALSE DOCTEST_CHECK_FALSE
#define CHECK_THROWS DOCTEST_CHECK_THROWS
#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS
#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW
#define REQUIRE DOCTEST_REQUIRE
#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE
#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS
#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS
#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW
#define testcase DOCTEST_TESTCASE
#define testcase_fixture DOCTEST_TESTCASE_FIXTURE
#define subcase DOCTEST_SUBCASE
#define testsuite DOCTEST_TESTSUITE
#define testsuite_end DOCTEST_TESTSUITE_END
#define check DOCTEST_CHECK
#define check_false DOCTEST_CHECK_FALSE
#define check_throws DOCTEST_CHECK_THROWS
#define check_throws_as DOCTEST_CHECK_THROWS_AS
#define check_nothrow DOCTEST_CHECK_NOTHROW
#define require DOCTEST_REQUIRE
#define require_false DOCTEST_REQUIRE_FALSE
#define require_throws DOCTEST_REQUIRE_THROWS
#define require_throws_as DOCTEST_REQUIRE_THROWS_AS
#define require_nothrow DOCTEST_REQUIRE_NOTHROW
#endif // DOCTEST_NO_SHORT_MACRO_NAMES
// this is here to clear the 'current test suite' for the current translation unit - at the top
doctest_testsuite_end;
#endif // DOCTEST_LIBRARY_INCLUDED
// =================================================================================================
// == WHAT FOLLOWS IS THE IMPLEMENTATION OF THE TEST RUNNER ==
// =================================================================================================
#if(defined(DOCTEST_IMPLEMENT) || defined(DOCTEST_IMPLEMENT_WITH_MAIN)) && !defined(DOCTEST_DISABLE)
#ifndef DOCTEST_LIBRARY_IMPLEMENTATION
#define DOCTEST_LIBRARY_IMPLEMENTATION
// required includes
#include <cstdio> // printf, sprintf and friends
#include <cstdlib> // malloc, free, qsort
#include <cstring> // strcpy, strtok
#include <new> // placement new (can be skipped if the containers require 'construct()' from T)
// the number of buckets used for the hash set
#if !defined(DOCTEST_HASH_TABLE_NUM_BUCKETS)
#define DOCTEST_HASH_TABLE_NUM_BUCKETS 1024
#endif // DOCTEST_HASH_TABLE_NUM_BUCKETS
// main namespace of the library
namespace doctest
{
// library internals namespace
namespace detail
{
// lowers ascii letters
char tolower(const char c) { return ((c >= 'A' && c <= 'Z') ? static_cast<char>(c + 32) : c); }
// not using std::strlen() because of valgrind errors when optimizations are turned on
// 'Invalid read of size 4' when the test suite len (with '\0') is not a multiple of 4
// for details see http://stackoverflow.com/questions/35671155
size_t my_strlen(const char* in) {
const char* temp = in;
while(*temp)
++temp;
return temp - in;
}
// case insensitive strcmp
int stricmp(char const* a, char const* b) {
for(;; a++, b++) {
int d = tolower(*a) - tolower(*b);
if(d != 0 || !*a)
return d;
}
}
// matching of a string against a wildcard mask (case sensitivity configurable) taken from
// http://www.emoticode.net/c/simple-wildcard-string-compare-globbing-function.html
int wildcmp(const char* str, const char* wild, bool caseSensitive) {
const char* cp = 0;
const char* mp = 0;
// rolled my own tolower() to not include more headers
while((*str) && (*wild != '*')) {
if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) &&
(*wild != '?')) {
return 0;
}
wild++;
str++;
}
while(*str) {
if(*wild == '*') {
if(!*++wild) {
return 1;
}
mp = wild;
cp = str + 1;
} else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) ||
(*wild == '?')) {
wild++;
str++;
} else {
wild = mp;
str = cp++;
}
}
while(*wild == '*') {
wild++;
}
return !*wild;
}
// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
unsigned hashStr(unsigned const char* str) {
unsigned long hash = 5381;
char c;
while((c = *str++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
// checks if the name matches any of the filters (and can be configured what to do when empty)
int matchesAny(const String& name, Vector<String> filters, int matchEmpty, bool caseSensitive) {
if(filters.size() == 0 && matchEmpty)
return 1;
for(size_t i = 0; i < filters.size(); ++i)
if(wildcmp(name.c_str(), filters[i].c_str(), caseSensitive))
return 1;
return 0;
}
template <class T>
Vector<T>::Vector()
: m_size(0)
, m_capacity(0)
, m_buffer(0) {}
template <class T>
Vector<T>::Vector(unsigned num)
: m_size(num)
, m_capacity(num)
, m_buffer(static_cast<T*>(malloc(sizeof(T) * m_capacity))) {
for(unsigned i = 0; i < m_size; ++i)
new(m_buffer + i) T();
}
template <class T>
Vector<T>::Vector(const Vector& other)
: m_size(other.m_size)
, m_capacity(other.m_capacity)
, m_buffer(static_cast<T*>(malloc(sizeof(T) * m_capacity))) {
for(unsigned i = 0; i < m_size; ++i)
new(m_buffer + i) T(other.m_buffer[i]);
}
template <class T>
Vector<T>::~Vector() {
for(unsigned i = 0; i < m_size; ++i)
(*(m_buffer + i)).~T();
free(m_buffer);
}
template <class T>
Vector<T>& Vector<T>::operator=(const Vector& other) {
if(this != &other) {
for(size_t i = 0; i < m_size; ++i)
(*(m_buffer + i)).~T();
free(m_buffer);
m_size = other.m_size;
m_capacity = other.m_capacity;
m_buffer = static_cast<T*>(malloc(sizeof(T) * m_capacity));
for(unsigned i = 0; i < m_size; ++i)
new(m_buffer + i) T(other.m_buffer[i]);
}
return *this;
}
template <class T>
void Vector<T>::clear() {
for(unsigned i = 0; i < m_size; ++i)
(*(m_buffer + i)).~T();
m_size = 0;
}
template <class T>
void Vector<T>::pop_back() {
if(m_size > 0)
(*(m_buffer + --m_size)).~T();
}
template <class T>
void Vector<T>::push_back(const T& item) {
if(m_size < m_capacity) {
new(m_buffer + m_size++) T(item);
} else {
if(m_capacity == 0)
m_capacity = 5; // initial capacity
else
m_capacity *= 2; // capacity growth factor
T* temp = static_cast<T*>(malloc(sizeof(T) * m_capacity));
for(unsigned i = 0; i < m_size; ++i) {
new(temp + i) T(m_buffer[i]);
(*(m_buffer + i)).~T();
}
new(temp + m_size++) T(item);
free(m_buffer);
m_buffer = temp;
}
}
unsigned Hash(const Subcase& in) {
return hashStr(reinterpret_cast<unsigned const char*>(in.m_file)) ^ in.m_line;
}
unsigned Hash(int in) { return in; }
// requires that 'bool operator==(T&)' and 'unsigned Hash(T&)' are both present for T
template <class T>
class HashTable
{
Vector<Vector<T> > buckets;
public:
explicit HashTable(unsigned num_buckets)
: buckets(num_buckets) {}
bool has(const T& in) const {
const Vector<T>& bucket = buckets[Hash(in) % buckets.size()];
for(unsigned i = 0; i < bucket.size(); ++i)
if(bucket[i] == in)
return true;
return false;
}
void insert(const T& in) {
if(!has(in))
buckets[Hash(in) % buckets.size()].push_back(in);
}
void clear() {
for(unsigned i = 0; i < buckets.size(); ++i)
buckets[i].clear();
}
const Vector<Vector<T> >& getBuckets() const { return buckets; }
};
// assertion macros use this to mark the current test as failed if an assertion fails
bool& getHasCurrentTestFailed() {
static bool data = false;
return data;
}
// stuff for subcases
HashTable<Subcase>& getSubcasesPassed() {
static HashTable<Subcase> data(100);
return data;
}
HashTable<int>& getSubcasesEnteredLevels() {
static HashTable<int> data(100);
return data;
}
int& getSubcasesCurrentLevel() {
static int data = 0;
return data;
}
bool& getSubcasesHasSkipped() {
static bool data = false;
return data;
}
Subcase::Subcase(const char* name, const char* file, int line)
: m_name(name)
, m_file(file)
, m_line(line)
, m_entered(false) {
// if we have already completed it
if(getSubcasesPassed().has(*this))
return;
// if a Subcase on the same level has already been entered
if(getSubcasesEnteredLevels().has(getSubcasesCurrentLevel())) {
getSubcasesHasSkipped() = true;
return;
}
getSubcasesEnteredLevels().insert(getSubcasesCurrentLevel()++);
m_entered = true;
}
Subcase::~Subcase() {
if(m_entered) {
getSubcasesCurrentLevel()--;
// only mark the subcase as passed if no subcases have been skipped
if(getSubcasesHasSkipped() == false)
getSubcasesPassed().insert(*this);
}
}
Subcase::Subcase(const Subcase& other)
: m_name(other.m_name)
, m_file(other.m_file)
, m_line(other.m_line)
, m_entered(other.m_entered) {}
Subcase& Subcase::operator=(const Subcase& other) {
m_name = other.m_name;
m_file = other.m_file;
m_line = other.m_line;
m_entered = other.m_entered;
return *this;
}
bool Subcase::operator==(const Subcase& other) const {
return m_line == other.m_line && strcmp(m_file, other.m_file) == 0;
}
String stringify(const char* in) { return String("\"") + in + "\""; }
String stringify(bool in) { return in ? "true" : "false"; }
String stringify(char in) {
char buf[64];
if(in < ' ')
sprintf(buf, "%d", in);
else
sprintf(buf, "%c", in);
return buf;
}
String stringify(int in) {
char buf[64];
sprintf(buf, "%d", in);
return buf;
}
String stringify(long in) {
char buf[64];
sprintf(buf, "%ld", in);
return buf;
}
//String stringify(long long in) {
// char buf[64];
// sprintf(buf, "%lld", in);
// return buf;
//}
String stringify(unsigned in) {
char buf[64];
sprintf(buf, "%u", in);
return buf;
}
String stringify(unsigned long in) {
char buf[64];
sprintf(buf, "%lu", in);
return buf;
}
//String stringify(unsigned long long in) {
// char buf[64];
// sprintf(buf, "%llu", in);
// return buf;
//}
String stringify(float in) {
char buf[64];
sprintf(buf, "%f", static_cast<double>(in));
return buf;
}
String stringify(double in) {
char buf[64];
sprintf(buf, "%f", in);
return buf;
}
String stringify(long double in) {
char buf[64];
sprintf(buf, "%Lf", in);
return buf;
}
// a struct defining a registered test callback
struct TestData
{
// not used for comparing
String m_suite; // the test suite in which the test was added
String m_name; // name of the test function
funcType m_f; // a function pointer to the test function
// fields by which difference of test functions shall be determined
const char* m_file; // the file in which the test was registered
unsigned m_line; // the line where the test was registered
TestData(const char* suite, const char* name, funcType f, const char* file, int line)
: m_suite()
, m_name()
, m_f(f)
, m_file(file)
, m_line(line) {
// trimming quotes of name
if(name) {
if(*name == '"')
++name;
size_t name_len = my_strlen(name);
if(name[name_len] != '"') {
m_name = name;
} else {
m_name = name;
m_name.c_str()[name_len] = '\0';
}
}
// trimming quotes of suite
if(suite) {
if(*suite == '"')
++suite;
size_t suite_len = my_strlen(suite);
if(suite[suite_len] != '"') {
m_suite = suite;
} else {
m_suite = suite;
m_suite[suite_len] = '\0';
}
}
}
bool operator==(const TestData& other) const {
return m_line == other.m_line && strcmp(m_file, other.m_file) == 0;
}
};
unsigned Hash(const TestData& in) {
return hashStr(reinterpret_cast<unsigned const char*>(in.m_file)) ^ in.m_line;
}
// a comparison function for using qsort on arrays with pointers to TestData structures
int TestDataComparator(const void* a, const void* b) {
const TestData* lhs = *static_cast<TestData* const*>(a);
const TestData* rhs = *static_cast<TestData* const*>(b);
#ifdef _MSC_VER
// this is needed because MSVC gives different case for drive letters
// for __FILE__ when evaluated in a header and a source file
int res = stricmp(lhs->m_file, rhs->m_file);
#else // _MSC_VER
int res = strcmp(lhs->m_file, rhs->m_file);
#endif // _MSC_VER
if(res != 0)
return res;
return static_cast<int>(lhs->m_line - rhs->m_line);
}
// holds the current test suite
const char*& getCurrentTestSuite() {
static const char* data = 0;
return data;
}
// sets the current test suite
int setTestSuiteName(const char* name) {
getCurrentTestSuite() = name;
return 0;
}
// all the registered tests
HashTable<TestData>& getRegisteredTests() {
static HashTable<TestData> data(DOCTEST_HASH_TABLE_NUM_BUCKETS);
return data;
}
// used by the macros for registering tests
int regTest(funcType f, unsigned line, const char* file, const char* name) {
getRegisteredTests().insert(TestData(getCurrentTestSuite(), name, f, file, line));
return 0;
}
// this is needed because MSVC does not permit mixing 2 exception handling schemes in a function
int callTestFunc(int (*testExecutionWrapper)(funcType), funcType f) {
int res = 0;
try {
if(testExecutionWrapper) {
res = testExecutionWrapper(f);
} else {
f();
}
if(getHasCurrentTestFailed())
res = 1;
} catch(const TestFailureException&) { return 1; } catch(...) {
printf("Unknown exception caught!\n");
res = 1;
}
return res;
}
// parses a comma separated list of words after a pattern in one of the arguments in argv
void parseFilter(int argc, char** argv, const char* pattern, Vector<String>& filters) {
String filtersString;
for(int i = 0; i < argc; ++i) {
const char* temp = strstr(argv[i], pattern);
if(temp) {
temp += my_strlen(pattern);
size_t len = my_strlen(temp);
if(len) {
filtersString = temp;
break;
}
}
}
// if we have found the filter string
if(filtersString.c_str()) {
// tokenize with "," as a separator
char* pch = strtok(filtersString.c_str(), ","); // modifies the string
while(pch != 0) {
if(my_strlen(pch))
filters.push_back(pch);
pch = strtok(0, ","); // uses the strtok() internal state to go to the next token
}
}
}
// parses an option from the command line (bool: type == 0, int: type == 1)
int parseOption(int argc, char** argv, const char* option, int type, int defaultVal) {
int outVal = defaultVal;
Vector<String> parsedValues;
parseFilter(argc, argv, option, parsedValues);
// if the option has been found (and there is only 1 value in the "comma separated list")
if(parsedValues.size() == 1) {
if(type == 0) {
// boolean
const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1
const char negative[][6] = {"0", "false", "off", "no"};
// if the value matches any of the positive/negative possibilities
for(size_t i = 0; i < 4; i++) {
if(parsedValues[0].compare(positive[i], true) == 0) {
outVal = 1;
break;
}
if(parsedValues[0].compare(negative[i], true) == 0) {
outVal = 0;
break;
}
}
} else {
// integer
int res = atoi(parsedValues[0].c_str());
if(res != 0)
outVal = res;
}
}
return outVal;
}
bool logAssert(const Result& res, bool threw, const char* expr, const char* assert_name,
bool is_check, const char* file, int line) {
if(!res.m_passed || threw) {
printf("%s(%d): FAILED! %s\n %s( %s )\n\n", file, line,
(threw ? "(threw exception)" : ""), assert_name,
(threw ? expr : res.m_decomposition.c_str()));
getHasCurrentTestFailed() = true;
return !is_check;
}
return false;
}
bool logAssertThrows(const char* expr, bool threw, bool is_check, const char* file, int line) {
if(!threw) {
printf("%s(%d): FAILED!\n %s( %s )\n\n", file, line,
(is_check ? "CHECK_THROWS" : "REQUIRE_THROWS"), expr);
getHasCurrentTestFailed() = true;
return !is_check;
}
return false;
}
bool logAssertThrowsAs(const char* expr, const char* as, bool threw, bool threw_as,
bool is_check, const char* file, int line) {
if(!threw || !threw_as) {
printf("%s(%d): FAILED! %s\n %s( %s , %s )\n\n", file, line,
(threw ? "(didn't throw an exception of the type)" : "(didn't throw at all)"),
(is_check ? "CHECK_THROWS_AS" : "REQUIRE_THROWS_AS"), expr, as);
getHasCurrentTestFailed() = true;
return !is_check;
}
return false;
}
bool logAssertNothrow(const char* expr, bool threw, bool is_check, const char* file, int line) {
if(threw) {
printf("%s(%d): FAILED!\n %s( %s )\n\n", file, line,
(is_check ? "CHECK_NOTHROW" : "REQUIRE_NOTHROW"), expr);
getHasCurrentTestFailed() = true;
return !is_check;
}
return false;
}
} // namespace detail
String::String(const char* in)
: m_str(0) {
if(in == 0)
return;
m_str = static_cast<char*>(malloc(detail::my_strlen(in) + 1));
strcpy(m_str, in);
}
String::String(const String& other)
: m_str(0) {
copy(other);
}
void String::copy(const String& other) {
if(m_str)
free(m_str);
m_str = 0;
if(other.m_str) {
// not using std::strlen() because of valgrind errors when optimizations are turned on
// 'Invalid read of size 4' when the test suite len (with '\0') is not a multiple of 4
// for details see http://stackoverflow.com/questions/35671155
const char* temp = other.m_str;
while(*temp)
++temp;
size_t len = temp - other.m_str;
m_str = static_cast<char*>(malloc(len + 1));
strcpy(m_str, other.m_str);
}
}
String::~String() {
if(m_str)
free(m_str);
}
String& String::operator=(const String& other) {
if(this != &other)
copy(other);
return *this;
}
String String::operator+(const String& other) const { return String(m_str) += other; }
String& String::operator+=(const String& other) {
using namespace detail;
if(m_str == 0) {
copy(other);
} else if(other.m_str != 0) {
char* newStr = static_cast<char*>(malloc(my_strlen(m_str) + my_strlen(other.m_str) + 1));
strcpy(newStr, m_str);
strcpy(newStr + my_strlen(m_str), other.m_str);
free(m_str);
m_str = newStr;
}
return *this;
}
int String::compare(const char* other, bool no_case) const {
if(no_case)
return detail::stricmp(m_str, other);
return strcmp(m_str, other);
}
int String::compare(const String& other, bool no_case) const {
if(no_case)
return detail::stricmp(m_str, other.m_str);
return strcmp(m_str, other.m_str);
}
Context::Context(int argc, char** argv)
: filters(6) // 6 different filters total
{
using namespace detail;
testExecutionWrapper = 0;
parseFilter(argc, argv, "-dt-file=", filters[0]);
parseFilter(argc, argv, "-dt-file-exclude=", filters[1]);
parseFilter(argc, argv, "-dt-suite=", filters[2]);
parseFilter(argc, argv, "-dt-suite-exclude=", filters[3]);
parseFilter(argc, argv, "-dt-name=", filters[4]);
parseFilter(argc, argv, "-dt-name-exclude=", filters[5]);
count = !!parseOption(argc, argv, "-dt-count=", 0, 0);
case_sensitive = !!parseOption(argc, argv, "-dt-case-sensitive=", 0, 0);
allow_overrides = !!parseOption(argc, argv, "-dt-override=", 0, 1);
separate_process = !!parseOption(argc, argv, "-dt-separate-process=", 0, 0);
exit_after_tests = !!parseOption(argc, argv, "-dt-exit=", 0, 0);
first = parseOption(argc, argv, "-dt-first=", 1, 1);
last = parseOption(argc, argv, "-dt-last=", 1, 0);
hash_table_histogram = !!parseOption(argc, argv, "-dt-hash-table-histogram=", 0, 0);
no_run = !!parseOption(argc, argv, "-dt-no-run=", 0, 0);
}
// allows the user to add procedurally to the filters from the command line
void Context::addFilter(const char* filter, const char* value) {
using namespace detail;
if(allow_overrides) {
size_t idx = 42;
if(strcmp(filter, "dt-file") == 0)
idx = 0;
if(strcmp(filter, "dt-file-exclude") == 0)
idx = 1;
if(strcmp(filter, "dt-suite") == 0)
idx = 2;
if(strcmp(filter, "dt-suite-exclude") == 0)
idx = 3;
if(strcmp(filter, "dt-name") == 0)
idx = 4;
if(strcmp(filter, "dt-name-exclude") == 0)
idx = 5;
// if the filter name is valid
if(idx != 42 && my_strlen(value))
filters[idx].push_back(value);
}
}
// allows the user to override procedurally the options from the command line
void Context::setOption(const char* option, int value) {
using namespace detail;
if(allow_overrides) {
if(strcmp(option, "dt-count") == 0)
count = !!value;
if(strcmp(option, "dt-case-sensitive") == 0)
case_sensitive = !!value;
if(strcmp(option, "dt-separate-process") == 0)
separate_process = !!value;
if(strcmp(option, "dt-exit") == 0)
exit_after_tests = !!value;
if(strcmp(option, "dt-first") == 0)
first = value;
if(strcmp(option, "dt-last") == 0)
last = value;
if(strcmp(option, "dt-hash-table-histogram") == 0)
hash_table_histogram = !!value;
if(strcmp(option, "dt-no-run") == 0)
no_run = !!value;
}
}
// allows the user to provide a test execution wrapper for custom exception handling
void Context::setTestExecutionWrapper(int (*f)(funcType)) {
using namespace detail;
testExecutionWrapper = f;
}
// the main function that does all the filtering and test running
int Context::runTests() {
using namespace detail;
// exit right now
if(no_run)
return 0;
const Vector<Vector<TestData> >& buckets = getRegisteredTests().getBuckets();
Vector<const TestData*> testDataArray;
for(size_t i = 0; i < buckets.size(); i++)
for(size_t k = 0; k < buckets[i].size(); k++)
testDataArray.push_back(&buckets[i][k]);
// sort the collected records
qsort(testDataArray.data(), testDataArray.size(), sizeof(TestData*), TestDataComparator);
if(hash_table_histogram) {
// find the most full bucket
size_t maxInBucket = 0;
for(size_t i = 0; i < buckets.size(); i++)
if(buckets[i].size() > maxInBucket)
maxInBucket = buckets[i].size();
// print a prettified histogram
printf("[doctest] hash table bucket histogram\n");
printf("============================================================\n");
printf("#bucket |count| relative count\n");
printf("============================================================\n");
for(size_t i = 0; i < buckets.size(); i++) {
printf("bucket %4d |%4d |", static_cast<int>(i), buckets[i].size());
float ratio = static_cast<float>(buckets[i].size()) / static_cast<float>(maxInBucket);
int numStars = static_cast<int>(ratio * 41);
for(int k = 0; k < numStars; ++k)
printf("*");
printf("\n");
}
printf("\n");
}
int numFilterPassedTests = 0;
int numFailed = 0;
// invoke the registered functions if they match the filter criteria (or just count them)
for(size_t i = 0; i < testDataArray.size(); i++) {
const TestData& data = *testDataArray[i];
if(!matchesAny(data.m_file, filters[0], 1, case_sensitive))
continue;
if(matchesAny(data.m_file, filters[1], 0, case_sensitive))
continue;
if(!matchesAny(data.m_suite, filters[2], 1, case_sensitive))
continue;
if(matchesAny(data.m_suite, filters[3], 0, case_sensitive))
continue;
if(!matchesAny(data.m_name, filters[4], 1, case_sensitive))
continue;
if(matchesAny(data.m_name, filters[5], 0, case_sensitive))
continue;
numFilterPassedTests++;
// do not execute the test if we are to only count the number of filter passing tests
if(count)
continue;
// skip the test if it is not in the execution range
if((last < numFilterPassedTests && first <= last) || (first > numFilterPassedTests))
continue;
// execute the test if it passes all the filtering
{
int res = 0;
#ifdef _MSC_VER
//__try {
#endif // _MSC_VER
getSubcasesPassed().clear();
do {
// reset the assertion state
getHasCurrentTestFailed() = false;
// reset some of the fields for subcases (except for the set of fully passed ones)
getSubcasesHasSkipped() = false;
getSubcasesCurrentLevel() = 0;
getSubcasesEnteredLevels().clear();
res += callTestFunc(testExecutionWrapper, data.m_f);
} while(getSubcasesHasSkipped() == true);
#ifdef _MSC_VER
//} __except(1) {
// printf("Unknown SEH exception caught!\n");
// res = 1;
//}
#endif // _MSC_VER
if(res) {
numFailed++;
}
}
}
if(count)
printf("[doctest] number of registered tests passing the current filters: "
"%d\n",
numFilterPassedTests);
if(exit_after_tests) {
exit(numFailed); // @TODO: is this legal? Or should I only pass EXIT_SUCCESS/EXIT_FAILURE?
}
return numFailed;
}
} // namespace doctest
#endif // DOCTEST_LIBRARY_IMPLEMENTATION
#endif // DOCTEST_IMPLEMENT
// == THIS SUPPLIES A MAIN FUNCTION AND SHOULD BE DONE ONLY IN ONE TRANSLATION UNIT
#if defined(DOCTEST_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_MAIN_CONFIGURED)
#define DOCTEST_MAIN_CONFIGURED
int main(int argc, char** argv) {
doctest::Context context(argc, argv);
return context.runTests();
}
#endif // DOCTEST_MAIN_CONFIGURED
#if defined(__clang__)
#pragma clang diagnostic pop
#endif // __clang__
#if defined(__GNUC__) && !defined(__clang__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 6)
#pragma GCC diagnostic pop
#endif // > gcc 4.6
#endif // __GNUC__
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER