drogon
C++14/17-based HTTP application framework
Loading...
Searching...
No Matches
drogon_test.h
1#pragma once
2#include <trantor/utils/NonCopyable.h>
3#include <drogon/DrObject.h>
4#include <drogon/exports.h>
5
6#include <memory>
7#include <mutex>
8#include <sstream>
9#include <atomic>
10#include <string_view>
11#include <cstddef>
12
18namespace drogon
19{
20namespace test
21{
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__
26
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)
31
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\"")
46// MSVC don't have an equlivent. Add other compilers here
47#else
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_
52#endif
53
54class Case;
55
56namespace internal
57{
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;
62
63DROGON_EXPORT void registerCase(Case *test);
64DROGON_EXPORT void unregisterCase(Case *test);
65
66template <typename _Tp, typename dummy = void>
67struct is_printable : std::false_type
68{
69};
70
71template <typename _Tp>
73 _Tp,
74 std::enable_if_t<std::is_same_v<decltype(std::cout << std::declval<_Tp>()),
75 std::ostream &>>> : std::true_type
76{
77};
78
79inline std::string escapeString(const std::string_view sv)
80{
81 std::string result;
82 result.reserve(sv.size());
83 for (auto ch : sv)
84 {
85 if (ch == '\n')
86 result += "\\n";
87 else if (ch == '\r')
88 result += "\\r";
89 else if (ch == '\t')
90 result += "\\t";
91 else if (ch == '\b')
92 result += "\\b";
93 else if (ch == '\\')
94 result += "\\\\";
95 else if (ch == '"')
96 result += "\"";
97 else if (ch == '\v')
98 result += "\\v";
99 else if (ch == '\a')
100 result += "\\a";
101 else
102 result.push_back(ch);
103 }
104 return result;
105}
106
107DROGON_EXPORT std::string prettifyString(const std::string_view sv,
108 size_t maxLength = 120);
109
110template <typename... Args>
111inline void outputReason(Args &&...args)
112{
113 (std::cout << ... << std::forward<Args>(args));
114}
115
116template <typename T>
117inline std::string attemptPrint(T &&v)
118{
119 using Type = std::remove_cv_t<std::remove_reference_t<T>>;
120 if constexpr (std::is_same_v<Type, std::nullptr_t>)
121 return "nullptr";
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)
127 {
128 std::stringstream ss;
129 ss << v;
130 return ss.str();
131 }
132 return "{un-printable}";
133}
134
135inline std::string stringifyFuncCall(const std::string &funcName)
136{
137 return funcName + "()";
138}
139
140inline std::string stringifyFuncCall(const std::string &funcName,
141 const std::string &param1)
142{
143 return funcName + "(" + param1 + ")";
144}
145
146inline std::string stringifyFuncCall(const std::string &funcName,
147 const std::string &param1,
148 const std::string &param2)
149{
150 return funcName + "(" + param1 + ", " + param2 + ")";
151}
152
153struct ComparsionResult
154{
155 std::pair<bool, std::string> result() const
156 {
157 return {comparsionResilt_, expansion_};
158 }
159
160 bool comparsionResilt_;
161 std::string expansion_;
162};
163
164template <typename T>
165struct Lhs
166{
167 template <typename _ = void> // HACK: prevent this function to be evaluated
168 // when not invoked
169 std::pair<bool, std::string> result() const
170 {
171 return {(bool)ref_, attemptPrint(ref_)};
172 }
173
174 Lhs(const T &lhs) : ref_(lhs)
175 {
176 }
177
178 const T &ref_;
179
180 template <typename RhsType>
181 ComparsionResult operator<(const RhsType &rhs)
182 {
183 return ComparsionResult{ref_ < rhs,
184 attemptPrint(ref_) + " < " +
185 attemptPrint(ref_)};
186 }
187
188 template <typename RhsType>
189 ComparsionResult operator>(const RhsType &rhs)
190 {
191 return ComparsionResult{ref_ > rhs,
192 attemptPrint(ref_) + " > " + attemptPrint(rhs)};
193 }
194
195 template <typename RhsType>
196 ComparsionResult operator<=(const RhsType &rhs)
197 {
198 return ComparsionResult{ref_ <= rhs,
199 attemptPrint(ref_) +
200 " <= " + attemptPrint(rhs)};
201 }
202
203 template <typename RhsType>
204 ComparsionResult operator>=(const RhsType &rhs)
205 {
206 return ComparsionResult{ref_ >= rhs,
207 attemptPrint(ref_) +
208 " >= " + attemptPrint(rhs)};
209 }
210
211 template <typename RhsType>
212 ComparsionResult operator==(const RhsType &rhs)
213 {
214 return ComparsionResult{ref_ == rhs,
215 attemptPrint(ref_) +
216 " == " + attemptPrint(rhs)};
217 }
218
219 template <typename RhsType>
220 ComparsionResult operator!=(const RhsType &rhs)
221 {
222 return ComparsionResult{ref_ != rhs,
223 attemptPrint(ref_) +
224 " != " + attemptPrint(rhs)};
225 }
226
227 template <typename RhsType>
228 ComparsionResult operator&&(const RhsType &rhs)
229 {
230 static_assert(!std::is_same_v<RhsType, void>,
231 " && is not supported in expression decomposition");
232 return {};
233 }
234
235 template <typename RhsType>
236 ComparsionResult operator||(const RhsType &rhs)
237 {
238 static_assert(!std::is_same_v<RhsType, void>,
239 " || is not supported in expression decomposition");
240 return {};
241 }
242
243 template <typename RhsType>
244 ComparsionResult operator|(const RhsType &rhs)
245 {
246 static_assert(!std::is_same_v<RhsType, void>,
247 " | is not supported in expression decomposition");
248 return {};
249 }
250
251 template <typename RhsType>
252 ComparsionResult operator&(const RhsType &rhs)
253 {
254 static_assert(!std::is_same_v<RhsType, void>,
255 " & is not supported in expression decomposition");
256 return {};
257 }
258};
259
260struct Decomposer
261{
262 template <typename T>
263 Lhs<T> operator<=(const T &other)
264 {
265 return Lhs<T>(other);
266 }
267};
268
269} // namespace internal
270
271class DROGON_EXPORT ThreadSafeStream final
272{
273 public:
274 ThreadSafeStream(std::ostream &os) : os_(os)
275 {
276 mtx_.lock();
277 }
278
279 ~ThreadSafeStream()
280 {
281 mtx_.unlock();
282 }
283
284 template <typename T>
285 std::ostream &operator<<(const T &rhs)
286 {
287 return os_ << rhs;
288 }
289
290 static std::mutex mtx_;
291 std::ostream &os_;
292};
293
294DROGON_EXPORT ThreadSafeStream print();
295DROGON_EXPORT ThreadSafeStream printErr();
296
297class CaseBase : public trantor::NonCopyable
298{
299 public:
300 CaseBase() = default;
301
302 CaseBase(const std::string &name) : name_(name)
303 {
304 }
305
306 CaseBase(std::shared_ptr<CaseBase> parent, const std::string &name)
307 : parent_(parent), name_(name)
308 {
309 }
310
311 virtual ~CaseBase() = default;
312
313 std::string fullname() const
314 {
315 std::string result;
316 auto curr = this;
317 while (curr != nullptr)
318 {
319 result = curr->name() + result;
320 if (curr->parent_ != nullptr)
321 result = "." + result;
322 curr = curr->parent_.get();
323 }
324 return result;
325 }
326
327 const std::string &name() const
328 {
329 return name_;
330 }
331
332 void setFailed()
333 {
334 if (failed_ == false)
335 {
336 internal::numFailedTestCases++;
337 failed_ = true;
338 }
339 }
340
341 bool failed() const
342 {
343 return failed_;
344 }
345
346 protected:
347 bool failed_ = false;
348 std::shared_ptr<CaseBase> parent_ = nullptr;
349 std::string name_;
350};
351
352class Case : public CaseBase
353{
354 public:
355 Case(const std::string &name) : CaseBase(name)
356 {
357 internal::registerCase(this);
358 }
359
360 Case(std::shared_ptr<Case> parent, const std::string &name)
361 : CaseBase(parent, name)
362 {
363 internal::registerCase(this);
364 }
365
366 virtual ~Case()
367 {
368 internal::unregisterCase(this);
369 }
370};
371
372struct TestCase : public CaseBase
373{
374 TestCase(const std::string &name) : CaseBase(name)
375 {
376 }
377
378 virtual ~TestCase() = default;
379 virtual void doTest_(std::shared_ptr<Case>) = 0;
380};
381
382DROGON_EXPORT void printTestStats();
383DROGON_EXPORT int run(int argc, char **argv);
384} // namespace test
385} // namespace drogon
386
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" \
392 << " \033[0;34m" \
393 << drogon::test::internal::stringifyFuncCall(func_name, expr) \
394 << "\x1B[0m\n"
395
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" \
401 << " \033[0;34m" \
402 << drogon::test::internal::stringifyFuncCall(func_name, expr) \
403 << "\x1B[0m\n"
404
405#define SET_TEST_SUCCESS__ \
406 do \
407 { \
408 TEST_FLAG_ = true; \
409 } while (0);
410
411#define TEST_INTERNAL__(func_name, \
412 expr, \
413 eval, \
414 on_exception, \
415 on_non_standard_exception, \
416 on_leaving) \
417 do \
418 { \
419 bool TEST_FLAG_ = false; \
420 using drogon::test::internal::stringifyFuncCall; \
421 using drogon::test::printErr; \
422 drogon::test::internal::numAssertions++; \
423 try \
424 { \
425 eval; \
426 } \
427 catch (const std::exception &e) \
428 { \
429 (void)e; \
430 on_exception; \
431 } \
432 catch (...) \
433 { \
434 on_non_standard_exception; \
435 } \
436 if (TEST_FLAG_) \
437 drogon::test::internal::numCorrectAssertions++; \
438 else \
439 TEST_CTX->setFailed(); \
440 on_leaving; \
441 } while (0);
442
443#define EVAL_AND_CHECK_TRUE__(func_name, expr) \
444 do \
445 { \
446 bool drresult__; \
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_ \
453 if (!drresult__) \
454 { \
455 ERROR_MSG(func_name, #expr) \
456 << "With expansion\n" \
457 << " \033[0;33m" << drexpansion__ << "\x1B[0m\n\n"; \
458 } \
459 else \
460 SET_TEST_SUCCESS__; \
461 } while (0);
462
463#define PRINT_UNEXPECTED_EXCEPTION__(func_name, expr) \
464 do \
465 { \
466 ERROR_MSG(func_name, expr) \
467 << "An unexpected exception is thrown. what():\n" \
468 << " \033[0;33m" << e.what() << "\x1B[0m\n\n"; \
469 } while (0);
470
471#define PRINT_PASSED__(func_name, expr) \
472 do \
473 { \
474 if (drogon::test::internal::printSuccessfulTests && TEST_FLAG_) \
475 { \
476 PASSED_MSG(func_name, expr) << "\n"; \
477 } \
478 } while (0);
479
480#define RETURN_ON_FAILURE__ \
481 do \
482 { \
483 if (!TEST_FLAG_) \
484 return; \
485 } while (0);
486
487#define CO_RETURN_ON_FAILURE__ \
488 do \
489 { \
490 if (!TEST_FLAG_) \
491 co_return; \
492 } while (0);
493
494#define DIE_ON_FAILURE__ \
495 do \
496 { \
497 using namespace drogon::test; \
498 if (!TEST_FLAG_) \
499 { \
500 printTestStats(); \
501 printErr() << "Force exiting due to a mandation failed.\n"; \
502 exit(1); \
503 } \
504 } while (0);
505
506#define PRINT_NONSTANDARD_EXCEPTION__(func_name, expr) \
507 do \
508 { \
509 ERROR_MSG(func_name, expr) \
510 << "Unexpected unknown exception is thrown.\n\n"; \
511 } while (0);
512
513#define EVAL__(expr) \
514 do \
515 { \
516 expr; \
517 } while (0);
518
519#define NOTHING__ \
520 { \
521 }
522
523#define PRINT_ERR_NOEXCEPTION__(expr, func_name) \
524 do \
525 { \
526 if (!TEST_FLAG_) \
527 ERROR_MSG(func_name, expr) \
528 << "With expecitation\n" \
529 << " Expected to throw an exception. But none are " \
530 "thrown.\n\n"; \
531 } while (0);
532
533#define PRINT_ERR_WITHEXCEPTION__(expr, func_name) \
534 do \
535 { \
536 if (!TEST_FLAG_) \
537 ERROR_MSG(func_name, expr) \
538 << "With expecitation\n" \
539 << " Should to not throw an exception. But one is " \
540 "thrown.\n\n"; \
541 } while (0);
542
543#define PRINT_ERR_BAD_EXCEPTION__( \
544 expr, func_name, excep_type, exceptionThrown, correctExceptionType) \
545 do \
546 { \
547 assert((exceptionThrown && correctExceptionType) || !exceptionThrown); \
548 if (exceptionThrown == true && correctExceptionType == false) \
549 { \
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"; \
554 } \
555 else if (exceptionThrown == false) \
556 { \
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" \
561 << "\033[0m.\n\n"; \
562 } \
563 } while (0);
564
565#define CHECK_INTERNAL__(expr, func_name, on_leave) \
566 do \
567 { \
568 TEST_INTERNAL__(func_name, \
569 expr, \
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)); \
574 } while (0)
575
576#define CHECK_THROWS_INTERNAL__(expr, func_name, on_leave) \
577 do \
578 { \
579 TEST_INTERNAL__(func_name, \
580 expr, \
581 EVAL__(expr), \
582 SET_TEST_SUCCESS__, \
583 SET_TEST_SUCCESS__, \
584 PRINT_ERR_NOEXCEPTION__(#expr, func_name) \
585 on_leave PRINT_PASSED__(func_name, #expr)); \
586 } while (0)
587
588#define CHECK_THROWS_AS_INTERNAL__(expr, func_name, except_type, on_leave) \
589 do \
590 { \
591 bool exceptionThrown = false; \
592 TEST_INTERNAL__( \
593 func_name, \
594 expr, \
595 EVAL__(expr), \
596 { \
597 exceptionThrown = true; \
598 if (dynamic_cast<const except_type *>(&e) != nullptr) \
599 SET_TEST_SUCCESS__; \
600 }, \
601 { exceptionThrown = true; }, \
602 PRINT_ERR_BAD_EXCEPTION__(#expr ", " #except_type, \
603 func_name, \
604 except_type, \
605 exceptionThrown, \
606 TEST_FLAG_) \
607 on_leave PRINT_PASSED__(func_name, #expr ", " #except_type)); \
608 } while (0)
609
610#define CHECK_NOTHROW_INTERNAL__(expr, func_name, on_leave) \
611 do \
612 { \
613 TEST_INTERNAL__(func_name, \
614 expr, \
615 EVAL__(expr) SET_TEST_SUCCESS__, \
616 NOTHING__, \
617 NOTHING__, \
618 PRINT_ERR_WITHEXCEPTION__(#expr, func_name) \
619 on_leave PRINT_PASSED__(func_name, #expr)); \
620 } while (0)
621
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__)
629
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", \
638 except_type, \
639 RETURN_ON_FAILURE__)
640
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", \
650 except_type, \
651 CO_RETURN_ON_FAILURE__)
652
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", \
661 except_type, \
662 DIE_ON_FAILURE__)
663
664#define STATIC_REQUIRE(expr) \
665 do \
666 { \
667 DROGON_TEST_START_SUPRESSION_ \
668 DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
669 TEST_CTX; \
670 DROGON_TEST_END_SUPRESSION_ \
671 drogon::test::internal::numAssertions++; \
672 static_assert((expr), #expr " failed."); \
673 drogon::test::internal::numCorrectAssertions++; \
674 } while (0)
675
676#define FAIL(...) \
677 do \
678 { \
679 using namespace drogon::test; \
680 TEST_CTX->setFailed(); \
681 printErr() << "\x1B[1;37mIn test case " << TEST_CTX->fullname() \
682 << "\n" \
683 << "\x1B[0;37m" << __FILE__ << ":" << __LINE__ \
684 << " \x1B[0;31m FAILED:\x1B[0m\n" \
685 << " Reason: "; \
686 drogon::test::internal::outputReason(__VA_ARGS__); \
687 printErr() << "\n\n"; \
688 drogon::test::internal::numAssertions++; \
689 } while (0)
690#define FAULT(...) \
691 do \
692 { \
693 using namespace drogon::test; \
694 FAIL(__VA_ARGS__); \
695 printTestStats(); \
696 printErr() << "Force exiting due to a FAULT statement.\n"; \
697 exit(1); \
698 } while (0)
699
700#define SUCCESS() \
701 do \
702 { \
703 DROGON_TEST_START_SUPRESSION_ \
704 DROGON_TEST_SUPPRESS_UNUSED_VALUE_WARNING_ \
705 TEST_CTX; \
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++; \
715 } while (0)
716
717#define DROGON_TEST_CLASS_NAME_(test_name) \
718 DROGON_TEST_CONCAT(DROGON_TESTCASE_PREIX_, test_name)
719
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 \
724 { \
725 DROGON_TEST_CLASS_NAME_(test_name) \
726 () : drogon::test::TestCase(#test_name) \
727 { \
728 } \
729 inline void doTest_(std::shared_ptr<drogon::test::Case>) override; \
730 }; \
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
STL namespace.
Definition drogon_test.h:68