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