Skip to main content

runner.cpp File

C++ source file with implementations for the µTest++ test runner methods. More...

Included Headers

#include <micro-os-plus/diag/trace.h> #include "micro-os-plus/micro-test-plus/runner.h" #include "micro-os-plus/micro-test-plus/utility.h" #include "micro-os-plus/micro-test-plus/reporter-tap.h" #include "micro-os-plus/micro-test-plus/reporter-human.h" #include <algorithm> #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...

Description

C++ source file with implementations for the µTest++ test runner methods.

This source file contains the core implementations for the test runner facilities of the µTest++ framework. It provides the logic for initialising the test environment, registering and managing test suites, handling command-line arguments, orchestrating test execution, and determining the overall test result. The implementation supports automated discovery and execution of test suites, flexible verbosity control, and robust mechanisms for aborting test execution in critical scenarios.

All definitions reside within the micro_os_plus::micro_test_plus namespace, ensuring clear separation from user code and minimising the risk of naming conflicts.

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

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 * Major parts of the code are inspired from v1.1.8 of the Boost UT project,
12 * released under the terms of the Boost Version 1 Software License,
13 * which can be obtained from https://www.boost.org/LICENSE_1_0.txt.
14 */
15
16// ----------------------------------------------------------------------------
17
38
39// ----------------------------------------------------------------------------
40
41#if __has_include(<micro-os-plus/project-config.h>)
42#include <micro-os-plus/project-config.h>
43#elif __has_include(<micro-os-plus/config.h>)
44#pragma message \
45 "micro-os-plus/config.h is deprecated, rename to micro-os-plus/project-config.h and include it instead of micro-os-plus/config.h"
46#include <micro-os-plus/config.h>
47#endif // __has_include(<micro-os-plus/project-config.h>)
48
49#if __has_include(<micro-os-plus/micro-test-plus-defines.h>)
50#include <micro-os-plus/micro-test-plus-defines.h>
51#endif // __has_include(<micro-os-plus/micro-test-plus-defines.h>)
52
53#include <micro-os-plus/diag/trace.h>
54
59
60#include <algorithm>
61#include <string>
62
63// ----------------------------------------------------------------------------
64
65#if defined(__GNUC__)
66#pragma GCC diagnostic ignored "-Waggregate-return"
67#if defined(__clang__)
68#pragma clang diagnostic ignored "-Wunknown-warning-option"
69#pragma clang diagnostic ignored "-Wc++98-compat"
70#pragma clang diagnostic ignored "-Wc++98-c++11-c++14-compat"
71#pragma clang diagnostic ignored "-Wpre-c++17-compat"
72#endif
73#endif
74
75// ============================================================================
76
78{
79 // --------------------------------------------------------------------------
80
87 runner&
88 detail::to_runner (static_runner& static_runner_ref) noexcept
89 {
90 return static_cast<runner&> (static_runner_ref);
91 }
92
99 void
101 static_suite& static_suite_ref)
102 {
103 static_runner::register_static_suite (static_runner_ref, static_suite_ref);
104 }
105
106 // --------------------------------------------------------------------------
107
114 runner::runner (void) : test_node{ "runner" }, top_suite_{ "", *this }
115 {
116#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
117#if defined(__GNUC__)
118#pragma GCC diagnostic push
119#if defined(__clang__)
120#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
121#endif
122#endif
123 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
124#if defined(__GNUC__)
125#pragma GCC diagnostic pop
126#endif
127#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
128 }
129
136 runner::runner (const char* top_suite_name)
137 : test_node{ "runner" }, top_suite_{ top_suite_name, *this }
138 {
139#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
140#if defined(__GNUC__)
141#pragma GCC diagnostic push
142#if defined(__clang__)
143#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
144#endif
145#endif
146 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
147#if defined(__GNUC__)
148#pragma GCC diagnostic pop
149#endif
150#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
151 }
152
159 {
160#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
161 trace::printf ("%s\n", __PRETTY_FUNCTION__);
162#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
163
164 // reporter_ is a unique_ptr; destroyed automatically.
165 }
166
167#if defined(__GNUC__)
168#pragma GCC diagnostic push
169#if defined(__clang__)
170#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
171#endif
172#endif
185 suite&
186 runner::initialise (int argc, char* argv[], const char* top_suite_name)
187 {
188#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
189 trace::printf ("%s\n", __PRETTY_FUNCTION__);
190#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
191
192#if !(defined(MICRO_OS_PLUS_INCLUDE_STARTUP) \
193 && defined(MICRO_OS_PLUS_DIAG_TRACE_ENABLED))
194#if defined(MICRO_OS_PLUS_DEBUG)
195 trace::printf ("argv[");
196 for (int i = 0; i < argc; ++i)
197 {
198 if (i > 0)
199 {
200 trace::printf (", ");
201 }
202 trace::printf ("'%s'", argv[i]);
203 }
204 trace::puts ("]");
205#endif // defined(MICRO_OS_PLUS_DEBUG)
206#endif // !defined(MICRO_OS_PLUS_INCLUDE_STARTUP)
207
208 if (strlen (top_suite_name) > 0)
209 {
210 // If provided by this call, use it, possibly override the
211 // deprecated constructor.
212 top_suite_name_ = top_suite_name;
213 top_suite_.name (top_suite_name_.c_str ());
214 }
215 else if (strlen (top_suite_.name ()) == 0)
216 {
217 // If not provided by the constructor or by this call, try to extract a
218 // name from argv[0], which is commonly the executable name. If that
219 // fails, use a default name.
220 if (argc > 0 && argv != nullptr && argv[0] != nullptr)
221 {
222 std::string_view top_suite_name_view{ utility::extract_file_name (
223 argv[0]) };
224
225 const auto dot_pos = top_suite_name_view.rfind ('.');
226 if (dot_pos != std::string_view::npos)
227 {
228 top_suite_name_view = top_suite_name_view.substr (0, dot_pos);
229 }
230
231 top_suite_name_ = top_suite_name_view;
232 }
233 else
234 {
235 top_suite_name_ = "default suite";
236 }
237 top_suite_.name (top_suite_name_.c_str ());
238 }
239
240 std::vector<std::string_view> argvs (argv, argv + argc);
241
242 std::string_view reporter_name{ "tap" };
243 static constexpr std::string_view reporter_prefix{ "--reporter=" };
244 for (size_t i = 0; i < argvs.size (); ++i)
245 {
246 if (argvs[i].starts_with (reporter_prefix))
247 {
248 reporter_name = argvs[i].substr (reporter_prefix.size ());
249 }
250 else if (argvs[i]
251 == reporter_prefix.substr (0, reporter_prefix.size () - 1))
252 {
253 if (i + 1 < argvs.size ())
254 {
255 reporter_name = argvs[++i];
256 }
257 else
258 {
259 fprintf (stderr, "error: --reporter option requires a "
260 "reporter name argument\n");
261 exit (1);
262 }
263 }
264 }
265
266 // Initialise and configure the reporter.
267 if (reporter_name == "human")
268 {
269 reporter_ = std::make_unique<reporter_human> (
270 std::make_unique<std::vector<std::string_view>> (
271 std::move (argvs)));
272 }
273 else if (reporter_name == "tap")
274 {
275 reporter_ = std::make_unique<reporter_tap> (
276 std::make_unique<std::vector<std::string_view>> (
277 std::move (argvs)));
278 }
279 else
280 {
281 fprintf (stderr, "error: unknown reporter '%.*s'\n",
282 static_cast<int> (reporter_name.size ()),
283 reporter_name.data ());
284 exit (1);
285 }
286
287 // ------------------------------------------------------------------------
288
289 timings_.timestamp_begin ();
290 reporter_->begin_session (*this);
291
292 top_suite_.timings ().timestamp_begin ();
293 reporter_->begin_suite (top_suite_);
294
295 return top_suite_;
296 }
297#if defined(__GNUC__)
298#pragma GCC diagnostic pop
299#endif
300
301 // --------------------------------------------------------------------------
302
310 void
311 runner::register_suite_ (std::unique_ptr<class suite> suite)
312 {
313#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
314#if defined(__GNUC__)
315#pragma GCC diagnostic push
316#if defined(__clang__)
317#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
318#endif
319#endif
320 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite->name ());
321#if defined(__GNUC__)
322#pragma GCC diagnostic pop
323#endif
324#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
325
326 children_suites_.push_back (std::move (suite));
327 }
328
339 void
341 {
342 // Use selection sort with unique_ptr::swap (returns void) to avoid
343 // std::sort triggering -Waggregate-return via std::move_backward,
344 // which returns a class-type iterator when operating on unique_ptr
345 // elements.
346 const size_t n = children_suites_.size ();
347 for (size_t i = 0; i < n; ++i)
348 {
349 size_t min_idx = i;
350 for (size_t j = i + 1; j < n; ++j)
351 {
352 if (std::string_view{ children_suites_[j]->name () }
353 < std::string_view{ children_suites_[min_idx]->name () })
354 min_idx = j;
355 }
356 if (min_idx != i)
357 children_suites_[i].swap (children_suites_[min_idx]);
358 }
359
360 for (size_t i = 0; i < n; ++i)
361 {
362 auto* suite_ptr = children_suites_[i].get ();
363
364 // 1 for 1-based index, 1 for top suite
365 suite_ptr->own_index (i + 1 + 1);
366
367 // Run the child suite immediately.
368 suite_ptr->run ();
369
370 // Accumulate the totals from the child suite into the runner
371 // totals.
372 // DO NOT increment executed_subtests here.
373 totals_ += suite_ptr->totals ();
374 }
375 }
376
386 int
388 {
389#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
390 trace::printf ("%s\n", __PRETTY_FUNCTION__);
391#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
392
393 if (reporter_ == nullptr)
394 {
395 fprintf (stderr, "error: test runner not initialised\n");
396 return 1;
397 }
398
399 top_suite_.timings ().timestamp_end ();
400 reporter_->end_suite (top_suite_);
401 totals_ += top_suite_.totals ();
402
403 run_suites_ ();
404
405 timings_.timestamp_end ();
406 reporter_->end_session (*this);
407
408 const int result = totals_.was_successful () ? 0 : 1;
409
410#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
411#if defined(__GNUC__)
412#pragma GCC diagnostic push
413#if defined(__clang__)
414#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
415#endif
416#endif
417 trace::printf ("%s -> %d\n", __PRETTY_FUNCTION__, result);
418#if defined(__GNUC__)
419#pragma GCC diagnostic pop
420#endif
421#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
422
423 return result;
424 }
425
431 void
433 {
434#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
435 trace::printf ("%s\n", __PRETTY_FUNCTION__);
436#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
437
438#if defined(__GNUC__)
439#pragma GCC diagnostic push
440#if defined(__clang__)
441#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
442#endif
443#endif
444 fprintf (stderr, "\nerror: test execution aborted at %s:%u\n",
446#if defined(__GNUC__)
447#pragma GCC diagnostic pop
448#endif
449
450 ::abort ();
451 }
452
458 size_t
459 runner::suites_count (void) const noexcept
460 {
461 return children_suites_.size () + 1;
462 }
463
469 size_t
470 runner::total_suites_count (void) const noexcept
471 {
472 return suites_count ();
473 }
474
475 // ==========================================================================
476
484 {
485#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
486#if defined(__GNUC__)
487#pragma GCC diagnostic push
488#if defined(__clang__)
489#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
490#endif
491#endif
492 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
493#if defined(__GNUC__)
494#pragma GCC diagnostic pop
495#endif
496#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
497 }
498
505 static_runner::static_runner (const char* top_suite_name)
506 : runner{ top_suite_name }
507 {
508#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
509#if defined(__GNUC__)
510#pragma GCC diagnostic push
511#if defined(__clang__)
512#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
513#endif
514#endif
515 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
516#if defined(__GNUC__)
517#pragma GCC diagnostic pop
518#endif
519#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
520 }
521
531 {
532#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
533 trace::printf ("%s\n", __PRETTY_FUNCTION__);
534#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
535
536 if (static_children_suites_ != nullptr)
537 {
538 // The tests are static, so we do not delete them, but we need to
539 // delete the array of pointers.
542 }
543 }
544
550 size_t
552 {
553 return static_children_suites_ != nullptr
555 : 0;
556 }
557
563 size_t
565 {
566 return suites_count () + static_suites_count ();
567 }
568
578 void
580 {
581#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
582 trace::printf ("%s\n", __PRETTY_FUNCTION__);
583#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
584
586
587 if (static_children_suites_ != nullptr)
588 {
589 // Use selection sort with std::swap on raw pointers (returns void)
590 // to avoid std::sort triggering -Waggregate-return via
591 // std::move_backward returning a class-type iterator.
592 const size_t n = static_children_suites_->size ();
593 auto& suites = *static_children_suites_;
594 for (size_t i = 0; i < n; ++i)
595 {
596 size_t min_idx = i;
597 for (size_t j = i + 1; j < n; ++j)
598 {
599 if (std::string_view{ suites[j]->name () }
600 < std::string_view{ suites[min_idx]->name () })
601 min_idx = j;
602 }
603 if (min_idx != i)
604 std::swap (suites[i], suites[min_idx]);
605 }
606
607 for (size_t i = 0; i < n; ++i)
608 {
609 auto* suite_ptr = suites[i];
610
611 suite_ptr->own_index (i + 1 + suites_count ());
612
613 // Run the child suite immediately.
614 suite_ptr->run ();
615
616 // Accumulate the totals from the static suite into the runner
617 // totals.
618 // DO NOT increment executed_subtests here.
619 totals_ += suite_ptr->totals ();
620 }
621 }
622 }
623
632 void
635 {
636#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
637#if defined(__GNUC__)
638#pragma GCC diagnostic push
639#if defined(__clang__)
640#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
641#endif
642#endif
643 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
644#if defined(__GNUC__)
645#pragma GCC diagnostic pop
646#endif
647#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
648
649 if (runner.static_children_suites_ == nullptr)
650 {
651#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
652 trace::printf ("%s new static_children_suites_ array\n",
653 __PRETTY_FUNCTION__);
654#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
655 runner.static_children_suites_ = new std::vector<static_suite*>;
656 }
657 runner.static_children_suites_->push_back (&suite);
658 }
659
660 // --------------------------------------------------------------------------
661} // namespace micro_os_plus::micro_test_plus
662
663// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.1 by Doxygen 1.17.0.