micro-test-plus 5.0.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
45
46#if __has_include("micro-os-plus/diag/trace.h")
47#include "micro-os-plus/diag/trace.h"
48#endif // __has_include("micro-os-plus/diag/trace.h")
49
50#include <algorithm>
51#include <string>
52
53// ----------------------------------------------------------------------------
54
55#if defined(__GNUC__)
56#pragma GCC diagnostic ignored "-Waggregate-return"
57#if defined(__clang__)
58#pragma clang diagnostic ignored "-Wunknown-warning-option"
59#pragma clang diagnostic ignored "-Wc++98-compat"
60#pragma clang diagnostic ignored "-Wc++98-c++11-c++14-compat"
61#pragma clang diagnostic ignored "-Wpre-c++17-compat"
62#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
63#endif // defined(__clang__)
64#endif // defined(__GNUC__)
65
66// ============================================================================
67
68#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
69
70// ----------------------------------------------------------------------------
71
73{
74 // --------------------------------------------------------------------------
75
82 runner&
83 detail::to_runner (static_runner& static_runner_ref) noexcept
84 {
85 return static_cast<runner&> (static_runner_ref);
86 }
87
94 void
96 static_suite& static_suite_ref)
97 {
98 static_runner::register_static_suite (static_runner_ref, static_suite_ref);
99 }
100
101 // --------------------------------------------------------------------------
102
109 runner::runner (void) : test_node{ "runner" }, top_suite_{ "", *this }
110 {
111#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
112 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
113#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
114 }
115
122 runner::runner (const char* top_suite_name)
123 : test_node{ "runner" }, top_suite_{ top_suite_name, *this }
124 {
125#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
126 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
127#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
128 }
129
136 {
137#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
138 trace::printf ("%s\n", __PRETTY_FUNCTION__);
139#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
140
141 // reporter_ is a unique_ptr; destroyed automatically.
142 }
143
144#if defined(__GNUC__)
145#pragma GCC diagnostic push
146
147#if defined(__clang__)
148#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
149#endif // defined(__clang__)
150#endif // defined(__GNUC__)
151
164 suite&
165 runner::initialise (int argc, char* argv[], const char* top_suite_name)
166 {
167#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
168 trace::printf ("%s\n", __PRETTY_FUNCTION__);
169
170#if !(defined(MICRO_OS_PLUS_STARTUP_ENABLED) \
171 && defined(MICRO_OS_PLUS_DIAG_TRACE_ENABLED))
172#if defined(MICRO_OS_PLUS_DEBUG_ENABLED)
173 trace::printf ("argv[");
174 for (int i = 0; i < argc; ++i)
175 {
176 if (i > 0)
177 {
178 trace::printf (", ");
179 }
180 trace::printf ("'%s'", argv[i]);
181 }
182 trace::puts ("]");
183#endif // defined(MICRO_OS_PLUS_DEBUG_ENABLED)
184#endif // !defined(MICRO_OS_PLUS_STARTUP_ENABLED)
185#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
186
187 if (strlen (top_suite_name) > 0)
188 {
189 // If provided by this call, use it, possibly override the
190 // deprecated constructor.
191 top_suite_name_ = top_suite_name;
192 top_suite_.name (top_suite_name_.c_str ());
193 }
194 else if (strlen (top_suite_.name ()) == 0)
195 {
196 // If not provided by the constructor or by this call, try to extract a
197 // name from argv[0], which is commonly the executable name. If that
198 // fails, use a default name.
199 if (argc > 0 && argv != nullptr && argv[0] != nullptr)
200 {
201 std::string_view top_suite_name_view{ utility::extract_file_name (
202 argv[0]) };
203
204 const auto dot_pos = top_suite_name_view.rfind ('.');
205 if (dot_pos != std::string_view::npos)
206 {
207 top_suite_name_view = top_suite_name_view.substr (0, dot_pos);
208 }
209
210 top_suite_name_ = top_suite_name_view;
211 }
212 else
213 {
214 top_suite_name_ = "default suite";
215 }
216 top_suite_.name (top_suite_name_.c_str ());
217 }
218
219 std::vector<std::string_view> argvs (argv, argv + argc);
220
221 std::string_view reporter_name{ "tap" };
222 static constexpr std::string_view reporter_prefix{ "--reporter=" };
223 for (size_t i = 0; i < argvs.size (); ++i)
224 {
225 if (argvs[i].starts_with (reporter_prefix))
226 {
227 reporter_name = argvs[i].substr (reporter_prefix.size ());
228 }
229 else if (argvs[i]
230 == reporter_prefix.substr (0, reporter_prefix.size () - 1))
231 {
232 if (i + 1 < argvs.size ())
233 {
234 reporter_name = argvs[++i];
235 }
236 else
237 {
238 fprintf (stderr, "error: --reporter option requires a "
239 "reporter name argument\n");
240 exit (1);
241 }
242 }
243 }
244
245 // Initialise and configure the reporter.
246 if (reporter_name == "human")
247 {
248 reporter_ = std::make_unique<reporter_human> (
249 std::make_unique<std::vector<std::string_view>> (
250 std::move (argvs)));
251 }
252 else if (reporter_name == "tap")
253 {
254 reporter_ = std::make_unique<reporter_tap> (
255 std::make_unique<std::vector<std::string_view>> (
256 std::move (argvs)));
257 }
258 else
259 {
260 fprintf (stderr, "error: unknown reporter '%.*s'\n",
261 static_cast<int> (reporter_name.size ()),
262 reporter_name.data ());
263 exit (1);
264 }
265
266 // ------------------------------------------------------------------------
267
268 timings_.timestamp_begin ();
269 reporter_->begin_session (*this);
270
271 top_suite_.timings ().timestamp_begin ();
272 reporter_->begin_suite (top_suite_);
273
274 return top_suite_;
275 }
276
277#if defined(__GNUC__)
278#pragma GCC diagnostic pop
279#endif // defined(__GNUC__)
280
281 // --------------------------------------------------------------------------
282
290 void
291 runner::register_suite_ (std::unique_ptr<class suite> suite)
292 {
293#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
294 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite->name ());
295#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
296
297 children_suites_.push_back (std::move (suite));
298 }
299
310 void
312 {
313 // Use selection sort with unique_ptr::swap (returns void) to avoid
314 // std::sort triggering -Waggregate-return via std::move_backward,
315 // which returns a class-type iterator when operating on unique_ptr
316 // elements.
317 const size_t n = children_suites_.size ();
318 for (size_t i = 0; i < n; ++i)
319 {
320 size_t min_idx = i;
321 for (size_t j = i + 1; j < n; ++j)
322 {
323 if (std::string_view{ children_suites_[j]->name () }
324 < std::string_view{ children_suites_[min_idx]->name () })
325 min_idx = j;
326 }
327 if (min_idx != i)
328 children_suites_[i].swap (children_suites_[min_idx]);
329 }
330
331 for (size_t i = 0; i < n; ++i)
332 {
333 auto* suite_ptr = children_suites_[i].get ();
334
335 // +1 for 1-based index, +1 for top suite
336 suite_ptr->own_index (i + 1 + 1);
337
338 // Run the child suite immediately.
339 suite_ptr->run ();
340
341 // Accumulate the totals from the child suite into the runner
342 // totals.
343 // DO NOT increment executed_subtests here.
344 totals_ += suite_ptr->totals ();
345 }
346 }
347
357 int
359 {
360#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
361 trace::printf ("%s\n", __PRETTY_FUNCTION__);
362#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
363
364 if (reporter_ == nullptr)
365 {
366 fprintf (stderr, "error: test runner not initialised\n");
367 return 1;
368 }
369
370 top_suite_.timings ().timestamp_end ();
371 reporter_->end_suite (top_suite_);
372 totals_ += top_suite_.totals ();
373
374 run_suites_ ();
375
376 timings_.timestamp_end ();
377 reporter_->end_session (*this);
378
379 const int result = totals_.was_successful () ? 0 : 1;
380
381#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
382 trace::printf ("%s -> %d\n", __PRETTY_FUNCTION__, result);
383#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
384
385 return result;
386 }
387
393 void
395 {
396#if 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 fprintf (stderr, "\nerror: test execution aborted at %s:%u\n",
401 reflection::short_name (sl.file_name ()), sl.line ());
402
403 ::abort ();
404 }
405
411 size_t
412 runner::suites_count (void) const noexcept
413 {
414 return children_suites_.size () + 1;
415 }
416
422 size_t
423 runner::total_suites_count (void) const noexcept
424 {
425 return suites_count ();
426 }
427
428 // ==========================================================================
429
437 {
438#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
439 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
440#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
441 }
442
449 static_runner::static_runner (const char* top_suite_name)
450 : runner{ top_suite_name }
451 {
452#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
453 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, name ());
454#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
455 }
456
466 {
467#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
468 trace::printf ("%s\n", __PRETTY_FUNCTION__);
469#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
470
471 if (static_children_suites_ != nullptr)
472 {
473 // The tests are static, so we do not delete them, but we need to
474 // delete the array of pointers.
476 static_children_suites_ = nullptr;
477 }
478 }
479
485 size_t
487 {
488 return static_children_suites_ != nullptr
489 ? static_children_suites_->size ()
490 : 0;
491 }
492
498 size_t
500 {
501 return suites_count () + static_suites_count ();
502 }
503
513 void
515 {
516#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
517 trace::printf ("%s\n", __PRETTY_FUNCTION__);
518#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
519
521
522 if (static_children_suites_ != nullptr)
523 {
524 // Use selection sort with std::swap on raw pointers (returns void)
525 // to avoid std::sort triggering -Waggregate-return via
526 // std::move_backward returning a class-type iterator.
527 const size_t n = static_children_suites_->size ();
528 auto& suites = *static_children_suites_;
529 for (size_t i = 0; i < n; ++i)
530 {
531 size_t min_idx = i;
532 for (size_t j = i + 1; j < n; ++j)
533 {
534 if (std::string_view{ suites[j]->name () }
535 < std::string_view{ suites[min_idx]->name () })
536 min_idx = j;
537 }
538 if (min_idx != i)
539 std::swap (suites[i], suites[min_idx]);
540 }
541
542 for (size_t i = 0; i < n; ++i)
543 {
544 auto* suite_ptr = suites[i];
545
546 suite_ptr->own_index (i + 1 + suites_count ());
547
548 // Run the child suite immediately.
549 suite_ptr->run ();
550
551 // Accumulate the totals from the static suite into the runner
552 // totals.
553 // DO NOT increment executed_subtests here.
554 totals_ += suite_ptr->totals ();
555 }
556 }
557 }
558
567 void
570 {
571#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
572 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
573#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
574
575 if (runner.static_children_suites_ == nullptr)
576 {
577#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
578 trace::printf ("%s new static_children_suites_ array\n",
579 __PRETTY_FUNCTION__);
580#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
581 runner.static_children_suites_ = new std::vector<static_suite*>;
582 }
583 runner.static_children_suites_->push_back (&suite);
584 }
585
586 // --------------------------------------------------------------------------
587} // namespace micro_os_plus::micro_test_plus
588
589// ----------------------------------------------------------------------------
590
591#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
592
593// ----------------------------------------------------------------------------
const char * name(void) const noexcept
Gets the node name.
runner_totals totals_
Totals for the test node, including nested cases.
Definition test.h:238
test_node(const char *name)
Constructs a test node.
Definition test.cpp:86
Local implementation of source location information for diagnostics.
Definition reflection.h:150
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:144
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:349
virtual ~runner() override
Destructor for the runner class.
Definition runner.cpp:135
runner(void)
Constructor for the runner class.
Definition runner.cpp:109
size_t suites_count(void) const noexcept
Returns the count of test suites.
Definition runner.cpp:412
void abort(const reflection::source_location &sl=reflection::source_location::current())
Aborts test execution immediately.
Definition runner.cpp:394
detail::timestamps timings_
Timings for this runner.
Definition runner.h:344
int exit_code(void)
Returns 0 if all tests were successful, 1 otherwise.
Definition runner.cpp:358
class top_suite top_suite_
The implicit top-level suite; always present and executed first.
Definition runner.h:324
std::vector< std::unique_ptr< class suite > > children_suites_
Owning collection of dynamically registered child suites.
Definition runner.h:334
void register_suite_(std::unique_ptr< class suite > suite)
Registers a test suite with the runner.
Definition runner.cpp:291
virtual size_t total_suites_count(void) const noexcept
Returns the total count of registered test suites.
Definition runner.cpp:423
std::unique_ptr< class reporter > reporter_
Pointer to the test reporter used for outputting test results.
Definition runner.h:339
class suite & initialise(int argc, char *argv[], const char *top_suite_name="")
Initialises the test runner with command-line arguments.
Definition runner.cpp:165
virtual void run_suites_(void)
Runs all registered test suites.
Definition runner.cpp:311
A runner variant that also manages statically-registered test suites.
Definition runner.h:378
static void register_static_suite(static_runner &runner, static_suite &suite)
Registers a static test suite with the runner.
Definition runner.cpp:568
std::vector< static_suite * > * static_children_suites_
Pointer to the vector of registered static test suites.
Definition runner.h:483
static_runner(void)
Constructor for the runner class.
Definition runner.cpp:436
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:499
virtual ~static_runner() override
Destructor for the static_runner class.
Definition runner.cpp:465
void run_suites_(void) override
Runs all child suites, including statically registered ones.
Definition runner.cpp:514
size_t static_suites_count(void) const noexcept
Returns the total count of registered static test suites.
Definition runner.cpp:486
A test suite designed for static (namespace-scope) registration with a static_runner.
Definition test.h:933
A named, runnable test suite registered with the test runner.
Definition test.h:723
const char * extract_file_name(const char *path) noexcept
Extracts the file name component from a full path.
Definition utility.cpp:87
runner & to_runner(static_runner &static_runner_ref) noexcept
Converts a static_runner reference to a runner reference.
Definition runner.cpp:83
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:95
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.