Imported Upstream version 1.72.0
[platform/upstream/boost.git] / boost / beast / _experimental / unit_test / suite.hpp
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
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*
80         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
94         log_buf(suite& self)
95             : suite_(self)
96         {
97         }
98
99         ~log_buf()
100         {
101             sync();
102         }
103
104         int
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
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
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*
185     this_suite()
186     {
187         return *p_this_suite();
188     }
189
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
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
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
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
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
304     unexcept(F&& f)
305     {
306         return unexcept(f, "");
307     }
308
309     /** Return the argument associated with the runner. */
310     std::string const&
311     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
325     unexpected(Condition shouldBeFalse)
326     {
327         return unexpected(shouldBeFalse, "");
328     }
329
330 private:
331     friend class thread;
332
333     static
334     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
366     ~scoped_testcase()
367     {
368         auto const& name = ss_.str();
369         if(! name.empty())
370             suite_.runner_->testcase(name);
371     }
372
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>
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&
394     operator<<(T const& t)
395     {
396         ss_ << t;
397         return *this;
398     }
399 };
400
401 //------------------------------------------------------------------------------
402
403 inline
404 void
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
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
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::
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::
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::
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::
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::
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::
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::
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::
552 pass()
553 {
554     propagate_abort();
555     runner_->pass();
556 }
557
558 // ::fail
559 template<class>
560 void
561 suite::
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::
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::
584 propagate_abort()
585 {
586     if(abort_ && aborted_)
587         BOOST_THROW_EXCEPTION(abort_exception());
588 }
589
590 template<class>
591 void
592 suite::
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