micro-test-plus 6.0.0
µTest++ Testing Framework
Loading...
Searching...
No Matches
test.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the µOS++ project (https://micro-os-plus.github.io/).
3 * Copyright (c) 2021-2026 Liviu Ionescu. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose is hereby granted, under the terms of the MIT license.
7 *
8 * If a copy of the license was not distributed with this file, it can be
9 * obtained from https://opensource.org/licenses/mit.
10 */
11
12// ----------------------------------------------------------------------------
13
32
33// ----------------------------------------------------------------------------
34
37
38#if __has_include("micro-os-plus/diag/trace.h")
39#include "micro-os-plus/diag/trace.h"
40#endif // __has_include("micro-os-plus/diag/trace.h")
41
42#include <cstdio>
43#include <cstring>
44#include <exception>
45#include <string>
46
47// ----------------------------------------------------------------------------
48
49#if defined(__GNUC__)
50#if defined(__clang__)
51#pragma clang diagnostic ignored "-Wunknown-warning-option"
52#pragma clang diagnostic ignored "-Wc++98-compat"
53#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
54#pragma clang diagnostic ignored "-Wc++20-compat"
55#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
56#else // GCC only
57#pragma GCC diagnostic ignored "-Wredundant-tags"
58#pragma GCC diagnostic ignored "-Wsuggest-final-types"
59#pragma GCC diagnostic ignored "-Wsuggest-final-methods"
60#endif // defined(__clang__)
61#endif // defined(__GNUC__)
62
63// ============================================================================
64
65#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
66
67// ----------------------------------------------------------------------------
68
70{
71 namespace detail
72 {
79 void
80 escape_control_characters (std::string& name)
81 {
82 const auto is_control = [] (char c)
83 {
84 const auto unit = static_cast<unsigned char> (c);
85 return (unit < 0x20 && c != '\t') || unit == 0x7F;
86 };
87
88 std::size_t first = 0;
89 while (first < name.size () && !is_control (name[first]))
90 {
91 ++first;
92 }
93 if (first == name.size ())
94 {
95 return;
96 }
97
98 std::string escaped (name, 0, first);
99 escaped.reserve (name.size () + 8);
100 for (std::size_t i = first; i < name.size (); ++i)
101 {
102 if (is_control (name[i]))
103 {
104 append_control_escape (escaped, name[i]);
105 }
106 else
107 {
108 escaped.push_back (name[i]);
109 }
110 }
111 name = std::move (escaped);
112 }
113
114 // ========================================================================
115
127 size_t own_index)
129 {
130#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
131 trace::printf ("%s '%s' %" MICRO_OS_PLUS_MICRO_TEST_PLUS_PRI_SIZE "\n",
132 __PRETTY_FUNCTION__, name, own_index_);
133#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
134 }
135
143 {
144#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
145 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
146#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
147 }
148
154 [[nodiscard]] reporter&
155 runnable_base::test_reporter (void) const noexcept
156 {
157 return *runner_.reporter_;
158 }
159
165 [[nodiscard]] test_case*
167 {
168 return runner_.running_test_case_;
169 }
170
176 [[nodiscard]] bool
177 runnable_base::is_aborted (void) const noexcept
178 {
179 return runner_.aborted_;
180 }
181
187 [[nodiscard]] bool
188 runnable_base::is_listing (void) const noexcept
189 {
190 return runner_.options_.list_tests;
191 }
192
198 bool
200 const suite& target) const noexcept
201 {
202 const test_case* running = runner_.running_test_case_;
203 return running != nullptr && &running->parent_suite_ == &target;
204 }
205
211 suite*
213 {
214 suite* previous = runner_.running_suite_;
215 runner_.running_suite_ = running;
216 return previous;
217 }
218
238 bool
240 const located_name& name)
241 {
242 if (runner_.running_suite_ == &target)
243 {
244 return true;
245 }
246
249 {
250 return false;
251 }
252
253 std::string message{ "test case '" };
254 message.append (name.name ());
255 message.append ("' started on suite '");
256 message.append (target.name ());
257 message.append ("', which is not running");
258
259 if (runner_.running_test_case_ != nullptr
261 {
263 name.location ());
264 return false;
265 }
266
267 suite* running = runner_.running_suite_;
268 if (running != nullptr && !runner_.options_.list_tests)
269 {
270 running->next_test_point_number ();
271 running->test_reporter ().fail_suite (message, *running);
273 return false;
274 }
275
276 runner_.reject_call (message, &name.location ());
277 return false;
278 }
279
288 void
290 {
291 std::string message{ "unexpected exception in the body of suite '" };
292 message.append (name_);
293 message.append ("'");
294
295 runner_.reject_call (message);
296 }
297
305 [[noreturn]] void
307 {
308 runner_.abort (sl, true);
309 }
310
349 void
351 suite& suite)
352 {
355 {
356 return;
357 }
358
360
361 // While the test cases are listed, the selected ones are listed and
362 // the others ignored; none is run.
364 {
365 if (runner_.is_selected (suite.name (), 0)
368 {
370 }
371 return;
372 }
373
374 // Skip the test cases not selected by the filter; the outer test
375 // cases, if any, were selected, otherwise this one was not created.
376 if (!runner_.is_selected (suite.name (), 0)
379 {
382 return;
383 }
384
385 // Run the child test case immediately, as the innermost running test
386 // case of the runner. The outer one is restored by the guard, also
387 // when an exception leaves run() (for example thrown by an abort
388 // handler), so that the runner never keeps a destroyed test case.
389 {
390 struct running_guard
391 {
392 class test_case*& running;
393 class test_case* outer;
394
395 ~running_guard ()
396 {
397 running = outer;
398 }
400
402 // A non-virtual call, so that run() is inlined here; each nested
403 // test case adds this call chain to the stack.
404 test_case.test_case::run ();
405 }
406
407 // This test executed one more test case.
408#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
409 trace::printf ("%s test case '%s' executed one more test case\n",
410 __PRETTY_FUNCTION__, name ());
411#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
413
414 // Do not accumulate the totals from the child test into the current test
415 // totals, each test case shows only its counters. But a failed child
416 // fails this test too, as in node-tap; the failure reaches all outer
417 // tests, one level at a time, when they end.
418 if (!test_case.totals ().passed ())
419 {
421 }
422
423#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
424 trace::printf ("%s suite '%s' totals\n", __PRETTY_FUNCTION__,
425 suite.name ());
426#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
427
428 // Accumulate the totals from the child test into the suite totals.
430 }
431 } // namespace detail
432
433 // ==========================================================================
434
446 detail::check_result&& result)
447 {
448 test_case& running = *running_test_case ();
449
450 std::string message{ "[check on test case '" };
451 message.append (name ());
452 message.append ("' while '");
453 message.append (running.name ());
454 message.append ("' runs] ");
455
456 result.init (false, kind, sl, running,
457 running.test_reporter ().expression_,
458 running.test_reporter ().writes_passed_checks ());
459
460 return std::move (std::move (result) << message);
461 }
462
471 void
473 {
474 test_case& running = *running_test_case ();
475
476 std::string message{ "[test case '" };
477 message.append (name.name ());
478 message.append ("' started on test case '");
479 message.append (this->name ());
480 message.append ("' while '");
481 message.append (running.name ());
482 message.append ("' runs]");
483
484 running.report_rejected_test (message, name.location ());
485 }
486
492 void
493 test_case::report_rejected_test (const std::string& message,
494 const reflection::source_location& location)
495 {
497 result.init (false, detail::check_kind::expectation, location, *this,
498 test_reporter ().expression_,
499 test_reporter ().writes_passed_checks ());
500 std::move (result) << message;
501 }
502
503 // ==========================================================================
504
513 {
514#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
515 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
516#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
517 }
518
519#if defined(__cpp_exceptions)
534 void
536 {
538
540 .message = "unexpected exception",
541 .has_expression = false,
542 .location = located_name_.location () };
543 if (what != nullptr)
544 {
545 failure.message.append (": ");
546 failure.message.append (what);
547 }
548 else
549 {
550 failure.message.append (" of unknown type");
551 }
552
553 const std::string expression{};
554 test_reporter ().fail (failure, expression, *this);
556 }
557#endif // defined(__cpp_exceptions)
558
559 // ==========================================================================
560
568 {
569#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
570 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
571#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
572
573 unlink ();
574 }
575
603 void
605 {
606#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
607 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
608#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
609
610 // Marks this suite as the one that accepts new test cases, until the
611 // body returns.
612 struct running_guard
613 {
614 suite& self;
615 suite* outer;
616
617 ~running_guard ()
618 {
619 self.exchange_running_suite (outer);
620 }
621 } guard{ *this, exchange_running_suite (this) };
622
623 // While the test cases are listed, the body only creates them, so
624 // that they are listed; the suite is not reported. An exception
625 // thrown by the body is reported on stderr and in the listing, and
626 // the listing continues with the next suite.
627 if (is_listing ())
628 {
629#if defined(__cpp_exceptions)
630 try
631 {
632 callable_ (*this);
633 }
634 catch (...)
635 {
636 if (is_aborted ())
637 {
638 throw;
639 }
641 }
642#else
643 callable_ (*this);
644#endif // defined(__cpp_exceptions)
645 return;
646 }
647
648 detail::reporter& reporter = this->test_reporter ();
649
650 this->stopwatch_.start ();
651 reporter.begin_suite (*this);
652
653#if defined(__cpp_exceptions)
654 try
655 {
656 // Invoke the callable, passing the self reference.
657 callable_ (*this);
658 }
659 catch (const std::exception& e)
660 {
661 if (is_aborted ())
662 {
663 // Thrown by the abort handler; the report is complete.
664 throw;
665 }
666 fail_unexpected_exception (e.what ());
667 }
668 catch (...)
669 {
670 if (is_aborted ())
671 {
672 // Thrown by the abort handler; the report is complete.
673 throw;
674 }
676 }
677#else
678 // Invoke the callable, passing the self reference.
679 callable_ (*this);
680#endif // defined(__cpp_exceptions)
681
682 this->stopwatch_.stop ();
683 reporter.end_suite (*this);
684 }
685
686#if defined(__cpp_exceptions)
700 void
702 {
704
705 std::string message{ "unexpected exception" };
706 if (what != nullptr)
707 {
708 message.append (" in the suite body: ");
709 message.append (what);
710 }
711 else
712 {
713 message.append (" of unknown type in the suite body");
714 }
715
716 test_reporter ().fail_suite (message, *this);
718 }
719#endif // defined(__cpp_exceptions)
720
727 constinit suite* suite::first_ = nullptr;
728
737 void
738 suite::link (void) noexcept
739 {
740#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
741 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
742#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
743
744 // std::strcmp() compares as unsigned char, like std::string_view,
745 // without a temporary object (which triggers -Waggregate-return).
746 suite** slot = &first_;
747 while (*slot != nullptr && std::strcmp ((*slot)->name_, name_) <= 0)
748 {
749 slot = &(*slot)->next_;
750 }
751 next_ = *slot;
752 *slot = this;
753 }
754
759 void
760 suite::unlink (void) noexcept
761 {
762 for (suite** slot = &first_; *slot != nullptr; slot = &(*slot)->next_)
763 {
764 if (*slot == this)
765 {
766 *slot = next_;
767 next_ = nullptr;
768 return;
769 }
770 }
771 }
772
773 // ==========================================================================
774
784 : suite{ located_name{ name }, test_runner, [] (suite&) noexcept {} }
785 {
786#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
787 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name);
788#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
789
790 own_index_ = 1;
791 }
792
800 {
801#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
802 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
803#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
804 }
805
806 // ==========================================================================
807
813 void
815 {
817 }
818
826 {
827#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
828 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name_);
829#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
830 }
831
832 // --------------------------------------------------------------------------
833} // namespace micro_os_plus::micro_test_plus
834
835// ----------------------------------------------------------------------------
836
837#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
838
839// ----------------------------------------------------------------------------
void increment_skipped_test_cases(size_t count=1) noexcept
Increments the skipped test cases counter.
bool passed(void) const noexcept
Checks whether all executed checks and test cases were successful.
void increment_executed_test_cases(size_t count=1) noexcept
Increments the executed test cases counter.
void increment_failed_test_cases(size_t count=1) noexcept
Increments the failed test cases counter.
void increment_failed_checks(size_t count=1) noexcept
Increments the failed-checks counter.
The result of one check, reported when destroyed.
void init(const Expr_T &expr, check_kind kind, const reflection::source_location &location, test_case &test_case, expression_formatter &expression, bool writes_passed)
Initialises the result with the check of an expression.
Abstract base class for the reporters that display test results, including operand values for failure...
Definition reporter.h:276
bool writes_passed_checks(void) const noexcept
Returns whether the passed checks are written anywhere.
Definition reporter.cpp:320
virtual void skip_test_case(const test_case &test_case)=0
Reports a test case skipped by the filter.
virtual void fail_suite(const std::string &message, const suite &suite)=0
Reports a failure of a test suite outside its test cases.
void fail(const failure_info &failure, const std::string &expression, const test_case &test_case)
Reports a failed condition.
Definition reporter.cpp:771
virtual void end_suite(const suite &suite)=0
Marks the end of a test suite.
expression_formatter expression_
Expression formatter for pass and fail reporting.
Definition reporter.h:1423
virtual void begin_suite(const suite &suite)=0
Marks the beginning of a test suite.
runner & test_runner(void) const noexcept
Returns the test runner that owns this test runnable.
size_t next_test_point_number(void) noexcept
Advances to the next child test point, and returns its number.
virtual ~runnable_base()
Virtual destructor.
Definition test.cpp:142
void reject_body_exception(void)
Reports the exception that escaped the body of a suite while the test cases are listed,...
Definition test.cpp:289
void after_test_case_create(class test_case &test_case, suite &suite)
Counts a newly constructed child test case and executes it immediately.
Definition test.cpp:350
class test_case * running_test_case(void) const noexcept
Returns the innermost test case being run by the owning runner, if any.
Definition test.cpp:166
bool is_listing(void) const noexcept
Checks whether the owning runner lists the test cases instead of running them.
Definition test.cpp:188
bool accepts_test_case(const suite &target, const located_name &name)
Checks whether a test case can be created on a suite.
Definition test.cpp:239
void abort(const reflection::source_location &sl=reflection::source_location::current()) const
Aborts test execution via the owning runner, for a failed requirement.
Definition test.cpp:306
friend class micro_test_plus::runner
The runner, which numbers and runs the suites.
Definition test.h:357
size_t own_index_
The test index, counting from 1.
Definition test.h:859
const char * name(void) const noexcept
Returns the node name.
suite * exchange_running_suite(suite *running) noexcept
Replaces the suite whose body is running in the owning runner, and returns the previous one.
Definition test.cpp:212
class reporter & test_reporter(void) const noexcept
Returns the test reporter associated with this test runnable.
Definition test.cpp:155
size_t own_index(void) const noexcept
Deprecated positional index of this node within its parent.
runnable_base(const char *name, runner &test_runner, size_t own_index)
Constructs a runnable_base with a name, runner, and index.
Definition test.cpp:126
const counters & totals(void) const noexcept
Returns the totals of the node.
bool is_aborted(void) const noexcept
Checks whether the test execution was aborted by the owning runner.
Definition test.cpp:177
runner & runner_
Reference to the test runner that owns this object.
Definition test.h:848
bool has_running_test_case_of(const suite &target) const noexcept
Checks whether the innermost running test case belongs to a suite.
Definition test.cpp:199
size_t child_test_cases_count_
The number of direct child test cases.
Definition test.h:883
void stop(void) noexcept
Records the stop time, from the system clock.
void start(void) noexcept
Records the start time, from the system clock.
Definition stopwatch.cpp:87
A name of a suite or of a test case, with the location where it was given.
Definition test.h:177
const char * name(void) const noexcept
Returns the name.
const reflection::source_location & location(void) const noexcept
Returns the location where the name was given.
Local implementation of source location information for diagnostics.
Definition reflection.h:143
bool help
Whether to display the usage and the list of the options, instead of running the tests.
bool list_tests
Whether the test cases are listed instead of run.
class suite * running_suite_
The suite whose body is running, if any.
Definition runner.h:1409
void list_test_case(const class suite &suite, const class test_case &test_case)
Lists a test case, instead of running it.
Definition runner.cpp:563
bool has_option_errors(void) const noexcept
Checks whether an option error was found by initialise().
static void link_static_suite(runner &test_runner, class suite &suite) noexcept
Inserts a static suite into the list of suites, or reject it.
Definition runner.cpp:728
bool is_selected(const char *name, size_t level) const
Checks whether a suite or a test case is selected by the filter pattern of its nesting level.
Definition runner.cpp:530
void reject_call(const std::string &message, const reflection::source_location *location=nullptr)
Reports a rejected call, and records it.
Definition runner.cpp:660
void abort(const reflection::source_location &sl=reflection::source_location::current())
Aborts test execution immediately.
Definition runner.cpp:1004
runner_options options_
The options of the test session.
Definition runner.h:1306
test_case * running_test_case_
The innermost test case being run, if any.
Definition runner.h:1397
bool aborted_
Whether the test execution was aborted.
Definition runner.h:1386
void link_or_reject(void) noexcept
Inserts this suite into the list of suites, or reject it.
Definition test.cpp:814
~static_suite() override
Virtual destructor.
Definition test.cpp:825
A named, runnable test suite registered with the test runner.
Definition test.h:2059
void fail_unexpected_exception(const char *what)
Reports an exception that escaped the suite body as a failed check of the suite.
Definition test.cpp:701
void unlink(void) noexcept
Removes this suite from the list of suites.
Definition test.cpp:760
void run(void) override
Executes the suite body by invoking the stored callable.
Definition test.cpp:604
void link(void) noexcept
Inserts this suite into the list of suites.
Definition test.cpp:738
static suite * first_
The first suite in the list of suites.
Definition test.h:2475
suite * next_
The next suite in the list of suites.
Definition test.h:2463
friend class top_suite
The top suite, which is constructed by the runner.
Definition test.h:2091
detail::stopwatch stopwatch_
Stopwatch of this suite.
Definition test.h:2441
~suite() override
Virtual destructor.
Definition test.cpp:567
suite(located_name &&name, runner &test_runner, Callable_T &&callable, Args_T &&... arguments)
Constructs a suite with a name, runner, and callable body.
std::function< void(suite &)> callable_
Callable storing the suite body and any bound arguments.
Definition test.h:2431
A named, runnable test case that lives inside a suite.
Definition test.h:1362
void report_rejected_test(const std::string &message, const reflection::source_location &location)
Reports a rejected call as a failed check of this test case.
Definition test.cpp:493
void fail_unexpected_exception(const char *what)
Reports an exception that escaped the test case body as a failed check.
Definition test.cpp:535
~test_case() override
Virtual destructor.
Definition test.cpp:512
suite & parent_suite_
Reference to the parent suite that owns this test case.
Definition test.h:2007
test_case(const located_name &name, runner &test_runner, suite &parent_suite, size_t own_index, size_t nesting_depth, detail::callable_ref< test_case > body)
Constructs a test case with a name, runner, parent suite, index, nesting depth, and body.
const located_name & located_name_
The name and the location of the call to test() that created this test case.
Definition test.h:1998
detail::check_result && report_misdirected(detail::check_kind kind, const reflection::source_location &sl, detail::check_result &&result)
Reports a check on this test case as a failure of the running one.
Definition test.cpp:444
void report_misdirected_test(const located_name &name)
Reports a nested test case started on this test case as a failure of the running one.
Definition test.cpp:472
size_t nesting_depth(void) const noexcept
Returns the nesting depth of this test case.
friend class micro_test_plus::runner
The runner, which owns the top suite.
Definition test.h:2506
~top_suite() override
Virtual destructor.
Definition test.cpp:799
#define MICRO_OS_PLUS_MICRO_TEST_PLUS_PRI_SIZE
Portable printf() conversion for the size_t values.
Definition detail.h:102
Internal implementation details for the µTest++ framework.
check_kind
The kind of a check, which determines what happens when it fails.
void append_control_escape(std::string &text, char c)
Appends the escape sequence of a control character.
void escape_control_characters(std::string &name)
Escapes the control characters of a name.
Definition test.cpp:80
Primary namespace for the µTest++ testing framework.
C++ header file with declarations for the µTest++ test runner.
The details of a check, displayed when it fails.
std::string message
The message passed via operator<<(), if any.
C++ header file with declarations for the µTest++ test hierarchy.