micro-test-plus 4.1.1
µTest++ Testing Framework
Loading...
Searching...
No Matches
runner.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 * 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.0 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#if defined(MICRO_OS_PLUS_TRACE)
54#include <micro-os-plus/diag/trace.h>
55#endif // MICRO_OS_PLUS_TRACE
56
61
62#include <algorithm>
63#include <string>
64
65// ----------------------------------------------------------------------------
66
67#if defined(__GNUC__)
68#pragma GCC diagnostic ignored "-Waggregate-return"
69#if defined(__clang__)
70#pragma clang diagnostic ignored "-Wunknown-warning-option"
71#pragma clang diagnostic ignored "-Wc++98-compat"
72#pragma clang diagnostic ignored "-Wc++98-c++11-c++14-compat"
73#pragma clang diagnostic ignored "-Wpre-c++17-compat"
74#endif
75#endif
76
77// ============================================================================
78
80{
81 // --------------------------------------------------------------------------
82
89 runner&
90 detail::to_runner (static_runner& static_runner_ref) noexcept
91 {
92 return static_cast<runner&> (static_runner_ref);
93 }
94
101 void
103 static_suite& static_suite_ref)
104 {
105 static_runner::register_static_suite (static_runner_ref, static_suite_ref);
106 }
107
108 // --------------------------------------------------------------------------
109
116 runner::runner (void) : test_node{ "runner" }, top_suite_{ "", *this }
117 {
118#if defined(MICRO_OS_PLUS_TRACE) \
119 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
120#if defined(__GNUC__)
121#pragma GCC diagnostic push
122#if defined(__clang__)
123#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
124#endif
125#endif
126 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
127#if defined(__GNUC__)
128#pragma GCC diagnostic pop
129#endif
130#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
131 }
132
139 runner::runner (const char* top_suite_name)
140 : test_node{ "runner" }, top_suite_{ top_suite_name, *this }
141 {
142#if defined(MICRO_OS_PLUS_TRACE) \
143 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
144#if defined(__GNUC__)
145#pragma GCC diagnostic push
146#if defined(__clang__)
147#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
148#endif
149#endif
150 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
151#if defined(__GNUC__)
152#pragma GCC diagnostic pop
153#endif
154#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
155 }
156
163 {
164#if defined(MICRO_OS_PLUS_TRACE) \
165 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
166 trace::printf ("%s\n", __PRETTY_FUNCTION__);
167#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
168
169 // reporter_ is a unique_ptr; destroyed automatically.
170 }
171
172#if defined(__GNUC__)
173#pragma GCC diagnostic push
174#if defined(__clang__)
175#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
176#endif
177#endif
190 suite&
191 runner::initialise (int argc, char* argv[], const char* top_suite_name)
192 {
193#if defined(MICRO_OS_PLUS_TRACE) \
194 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
195 trace::printf ("%s\n", __PRETTY_FUNCTION__);
196#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
197
198#if !(defined(MICRO_OS_PLUS_INCLUDE_STARTUP) && defined(MICRO_OS_PLUS_TRACE))
199#if defined(MICRO_OS_PLUS_DEBUG)
200 trace::printf ("argv[");
201 for (int i = 0; i < argc; ++i)
202 {
203 if (i > 0)
204 {
205 trace::printf (", ");
206 }
207 trace::printf ("'%s'", argv[i]);
208 }
209 trace::puts ("]");
210#endif // defined(MICRO_OS_PLUS_DEBUG)
211#endif // !defined(MICRO_OS_PLUS_INCLUDE_STARTUP)
212
213 if (strlen (top_suite_name) > 0)
214 {
215 // If provided by this call, use it, possibly override the
216 // deprecated constructor.
217 top_suite_name_ = top_suite_name;
218 top_suite_.name (top_suite_name_.c_str ());
219 }
220 else if (strlen (top_suite_.name ()) == 0)
221 {
222 // If not provided by the constructor or by this call, try to extract a
223 // name from argv[0], which is commonly the executable name. If that
224 // fails, use a default name.
225 if (argc > 0 && argv != nullptr && argv[0] != nullptr)
226 {
227 std::string_view top_suite_name_view{ utility::extract_file_name (
228 argv[0]) };
229
230 const auto dot_pos = top_suite_name_view.rfind ('.');
231 if (dot_pos != std::string_view::npos)
232 {
233 top_suite_name_view = top_suite_name_view.substr (0, dot_pos);
234 }
235
236 top_suite_name_ = top_suite_name_view;
237 }
238 else
239 {
240 top_suite_name_ = "default suite";
241 }
242 top_suite_.name (top_suite_name_.c_str ());
243 }
244
245 std::vector<std::string_view> argvs (argv, argv + argc);
246
247 std::string_view reporter_name{ "tap" };
248 static constexpr std::string_view reporter_prefix{ "--reporter=" };
249 for (size_t i = 0; i < argvs.size (); ++i)
250 {
251 if (argvs[i].starts_with (reporter_prefix))
252 {
253 reporter_name = argvs[i].substr (reporter_prefix.size ());
254 }
255 else if (argvs[i]
256 == reporter_prefix.substr (0, reporter_prefix.size () - 1))
257 {
258 if (i + 1 < argvs.size ())
259 {
260 reporter_name = argvs[++i];
261 }
262 else
263 {
264 fprintf (stderr, "error: --reporter option requires a "
265 "reporter name argument\n");
266 exit (1);
267 }
268 }
269 }
270
271 // Initialise and configure the reporter.
272 if (reporter_name == "human")
273 {
274 reporter_ = std::make_unique<reporter_human> (
275 std::make_unique<std::vector<std::string_view>> (
276 std::move (argvs)));
277 }
278 else if (reporter_name == "tap")
279 {
280 reporter_ = std::make_unique<reporter_tap> (
281 std::make_unique<std::vector<std::string_view>> (
282 std::move (argvs)));
283 }
284 else
285 {
286 fprintf (stderr, "error: unknown reporter '%.*s'\n",
287 static_cast<int> (reporter_name.size ()),
288 reporter_name.data ());
289 exit (1);
290 }
291
292 // ------------------------------------------------------------------------
293
294 timings_.timestamp_begin ();
295 reporter_->begin_session (*this);
296
297 top_suite_.timings ().timestamp_begin ();
298 reporter_->begin_suite (top_suite_);
299
300 return top_suite_;
301 }
302#if defined(__GNUC__)
303#pragma GCC diagnostic pop
304#endif
305
306 // --------------------------------------------------------------------------
307
315 void
316 runner::register_suite_ (std::unique_ptr<class suite> suite)
317 {
318#if defined(MICRO_OS_PLUS_TRACE) \
319 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
320#if defined(__GNUC__)
321#pragma GCC diagnostic push
322#if defined(__clang__)
323#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
324#endif
325#endif
326 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite->name ());
327#if defined(__GNUC__)
328#pragma GCC diagnostic pop
329#endif
330#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
331
332 children_suites_.push_back (std::move (suite));
333 }
334
345 void
347 {
348 // Use selection sort with unique_ptr::swap (returns void) to avoid
349 // std::sort triggering -Waggregate-return via std::move_backward,
350 // which returns a class-type iterator when operating on unique_ptr
351 // elements.
352 const size_t n = children_suites_.size ();
353 for (size_t i = 0; i < n; ++i)
354 {
355 size_t min_idx = i;
356 for (size_t j = i + 1; j < n; ++j)
357 {
358 if (std::string_view{ children_suites_[j]->name () }
359 < std::string_view{ children_suites_[min_idx]->name () })
360 min_idx = j;
361 }
362 if (min_idx != i)
363 children_suites_[i].swap (children_suites_[min_idx]);
364 }
365
366 for (size_t i = 0; i < n; ++i)
367 {
368 auto* suite_ptr = children_suites_[i].get ();
369
370 // +1 for 1-based index, +1 for top suite
371 suite_ptr->own_index (i + 1 + 1);
372
373 // Run the child suite immediately.
374 suite_ptr->run ();
375
376 // Accumulate the totals from the child suite into the runner
377 // totals.
378 // DO NOT increment executed_subtests here.
379 totals_ += suite_ptr->totals ();
380 }
381 }
382
392 int
394 {
395#if defined(MICRO_OS_PLUS_TRACE) \
396 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
397 trace::printf ("%s\n", __PRETTY_FUNCTION__);
398#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
399
400 if (reporter_ == nullptr)
401 {
402 fprintf (stderr, "error: test runner not initialised\n");
403 return 1;
404 }
405
406 top_suite_.timings ().timestamp_end ();
407 reporter_->end_suite (top_suite_);
408 totals_ += top_suite_.totals ();
409
410 run_suites_ ();
411
412 timings_.timestamp_end ();
413 reporter_->end_session (*this);
414
415 const int result = totals_.was_successful () ? 0 : 1;
416
417#if defined(MICRO_OS_PLUS_TRACE) \
418 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
419#if defined(__GNUC__)
420#pragma GCC diagnostic push
421#if defined(__clang__)
422#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
423#endif
424#endif
425 trace::printf ("%s -> %d\n", __PRETTY_FUNCTION__, result);
426#if defined(__GNUC__)
427#pragma GCC diagnostic pop
428#endif
429#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
430
431 return result;
432 }
433
439 void
441 {
442#if defined(MICRO_OS_PLUS_TRACE) \
443 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
444 trace::printf ("%s\n", __PRETTY_FUNCTION__);
445#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
446
447#if defined(__GNUC__)
448#pragma GCC diagnostic push
449#if defined(__clang__)
450#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
451#endif
452#endif
453 fprintf (stderr, "\nerror: test execution aborted at %s:%u\n",
454 reflection::short_name (sl.file_name ()), sl.line ());
455#if defined(__GNUC__)
456#pragma GCC diagnostic pop
457#endif
458
459 ::abort ();
460 }
461
467 size_t
468 runner::suites_count (void) const noexcept
469 {
470 return children_suites_.size () + 1;
471 }
472
478 size_t
479 runner::total_suites_count (void) const noexcept
480 {
481 return suites_count ();
482 }
483
484 // ==========================================================================
485
493 {
494#if defined(MICRO_OS_PLUS_TRACE) \
495 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
496#if defined(__GNUC__)
497#pragma GCC diagnostic push
498#if defined(__clang__)
499#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
500#endif
501#endif
502 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
503#if defined(__GNUC__)
504#pragma GCC diagnostic pop
505#endif
506#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
507 }
508
515 static_runner::static_runner (const char* top_suite_name)
516 : runner{ top_suite_name }
517 {
518#if defined(MICRO_OS_PLUS_TRACE) \
519 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
520#if defined(__GNUC__)
521#pragma GCC diagnostic push
522#if defined(__clang__)
523#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
524#endif
525#endif
526 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
527#if defined(__GNUC__)
528#pragma GCC diagnostic pop
529#endif
530#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
531 }
532
542 {
543#if defined(MICRO_OS_PLUS_TRACE) \
544 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
545 trace::printf ("%s\n", __PRETTY_FUNCTION__);
546#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
547
548 if (static_children_suites_ != nullptr)
549 {
550 // The tests are static, so we do not delete them, but we need to
551 // delete the array of pointers.
553 static_children_suites_ = nullptr;
554 }
555 }
556
562 size_t
564 {
565 return static_children_suites_ != nullptr
566 ? static_children_suites_->size ()
567 : 0;
568 }
569
575 size_t
577 {
578 return suites_count () + static_suites_count ();
579 }
580
590 void
592 {
593#if defined(MICRO_OS_PLUS_TRACE) \
594 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
595 trace::printf ("%s\n", __PRETTY_FUNCTION__);
596#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
597
599
600 if (static_children_suites_ != nullptr)
601 {
602 // Use selection sort with std::swap on raw pointers (returns void)
603 // to avoid std::sort triggering -Waggregate-return via
604 // std::move_backward returning a class-type iterator.
605 const size_t n = static_children_suites_->size ();
606 auto& suites = *static_children_suites_;
607 for (size_t i = 0; i < n; ++i)
608 {
609 size_t min_idx = i;
610 for (size_t j = i + 1; j < n; ++j)
611 {
612 if (std::string_view{ suites[j]->name () }
613 < std::string_view{ suites[min_idx]->name () })
614 min_idx = j;
615 }
616 if (min_idx != i)
617 std::swap (suites[i], suites[min_idx]);
618 }
619
620 for (size_t i = 0; i < n; ++i)
621 {
622 auto* suite_ptr = suites[i];
623
624 suite_ptr->own_index (i + 1 + suites_count ());
625
626 // Run the child suite immediately.
627 suite_ptr->run ();
628
629 // Accumulate the totals from the static suite into the runner
630 // totals.
631 // DO NOT increment executed_subtests here.
632 totals_ += suite_ptr->totals ();
633 }
634 }
635 }
636
645 void
648 {
649#if defined(MICRO_OS_PLUS_TRACE) \
650 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
651#if defined(__GNUC__)
652#pragma GCC diagnostic push
653#if defined(__clang__)
654#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
655#endif
656#endif
657 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
658#if defined(__GNUC__)
659#pragma GCC diagnostic pop
660#endif
661#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
662
663 if (runner.static_children_suites_ == nullptr)
664 {
665#if defined(MICRO_OS_PLUS_TRACE) \
666 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
667 trace::printf ("%s new static_children_suites_ array\n",
668 __PRETTY_FUNCTION__);
669#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
670 runner.static_children_suites_ = new std::vector<static_suite*>;
671 }
672 runner.static_children_suites_->push_back (&suite);
673 }
674
675 // --------------------------------------------------------------------------
676} // namespace micro_os_plus::micro_test_plus
677
678// ----------------------------------------------------------------------------
const char * name(void) const noexcept
Gets the node name.
runner_totals totals_
Totals for the test node, including nested cases.
Definition test.h:229
test_node(const char *name)
Constructs a test node.
Definition test.cpp:93
Local implementation of source location information for diagnostics.
Definition reflection.h:138
constexpr auto file_name(void) const noexcept
Retrieve the file name associated with this source location.
constexpr auto line(void) const noexcept
Retrieve the line number associated with this source location.
The test runner for the µTest++ framework.
Definition runner.h:111
void suite(const char *name, Callable_T &&callable, Args_T &&... arguments)
Adds a test suite to the runner.
std::string top_suite_name_
Owned storage for the implicit top-suite name.
Definition runner.h:316
virtual ~runner() override
Destructor for the runner class.
Definition runner.cpp:162
runner(void)
Constructor for the runner class.
Definition runner.cpp:116
size_t suites_count(void) const noexcept
Returns the count of test suites.
Definition runner.cpp:468
void abort(const reflection::source_location &sl=reflection::source_location::current())
Aborts test execution immediately.
Definition runner.cpp:440
detail::timestamps timings_
Timings for this runner.
Definition runner.h:311
int exit_code(void)
Returns 0 if all tests were successful, 1 otherwise.
Definition runner.cpp:393
class top_suite top_suite_
The implicit top-level suite; always present and executed first.
Definition runner.h:291
std::vector< std::unique_ptr< class suite > > children_suites_
Owning collection of dynamically registered child suites.
Definition runner.h:301
void register_suite_(std::unique_ptr< class suite > suite)
Registers a test suite with the runner.
Definition runner.cpp:316
virtual size_t total_suites_count(void) const noexcept
Returns the total count of registered test suites.
Definition runner.cpp:479
std::unique_ptr< class reporter > reporter_
Pointer to the test reporter used for outputting test results.
Definition runner.h:306
class suite & initialise(int argc, char *argv[], const char *top_suite_name="")
Initialises the test runner with command-line arguments.
Definition runner.cpp:191
virtual void run_suites_(void)
Runs all registered test suites.
Definition runner.cpp:346
A runner variant that also manages statically-registered test suites.
Definition runner.h:345
static void register_static_suite(static_runner &runner, static_suite &suite)
Registers a static test suite with the runner.
Definition runner.cpp:646
std::vector< static_suite * > * static_children_suites_
Pointer to the vector of registered static test suites.
Definition runner.h:450
static_runner(void)
Constructor for the runner class.
Definition runner.cpp:492
virtual size_t total_suites_count(void) const noexcept final override
Returns the total count of all test suites, including static and dynamic.
Definition runner.cpp:576
virtual ~static_runner() override
Destructor for the static_runner class.
Definition runner.cpp:541
void run_suites_(void) override
Runs all child suites, including statically registered ones.
Definition runner.cpp:591
size_t static_suites_count(void) const noexcept
Returns the total count of registered static test suites.
Definition runner.cpp:563
A test suite designed for static (namespace-scope) registration with a static_runner.
Definition test.h:924
A named, runnable test suite registered with the test runner.
Definition test.h:714
const char * extract_file_name(const char *path) noexcept
Extracts the file name component from a full path.
Definition utility.cpp:93
runner & to_runner(static_runner &static_runner_ref) noexcept
Converts a static_runner reference to a runner reference.
Definition runner.cpp:90
void register_static_suite(static_runner &static_runner_ref, static_suite &static_suite_ref)
Registers a static suite with a static runner.
Definition runner.cpp:102
const char * short_name(const char *name) noexcept
Extract a short type or function name from a fully qualified name.
Primary namespace for the µTest++ testing framework.
C++ header file with declarations for the µTest++ human test reporter.
C++ header file with declarations for the µTest++ TAP test reporter.
C++ header file with declarations for the µTest++ test runner.
C++ header file with declarations for the µTest++ utility helpers.