libwreport 3.42
utils/tests.h
1#ifndef WREPORT_TESTS_H
2#define WREPORT_TESTS_H
3
12
13#include <cstdint>
14#include <exception>
15#include <filesystem>
16#include <functional>
17#include <sstream>
18#include <string>
19#include <vector>
20
21namespace wreport::tests {
22struct LocationInfo;
23} // namespace wreport::tests
24
25/*
26 * These global arguments will be shadowed by local variables in functions that
27 * implement tests.
28 *
29 * They are here to act as default root nodes to fulfill method signatures when
30 * tests are called from outside other tests.
31 */
32extern const wreport::tests::LocationInfo wreport_test_location_info;
33
34namespace wreport::tests {
35
53struct LocationInfo : public std::stringstream
54{
55 LocationInfo() {}
56
61 std::ostream& operator()();
62};
63
65struct TestStackFrame
66{
67 const char* file;
68 int line;
69 const char* call;
70 std::string local_info;
71
72 TestStackFrame(const char* file_, int line_, const char* call_)
73 : file(file_), line(line_), call(call_)
74 {
75 }
76
77 TestStackFrame(const char* file_, int line_, const char* call_,
78 const LocationInfo& local_info_)
79 : file(file_), line(line_), call(call_), local_info(local_info_.str())
80 {
81 }
82
83 std::string format() const;
84
85 void format(std::ostream& out) const;
86};
87
88struct TestStack : public std::vector<TestStackFrame>
89{
90 using vector::vector;
91
93 std::string backtrace() const;
94
96 void backtrace(std::ostream& out) const;
97};
98
103struct TestFailed : public std::exception
104{
105 std::string message;
106 TestStack stack;
107
108 explicit TestFailed(const std::exception& e);
109
110 template <typename... Args>
111 TestFailed(const std::exception& e, Args&&... args) : TestFailed(e)
112 {
113 add_stack_info(std::forward<Args>(args)...);
114 }
115
116 explicit TestFailed(const std::string& message_) : message(message_) {}
117
118 template <typename... Args>
119 TestFailed(const std::string& message_, Args&&... args)
120 : TestFailed(message_)
121 {
122 add_stack_info(std::forward<Args>(args)...);
123 }
124
125 const char* what() const noexcept override { return message.c_str(); }
126
127 template <typename... Args> void add_stack_info(Args&&... args)
128 {
129 stack.emplace_back(std::forward<Args>(args)...);
130 }
131};
132
136struct TestSkipped : public std::exception
137{
138 std::string reason;
139
140 TestSkipped();
141 explicit TestSkipped(const std::string& reason);
142};
143
148#define WREPORT_TEST_INFO(name) \
149 wreport::tests::LocationInfo wreport_test_location_info; \
150 wreport::tests::LocationInfo& name = wreport_test_location_info
151
158
160template <typename A> void assert_true(const A& actual)
161{
162 if (actual)
163 return;
164 std::stringstream ss;
165 ss << "actual value " << actual << " is not true";
166 throw TestFailed(ss.str());
167}
168
169[[noreturn]] void assert_true(std::nullptr_t actual);
170
172template <typename A> void assert_false(const A& actual)
173{
174 if (!actual)
175 return;
176 std::stringstream ss;
177 ss << "actual value " << actual << " is not false";
178 throw TestFailed(ss.str());
179}
180
181void assert_false(std::nullptr_t actual);
182
183template <typename LIST>
184static inline void _format_list(std::ostream& o, const LIST& list)
185{
186 bool first = true;
187 o << "[";
188 for (const auto& v : list)
189 {
190 if (first)
191 first = false;
192 else
193 o << ", ";
194 o << v;
195 }
196 o << "]";
197}
198
199template <typename T>
200void assert_equal(const std::vector<T>& actual, const std::vector<T>& expected)
201{
202 if (actual == expected)
203 return;
204 std::stringstream ss;
205 ss << "value ";
206 _format_list(ss, actual);
207 ss << " is different than the expected ";
208 _format_list(ss, expected);
209 throw TestFailed(ss.str());
210}
211
212template <typename T>
213void assert_equal(const std::vector<T>& actual,
214 const std::initializer_list<T>& expected)
215{
216 if (actual == expected)
217 return;
218 std::stringstream ss;
219 ss << "value ";
220 _format_list(ss, actual);
221 ss << " is different than the expected ";
222 _format_list(ss, expected);
223 throw TestFailed(ss.str());
224}
225
230template <typename A, typename E>
231void assert_equal(const A& actual, const E& expected)
232{
233 if (actual == expected)
234 return;
235 std::stringstream ss;
236 ss << "value '" << actual << "' is different than the expected '"
237 << expected << "'";
238 throw TestFailed(ss.str());
239}
240
245template <typename A, typename E>
246void assert_not_equal(const A& actual, const E& expected)
247{
248 if (actual != expected)
249 return;
250 std::stringstream ss;
251 ss << "value '" << actual << "' is not different than the expected '"
252 << expected << "'";
253 throw TestFailed(ss.str());
254}
255
257template <typename A, typename E>
258void assert_less(const A& actual, const E& expected)
259{
260 if (actual < expected)
261 return;
262 std::stringstream ss;
263 ss << "value '" << actual << "' is not less than the expected '" << expected
264 << "'";
265 throw TestFailed(ss.str());
266}
267
269template <typename A, typename E>
270void assert_less_equal(const A& actual, const E& expected)
271{
272 if (actual <= expected)
273 return;
274 std::stringstream ss;
275 ss << "value '" << actual
276 << "' is not less than or equals to the expected '" << expected << "'";
277 throw TestFailed(ss.str());
278}
279
281template <typename A, typename E>
282void assert_greater(const A& actual, const E& expected)
283{
284 if (actual > expected)
285 return;
286 std::stringstream ss;
287 ss << "value '" << actual << "' is not greater than the expected '"
288 << expected << "'";
289 throw TestFailed(ss.str());
290}
291
293template <typename A, typename E>
294void assert_greater_equal(const A& actual, const E& expected)
295{
296 if (actual >= expected)
297 return;
298 std::stringstream ss;
299 ss << "value '" << actual
300 << "' is not greater than or equals to the expected '" << expected
301 << "'";
302 throw TestFailed(ss.str());
303}
304
306void assert_startswith(const std::string& actual, const std::string& expected);
307
309void assert_endswith(const std::string& actual, const std::string& expected);
310
312void assert_contains(const std::string& actual, const std::string& expected);
313
315void assert_not_contains(const std::string& actual,
316 const std::string& expected);
317
324void assert_re_matches(const std::string& actual, const std::string& expected);
325
332void assert_not_re_matches(const std::string& actual,
333 const std::string& expected);
334
335template <class A> struct Actual
336{
337 A _actual;
338 Actual(const A& actual) : _actual(actual) {}
339 Actual(const Actual&) = default;
340 Actual(Actual&&) = default;
341 ~Actual() = default;
342 Actual& operator=(const Actual&) = delete;
343 Actual& operator=(Actual&&) = delete;
344
345 void istrue() const { assert_true(_actual); }
346 void isfalse() const { assert_false(_actual); }
347 template <typename E> void operator==(const E& expected) const
348 {
349 assert_equal(_actual, expected);
350 }
351 template <typename E> void operator!=(const E& expected) const
352 {
353 assert_not_equal(_actual, expected);
354 }
355 template <typename E> void operator<(const E& expected) const
356 {
357 return assert_less(_actual, expected);
358 }
359 template <typename E> void operator<=(const E& expected) const
360 {
361 return assert_less_equal(_actual, expected);
362 }
363 template <typename E> void operator>(const E& expected) const
364 {
365 return assert_greater(_actual, expected);
366 }
367 template <typename E> void operator>=(const E& expected) const
368 {
369 return assert_greater_equal(_actual, expected);
370 }
371};
372
373template <typename T> inline Actual<int> actual_int(const T& value)
374{
375 return Actual<int>(value);
376}
377template <typename T> inline Actual<unsigned> actual_unsigned(const T& value)
378{
379 return Actual<unsigned>(value);
380}
381template <typename T> inline Actual<double> actual_double(const T& value)
382{
383 return Actual<double>(value);
384}
385
386struct ActualCString
387{
388 const char* _actual;
389 ActualCString(const char* s) : _actual(s) {}
390
391 void istrue() const { return assert_true(_actual); }
392 void isfalse() const { return assert_false(_actual); }
393 void operator==(const char* expected) const;
394 void operator==(const std::string& expected) const;
395 void operator!=(const char* expected) const;
396 void operator!=(const std::string& expected) const;
397 void operator<(const std::string& expected) const;
398 void operator<=(const std::string& expected) const;
399 void operator>(const std::string& expected) const;
400 void operator>=(const std::string& expected) const;
401 void startswith(const std::string& expected) const;
402 void endswith(const std::string& expected) const;
403 void contains(const std::string& expected) const;
404 void not_contains(const std::string& expected) const;
405 void matches(const std::string& re) const;
406 void not_matches(const std::string& re) const;
407};
408
409struct ActualStdString : public Actual<std::string>
410{
411 explicit ActualStdString(const std::string& s) : Actual<std::string>(s) {}
412
413 using Actual<std::string>::operator==;
414 void operator==(const std::vector<uint8_t>& expected) const;
415 using Actual<std::string>::operator!=;
416 void operator!=(const std::vector<uint8_t>& expected) const;
417 void startswith(const std::string& expected) const;
418 void endswith(const std::string& expected) const;
419 void contains(const std::string& expected) const;
420 void not_contains(const std::string& expected) const;
421 void matches(const std::string& re) const;
422 void not_matches(const std::string& re) const;
423};
424
425struct ActualPath : public Actual<std::filesystem::path>
426{
427 explicit ActualPath(const std::filesystem::path& p)
428 : Actual<std::filesystem::path>(p)
429 {
430 }
431
432 using Actual<std::filesystem::path>::operator==;
433 using Actual<std::filesystem::path>::operator!=;
434
435 // Check if the normalized paths match
436 void is(const std::filesystem::path& expected) const;
437 [[deprecated("Use path_startswith")]] void
438 startswith(const std::string& data) const;
439
440 void path_startswith(const std::filesystem::path& expected) const;
441 void path_endswith(const std::filesystem::path& expected) const;
442 void path_contains(const std::filesystem::path& expected) const;
443 void path_not_contains(const std::filesystem::path& expected) const;
444
445 void exists() const;
446 void not_exists() const;
447 void empty() const;
448 void not_empty() const;
449
450 void contents_startwith(const std::string& data) const;
451 void contents_equal(const std::string& data) const;
452 void contents_equal(const std::vector<uint8_t>& data) const;
453 void contents_equal(const std::initializer_list<std::string>& lines) const;
454 void contents_match(const std::string& data_re) const;
455 void
456 contents_match(const std::initializer_list<std::string>& lines_re) const;
457};
458
459struct ActualDouble : public Actual<double>
460{
461 using Actual::Actual;
462
463 void almost_equal(double expected, unsigned places) const;
464 void not_almost_equal(double expected, unsigned places) const;
465};
466
467template <typename A> inline Actual<A> actual(const A& actual)
468{
469 return Actual<A>(actual);
470}
471inline ActualCString actual(const char* actual)
472{
473 return ActualCString(actual);
474}
475inline ActualCString actual(char* actual) { return ActualCString(actual); }
476inline ActualStdString actual(const std::string& actual)
477{
478 return ActualStdString(actual);
479}
480inline ActualStdString actual(const std::vector<uint8_t>& actual)
481{
482 return ActualStdString(std::string(actual.begin(), actual.end()));
483}
484inline ActualPath actual(const std::filesystem::path& actual)
485{
486 return ActualPath(actual);
487}
488inline ActualDouble actual(double actual) { return ActualDouble(actual); }
489
490struct ActualFunction : public Actual<std::function<void()>>
491{
492 using Actual::Actual;
493
494 void throws(const std::string& what_match) const;
495};
496
497inline ActualFunction actual_function(std::function<void()> actual)
498{
499 return ActualFunction(actual);
500}
501
502inline ActualPath actual_path(const char* pathname)
503{
504 return ActualPath(pathname);
505}
506inline ActualPath actual_path(const std::string& pathname)
507{
508 return ActualPath(pathname);
509}
510inline ActualPath actual_file(const char* pathname)
511{
512 return ActualPath(pathname);
513}
514inline ActualPath actual_file(const std::string& pathname)
515{
516 return ActualPath(pathname);
517}
518
519template <typename T> struct ActualVector : public Actual<std::vector<T>>
520{
521 explicit ActualVector(const std::vector<T>& v) : Actual<std::vector<T>>(v)
522 {
523 }
524
525 void _print_vector(std::ostream& o, const std::vector<T>& value) const
526 {
527 o << "[";
528 bool first = true;
529 for (const auto& el : value)
530 {
531 if (first)
532 first = false;
533 else
534 o << ", ";
535 o << el;
536 }
537 o << "]";
538 }
539
540 void operator==(const std::vector<T>& expected)
541 {
542 if (expected == this->_actual)
543 return;
544 std::stringstream ss;
545 ss << "Vector ";
546 _print_vector(ss, this->_actual);
547 ss << " is not the same as expected ";
548 _print_vector(ss, expected);
549 throw TestFailed(ss.str());
550 }
551
552 void operator!=(const std::vector<T>& expected)
553 {
554 if (expected != this->_actual)
555 return;
556 std::stringstream ss;
557 ss << "Vector ";
558 _print_vector(ss, this->_actual);
559 ss << " unexpectedly matches the value provided";
560 throw TestFailed(ss.str());
561 }
562};
563
564template <typename T>
565inline ActualVector<T> actual(const std::vector<T>& actual)
566{
567 return ActualVector(actual);
568}
569
577#define wassert(...) \
578 do \
579 { \
580 try \
581 { \
582 __VA_ARGS__; \
583 } \
584 catch (wreport::tests::TestFailed & e1) \
585 { \
586 e1.add_stack_info(__FILE__, __LINE__, #__VA_ARGS__, \
587 wreport_test_location_info); \
588 throw; \
589 } \
590 catch (std::exception & e2) \
591 { \
592 throw wreport::tests::TestFailed(e2, __FILE__, __LINE__, \
593 #__VA_ARGS__, \
594 wreport_test_location_info); \
595 } \
596 } while (0)
597
599#define wassert_true(...) wassert(actual(__VA_ARGS__).istrue())
600
602#define wassert_false(...) wassert(actual(__VA_ARGS__).isfalse())
603
609#define wassert_throws(exc, ...) \
610 [&]() { \
611 try \
612 { \
613 __VA_ARGS__; \
614 wfail_test(#__VA_ARGS__ " did not throw " #exc); \
615 } \
616 catch (TestFailed & e1) \
617 { \
618 throw; \
619 } \
620 catch (exc & e2) \
621 { \
622 return e2; \
623 } \
624 catch (std::exception & e3) \
625 { \
626 std::string msg(#__VA_ARGS__ " did not throw " #exc \
627 " but threw "); \
628 msg += typeid(e3).name(); \
629 msg += " instead"; \
630 wfail_test(msg); \
631 } \
632 }()
633
641#define wcallchecked(func) \
642 [&]() { \
643 try \
644 { \
645 return func; \
646 } \
647 catch (wreport::tests::TestFailed & e) \
648 { \
649 e.add_stack_info(__FILE__, __LINE__, #func, \
650 wreport_test_location_info); \
651 throw; \
652 } \
653 catch (std::exception & e) \
654 { \
655 throw wreport::tests::TestFailed(e, __FILE__, __LINE__, #func, \
656 wreport_test_location_info); \
657 } \
658 }()
659
663#define wfail_test(msg) wassert(throw wreport::tests::TestFailed((msg)))
664
665struct TestCase;
666struct TestController;
667struct TestRegistry;
668struct TestCaseResult;
669struct TestMethod;
670struct TestMethodResult;
671
675struct TestMethod
676{
678 std::string name;
679
681 std::string doc;
682
688 std::function<void()> test_function;
689
690 TestMethod(const std::string& name_) : name(name_), test_function() {}
691
692 TestMethod(const std::string& name_, std::function<void()> test_function_)
693 : name(name_), test_function(test_function_)
694 {
695 }
696};
697
702struct TestCase
703{
705 std::string name;
706
708 std::vector<TestMethod> methods;
709
711 bool tests_registered = false;
712
713 TestCase(const std::string& name);
714 virtual ~TestCase() {}
715
720
728 virtual void register_tests() = 0;
729
733 virtual void setup() {}
734
738 virtual void teardown() {}
739
743 virtual void test_setup() {}
744
748 virtual void test_teardown() {}
749
754
759
768
782 TestMethod& method);
783
788 TestMethod& add_method(const std::string& name_)
789 {
790 methods.emplace_back(name_);
791 return methods.back();
792 }
793
797 template <typename... Args>
798 TestMethod& add_method(const std::string& name_,
799 std::function<void()> test_function)
800 {
801 methods.emplace_back(name_, test_function);
802 return methods.back();
803 }
804
808 template <typename... Args>
809 TestMethod& add_method(const std::string& name_, const std::string& doc,
810 std::function<void()> test_function)
811 {
812 methods.emplace_back(name_, test_function);
813 methods.back().doc = doc;
814 return methods.back();
815 }
816};
817
829{
830 // Called before each test
831 void test_setup() {}
832
833 // Called after each test
834 void test_teardown() {}
835};
836
837template <typename Fixture, typename... Args>
838static inline Fixture* fixture_factory(Args... args)
839{
840 return new Fixture(args...);
841}
842
846template <typename FIXTURE> class FixtureTestCase : public TestCase
847{
848public:
849 typedef FIXTURE Fixture;
850
851 Fixture* fixture = nullptr;
852 std::function<Fixture*()> make_fixture;
853
854 template <typename... Args>
855 FixtureTestCase(const std::string& name_, Args... args) : TestCase(name_)
856 {
857 make_fixture = std::bind(fixture_factory<FIXTURE, Args...>, args...);
858 }
859 FixtureTestCase(const FixtureTestCase&) = delete;
860 FixtureTestCase(FixtureTestCase&&) = delete;
861 FixtureTestCase& operator=(const FixtureTestCase&) = delete;
862 FixtureTestCase& operator=(FixtureTestCase&) = delete;
863
864 void setup() override
865 {
867 fixture = make_fixture();
868 }
869
870 void teardown() override
871 {
872 delete fixture;
873 fixture = nullptr;
875 }
876
878 {
880 if (fixture)
881 fixture->test_setup();
882 }
883
885 {
886 if (fixture)
887 fixture->test_teardown();
889 }
890
895 template <typename... Args>
896 TestMethod& add_method(const std::string& name_,
897 std::function<void(FIXTURE&)> test_function)
898 {
899 return TestCase::add_method(name_, [=]() { test_function(*fixture); });
900 }
901
906 template <typename... Args>
907 TestMethod& add_method(const std::string& name_, const std::string& doc,
908 std::function<void(FIXTURE&)> test_function)
909 {
910 return TestCase::add_method(name_, doc,
911 [=]() { test_function(*fixture); });
912 }
913};
914
915} // namespace wreport::tests
916#endif
void setup() override
Set up the test case before it is run.
Definition utils/tests.h:864
TestMethod & add_method(const std::string &name_, std::function< void(FIXTURE &)> test_function)
Register a new test method that takes a reference to the fixture as argument.
Definition utils/tests.h:896
void method_teardown(TestMethodResult &mr) override
Clean up after the test method is run.
Definition utils/tests.h:884
void teardown() override
Clean up after the test case is run.
Definition utils/tests.h:870
void method_setup(TestMethodResult &mr) override
Set up before the test method is run.
Definition utils/tests.h:877
TestMethod & add_method(const std::string &name_, const std::string &doc, std::function< void(FIXTURE &)> test_function)
Register a new test method that takes a reference to the fixture as argument, including documentation...
Definition utils/tests.h:907
Utility functions for the unit tests.
Definition tests.h:38
void assert_false(const A &actual)
Test function that ensures that the actual value is false.
Definition utils/tests.h:172
void assert_greater(const A &actual, const E &expected)
Ensure that the actual value is greater than the reference value.
Definition utils/tests.h:282
void assert_contains(const std::string &actual, const std::string &expected)
Ensure that the string actual contains expected.
void assert_endswith(const std::string &actual, const std::string &expected)
Ensure that the string actual ends with expected.
void assert_less(const A &actual, const E &expected)
Ensure that the actual value is less than the reference value.
Definition utils/tests.h:258
void assert_greater_equal(const A &actual, const E &expected)
Ensure that the actual value is greather or equal than the reference value.
Definition utils/tests.h:294
void assert_re_matches(const std::string &actual, const std::string &expected)
Ensure that the string actual matches the extended regular expression expected.
void assert_true(const A &actual)
The following assert_* functions throw TestFailed without capturing file/line numbers,...
Definition utils/tests.h:160
void assert_not_equal(const A &actual, const E &expected)
Test function that ensures that the actual value is different than a reference one.
Definition utils/tests.h:246
void assert_less_equal(const A &actual, const E &expected)
Ensure that the actual value is less or equal than the reference value.
Definition utils/tests.h:270
void assert_not_re_matches(const std::string &actual, const std::string &expected)
Ensure that the string actual does not match the extended regular expression expected.
void assert_not_contains(const std::string &actual, const std::string &expected)
Ensure that the string actual does not contain expected.
void assert_startswith(const std::string &actual, const std::string &expected)
Ensure that the string actual starts with expected.
Definition utils/tests.h:460
Definition utils/tests.h:491
Definition utils/tests.h:426
Definition utils/tests.h:410
Definition utils/tests.h:520
Definition utils/tests.h:336
Base class for test fixtures.
Definition utils/tests.h:829
Add information to the test backtrace for the tests run in the current scope.
Definition utils/tests.h:54
std::ostream & operator()()
Clear the current information and return the output stream to which new information can be sent.
Result of running a whole test case.
Definition testrunner.h:91
virtual void test_setup()
Set up before each test method is run.
Definition utils/tests.h:743
virtual TestCaseResult run_tests(TestController &controller)
Call setup(), run all the tests that have been registered, then call teardown().
TestMethod & add_method(const std::string &name_, std::function< void()> test_function)
Register a new test method.
Definition utils/tests.h:798
virtual void register_tests()=0
This will be called before running the test case, to populate it with its test methods.
std::vector< TestMethod > methods
All registered test methods.
Definition utils/tests.h:708
virtual void setup()
Set up the test case before it is run.
Definition utils/tests.h:733
TestMethod & add_method(const std::string &name_, const std::string &doc, std::function< void()> test_function)
Register a new test method, including documentation.
Definition utils/tests.h:809
virtual void method_teardown(TestMethodResult &)
Clean up after the test method is run.
Definition utils/tests.h:758
virtual void method_setup(TestMethodResult &)
Set up before the test method is run.
Definition utils/tests.h:753
std::string name
Name of the test case.
Definition utils/tests.h:705
bool tests_registered
Set to true the first time register_tests_once is run.
Definition utils/tests.h:711
void register_tests_once()
Idempotent wrapper for register_tests()
virtual void teardown()
Clean up after the test case is run.
Definition utils/tests.h:738
virtual TestMethodResult run_test(TestController &controller, TestMethod &method)
Run a test method.
TestMethod & add_method(const std::string &name_)
Register a new test method, with the actual test function to be added later.
Definition utils/tests.h:788
virtual void test_teardown()
Clean up after each test method is run.
Definition utils/tests.h:748
Abstract interface for the objects that supervise test execution.
Definition testrunner.h:156
Exception thrown when a test assertion fails, normally by Location::fail_test.
Definition utils/tests.h:104
Result of running a test method.
Definition testrunner.h:24
Test method information.
Definition utils/tests.h:676
std::string name
Name of the test method.
Definition utils/tests.h:678
std::function< void()> test_function
Main body of the test method.
Definition utils/tests.h:688
std::string doc
Documentation attached to this test method.
Definition utils/tests.h:681
Definition utils/tests.h:89
std::string backtrace() const
Return the formatted backtrace for this location.
void backtrace(std::ostream &out) const
Write the formatted backtrace for this location to out.