1 //
2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/beast
8 //
9
10 #ifndef BOOST_BEAST_UNIT_TEST_SUITE_HPP
11 #define BOOST_BEAST_UNIT_TEST_SUITE_HPP
12
13 #include <boost/beast/_experimental/unit_test/runner.hpp>
14 #include <boost/throw_exception.hpp>
15 #include <ostream>
16 #include <sstream>
17 #include <string>
18
19 namespace boost {
20 namespace beast {
21 namespace unit_test {
22
23 namespace detail {
24
25 template<class String>
26 std::string
make_reason(String const & reason,char const * file,int line)27 make_reason(String const& reason,
28 char const* file, int line)
29 {
30 std::string s(reason);
31 if(! s.empty())
32 s.append(": ");
33 char const* path = file + strlen(file);
34 while(path != file)
35 {
36 #ifdef _MSC_VER
37 if(path[-1] == '\\')
38 #else
39 if(path[-1] == '/')
40 #endif
41 break;
42 --path;
43 }
44 s.append(path);
45 s.append("(");
46 s.append(std::to_string(line));
47 s.append(")");
48 return s;
49 }
50
51 } // detail
52
53 class thread;
54
55 enum abort_t
56 {
57 no_abort_on_fail,
58 abort_on_fail
59 };
60
61 /** A testsuite class.
62
63 Derived classes execute a series of testcases, where each testcase is
64 a series of pass/fail tests. To provide a unit test using this class,
65 derive from it and use the BOOST_BEAST_DEFINE_UNIT_TEST macro in a
66 translation unit.
67 */
68 class suite
69 {
70 private:
71 bool abort_ = false;
72 bool aborted_ = false;
73 runner* runner_ = nullptr;
74
75 // This exception is thrown internally to stop the current suite
76 // in the event of a failure, if the option to stop is set.
77 struct abort_exception : public std::exception
78 {
79 char const*
whatboost::beast::unit_test::suite::abort_exception80 what() const noexcept override
81 {
82 return "test suite aborted";
83 }
84 };
85
86 template<class CharT, class Traits, class Allocator>
87 class log_buf
88 : public std::basic_stringbuf<CharT, Traits, Allocator>
89 {
90 suite& suite_;
91
92 public:
93 explicit
log_buf(suite & self)94 log_buf(suite& self)
95 : suite_(self)
96 {
97 }
98
~log_buf()99 ~log_buf()
100 {
101 sync();
102 }
103
104 int
sync()105 sync() override
106 {
107 auto const& s = this->str();
108 if(s.size() > 0)
109 suite_.runner_->log(s);
110 this->str("");
111 return 0;
112 }
113 };
114
115 template<
116 class CharT,
117 class Traits = std::char_traits<CharT>,
118 class Allocator = std::allocator<CharT>
119 >
120 class log_os : public std::basic_ostream<CharT, Traits>
121 {
122 log_buf<CharT, Traits, Allocator> buf_;
123
124 public:
125 explicit
log_os(suite & self)126 log_os(suite& self)
127 : std::basic_ostream<CharT, Traits>(&buf_)
128 , buf_(self)
129 {
130 }
131 };
132
133 class scoped_testcase;
134
135 class testcase_t
136 {
137 suite& suite_;
138 std::stringstream ss_;
139
140 public:
141 explicit
testcase_t(suite & self)142 testcase_t(suite& self)
143 : suite_(self)
144 {
145 }
146
147 /** Open a new testcase.
148
149 A testcase is a series of evaluated test conditions. A test
150 suite may have multiple test cases. A test is associated with
151 the last opened testcase. When the test first runs, a default
152 unnamed case is opened. Tests with only one case may omit the
153 call to testcase.
154
155 @param abort Determines if suite continues running after a failure.
156 */
157 void
158 operator()(std::string const& name,
159 abort_t abort = no_abort_on_fail);
160
161 scoped_testcase
162 operator()(abort_t abort);
163
164 template<class T>
165 scoped_testcase
166 operator<<(T const& t);
167 };
168
169 public:
170 /** Logging output stream.
171
172 Text sent to the log output stream will be forwarded to
173 the output stream associated with the runner.
174 */
175 log_os<char> log;
176
177 /** Memberspace for declaring test cases. */
178 testcase_t testcase;
179
180 /** Returns the "current" running suite.
181 If no suite is running, nullptr is returned.
182 */
183 static
184 suite*
this_suite()185 this_suite()
186 {
187 return *p_this_suite();
188 }
189
suite()190 suite()
191 : log(*this)
192 , testcase(*this)
193 {
194 }
195
196 virtual ~suite() = default;
197 suite(suite const&) = delete;
198 suite& operator=(suite const&) = delete;
199
200 /** Invokes the test using the specified runner.
201
202 Data members are set up here instead of the constructor as a
203 convenience to writing the derived class to avoid repetition of
204 forwarded constructor arguments to the base.
205 Normally this is called by the framework for you.
206 */
207 template<class = void>
208 void
209 operator()(runner& r);
210
211 /** Record a successful test condition. */
212 template<class = void>
213 void
214 pass();
215
216 /** Record a failure.
217
218 @param reason Optional text added to the output on a failure.
219
220 @param file The source code file where the test failed.
221
222 @param line The source code line number where the test failed.
223 */
224 /** @{ */
225 template<class String>
226 void
227 fail(String const& reason, char const* file, int line);
228
229 template<class = void>
230 void
231 fail(std::string const& reason = "");
232 /** @} */
233
234 /** Evaluate a test condition.
235
236 This function provides improved logging by incorporating the
237 file name and line number into the reported output on failure,
238 as well as additional text specified by the caller.
239
240 @param shouldBeTrue The condition to test. The condition
241 is evaluated in a boolean context.
242
243 @param reason Optional added text to output on a failure.
244
245 @param file The source code file where the test failed.
246
247 @param line The source code line number where the test failed.
248
249 @return `true` if the test condition indicates success.
250 */
251 /** @{ */
252 template<class Condition>
253 bool
expect(Condition const & shouldBeTrue)254 expect(Condition const& shouldBeTrue)
255 {
256 return expect(shouldBeTrue, "");
257 }
258
259 template<class Condition, class String>
260 bool
261 expect(Condition const& shouldBeTrue, String const& reason);
262
263 template<class Condition>
264 bool
expect(Condition const & shouldBeTrue,char const * file,int line)265 expect(Condition const& shouldBeTrue,
266 char const* file, int line)
267 {
268 return expect(shouldBeTrue, "", file, line);
269 }
270
271 template<class Condition, class String>
272 bool
273 expect(Condition const& shouldBeTrue,
274 String const& reason, char const* file, int line);
275 /** @} */
276
277 //
278 // DEPRECATED
279 //
280 // Expect an exception from f()
281 template<class F, class String>
282 bool
283 except(F&& f, String const& reason);
284 template<class F>
285 bool
except(F && f)286 except(F&& f)
287 {
288 return except(f, "");
289 }
290 template<class E, class F, class String>
291 bool
292 except(F&& f, String const& reason);
293 template<class E, class F>
294 bool
except(F && f)295 except(F&& f)
296 {
297 return except<E>(f, "");
298 }
299 template<class F, class String>
300 bool
301 unexcept(F&& f, String const& reason);
302 template<class F>
303 bool
unexcept(F && f)304 unexcept(F&& f)
305 {
306 return unexcept(f, "");
307 }
308
309 /** Return the argument associated with the runner. */
310 std::string const&
arg() const311 arg() const
312 {
313 return runner_->arg();
314 }
315
316 // DEPRECATED
317 // @return `true` if the test condition indicates success(a false value)
318 template<class Condition, class String>
319 bool
320 unexpected(Condition shouldBeFalse,
321 String const& reason);
322
323 template<class Condition>
324 bool
unexpected(Condition shouldBeFalse)325 unexpected(Condition shouldBeFalse)
326 {
327 return unexpected(shouldBeFalse, "");
328 }
329
330 private:
331 friend class thread;
332
333 static
334 suite**
p_this_suite()335 p_this_suite()
336 {
337 static suite* pts = nullptr;
338 return &pts;
339 }
340
341 /** Runs the suite. */
342 virtual
343 void
344 run() = 0;
345
346 void
347 propagate_abort();
348
349 template<class = void>
350 void
351 run(runner& r);
352 };
353
354 //------------------------------------------------------------------------------
355
356 // Helper for streaming testcase names
357 class suite::scoped_testcase
358 {
359 private:
360 suite& suite_;
361 std::stringstream& ss_;
362
363 public:
364 scoped_testcase& operator=(scoped_testcase const&) = delete;
365
~scoped_testcase()366 ~scoped_testcase()
367 {
368 auto const& name = ss_.str();
369 if(! name.empty())
370 suite_.runner_->testcase(name);
371 }
372
scoped_testcase(suite & self,std::stringstream & ss)373 scoped_testcase(suite& self, std::stringstream& ss)
374 : suite_(self)
375 , ss_(ss)
376 {
377 ss_.clear();
378 ss_.str({});
379 }
380
381 template<class T>
scoped_testcase(suite & self,std::stringstream & ss,T const & t)382 scoped_testcase(suite& self,
383 std::stringstream& ss, T const& t)
384 : suite_(self)
385 , ss_(ss)
386 {
387 ss_.clear();
388 ss_.str({});
389 ss_ << t;
390 }
391
392 template<class T>
393 scoped_testcase&
operator <<(T const & t)394 operator<<(T const& t)
395 {
396 ss_ << t;
397 return *this;
398 }
399 };
400
401 //------------------------------------------------------------------------------
402
403 inline
404 void
operator ()(std::string const & name,abort_t abort)405 suite::testcase_t::operator()(
406 std::string const& name, abort_t abort)
407 {
408 suite_.abort_ = abort == abort_on_fail;
409 suite_.runner_->testcase(name);
410 }
411
412 inline
413 suite::scoped_testcase
operator ()(abort_t abort)414 suite::testcase_t::operator()(abort_t abort)
415 {
416 suite_.abort_ = abort == abort_on_fail;
417 return { suite_, ss_ };
418 }
419
420 template<class T>
421 inline
422 suite::scoped_testcase
operator <<(T const & t)423 suite::testcase_t::operator<<(T const& t)
424 {
425 return { suite_, ss_, t };
426 }
427
428 //------------------------------------------------------------------------------
429
430 template<class>
431 void
432 suite::
operator ()(runner & r)433 operator()(runner& r)
434 {
435 *p_this_suite() = this;
436 try
437 {
438 run(r);
439 *p_this_suite() = nullptr;
440 }
441 catch(...)
442 {
443 *p_this_suite() = nullptr;
444 throw;
445 }
446 }
447
448 template<class Condition, class String>
449 bool
450 suite::
expect(Condition const & shouldBeTrue,String const & reason)451 expect(
452 Condition const& shouldBeTrue, String const& reason)
453 {
454 if(shouldBeTrue)
455 {
456 pass();
457 return true;
458 }
459 fail(reason);
460 return false;
461 }
462
463 template<class Condition, class String>
464 bool
465 suite::
expect(Condition const & shouldBeTrue,String const & reason,char const * file,int line)466 expect(Condition const& shouldBeTrue,
467 String const& reason, char const* file, int line)
468 {
469 if(shouldBeTrue)
470 {
471 pass();
472 return true;
473 }
474 fail(detail::make_reason(reason, file, line));
475 return false;
476 }
477
478 // DEPRECATED
479
480 template<class F, class String>
481 bool
482 suite::
except(F && f,String const & reason)483 except(F&& f, String const& reason)
484 {
485 try
486 {
487 f();
488 fail(reason);
489 return false;
490 }
491 catch(...)
492 {
493 pass();
494 }
495 return true;
496 }
497
498 template<class E, class F, class String>
499 bool
500 suite::
except(F && f,String const & reason)501 except(F&& f, String const& reason)
502 {
503 try
504 {
505 f();
506 fail(reason);
507 return false;
508 }
509 catch(E const&)
510 {
511 pass();
512 }
513 return true;
514 }
515
516 template<class F, class String>
517 bool
518 suite::
unexcept(F && f,String const & reason)519 unexcept(F&& f, String const& reason)
520 {
521 try
522 {
523 f();
524 pass();
525 return true;
526 }
527 catch(...)
528 {
529 fail(reason);
530 }
531 return false;
532 }
533
534 template<class Condition, class String>
535 bool
536 suite::
unexpected(Condition shouldBeFalse,String const & reason)537 unexpected(
538 Condition shouldBeFalse, String const& reason)
539 {
540 bool const b =
541 static_cast<bool>(shouldBeFalse);
542 if(! b)
543 pass();
544 else
545 fail(reason);
546 return ! b;
547 }
548
549 template<class>
550 void
551 suite::
pass()552 pass()
553 {
554 propagate_abort();
555 runner_->pass();
556 }
557
558 // ::fail
559 template<class>
560 void
561 suite::
fail(std::string const & reason)562 fail(std::string const& reason)
563 {
564 propagate_abort();
565 runner_->fail(reason);
566 if(abort_)
567 {
568 aborted_ = true;
569 BOOST_THROW_EXCEPTION(abort_exception());
570 }
571 }
572
573 template<class String>
574 void
575 suite::
fail(String const & reason,char const * file,int line)576 fail(String const& reason, char const* file, int line)
577 {
578 fail(detail::make_reason(reason, file, line));
579 }
580
581 inline
582 void
583 suite::
propagate_abort()584 propagate_abort()
585 {
586 if(abort_ && aborted_)
587 BOOST_THROW_EXCEPTION(abort_exception());
588 }
589
590 template<class>
591 void
592 suite::
run(runner & r)593 run(runner& r)
594 {
595 runner_ = &r;
596
597 try
598 {
599 run();
600 }
601 catch(abort_exception const&)
602 {
603 // ends the suite
604 }
605 catch(std::exception const& e)
606 {
607 runner_->fail("unhandled exception: " +
608 std::string(e.what()));
609 }
610 catch(...)
611 {
612 runner_->fail("unhandled exception");
613 }
614 }
615
616 #ifndef BEAST_PASS
617 #define BEAST_PASS() ::boost::beast::unit_test::suite::this_suite()->pass()
618 #endif
619
620 #ifndef BEAST_FAIL
621 #define BEAST_FAIL() ::boost::beast::unit_test::suite::this_suite()->fail("", __FILE__, __LINE__)
622 #endif
623
624 #ifndef BEAST_EXPECT
625 /** Check a precondition.
626
627 If the condition is false, the file and line number are reported.
628 */
629 #define BEAST_EXPECT(cond) ::boost::beast::unit_test::suite::this_suite()->expect(cond, __FILE__, __LINE__)
630 #endif
631
632 #ifndef BEAST_EXPECTS
633 /** Check a precondition.
634
635 If the condition is false, the file and line number are reported.
636 */
637 #define BEAST_EXPECTS(cond, reason) ((cond) ? \
638 (::boost::beast::unit_test::suite::this_suite()->pass(), true) : \
639 (::boost::beast::unit_test::suite::this_suite()->fail((reason), __FILE__, __LINE__), false))
640 #endif
641
642 /** Ensure an exception is thrown
643 */
644 #define BEAST_THROWS( EXPR, EXCEP ) \
645 try { \
646 EXPR; \
647 BEAST_FAIL(); \
648 } \
649 catch(EXCEP const&) { \
650 BEAST_PASS(); \
651 } \
652 catch(...) { \
653 BEAST_FAIL(); \
654 }
655
656 } // unit_test
657 } // beast
658 } // boost
659
660 //------------------------------------------------------------------------------
661
662 // detail:
663 // This inserts the suite with the given manual flag
664 #define BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,manual) \
665 static ::boost::beast::unit_test::detail::insert_suite <Class##_test> \
666 Library ## Module ## Class ## _test_instance( \
667 #Class, #Module, #Library, manual)
668
669 //------------------------------------------------------------------------------
670
671 // Preprocessor directives for controlling unit test definitions.
672
673 // If this is already defined, don't redefine it. This allows
674 // programs to provide custom behavior for testsuite definitions
675 //
676 #ifndef BEAST_DEFINE_TESTSUITE
677
678 /** Enables insertion of test suites into the global container.
679 The default is to insert all test suite definitions into the global
680 container. If BEAST_DEFINE_TESTSUITE is user defined, this macro
681 has no effect.
682 */
683 #ifndef BEAST_NO_UNIT_TEST_INLINE
684 #define BEAST_NO_UNIT_TEST_INLINE 0
685 #endif
686
687 /** Define a unit test suite.
688
689 Library Identifies the library.
690 Module Identifies the module.
691 Class The type representing the class being tested.
692
693 The declaration for the class implementing the test should be the same
694 as Class ## _test. For example, if Class is aged_ordered_container, the
695 test class must be declared as:
696
697 @code
698
699 struct aged_ordered_container_test : beast::unit_test::suite
700 {
701 //...
702 };
703
704 @endcode
705
706 The macro invocation must appear in the same namespace as the test class.
707 */
708
709 #if BEAST_NO_UNIT_TEST_INLINE
710 #define BEAST_DEFINE_TESTSUITE(Class,Module,Library)
711
712 #else
713 #include <boost/beast/_experimental/unit_test/global_suites.hpp>
714 #define BEAST_DEFINE_TESTSUITE(Library,Module,Class) \
715 BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,false)
716 #define BEAST_DEFINE_TESTSUITE_MANUAL(Library,Module,Class) \
717 BEAST_DEFINE_TESTSUITE_INSERT(Library,Module,Class,true)
718
719 #endif
720
721 #endif
722
723 //------------------------------------------------------------------------------
724
725 #endif
726