786 lines
31 KiB
C++
786 lines
31 KiB
C++
#pragma once
|
|
#include <trantor/utils/NonCopyable.h>
|
|
#include <drogon/DrObject.h>
|
|
#include <drogon/utils/string_view.h>
|
|
#include <drogon/exports.h>
|
|
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
/**
|
|
* @brief Drogon Test is a minimal effort test framework developed because the
|
|
* major C++ test frameworks doesn't handle async programs well. Drogon Test's
|
|
* syntax is inspired by both Google Test and Catch2
|
|
*/
|
|
namespace drogon
|
|
{
|
|
namespace test
|
|
{
|
|
#define TEST_CTX drogon_test_ctx_
|
|
#define DROGON_TESTCASE_PREIX_ drtest__
|
|
#define DROGON_TESTCASE_PREIX_STR_ "drtest__"
|
|
#define TEST_FLAG_ drgood__
|
|
|
|
#define DROGON_TEST_STRINGIFY__(x) #x
|
|
#define DROGON_TEST_STRINGIFY(x) DROGON_TEST_STRINGIFY__(x)
|
|
#define DROGON_TEST_CONCAT__(a, b) a##b
|
|
#define DROGON_TEST_CONCAT(a, b) DROGON_TEST_CONCAT__(a, b)
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC)
|
|
#define DROGON_TEST_START_SUPRESSION_ _Pragma("GCC diagnostic push")
|
|
#define DROGON_TEST_END_SUPRESSION_ _Pragma("GCC diagnostic pop")
|
|
#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
|
|
_Pragma("GCC diagnostic ignored \"-Wparentheses\"")
|
|
#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
|
|
_Pragma("GCC diagnostic ignored \"-Wunused-value\"")
|
|
#elif defined(__clang__) && !defined(_MSC_VER)
|
|
#define DROGON_TEST_START_SUPRESSION_ _Pragma("clang diagnostic push")
|
|
#define DROGON_TEST_END_SUPRESSION_ _Pragma("clang diagnostic pop")
|
|
#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
|
|
_Pragma("clang diagnostic ignored \"-Wparentheses\"")
|
|
#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
|
|
_Pragma("clang diagnostic ignored \"-Wunused-value\"")
|
|
// MSVC don't have an equlivent. Add other compilers here
|
|
#else
|
|
#define DROGON_TEST_START_SUPRESSION_
|
|
#define DROGON_TEST_END_SUPRESSION_
|
|
#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_
|
|
#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_
|
|
#endif
|
|
|
|
class Case;
|
|
|
|
namespace internal
|
|
{
|
|
DROGON_EXPORT extern std::atomic<size_t> numAssertions;
|
|
DROGON_EXPORT extern std::atomic<size_t> numCorrectAssertions;
|
|
DROGON_EXPORT extern std::atomic<size_t> numFailedTestCases;
|
|
DROGON_EXPORT extern bool printSuccessfulTests;
|
|
|
|
DROGON_EXPORT void registerCase(Case* test);
|
|
DROGON_EXPORT void unregisterCase(Case* test);
|
|
|
|
template <typename _Tp, typename dummy = void>
|
|
struct is_printable : std::false_type
|
|
{
|
|
};
|
|
|
|
template <typename _Tp>
|
|
struct is_printable<_Tp,
|
|
typename std::enable_if<
|
|
std::is_same<decltype(std::cout << std::declval<_Tp>()),
|
|
std::ostream&>::value>::type>
|
|
: std::true_type
|
|
{
|
|
};
|
|
|
|
inline std::string escapeString(const string_view sv)
|
|
{
|
|
std::string result;
|
|
result.reserve(sv.size());
|
|
for (auto ch : sv)
|
|
{
|
|
if (ch == '\n')
|
|
result += "\\n";
|
|
else if (ch == '\r')
|
|
result += "\\r";
|
|
else if (ch == '\t')
|
|
result += "\\t";
|
|
else if (ch == '\b')
|
|
result += "\\b";
|
|
else if (ch == '\\')
|
|
result += "\\\\";
|
|
else if (ch == '"')
|
|
result += "\"";
|
|
else if (ch == '\v')
|
|
result += "\\v";
|
|
else if (ch == '\a')
|
|
result += "\\a";
|
|
else
|
|
result.push_back(ch);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DROGON_EXPORT std::string prettifyString(const string_view sv,
|
|
size_t maxLength = 120);
|
|
|
|
#ifdef __cpp_fold_expressions
|
|
template <typename... Args>
|
|
inline void outputReason(Args&&... args)
|
|
{
|
|
(std::cout << ... << std::forward<Args>(args));
|
|
}
|
|
#else
|
|
template <typename Head>
|
|
inline void outputReason(Head&& head)
|
|
{
|
|
std::cout << std::forward<Head>(head);
|
|
}
|
|
template <typename Head, typename... Tail>
|
|
inline void outputReason(Head&& head, Tail&&... tail)
|
|
{
|
|
std::cout << std::forward<Head>(head);
|
|
outputReason(std::forward<Tail>(tail)...);
|
|
}
|
|
#endif
|
|
|
|
template <bool P>
|
|
struct AttemptPrintViaStream
|
|
{
|
|
template <typename T>
|
|
std::string operator()(const T& v)
|
|
{
|
|
return "{un-printable}";
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct AttemptPrintViaStream<true>
|
|
{
|
|
template <typename T>
|
|
std::string operator()(const T& v)
|
|
{
|
|
std::stringstream ss;
|
|
ss << v;
|
|
return ss.str();
|
|
}
|
|
};
|
|
|
|
struct StringPrinter
|
|
{
|
|
std::string operator()(const string_view& v)
|
|
{
|
|
return prettifyString(v);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
inline std::string attemptPrint(T&& v)
|
|
{
|
|
using DefaultPrinter =
|
|
internal::AttemptPrintViaStream<is_printable<T>::value>;
|
|
|
|
// Poor man's if constexpr because SFINAE don't disambiguate between
|
|
// possible resolutions
|
|
return typename std::conditional<std::is_convertible<T, string_view>::value,
|
|
internal::StringPrinter,
|
|
DefaultPrinter>::type()(v);
|
|
}
|
|
|
|
// Specializations to reduce template construction
|
|
template <>
|
|
inline std::string attemptPrint(const std::nullptr_t& v)
|
|
{
|
|
return "nullptr";
|
|
}
|
|
|
|
template <>
|
|
inline std::string attemptPrint(const char& v)
|
|
{
|
|
return "'" + std::string(1, v) + "'";
|
|
}
|
|
|
|
inline std::string stringifyFuncCall(const std::string& funcName)
|
|
{
|
|
return funcName + "()";
|
|
}
|
|
|
|
inline std::string stringifyFuncCall(const std::string& funcName,
|
|
const std::string& param1)
|
|
{
|
|
return funcName + "(" + param1 + ")";
|
|
}
|
|
|
|
inline std::string stringifyFuncCall(const std::string& funcName,
|
|
const std::string& param1,
|
|
const std::string& param2)
|
|
{
|
|
return funcName + "(" + param1 + ", " + param2 + ")";
|
|
}
|
|
|
|
struct ComparsionResult
|
|
{
|
|
std::pair<bool, std::string> result() const
|
|
{
|
|
return {comparsionResilt_, expansion_};
|
|
}
|
|
|
|
bool comparsionResilt_;
|
|
std::string expansion_;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Lhs
|
|
{
|
|
template <typename _ = void> // HACK: prevent this function to be evaluated
|
|
// when not invoked
|
|
std::pair<bool, std::string> result() const
|
|
{
|
|
return {(bool)ref_, attemptPrint(ref_)};
|
|
}
|
|
|
|
Lhs(const T& lhs) : ref_(lhs)
|
|
{
|
|
}
|
|
const T& ref_;
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator<(const RhsType& rhs)
|
|
{
|
|
return ComparsionResult{ref_ < rhs,
|
|
attemptPrint(ref_) + " < " +
|
|
attemptPrint(ref_)};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator>(const RhsType& rhs)
|
|
{
|
|
return ComparsionResult{ref_ > rhs,
|
|
attemptPrint(ref_) + " > " + attemptPrint(rhs)};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator<=(const RhsType& rhs)
|
|
{
|
|
return ComparsionResult{ref_ <= rhs,
|
|
attemptPrint(ref_) +
|
|
" <= " + attemptPrint(rhs)};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator>=(const RhsType& rhs)
|
|
{
|
|
return ComparsionResult{ref_ >= rhs,
|
|
attemptPrint(ref_) +
|
|
" >= " + attemptPrint(rhs)};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator==(const RhsType& rhs)
|
|
{
|
|
return ComparsionResult{ref_ == rhs,
|
|
attemptPrint(ref_) +
|
|
" == " + attemptPrint(rhs)};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator!=(const RhsType& rhs)
|
|
{
|
|
return ComparsionResult{ref_ != rhs,
|
|
attemptPrint(ref_) +
|
|
" != " + attemptPrint(rhs)};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator&&(const RhsType& rhs)
|
|
{
|
|
static_assert(!std::is_same<RhsType, void>::value,
|
|
" && is not supported in expression decomposition");
|
|
return {};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator||(const RhsType& rhs)
|
|
{
|
|
static_assert(!std::is_same<RhsType, void>::value,
|
|
" || is not supported in expression decomposition");
|
|
return {};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator|(const RhsType& rhs)
|
|
{
|
|
static_assert(!std::is_same<RhsType, void>::value,
|
|
" | is not supported in expression decomposition");
|
|
return {};
|
|
}
|
|
|
|
template <typename RhsType>
|
|
ComparsionResult operator&(const RhsType& rhs)
|
|
{
|
|
static_assert(!std::is_same<RhsType, void>::value,
|
|
" & is not supported in expression decomposition");
|
|
return {};
|
|
}
|
|
};
|
|
|
|
struct Decomposer
|
|
{
|
|
template <typename T>
|
|
Lhs<T> operator<=(const T& other)
|
|
{
|
|
return Lhs<T>(other);
|
|
}
|
|
};
|
|
|
|
} // namespace internal
|
|
|
|
class DROGON_EXPORT ThreadSafeStream final
|
|
{
|
|
public:
|
|
ThreadSafeStream(std::ostream& os) : os_(os)
|
|
{
|
|
mtx_.lock();
|
|
}
|
|
|
|
~ThreadSafeStream()
|
|
{
|
|
mtx_.unlock();
|
|
}
|
|
|
|
template <typename T>
|
|
std::ostream& operator<<(const T& rhs)
|
|
{
|
|
return os_ << rhs;
|
|
}
|
|
|
|
static std::mutex mtx_;
|
|
std::ostream& os_;
|
|
};
|
|
|
|
DROGON_EXPORT ThreadSafeStream print();
|
|
DROGON_EXPORT ThreadSafeStream printErr();
|
|
|
|
class CaseBase : public trantor::NonCopyable
|
|
{
|
|
public:
|
|
CaseBase() = default;
|
|
CaseBase(const std::string& name) : name_(name)
|
|
{
|
|
}
|
|
CaseBase(std::shared_ptr<CaseBase> parent, const std::string& name)
|
|
: parent_(parent), name_(name)
|
|
{
|
|
}
|
|
virtual ~CaseBase() = default;
|
|
|
|
std::string fullname() const
|
|
{
|
|
std::string result;
|
|
auto curr = this;
|
|
while (curr != nullptr)
|
|
{
|
|
result = curr->name() + result;
|
|
if (curr->parent_ != nullptr)
|
|
result = "." + result;
|
|
curr = curr->parent_.get();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const std::string& name() const
|
|
{
|
|
return name_;
|
|
}
|
|
|
|
void setFailed()
|
|
{
|
|
if (failed_ == false)
|
|
{
|
|
internal::numFailedTestCases++;
|
|
failed_ = true;
|
|
}
|
|
}
|
|
|
|
bool failed() const
|
|
{
|
|
return failed_;
|
|
}
|
|
|
|
protected:
|
|
bool failed_ = false;
|
|
std::shared_ptr<CaseBase> parent_ = nullptr;
|
|
std::string name_;
|
|
};
|
|
|
|
class Case : public CaseBase
|
|
{
|
|
public:
|
|
Case(const std::string& name) : CaseBase(name)
|
|
{
|
|
internal::registerCase(this);
|
|
}
|
|
|
|
Case(std::shared_ptr<Case> parent, const std::string& name)
|
|
: CaseBase(parent, name)
|
|
{
|
|
internal::registerCase(this);
|
|
}
|
|
|
|
virtual ~Case()
|
|
{
|
|
internal::unregisterCase(this);
|
|
}
|
|
};
|
|
|
|
struct TestCase : public CaseBase
|
|
{
|
|
TestCase(const std::string& name) : CaseBase(name)
|
|
{
|
|
}
|
|
virtual ~TestCase() = default;
|
|
virtual void doTest_(std::shared_ptr<Case>) = 0;
|
|
};
|
|
|
|
DROGON_EXPORT void printTestStats();
|
|
DROGON_EXPORT int run(int argc, char** argv);
|
|
} // namespace test
|
|
} // namespace drogon
|
|
|
|
#define ERROR_MSG(func_name, expr) \
|
|
drogon::test::printErr() \
|
|
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
|
|
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
|
|
<< " \x1B[0;31m FAILED:\x1B[0m\n" \
|
|
<< " \033[0;34m" \
|
|
<< drogon::test::internal::stringifyFuncCall(func_name, expr) \
|
|
<< "\x1B[0m\n"
|
|
|
|
#define PASSED_MSG(func_name, expr) \
|
|
drogon::test::print() \
|
|
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
|
|
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
|
|
<< " \x1B[0;32m PASSED:\x1B[0m\n" \
|
|
<< " \033[0;34m" \
|
|
<< drogon::test::internal::stringifyFuncCall(func_name, expr) \
|
|
<< "\x1B[0m\n"
|
|
|
|
#define SET_TEST_SUCCESS__ \
|
|
do \
|
|
{ \
|
|
TEST_FLAG_ = true; \
|
|
} while (0);
|
|
|
|
#define TEST_INTERNAL__(func_name, \
|
|
expr, \
|
|
eval, \
|
|
on_exception, \
|
|
on_non_standard_exception, \
|
|
on_leaving) \
|
|
do \
|
|
{ \
|
|
bool TEST_FLAG_ = false; \
|
|
using drogon::test::internal::stringifyFuncCall; \
|
|
using drogon::test::printErr; \
|
|
drogon::test::internal::numAssertions++; \
|
|
try \
|
|
{ \
|
|
eval; \
|
|
} \
|
|
catch (const std::exception& e) \
|
|
{ \
|
|
(void)e; \
|
|
on_exception; \
|
|
} \
|
|
catch (...) \
|
|
{ \
|
|
on_non_standard_exception; \
|
|
} \
|
|
if (TEST_FLAG_) \
|
|
drogon::test::internal::numCorrectAssertions++; \
|
|
else \
|
|
TEST_CTX->setFailed(); \
|
|
on_leaving; \
|
|
} while (0);
|
|
|
|
#define EVAL_AND_CHECK_TRUE__(func_name, expr) \
|
|
do \
|
|
{ \
|
|
bool drresult__; \
|
|
std::string drexpansion__; \
|
|
DROGON_TEST_START_SUPRESSION_ \
|
|
DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
|
|
std::tie(drresult__, drexpansion__) = \
|
|
(drogon::test::internal::Decomposer() <= expr).result(); \
|
|
DROGON_TEST_END_SUPRESSION_ \
|
|
if (!drresult__) \
|
|
{ \
|
|
ERROR_MSG(func_name, #expr) \
|
|
<< "With expansion\n" \
|
|
<< " \033[0;33m" << drexpansion__ << "\x1B[0m\n\n"; \
|
|
} \
|
|
else \
|
|
SET_TEST_SUCCESS__; \
|
|
} while (0);
|
|
|
|
#define PRINT_UNEXPECTED_EXCEPTION__(func_name, expr) \
|
|
do \
|
|
{ \
|
|
ERROR_MSG(func_name, expr) \
|
|
<< "An unexpected exception is thrown. what():\n" \
|
|
<< " \033[0;33m" << e.what() << "\x1B[0m\n\n"; \
|
|
} while (0);
|
|
|
|
#define PRINT_PASSED__(func_name, expr) \
|
|
do \
|
|
{ \
|
|
if (drogon::test::internal::printSuccessfulTests && TEST_FLAG_) \
|
|
{ \
|
|
PASSED_MSG(func_name, expr) << "\n"; \
|
|
} \
|
|
} while (0);
|
|
|
|
#define RETURN_ON_FAILURE__ \
|
|
do \
|
|
{ \
|
|
if (!TEST_FLAG_) \
|
|
return; \
|
|
} while (0);
|
|
|
|
#define CO_RETURN_ON_FAILURE__ \
|
|
do \
|
|
{ \
|
|
if (!TEST_FLAG_) \
|
|
co_return; \
|
|
} while (0);
|
|
|
|
#define DIE_ON_FAILURE__ \
|
|
do \
|
|
{ \
|
|
using namespace drogon::test; \
|
|
if (!TEST_FLAG_) \
|
|
{ \
|
|
printTestStats(); \
|
|
printErr() << "Force exiting due to a mandation failed.\n"; \
|
|
exit(1); \
|
|
} \
|
|
} while (0);
|
|
|
|
#define PRINT_NONSTANDARD_EXCEPTION__(func_name, expr) \
|
|
do \
|
|
{ \
|
|
ERROR_MSG(func_name, expr) \
|
|
<< "Unexpected unknown exception is thrown.\n\n"; \
|
|
} while (0);
|
|
|
|
#define EVAL__(expr) \
|
|
do \
|
|
{ \
|
|
expr; \
|
|
} while (0);
|
|
|
|
#define NOTHING__ \
|
|
{ \
|
|
}
|
|
|
|
#define PRINT_ERR_NOEXCEPTION__(expr, func_name) \
|
|
do \
|
|
{ \
|
|
if (!TEST_FLAG_) \
|
|
ERROR_MSG(func_name, expr) \
|
|
<< "With expecitation\n" \
|
|
<< " Expected to throw an exception. But none are " \
|
|
"thrown.\n\n"; \
|
|
} while (0);
|
|
|
|
#define PRINT_ERR_WITHEXCEPTION__(expr, func_name) \
|
|
do \
|
|
{ \
|
|
if (!TEST_FLAG_) \
|
|
ERROR_MSG(func_name, expr) \
|
|
<< "With expecitation\n" \
|
|
<< " Should to not throw an exception. But one is " \
|
|
"thrown.\n\n"; \
|
|
} while (0);
|
|
|
|
#define PRINT_ERR_BAD_EXCEPTION__( \
|
|
expr, func_name, excep_type, exceptionThrown, correctExceptionType) \
|
|
do \
|
|
{ \
|
|
assert((exceptionThrown && correctExceptionType) || !exceptionThrown); \
|
|
if (exceptionThrown == true && correctExceptionType == false) \
|
|
{ \
|
|
ERROR_MSG(func_name, expr) \
|
|
<< "With expecitation\n" \
|
|
<< " Exception have been throw but not of type \033[0;33m" \
|
|
<< #excep_type << "\033[0m.\n\n"; \
|
|
} \
|
|
else if (exceptionThrown == false) \
|
|
{ \
|
|
ERROR_MSG(func_name, expr) \
|
|
<< "With expecitation\n" \
|
|
<< " A \033[0;33m" << #excep_type \
|
|
<< "\033[0m exception is expected. But nothing was thrown" \
|
|
<< "\033[0m.\n\n"; \
|
|
} \
|
|
} while (0);
|
|
|
|
#define CHECK_INTERNAL__(expr, func_name, on_leave) \
|
|
do \
|
|
{ \
|
|
TEST_INTERNAL__(func_name, \
|
|
expr, \
|
|
EVAL_AND_CHECK_TRUE__(func_name, expr), \
|
|
PRINT_UNEXPECTED_EXCEPTION__(func_name, #expr), \
|
|
PRINT_NONSTANDARD_EXCEPTION__(func_name, #expr), \
|
|
on_leave PRINT_PASSED__(func_name, #expr)); \
|
|
} while (0)
|
|
|
|
#define CHECK_THROWS_INTERNAL__(expr, func_name, on_leave) \
|
|
do \
|
|
{ \
|
|
TEST_INTERNAL__(func_name, \
|
|
expr, \
|
|
EVAL__(expr), \
|
|
SET_TEST_SUCCESS__, \
|
|
SET_TEST_SUCCESS__, \
|
|
PRINT_ERR_NOEXCEPTION__(#expr, func_name) \
|
|
on_leave PRINT_PASSED__(func_name, #expr)); \
|
|
} while (0)
|
|
|
|
#define CHECK_THROWS_AS_INTERNAL__(expr, func_name, except_type, on_leave) \
|
|
do \
|
|
{ \
|
|
bool exceptionThrown = false; \
|
|
TEST_INTERNAL__( \
|
|
func_name, \
|
|
expr, \
|
|
EVAL__(expr), \
|
|
{ \
|
|
exceptionThrown = true; \
|
|
if (dynamic_cast<const except_type*>(&e) != nullptr) \
|
|
SET_TEST_SUCCESS__; \
|
|
}, \
|
|
{ exceptionThrown = true; }, \
|
|
PRINT_ERR_BAD_EXCEPTION__(#expr ", " #except_type, \
|
|
func_name, \
|
|
except_type, \
|
|
exceptionThrown, \
|
|
TEST_FLAG_) \
|
|
on_leave PRINT_PASSED__(func_name, #expr ", " #except_type)); \
|
|
} while (0)
|
|
|
|
#define CHECK_NOTHROW_INTERNAL__(expr, func_name, on_leave) \
|
|
do \
|
|
{ \
|
|
TEST_INTERNAL__(func_name, \
|
|
expr, \
|
|
EVAL__(expr) SET_TEST_SUCCESS__, \
|
|
NOTHING__, \
|
|
NOTHING__, \
|
|
PRINT_ERR_WITHEXCEPTION__(#expr, func_name) \
|
|
on_leave PRINT_PASSED__(func_name, #expr)); \
|
|
} while (0)
|
|
|
|
#define CHECK(expr) CHECK_INTERNAL__(expr, "CHECK", NOTHING__)
|
|
#define CHECK_THROWS(expr) \
|
|
CHECK_THROWS_INTERNAL__(expr, "CHECK_THROWS", NOTHING__)
|
|
#define CHECK_NOTHROW(expr) \
|
|
CHECK_NOTHROW_INTERNAL__(expr, "CHECK_NOTHROW", NOTHING__)
|
|
#define CHECK_THROWS_AS(expr, except_type) \
|
|
CHECK_THROWS_AS_INTERNAL__(expr, "CHECK_THROWS_AS", except_type, NOTHING__)
|
|
|
|
#define REQUIRE(expr) CHECK_INTERNAL__(expr, "REQUIRE", RETURN_ON_FAILURE__)
|
|
#define REQUIRE_THROWS(expr) \
|
|
CHECK_THROWS_INTERNAL__(expr, "REQUIRE_THROWS", RETURN_ON_FAILURE__)
|
|
#define REQUIRE_NOTHROW(expr) \
|
|
CHECK_NOTHROW_INTERNAL__(expr, "REQUIRE_NOTHROW", RETURN_ON_FAILURE__)
|
|
#define REQUIRE_THROWS_AS(expr, except_type) \
|
|
CHECK_THROWS_AS_INTERNAL__(expr, \
|
|
"REQUIRE_THROWS_AS", \
|
|
except_type, \
|
|
RETURN_ON_FAILURE__)
|
|
|
|
#define CO_REQUIRE(expr) \
|
|
CHECK_INTERNAL__(expr, "CO_REQUIRE", CO_RETURN_ON_FAILURE__)
|
|
#define CO_REQUIRE_THROWS(expr) \
|
|
CHECK_THROWS_INTERNAL__(expr, "CO_REQUIRE_THROWS", CO_RETURN_ON_FAILURE__)
|
|
#define CO_REQUIRE_NOTHROW(expr) \
|
|
CHECK_NOTHROW_INTERNAL__(expr, "CO_REQUIRE_NOTHROW", CO_RETURN_ON_FAILURE__)
|
|
#define CO_REQUIRE_THROWS_AS(expr, except_type) \
|
|
CHECK_THROWS_AS_INTERNAL__(expr, \
|
|
"CO_REQUIRE_THROWS_AS", \
|
|
except_type, \
|
|
CO_RETURN_ON_FAILURE__)
|
|
|
|
#define MANDATE(expr) CHECK_INTERNAL__(expr, "MANDATE", DIE_ON_FAILURE__)
|
|
#define MANDATE_THROWS(expr) \
|
|
CHECK_THROWS_INTERNAL__(expr, "MANDATE_THROWS", DIE_ON_FAILURE__)
|
|
#define MANDATE_NOTHROW(expr) \
|
|
CHECK_NOTHROW_INTERNAL__(expr, "MANDATE_NOTHROW", DIE_ON_FAILURE__)
|
|
#define MANDATE_THROWS_AS(expr, except_type) \
|
|
CHECK_THROWS_AS_INTERNAL__(expr, \
|
|
"MANDATE_THROWS_AS", \
|
|
except_type, \
|
|
DIE_ON_FAILURE__)
|
|
|
|
#define STATIC_REQUIRE(expr) \
|
|
do \
|
|
{ \
|
|
DROGON_TEST_START_SUPRESSION_ \
|
|
DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
|
|
TEST_CTX; \
|
|
DROGON_TEST_END_SUPRESSION_ \
|
|
drogon::test::internal::numAssertions++; \
|
|
static_assert((expr), #expr " failed."); \
|
|
drogon::test::internal::numCorrectAssertions++; \
|
|
} while (0)
|
|
|
|
#define FAIL(...) \
|
|
do \
|
|
{ \
|
|
using namespace drogon::test; \
|
|
TEST_CTX->setFailed(); \
|
|
printErr() << "\x1B[1;37mIn test case " << TEST_CTX->fullname() \
|
|
<< "\n" \
|
|
<< "\x1B[0;37m" << __FILE__ << ":" << __LINE__ \
|
|
<< " \x1B[0;31m FAILED:\x1B[0m\n" \
|
|
<< " Reason: "; \
|
|
drogon::test::internal::outputReason(__VA_ARGS__); \
|
|
printErr() << "\n\n"; \
|
|
drogon::test::internal::numAssertions++; \
|
|
} while (0)
|
|
#define FAULT(...) \
|
|
do \
|
|
{ \
|
|
using namespace drogon::test; \
|
|
FAIL(__VA_ARGS__); \
|
|
printTestStats(); \
|
|
printErr() << "Force exiting due to a FAULT statement.\n"; \
|
|
exit(1); \
|
|
} while (0)
|
|
|
|
#define SUCCESS() \
|
|
do \
|
|
{ \
|
|
DROGON_TEST_START_SUPRESSION_ \
|
|
DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
|
|
TEST_CTX; \
|
|
DROGON_TEST_END_SUPRESSION_ \
|
|
if (drogon::test::internal::printSuccessfulTests) \
|
|
drogon::test::print() \
|
|
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
|
|
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
|
|
<< " \x1B[0;32m PASSED:\x1B[0m\n" \
|
|
<< " \033[0;34mSUCCESS()\x1B[0m\n\n"; \
|
|
drogon::test::internal::numAssertions++; \
|
|
drogon::test::internal::numCorrectAssertions++; \
|
|
} while (0)
|
|
|
|
#define DROGON_TEST_CLASS_NAME_(test_name) \
|
|
DROGON_TEST_CONCAT(DROGON_TESTCASE_PREIX_, test_name)
|
|
|
|
#define DROGON_TEST(test_name) \
|
|
struct DROGON_TEST_CLASS_NAME_(test_name) \
|
|
: public drogon::DrObject<DROGON_TEST_CLASS_NAME_(test_name)>, \
|
|
public drogon::test::TestCase \
|
|
{ \
|
|
DROGON_TEST_CLASS_NAME_(test_name) \
|
|
() : drogon::test::TestCase(#test_name) \
|
|
{ \
|
|
} \
|
|
inline void doTest_(std::shared_ptr<drogon::test::Case>) override; \
|
|
}; \
|
|
void DROGON_TEST_CLASS_NAME_(test_name)::doTest_( \
|
|
std::shared_ptr<drogon::test::Case> TEST_CTX)
|
|
#define SUBTEST(name) (std::make_shared<drogon::test::Case>(TEST_CTX, name))
|
|
#define SUBSECTION(name) \
|
|
for (std::shared_ptr<drogon::test::Case> ctx_hold__ = TEST_CTX, \
|
|
ctx_tmp__ = SUBTEST(#name); \
|
|
ctx_tmp__ != nullptr; \
|
|
TEST_CTX = ctx_hold__, ctx_tmp__ = nullptr) \
|
|
if (TEST_CTX = ctx_tmp__, TEST_CTX != nullptr)
|