Skip to main content

runner.cpp File

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

Included Headers

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#include <algorithm>
42#include <string>
43
44#if defined(MICRO_OS_PLUS_INCLUDE_CONFIG_H)
45#include <micro-os-plus/config.h>
46#endif // MICRO_OS_PLUS_INCLUDE_CONFIG_H
47
48#if defined(MICRO_OS_PLUS_TRACE)
49#include <micro-os-plus/diag/trace.h>
50#endif // MICRO_OS_PLUS_TRACE
51
56
57// ----------------------------------------------------------------------------
58
59#if defined(__GNUC__)
60#pragma GCC diagnostic ignored "-Waggregate-return"
61#if defined(__clang__)
62#pragma clang diagnostic ignored "-Wc++98-compat"
63#pragma clang diagnostic ignored "-Wc++98-c++11-c++14-compat"
64#pragma clang diagnostic ignored "-Wpre-c++17-compat"
65#endif
66#endif
67
68// ============================================================================
69
71{
72 // --------------------------------------------------------------------------
73
80 runner&
81 detail::to_runner (static_runner& static_runner_ref) noexcept
82 {
83 return static_cast<runner&> (static_runner_ref);
84 }
85
92 void
94 static_suite& static_suite_ref)
95 {
96 static_runner::register_static_suite (static_runner_ref, static_suite_ref);
97 }
98
99 // --------------------------------------------------------------------------
100
107 runner::runner (void) : test_node{ "runner" }, top_suite_{ "", *this }
108 {
109#if defined(MICRO_OS_PLUS_TRACE) \
110 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
111#if defined(__GNUC__)
112#pragma GCC diagnostic push
113#if defined(__clang__)
114#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
115#endif
116#endif
117 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
118#if defined(__GNUC__)
119#pragma GCC diagnostic pop
120#endif
121#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
122 }
123
130 runner::runner (const char* top_suite_name)
131 : test_node{ "runner" }, top_suite_{ top_suite_name, *this }
132 {
133#if defined(MICRO_OS_PLUS_TRACE) \
134 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
135#if defined(__GNUC__)
136#pragma GCC diagnostic push
137#if defined(__clang__)
138#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
139#endif
140#endif
141 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
142#if defined(__GNUC__)
143#pragma GCC diagnostic pop
144#endif
145#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
146 }
147
154 {
155#if defined(MICRO_OS_PLUS_TRACE) \
156 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
157 trace::printf ("%s\n", __PRETTY_FUNCTION__);
158#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
159
160 // reporter_ is a unique_ptr; destroyed automatically.
161 }
162
163#if defined(__GNUC__)
164#pragma GCC diagnostic push
165#if defined(__clang__)
166#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
167#endif
168#endif
181 suite&
182 runner::initialise (int argc, char* argv[], const char* top_suite_name)
183 {
184#if defined(MICRO_OS_PLUS_TRACE) \
185 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
186 trace::printf ("%s\n", __PRETTY_FUNCTION__);
187#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
188
189#if !(defined(MICRO_OS_PLUS_INCLUDE_STARTUP) && defined(MICRO_OS_PLUS_TRACE))
190#if defined(MICRO_OS_PLUS_DEBUG)
191 trace::printf ("argv[");
192 for (int i = 0; i < argc; ++i)
193 {
194 if (i > 0)
195 {
196 trace::printf (", ");
197 }
198 trace::printf ("'%s'", argv[i]);
199 }
200 trace::puts ("]");
201#endif // defined(MICRO_OS_PLUS_DEBUG)
202#endif // !defined(MICRO_OS_PLUS_INCLUDE_STARTUP)
203
204 if (strlen (top_suite_name) > 0)
205 {
206 // If provided by this call, use it, possibly override the
207 // deprecated constructor.
208 top_suite_name_ = top_suite_name;
209 top_suite_.name (top_suite_name_.c_str ());
210 }
211 else if (strlen (top_suite_.name ()) == 0)
212 {
213 // If not provided by the constructor or by this call, try to extract a
214 // name from argv[0], which is commonly the executable name. If that
215 // fails, use a default name.
216 if (argc > 0 && argv != nullptr && argv[0] != nullptr)
217 {
218 std::string_view top_suite_name_view{ utility::extract_file_name (
219 argv[0]) };
220
221 const auto dot_pos = top_suite_name_view.rfind ('.');
222 if (dot_pos != std::string_view::npos)
223 {
224 top_suite_name_view = top_suite_name_view.substr (0, dot_pos);
225 }
226
227 top_suite_name_ = top_suite_name_view;
228 }
229 else
230 {
231 top_suite_name_ = "default suite";
232 }
233 top_suite_.name (top_suite_name_.c_str ());
234 }
235
236 std::vector<std::string_view> argvs (argv, argv + argc);
237
238 std::string_view reporter_name{ "tap" };
239 static constexpr std::string_view reporter_prefix{ "--reporter=" };
240 for (size_t i = 0; i < argvs.size (); ++i)
241 {
242 if (argvs[i].starts_with (reporter_prefix))
243 {
244 reporter_name = argvs[i].substr (reporter_prefix.size ());
245 }
246 else if (argvs[i]
247 == reporter_prefix.substr (0, reporter_prefix.size () - 1))
248 {
249 if (i + 1 < argvs.size ())
250 {
251 reporter_name = argvs[++i];
252 }
253 else
254 {
255 fprintf (stderr, "error: --reporter option requires a "
256 "reporter name argument\n");
257 exit (1);
258 }
259 }
260 }
261
262 // Initialise and configure the reporter.
263 if (reporter_name == "human")
264 {
265 reporter_ = std::make_unique<reporter_human> (
266 std::make_unique<std::vector<std::string_view>> (
267 std::move (argvs)));
268 }
269 else if (reporter_name == "tap")
270 {
271 reporter_ = std::make_unique<reporter_tap> (
272 std::make_unique<std::vector<std::string_view>> (
273 std::move (argvs)));
274 }
275 else
276 {
277 fprintf (stderr, "error: unknown reporter '%.*s'\n",
278 static_cast<int> (reporter_name.size ()),
279 reporter_name.data ());
280 exit (1);
281 }
282
283 // ------------------------------------------------------------------------
284
285 timings_.timestamp_begin ();
286 reporter_->begin_session (*this);
287
288 top_suite_.timings ().timestamp_begin ();
289 reporter_->begin_suite (top_suite_);
290
291 return top_suite_;
292 }
293#if defined(__GNUC__)
294#pragma GCC diagnostic pop
295#endif
296
297 // --------------------------------------------------------------------------
298
306 void
307 runner::register_suite_ (std::unique_ptr<class suite> suite)
308 {
309#if defined(MICRO_OS_PLUS_TRACE) \
310 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
311#if defined(__GNUC__)
312#pragma GCC diagnostic push
313#if defined(__clang__)
314#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
315#endif
316#endif
317 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite->name ());
318#if defined(__GNUC__)
319#pragma GCC diagnostic pop
320#endif
321#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
322
323 children_suites_.push_back (std::move (suite));
324 }
325
336 void
338 {
339 // Use selection sort with unique_ptr::swap (returns void) to avoid
340 // std::sort triggering -Waggregate-return via std::move_backward,
341 // which returns a class-type iterator when operating on unique_ptr
342 // elements.
343 const size_t n = children_suites_.size ();
344 for (size_t i = 0; i < n; ++i)
345 {
346 size_t min_idx = i;
347 for (size_t j = i + 1; j < n; ++j)
348 {
349 if (std::string_view{ children_suites_[j]->name () }
350 < std::string_view{ children_suites_[min_idx]->name () })
351 min_idx = j;
352 }
353 if (min_idx != i)
354 children_suites_[i].swap (children_suites_[min_idx]);
355 }
356
357 for (size_t i = 0; i < n; ++i)
358 {
359 auto* suite_ptr = children_suites_[i].get ();
360
361 // 1 for 1-based index, 1 for top suite
362 suite_ptr->own_index (i + 1 + 1);
363
364 // Run the child suite immediately.
365 suite_ptr->run ();
366
367 // Accumulate the totals from the child suite into the runner
368 // totals.
369 // DO NOT increment executed_subtests here.
370 totals_ += suite_ptr->totals ();
371 }
372 }
373
383 int
385 {
386#if defined(MICRO_OS_PLUS_TRACE) \
387 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
388 trace::printf ("%s\n", __PRETTY_FUNCTION__);
389#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
390
391 if (reporter_ == nullptr)
392 {
393 fprintf (stderr, "error: test runner not initialised\n");
394 return 1;
395 }
396
397 top_suite_.timings ().timestamp_end ();
398 reporter_->end_suite (top_suite_);
399 totals_ += top_suite_.totals ();
400
401 run_suites_ ();
402
403 timings_.timestamp_end ();
404 reporter_->end_session (*this);
405
406 const int result = totals_.was_successful () ? 0 : 1;
407
408#if defined(MICRO_OS_PLUS_TRACE) \
409 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
410#if defined(__GNUC__)
411#pragma GCC diagnostic push
412#if defined(__clang__)
413#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
414#endif
415#endif
416 trace::printf ("%s -> %d\n", __PRETTY_FUNCTION__, result);
417#if defined(__GNUC__)
418#pragma GCC diagnostic pop
419#endif
420#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
421
422 return result;
423 }
424
430 void
432 {
433#if defined(MICRO_OS_PLUS_TRACE) \
434 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
435 trace::printf ("%s\n", __PRETTY_FUNCTION__);
436#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
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_TRACE) \
486 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
487#if defined(__GNUC__)
488#pragma GCC diagnostic push
489#if defined(__clang__)
490#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
491#endif
492#endif
493 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
494#if defined(__GNUC__)
495#pragma GCC diagnostic pop
496#endif
497#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
498 }
499
506 static_runner::static_runner (const char* top_suite_name)
507 : runner{ top_suite_name }
508 {
509#if defined(MICRO_OS_PLUS_TRACE) \
510 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
511#if defined(__GNUC__)
512#pragma GCC diagnostic push
513#if defined(__clang__)
514#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
515#endif
516#endif
517 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
518#if defined(__GNUC__)
519#pragma GCC diagnostic pop
520#endif
521#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
522 }
523
533 {
534#if defined(MICRO_OS_PLUS_TRACE) \
535 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
536 trace::printf ("%s\n", __PRETTY_FUNCTION__);
537#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
538
539 if (static_children_suites_ != nullptr)
540 {
541 // The tests are static, so we do not delete them, but we need to
542 // delete the array of pointers.
545 }
546 }
547
553 size_t
555 {
556 return static_children_suites_ != nullptr
558 : 0;
559 }
560
566 size_t
568 {
569 return suites_count () + static_suites_count ();
570 }
571
581 void
583 {
584#if defined(MICRO_OS_PLUS_TRACE) \
585 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
586 trace::printf ("%s\n", __PRETTY_FUNCTION__);
587#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
588
590
591 if (static_children_suites_ != nullptr)
592 {
593 // Use selection sort with std::swap on raw pointers (returns void)
594 // to avoid std::sort triggering -Waggregate-return via
595 // std::move_backward returning a class-type iterator.
596 const size_t n = static_children_suites_->size ();
597 auto& suites = *static_children_suites_;
598 for (size_t i = 0; i < n; ++i)
599 {
600 size_t min_idx = i;
601 for (size_t j = i + 1; j < n; ++j)
602 {
603 if (std::string_view{ suites[j]->name () }
604 < std::string_view{ suites[min_idx]->name () })
605 min_idx = j;
606 }
607 if (min_idx != i)
608 std::swap (suites[i], suites[min_idx]);
609 }
610
611 for (size_t i = 0; i < n; ++i)
612 {
613 auto* suite_ptr = suites[i];
614
615 suite_ptr->own_index (i + 1 + suites_count ());
616
617 // Run the child suite immediately.
618 suite_ptr->run ();
619
620 // Accumulate the totals from the static suite into the runner
621 // totals.
622 // DO NOT increment executed_subtests here.
623 totals_ += suite_ptr->totals ();
624 }
625 }
626 }
627
636 void
639 {
640#if defined(MICRO_OS_PLUS_TRACE) \
641 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
642#if defined(__GNUC__)
643#pragma GCC diagnostic push
644#if defined(__clang__)
645#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
646#endif
647#endif
648 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
649#if defined(__GNUC__)
650#pragma GCC diagnostic pop
651#endif
652#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
653
654 if (runner.static_children_suites_ == nullptr)
655 {
656#if defined(MICRO_OS_PLUS_TRACE) \
657 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
658 trace::printf ("%s new static_children_suites_ array\n",
659 __PRETTY_FUNCTION__);
660#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
661 runner.static_children_suites_ = new std::vector<static_suite*>;
662 }
663 runner.static_children_suites_->push_back (&suite);
664 }
665
666 // --------------------------------------------------------------------------
667} // namespace micro_os_plus::micro_test_plus
668
669// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.0 by Doxygen 1.17.0.