micro-test-plus 4.1.0
µ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#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",
445 reflection::short_name (sl.file_name ()), sl.line ());
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.
544 static_children_suites_ = nullptr;
545 }
546 }
547
553 size_t
555 {
556 return static_children_suites_ != nullptr
557 ? static_children_suites_->size ()
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// ----------------------------------------------------------------------------
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:84
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:153
runner(void)
Constructor for the runner class.
Definition runner.cpp:107
size_t suites_count(void) const noexcept
Returns the count of test suites.
Definition runner.cpp:459
void abort(const reflection::source_location &sl=reflection::source_location::current())
Aborts test execution immediately.
Definition runner.cpp:431
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:384
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:307
virtual size_t total_suites_count(void) const noexcept
Returns the total count of registered test suites.
Definition runner.cpp:470
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:182
virtual void run_suites_(void)
Runs all registered test suites.
Definition runner.cpp:337
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:637
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:483
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:567
virtual ~static_runner() override
Destructor for the static_runner class.
Definition runner.cpp:532
void run_suites_(void) override
Runs all child suites, including statically registered ones.
Definition runner.cpp:582
size_t static_suites_count(void) const noexcept
Returns the total count of registered static test suites.
Definition runner.cpp:554
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:85
runner & to_runner(static_runner &static_runner_ref) noexcept
Converts a static_runner reference to a runner reference.
Definition runner.cpp:81
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:93
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.