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