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