2#include <trantor/utils/NonCopyable.h>
4#include <drogon/exports.h>
22#define TEST_CTX drogon_test_ctx_
23#define DROGON_TESTCASE_PREIX_ drtest__
24#define DROGON_TESTCASE_PREIX_STR_ "drtest__"
25#define TEST_FLAG_ drgood__
27#define DROGON_TEST_STRINGIFY__(x) #x
28#define DROGON_TEST_STRINGIFY(x) DROGON_TEST_STRINGIFY__(x)
29#define DROGON_TEST_CONCAT__(a, b) a##b
30#define DROGON_TEST_CONCAT(a, b) DROGON_TEST_CONCAT__(a, b)
32#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC)
33#define DROGON_TEST_START_SUPRESSION_ _Pragma("GCC diagnostic push")
34#define DROGON_TEST_END_SUPRESSION_ _Pragma("GCC diagnostic pop")
35#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
36 _Pragma("GCC diagnostic ignored \"-Wparentheses\"")
37#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
38 _Pragma("GCC diagnostic ignored \"-Wunused-value\"")
39#elif defined(__clang__) && !defined(_MSC_VER)
40#define DROGON_TEST_START_SUPRESSION_ _Pragma("clang diagnostic push")
41#define DROGON_TEST_END_SUPRESSION_ _Pragma("clang diagnostic pop")
42#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
43 _Pragma("clang diagnostic ignored \"-Wparentheses\"")
44#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
45 _Pragma("clang diagnostic ignored \"-Wunused-value\"")
48#define DROGON_TEST_START_SUPRESSION_
49#define DROGON_TEST_END_SUPRESSION_
50#define DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_
51#define DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_
58DROGON_EXPORT
extern std::atomic<size_t> numAssertions;
59DROGON_EXPORT
extern std::atomic<size_t> numCorrectAssertions;
60DROGON_EXPORT
extern std::atomic<size_t> numFailedTestCases;
61DROGON_EXPORT
extern bool printSuccessfulTests;
63DROGON_EXPORT
void registerCase(Case *test);
64DROGON_EXPORT
void unregisterCase(Case *test);
66template <
typename _Tp,
typename dummy =
void>
71template <
typename _Tp>
74 std::enable_if_t<std::is_same_v<decltype(std::cout << std::declval<_Tp>()),
75 std::ostream &>>> : std::true_type
79inline std::string escapeString(const std::string_view sv)
82 result.reserve(sv.size());
102 result.push_back(ch);
107DROGON_EXPORT std::string prettifyString(const std::string_view sv,
108 size_t maxLength = 120);
110template <typename... Args>
111inline void outputReason(Args &&...args)
113 (std::cout << ... << std::forward<Args>(args));
117inline std::string attemptPrint(T &&v)
119 using Type = std::remove_cv_t<std::remove_reference_t<T>>;
120 if constexpr (std::is_same_v<Type, std::nullptr_t>)
122 else if constexpr (std::is_same_v<Type, char>)
123 return "'" + std::string(1, v) + "'";
124 else if constexpr (std::is_convertible_v<Type, std::string_view>)
125 return prettifyString(v);
126 else if constexpr (internal::is_printable<Type>::value)
128 std::stringstream ss;
132 return "{un-printable}";
135inline std::string stringifyFuncCall(const std::string &funcName)
137 return funcName + "()";
140inline std::string stringifyFuncCall(const std::string &funcName,
141 const std::string ¶m1)
143 return funcName + "(" + param1 + ")";
146inline std::string stringifyFuncCall(const std::string &funcName,
147 const std::string ¶m1,
148 const std::string ¶m2)
150 return funcName + "(" + param1 + ", " + param2 + ")";
153struct ComparsionResult
155 std::pair<bool, std::string> result() const
157 return {comparsionResilt_, expansion_};
160 bool comparsionResilt_;
161 std::string expansion_;
167 template <typename _ = void>
169 std::pair<bool, std::string> result() const
171 return {(bool)ref_, attemptPrint(ref_)};
174 Lhs(const T &lhs) : ref_(lhs)
180 template <typename RhsType>
181 ComparsionResult operator<(const RhsType &rhs)
183 return ComparsionResult{ref_ < rhs,
184 attemptPrint(ref_) + " < " +
188 template <typename RhsType>
189 ComparsionResult operator>(const RhsType &rhs)
191 return ComparsionResult{ref_ > rhs,
192 attemptPrint(ref_) + " > " + attemptPrint(rhs)};
195 template <typename RhsType>
196 ComparsionResult operator<=(const RhsType &rhs)
198 return ComparsionResult{ref_ <= rhs,
200 " <= " + attemptPrint(rhs)};
203 template <typename RhsType>
204 ComparsionResult operator>=(const RhsType &rhs)
206 return ComparsionResult{ref_ >= rhs,
208 " >= " + attemptPrint(rhs)};
211 template <typename RhsType>
212 ComparsionResult operator==(const RhsType &rhs)
214 return ComparsionResult{ref_ == rhs,
216 " == " + attemptPrint(rhs)};
219 template <typename RhsType>
220 ComparsionResult operator!=(const RhsType &rhs)
222 return ComparsionResult{ref_ != rhs,
224 " != " + attemptPrint(rhs)};
227 template <typename RhsType>
228 ComparsionResult operator&&(const RhsType &rhs)
230 static_assert(!std::is_same_v<RhsType, void>,
231 " && is not supported in expression decomposition");
235 template <typename RhsType>
236 ComparsionResult operator||(const RhsType &rhs)
238 static_assert(!std::is_same_v<RhsType, void>,
239 " || is not supported in expression decomposition");
243 template <typename RhsType>
244 ComparsionResult operator|(const RhsType &rhs)
246 static_assert(!std::is_same_v<RhsType, void>,
247 " | is not supported in expression decomposition");
251 template <typename RhsType>
252 ComparsionResult operator&(const RhsType &rhs)
254 static_assert(!std::is_same_v<RhsType, void>,
255 " & is not supported in expression decomposition");
262 template <typename T>
263 Lhs<T> operator<=(const T &other)
265 return Lhs<T>(other);
271class DROGON_EXPORT ThreadSafeStream final
274 ThreadSafeStream(std::ostream &os) : os_(os)
284 template <typename T>
285 std::ostream &operator<<(const T &rhs)
290 static std::mutex mtx_;
294DROGON_EXPORT ThreadSafeStream print();
295DROGON_EXPORT ThreadSafeStream printErr();
297class CaseBase : public trantor::NonCopyable
300 CaseBase() = default;
302 CaseBase(const std::string &name) : name_(name)
306 CaseBase(std::shared_ptr<CaseBase> parent, const std::string &name)
307 : parent_(parent), name_(name)
311 virtual ~CaseBase() = default;
313 std::string fullname() const
317 while (curr != nullptr)
319 result = curr->name() + result;
320 if (curr->parent_ != nullptr)
321 result = "." + result;
322 curr = curr->parent_.get();
327 const std::string &name() const
334 if (failed_ == false)
336 internal::numFailedTestCases++;
347 bool failed_ = false;
348 std::shared_ptr<CaseBase> parent_ = nullptr;
352class Case : public CaseBase
355 Case(const std::string &name) : CaseBase(name)
357 internal::registerCase(this);
360 Case(std::shared_ptr<Case> parent, const std::string &name)
361 : CaseBase(parent, name)
363 internal::registerCase(this);
368 internal::unregisterCase(this);
372struct TestCase : public CaseBase
374 TestCase(const std::string &name) : CaseBase(name)
378 virtual ~TestCase() = default;
379 virtual void doTest_(std::shared_ptr<Case>) = 0;
382DROGON_EXPORT void printTestStats();
383DROGON_EXPORT int run(int argc, char **argv);
387#define ERROR_MSG(func_name, expr) \
388 drogon::test::printErr() \
389 << "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
390 << "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
391 << " \x1B[0;31m FAILED:\x1B[0m\n" \
393 << drogon::test::internal::stringifyFuncCall(func_name, expr) \
396#define PASSED_MSG(func_name, expr) \
397 drogon::test::print() \
398 << "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
399 << "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
400 << " \x1B[0;32m PASSED:\x1B[0m\n" \
402 << drogon::test::internal::stringifyFuncCall(func_name, expr) \
405#define SET_TEST_SUCCESS__ \
411#define TEST_INTERNAL__(func_name, \
415 on_non_standard_exception, \
419 bool TEST_FLAG_ = false; \
420 using drogon::test::internal::stringifyFuncCall; \
421 using drogon::test::printErr; \
422 drogon::test::internal::numAssertions++; \
427 catch (const std::exception &e) \
434 on_non_standard_exception; \
437 drogon::test::internal::numCorrectAssertions++; \
439 TEST_CTX->setFailed(); \
443#define EVAL_AND_CHECK_TRUE__(func_name, expr) \
447 std::string drexpansion__; \
448 DROGON_TEST_START_SUPRESSION_ \
449 DROGON_TEST_SUPPRESS_PARENTHESES_WARNING_ \
450 std::tie(drresult__, drexpansion__) = \
451 (drogon::test::internal::Decomposer() <= expr).result(); \
452 DROGON_TEST_END_SUPRESSION_ \
455 ERROR_MSG(func_name, #expr) \
456 << "With expansion\n" \
457 << " \033[0;33m" << drexpansion__ << "\x1B[0m\n\n"; \
460 SET_TEST_SUCCESS__; \
463#define PRINT_UNEXPECTED_EXCEPTION__(func_name, expr) \
466 ERROR_MSG(func_name, expr) \
467 << "An unexpected exception is thrown. what():\n" \
468 << " \033[0;33m" << e.what() << "\x1B[0m\n\n"; \
471#define PRINT_PASSED__(func_name, expr) \
474 if (drogon::test::internal::printSuccessfulTests && TEST_FLAG_) \
476 PASSED_MSG(func_name, expr) << "\n"; \
480#define RETURN_ON_FAILURE__ \
487#define CO_RETURN_ON_FAILURE__ \
494#define DIE_ON_FAILURE__ \
497 using namespace drogon::test; \
501 printErr() << "Force exiting due to a mandation failed.\n"; \
506#define PRINT_NONSTANDARD_EXCEPTION__(func_name, expr) \
509 ERROR_MSG(func_name, expr) \
510 << "Unexpected unknown exception is thrown.\n\n"; \
513#define EVAL__(expr) \
523#define PRINT_ERR_NOEXCEPTION__(expr, func_name) \
527 ERROR_MSG(func_name, expr) \
528 << "With expecitation\n" \
529 << " Expected to throw an exception. But none are " \
533#define PRINT_ERR_WITHEXCEPTION__(expr, func_name) \
537 ERROR_MSG(func_name, expr) \
538 << "With expecitation\n" \
539 << " Should to not throw an exception. But one is " \
543#define PRINT_ERR_BAD_EXCEPTION__( \
544 expr, func_name, excep_type, exceptionThrown, correctExceptionType) \
547 assert((exceptionThrown && correctExceptionType) || !exceptionThrown); \
548 if (exceptionThrown == true && correctExceptionType == false) \
550 ERROR_MSG(func_name, expr) \
551 << "With expecitation\n" \
552 << " Exception have been throw but not of type \033[0;33m" \
553 << #excep_type << "\033[0m.\n\n"; \
555 else if (exceptionThrown == false) \
557 ERROR_MSG(func_name, expr) \
558 << "With expecitation\n" \
559 << " A \033[0;33m" << #excep_type \
560 << "\033[0m exception is expected. But nothing was thrown" \
565#define CHECK_INTERNAL__(expr, func_name, on_leave) \
568 TEST_INTERNAL__(func_name, \
570 EVAL_AND_CHECK_TRUE__(func_name, expr), \
571 PRINT_UNEXPECTED_EXCEPTION__(func_name, #expr), \
572 PRINT_NONSTANDARD_EXCEPTION__(func_name, #expr), \
573 on_leave PRINT_PASSED__(func_name, #expr)); \
576#define CHECK_THROWS_INTERNAL__(expr, func_name, on_leave) \
579 TEST_INTERNAL__(func_name, \
582 SET_TEST_SUCCESS__, \
583 SET_TEST_SUCCESS__, \
584 PRINT_ERR_NOEXCEPTION__(#expr, func_name) \
585 on_leave PRINT_PASSED__(func_name, #expr)); \
588#define CHECK_THROWS_AS_INTERNAL__(expr, func_name, except_type, on_leave) \
591 bool exceptionThrown = false; \
597 exceptionThrown = true; \
598 if (dynamic_cast<const except_type *>(&e) != nullptr) \
599 SET_TEST_SUCCESS__; \
601 { exceptionThrown = true; }, \
602 PRINT_ERR_BAD_EXCEPTION__(#expr ", " #except_type, \
607 on_leave PRINT_PASSED__(func_name, #expr ", " #except_type)); \
610#define CHECK_NOTHROW_INTERNAL__(expr, func_name, on_leave) \
613 TEST_INTERNAL__(func_name, \
615 EVAL__(expr) SET_TEST_SUCCESS__, \
618 PRINT_ERR_WITHEXCEPTION__(#expr, func_name) \
619 on_leave PRINT_PASSED__(func_name, #expr)); \
622#define CHECK(expr) CHECK_INTERNAL__(expr, "CHECK", NOTHING__)
623#define CHECK_THROWS(expr) \
624 CHECK_THROWS_INTERNAL__(expr, "CHECK_THROWS", NOTHING__)
625#define CHECK_NOTHROW(expr) \
626 CHECK_NOTHROW_INTERNAL__(expr, "CHECK_NOTHROW", NOTHING__)
627#define CHECK_THROWS_AS(expr, except_type) \
628 CHECK_THROWS_AS_INTERNAL__(expr, "CHECK_THROWS_AS", except_type, NOTHING__)
630#define REQUIRE(expr) CHECK_INTERNAL__(expr, "REQUIRE", RETURN_ON_FAILURE__)
631#define REQUIRE_THROWS(expr) \
632 CHECK_THROWS_INTERNAL__(expr, "REQUIRE_THROWS", RETURN_ON_FAILURE__)
633#define REQUIRE_NOTHROW(expr) \
634 CHECK_NOTHROW_INTERNAL__(expr, "REQUIRE_NOTHROW", RETURN_ON_FAILURE__)
635#define REQUIRE_THROWS_AS(expr, except_type) \
636 CHECK_THROWS_AS_INTERNAL__(expr, \
637 "REQUIRE_THROWS_AS", \
641#define CO_REQUIRE(expr) \
642 CHECK_INTERNAL__(expr, "CO_REQUIRE", CO_RETURN_ON_FAILURE__)
643#define CO_REQUIRE_THROWS(expr) \
644 CHECK_THROWS_INTERNAL__(expr, "CO_REQUIRE_THROWS", CO_RETURN_ON_FAILURE__)
645#define CO_REQUIRE_NOTHROW(expr) \
646 CHECK_NOTHROW_INTERNAL__(expr, "CO_REQUIRE_NOTHROW", CO_RETURN_ON_FAILURE__)
647#define CO_REQUIRE_THROWS_AS(expr, except_type) \
648 CHECK_THROWS_AS_INTERNAL__(expr, \
649 "CO_REQUIRE_THROWS_AS", \
651 CO_RETURN_ON_FAILURE__)
653#define MANDATE(expr) CHECK_INTERNAL__(expr, "MANDATE", DIE_ON_FAILURE__)
654#define MANDATE_THROWS(expr) \
655 CHECK_THROWS_INTERNAL__(expr, "MANDATE_THROWS", DIE_ON_FAILURE__)
656#define MANDATE_NOTHROW(expr) \
657 CHECK_NOTHROW_INTERNAL__(expr, "MANDATE_NOTHROW", DIE_ON_FAILURE__)
658#define MANDATE_THROWS_AS(expr, except_type) \
659 CHECK_THROWS_AS_INTERNAL__(expr, \
660 "MANDATE_THROWS_AS", \
664#define STATIC_REQUIRE(expr) \
667 DROGON_TEST_START_SUPRESSION_ \
668 DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
670 DROGON_TEST_END_SUPRESSION_ \
671 drogon::test::internal::numAssertions++; \
672 static_assert((expr), #expr " failed."); \
673 drogon::test::internal::numCorrectAssertions++; \
679 using namespace drogon::test; \
680 TEST_CTX->setFailed(); \
681 printErr() << "\x1B[1;37mIn test case " << TEST_CTX->fullname() \
683 << "\x1B[0;37m" << __FILE__ << ":" << __LINE__ \
684 << " \x1B[0;31m FAILED:\x1B[0m\n" \
686 drogon::test::internal::outputReason(__VA_ARGS__); \
687 printErr() << "\n\n"; \
688 drogon::test::internal::numAssertions++; \
693 using namespace drogon::test; \
696 printErr() << "Force exiting due to a FAULT statement.\n"; \
703 DROGON_TEST_START_SUPRESSION_ \
704 DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
706 DROGON_TEST_END_SUPRESSION_ \
707 if (drogon::test::internal::printSuccessfulTests) \
708 drogon::test::print() \
709 << "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
710 << "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
711 << " \x1B[0;32m PASSED:\x1B[0m\n" \
712 << " \033[0;34mSUCCESS()\x1B[0m\n\n"; \
713 drogon::test::internal::numAssertions++; \
714 drogon::test::internal::numCorrectAssertions++; \
717#define DROGON_TEST_CLASS_NAME_(test_name) \
718 DROGON_TEST_CONCAT(DROGON_TESTCASE_PREIX_, test_name)
720#define DROGON_TEST(test_name) \
721 struct DROGON_TEST_CLASS_NAME_(test_name) \
722 : public drogon::DrObject<DROGON_TEST_CLASS_NAME_(test_name)>, \
723 public drogon::test::TestCase \
725 DROGON_TEST_CLASS_NAME_(test_name) \
726 () : drogon::test::TestCase(#test_name) \
729 inline void doTest_(std::shared_ptr<drogon::test::Case>) override; \
731 void DROGON_TEST_CLASS_NAME_(test_name)::doTest_( \
732 std::shared_ptr<drogon::test::Case> TEST_CTX)
733#define SUBTEST(name) (std::make_shared<drogon::test::Case>(TEST_CTX, name))
734#define SUBSECTION(name) \
735 for (std::shared_ptr<drogon::test::Case> ctx_hold__ = TEST_CTX, \
736 ctx_tmp__ = SUBTEST(#name); \
737 ctx_tmp__ != nullptr; \
738 TEST_CTX = ctx_hold__, ctx_tmp__ = nullptr) \
739 if (TEST_CTX = ctx_tmp__, TEST_CTX != nullptr)
Definition drogon_test.h:353
Drogon Test is a minimal effort test framework developed because the major C++ test frameworks doesn'...
Definition Attribute.h:23
Definition drogon_test.h:68