Skip to main content

test.cpp File

C++ source file with implementations for the µTest++ test hierarchy member functions. More...

Included Headers​

#include "micro-os-plus/micro-test-plus/test.h" #include "micro-os-plus/micro-test-plus/runner.h" #include <cstdio> #include <cstring> #include <exception> #include <string>

Namespaces Index​

namespacemicro_os_plus

The primary namespace for the µOS++ framework. More...

namespacemicro_test_plus

Primary namespace for the µTest++ testing framework. More...

namespacedetail

Internal implementation details for the µTest++ framework. More...

Functions Index​

voidescape_control_characters (std::string &name)

Escapes the control characters of a name. More...

Description​

C++ source file with implementations for the µTest++ test hierarchy member functions.

This source file contains the non-template implementations for the test hierarchy of the µTest++ framework: the constructors and destructors, the run() member functions of the test cases and suites, which call the reporter before and after invoking the stored callable, and the registration of the child test cases, including the accumulation of their totals.

All definitions reside within the micro_os_plus::micro_test_plus namespace.

This file must be included when building the µTest++ library.

Functions​

escape_control_characters()​

void micro_os_plus::micro_test_plus::detail::escape_control_characters (std::string & name)

Escapes the control characters of a name.

Replaces, in place, the control characters other than the tab, including the line feed and the NUL, with their escape sequences, so that the names reach the console and the output files as one line of visible text. Defined in src/test.cpp.

Parameters
name

The name to escape.

The text is scanned first, so that a name without control characters is not touched. The tab is kept; all the other control characters, including the line feed and the NUL, and DEL, are replaced.

Definition at line 80 of file test.cpp.

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 }

References micro_os_plus::micro_test_plus::detail::append_control_escape and micro_os_plus::micro_test_plus::detail::escape_control_characters.

Referenced by micro_os_plus::micro_test_plus::located_name::located_name, micro_os_plus::micro_test_plus::located_name::located_name, micro_os_plus::micro_test_plus::located_name::located_name, micro_os_plus::micro_test_plus::located_name::located_name and micro_os_plus::micro_test_plus::detail::escape_control_characters.

File Listing​

The file content with the documentation metadata removed is:

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_,
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// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.8 by Doxygen 1.18.0.