@micro-os-plus/micro-test-plus 3.3.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +330 -2
  2. package/CMakeLists.txt +79 -23
  3. package/README.md +1 -1
  4. package/config/xcdl-build.json +11 -4
  5. package/include/micro-os-plus/micro-test-plus/deferred-reporter.h +292 -0
  6. package/include/micro-os-plus/micro-test-plus/detail.h +462 -1076
  7. package/include/micro-os-plus/micro-test-plus/exceptions.h +126 -0
  8. package/include/micro-os-plus/micro-test-plus/function-comparators.h +10 -7
  9. package/include/micro-os-plus/micro-test-plus/inlines/{details-inlines.h → deferred-reporter-inlines.h} +49 -22
  10. package/include/micro-os-plus/micro-test-plus/inlines/function-comparators-inlines.h +67 -4
  11. package/include/micro-os-plus/micro-test-plus/inlines/literals-inlines.h +3 -6
  12. package/include/micro-os-plus/micro-test-plus/inlines/math-inlines.h +21 -15
  13. package/include/micro-os-plus/micro-test-plus/inlines/reflection-inlines.h +35 -17
  14. package/include/micro-os-plus/micro-test-plus/inlines/{test-reporter-inlines.h → reporter-inlines.h} +176 -106
  15. package/include/micro-os-plus/micro-test-plus/inlines/{test-suite-inlines.h → runner-inlines.h} +41 -43
  16. package/include/micro-os-plus/micro-test-plus/inlines/test-inlines.h +369 -0
  17. package/include/micro-os-plus/micro-test-plus/inlines/utility-inlines.h +126 -0
  18. package/include/micro-os-plus/micro-test-plus/literals.h +4 -3
  19. package/include/micro-os-plus/micro-test-plus/math.h +9 -6
  20. package/include/micro-os-plus/micro-test-plus/operators.h +38 -44
  21. package/include/micro-os-plus/micro-test-plus/reflection.h +15 -4
  22. package/include/micro-os-plus/micro-test-plus/{test-reporter-basic.h → reporter-human.h} +72 -72
  23. package/include/micro-os-plus/micro-test-plus/{test-reporter-tap.h → reporter-tap.h} +69 -69
  24. package/include/micro-os-plus/micro-test-plus/{test-reporter.h → reporter.h} +296 -200
  25. package/include/micro-os-plus/micro-test-plus/runner-totals.h +264 -0
  26. package/include/micro-os-plus/micro-test-plus/runner.h +453 -0
  27. package/include/micro-os-plus/micro-test-plus/test.h +1069 -0
  28. package/include/micro-os-plus/micro-test-plus/timings.h +366 -0
  29. package/include/micro-os-plus/micro-test-plus/type-traits.h +239 -545
  30. package/include/micro-os-plus/micro-test-plus/utility.h +135 -0
  31. package/include/micro-os-plus/micro-test-plus.h +25 -228
  32. package/meson.build +10 -6
  33. package/package.json +1 -1
  34. package/src/deferred-reporter.cpp +118 -0
  35. package/src/reflection.cpp +95 -0
  36. package/src/reporter-human.cpp +822 -0
  37. package/src/reporter-tap.cpp +782 -0
  38. package/src/reporter.cpp +676 -0
  39. package/src/runner-totals.cpp +95 -0
  40. package/src/runner.cpp +563 -0
  41. package/src/test.cpp +496 -0
  42. package/src/timings.cpp +209 -0
  43. package/src/utility.cpp +163 -0
  44. package/.cmake-format.yaml +0 -11
  45. package/include/micro-os-plus/micro-test-plus/inlines/micro-test-plus-inlines.h +0 -313
  46. package/include/micro-os-plus/micro-test-plus/test-runner.h +0 -281
  47. package/include/micro-os-plus/micro-test-plus/test-suite.h +0 -492
  48. package/src/micro-test-plus.cpp +0 -316
  49. package/src/test-reporter-basic.cpp +0 -466
  50. package/src/test-reporter-tap.cpp +0 -530
  51. package/src/test-reporter.cpp +0 -399
  52. package/src/test-runner.cpp +0 -311
  53. package/src/test-suite.cpp +0 -304
@@ -0,0 +1,822 @@
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
+
18
+ /**
19
+ * @file
20
+ * @brief C++ source file with implementations for the µTest++ human test
21
+ * reporter methods.
22
+ *
23
+ * @details
24
+ * This source file contains the implementations for `reporter_human`,
25
+ * the default concrete implementation of the `reporter` abstract
26
+ * interface. It formats and presents test results using `printf`-based
27
+ * standard output, accumulating output in an internal string buffer and
28
+ * supporting colour-coded diagnostics and multiple verbosity levels.
29
+ *
30
+ * All definitions reside within the `micro_os_plus::micro_test_plus`
31
+ * namespace, ensuring clear separation from user code and minimising the risk
32
+ * of naming conflicts.
33
+ *
34
+ * This file must be included when building the µTest++ library.
35
+ */
36
+
37
+ // ----------------------------------------------------------------------------
38
+
39
+ #if defined(MICRO_OS_PLUS_INCLUDE_CONFIG_H)
40
+ #include <micro-os-plus/config.h>
41
+ #endif // MICRO_OS_PLUS_INCLUDE_CONFIG_H
42
+
43
+ #include <micro-os-plus/micro-test-plus.h>
44
+ #include <micro-os-plus/diag/trace.h>
45
+
46
+ // For the PRIu32 macro used in snprintf() formatting of uint32_t values.
47
+ #include <cinttypes>
48
+
49
+ #if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
50
+ // For isatty() to detect if stdout is a terminal, enabling colour output.
51
+ #include <unistd.h>
52
+ #endif
53
+
54
+ // ----------------------------------------------------------------------------
55
+
56
+ #if defined(__GNUC__)
57
+ #pragma GCC diagnostic ignored "-Waggregate-return"
58
+ #if defined(__clang__)
59
+ #pragma clang diagnostic ignored "-Wunknown-warning-option"
60
+ #pragma clang diagnostic ignored "-Wc++98-compat"
61
+ #pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
62
+ #endif
63
+ #endif
64
+
65
+ // ============================================================================
66
+
67
+ namespace micro_os_plus::micro_test_plus
68
+ {
69
+ // --------------------------------------------------------------------------
70
+
71
+ /**
72
+ * @details
73
+ * Delegates construction to the `reporter` base class with the supplied
74
+ * argument vector. On POSIX platforms, if `stdout` is connected to a
75
+ * terminal (`isatty()`), colour output is enabled by selecting the
76
+ * red/green colour scheme. If tracing is enabled, the function
77
+ * signature is output for diagnostic purposes.
78
+ */
79
+ reporter_human::reporter_human (
80
+ std::unique_ptr<std::vector<std::string_view>> argvs)
81
+ : reporter{ std::move (argvs) }
82
+ {
83
+ #if defined(MICRO_OS_PLUS_TRACE) \
84
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
85
+ trace::printf ("%s\n", __PRETTY_FUNCTION__);
86
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
87
+
88
+ #if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
89
+ if (isatty (fileno (stdout)))
90
+ {
91
+ colours_ = colours_red_green;
92
+ }
93
+ #endif
94
+ }
95
+
96
+ /**
97
+ * @details
98
+ * No resources are owned directly by `reporter_human`; the destructor
99
+ * performs no explicit clean-up. If tracing is enabled, the function
100
+ * signature is output for diagnostic purposes.
101
+ */
102
+ reporter_human::~reporter_human ()
103
+ {
104
+ #if defined(MICRO_OS_PLUS_TRACE) \
105
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
106
+ trace::printf ("%s\n", __PRETTY_FUNCTION__);
107
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
108
+ }
109
+
110
+ // --------------------------------------------------------------------------
111
+
112
+ /**
113
+ * @brief Number of spaces per indentation level.
114
+ *
115
+ * @details
116
+ * Used by `operator<<(indent_t)` and related methods to compute
117
+ * the leading whitespace for each nesting depth.
118
+ */
119
+ constexpr size_t indent_size = 4;
120
+
121
+ /**
122
+ * @details
123
+ * This operator overload appends spaces to the internal output buffer
124
+ * corresponding to `m.level` two-space indentation levels. It enables
125
+ * structured, readable nesting of test output across all test cases and
126
+ * folders by allowing `*this << indent(n) << "text"` chaining.
127
+ */
128
+ reporter_human&
129
+ reporter_human::operator<< (indent_t m)
130
+ {
131
+ buffer_.append (m.level * indent_size, ' ');
132
+ return *this;
133
+ }
134
+
135
+ // --------------------------------------------------------------------------
136
+
137
+ /**
138
+ * @details
139
+ * If verbosity is not silent, a blank line is printed to `stdout`
140
+ * before the build-information block emitted by `write_info_()`. A
141
+ * fixed "µTest++ human report" heading is then written both to the
142
+ * output file (if open) and to `stdout`. The `add_empty_line_` flag
143
+ * is set so that subsequent suite output is visually separated.
144
+ */
145
+ void
146
+ reporter_human::begin_session ([[maybe_unused]] runner& runner)
147
+ {
148
+ #if defined(MICRO_OS_PLUS_TRACE) \
149
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
150
+ trace::printf ("%s\n", __PRETTY_FUNCTION__);
151
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
152
+
153
+ #if defined(__GNUC__)
154
+ #pragma GCC diagnostic push
155
+ #if defined(__clang__)
156
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
157
+ #endif
158
+ #endif
159
+
160
+ if (verbosity_ != verbosity::silent)
161
+ {
162
+ printf ("\n");
163
+ }
164
+
165
+ write_info_ ();
166
+
167
+ const char* message = "µTest++ human report";
168
+ if (output_file_ != nullptr)
169
+ {
170
+ fprintf (output_file_, "%s\n", message);
171
+ }
172
+
173
+ if (verbosity_ != verbosity::silent)
174
+ {
175
+ printf ("%s\n", message);
176
+
177
+ flush ();
178
+ }
179
+
180
+ add_empty_line_ = true;
181
+
182
+ #if defined(__GNUC__)
183
+ #pragma GCC diagnostic pop
184
+ #endif
185
+ }
186
+
187
+ /**
188
+ * @details
189
+ * Prints a summary line to `stdout` (and to the output file if open)
190
+ * showing the total number of successful checks, failed checks,
191
+ * executed test cases, and test suites, together with the elapsed
192
+ * time when timing data is available. The line is prefixed with a
193
+ * green `✓` tick on success or a red `✗` cross on failure, using
194
+ * ANSI colour codes when colour output is enabled.
195
+ */
196
+ void
197
+ reporter_human::end_session (runner& runner)
198
+ {
199
+ #if defined(MICRO_OS_PLUS_TRACE) \
200
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
201
+ trace::printf ("%s\n", __PRETTY_FUNCTION__);
202
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
203
+
204
+ #if defined(__GNUC__)
205
+ #pragma GCC diagnostic push
206
+ #if defined(__clang__)
207
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
208
+ #endif
209
+ #endif
210
+
211
+ if (verbosity_ != verbosity::silent)
212
+ {
213
+ if (add_empty_line_)
214
+ {
215
+ printf ("\n");
216
+ }
217
+
218
+ size_t total_suites_count = runner.total_suites_count ();
219
+
220
+ uint32_t milliseconds = 0;
221
+ uint32_t microseconds = 0;
222
+ if (runner.timings ().has_timestamps ())
223
+ {
224
+ runner.timings ().compute_elapsed_time (milliseconds,
225
+ microseconds);
226
+ }
227
+
228
+ char message_totals[300];
229
+ snprintf (message_totals, sizeof (message_totals),
230
+ "Total: %zu check%s passed, %zu failed, in %zu test "
231
+ "case%s, %zu test suite%s",
232
+ runner.totals ().successful_checks (),
233
+ runner.totals ().successful_checks () == 1 ? "" : "s",
234
+ runner.totals ().failed_checks (),
235
+ runner.totals ().executed_subtests (),
236
+ runner.totals ().executed_subtests () == 1 ? "" : "s",
237
+ total_suites_count, total_suites_count == 1 ? "" : "s");
238
+
239
+ char message_time[120] = "";
240
+ if (milliseconds > 0 || microseconds > 0)
241
+ {
242
+ snprintf (message_time, sizeof (message_time),
243
+ ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
244
+ microseconds);
245
+ }
246
+
247
+ if (runner.totals ().was_successful ()) [[likely]]
248
+ {
249
+ if (output_file_ != nullptr)
250
+ {
251
+ fprintf (output_file_, "✓ %s%s\n", message_totals,
252
+ message_time);
253
+ }
254
+
255
+ printf ("%s✓%s %s%s\n", colours_.pass, colours_.none,
256
+ message_totals, message_time);
257
+ }
258
+ else
259
+ {
260
+ if (output_file_ != nullptr)
261
+ {
262
+ fprintf (output_file_, "✗ %s%s\n", message_totals,
263
+ message_time);
264
+ }
265
+
266
+ printf ("%s✗%s %s%s\n", colours_.fail, colours_.none,
267
+ message_totals, message_time);
268
+ }
269
+
270
+ flush ();
271
+ }
272
+
273
+ #if defined(__GNUC__)
274
+ #pragma GCC diagnostic pop
275
+ #endif
276
+ }
277
+
278
+ // --------------------------------------------------------------------------
279
+
280
+ /**
281
+ * @details
282
+ * This method marks the beginning of a test suite, ensuring that output is
283
+ * properly separated and clearly presented. If there is pending output, the
284
+ * stream is flushed and an empty line is added for clarity. For silent or
285
+ * quiet verbosity levels, output is suppressed. Otherwise, a message
286
+ * indicating the start of the test suite is displayed. This approach
287
+ * enhances the organisation and readability of test results across all test
288
+ * cases and folders.
289
+ */
290
+ void
291
+ reporter_human::begin_suite ([[maybe_unused]] suite& suite)
292
+ {
293
+ #if defined(MICRO_OS_PLUS_TRACE) \
294
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
295
+ #pragma GCC diagnostic push
296
+ #if defined(__clang__)
297
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
298
+ #endif
299
+ trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
300
+ #pragma GCC diagnostic pop
301
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
302
+
303
+ #if defined(__GNUC__)
304
+ #pragma GCC diagnostic push
305
+ #if defined(__clang__)
306
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
307
+ #endif
308
+ #endif
309
+
310
+ if (verbosity_ == verbosity::normal || verbosity_ == verbosity::verbose)
311
+ {
312
+ if (add_empty_line_)
313
+ {
314
+ printf ("\n");
315
+ }
316
+
317
+ if (output_file_ != nullptr)
318
+ {
319
+ fprintf (output_file_, "• %s\n", suite.name ());
320
+ }
321
+
322
+ printf ("• %s\n", suite.name ());
323
+
324
+ flush ();
325
+
326
+ add_empty_line_ = true;
327
+ }
328
+
329
+ #if defined(__GNUC__)
330
+ #pragma GCC diagnostic pop
331
+ #endif
332
+ }
333
+
334
+ /**
335
+ * @details
336
+ * This method marks the end of a test suite, summarising the overall results
337
+ * and presenting them with appropriate formatting and colour coding. If the
338
+ * suite contains test cases and the verbosity is not set to quiet, an empty
339
+ * line is added for clarity. For suites with no failed checks and at least
340
+ * one successful check, a success message is displayed, including the number
341
+ * of checks and test cases. Otherwise, a failure message is shown, detailing
342
+ * the number of successful and failed checks, as well as the total number of
343
+ * test cases. The output is immediately flushed to ensure prompt and
344
+ * organised reporting across all test cases and folders.
345
+ */
346
+ void
347
+ reporter_human::end_suite (suite& suite)
348
+ {
349
+ #if defined(MICRO_OS_PLUS_TRACE) \
350
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
351
+ #if defined(__GNUC__)
352
+ #pragma GCC diagnostic push
353
+ #if defined(__clang__)
354
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
355
+ #endif
356
+ #endif
357
+ trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
358
+ #if defined(__GNUC__)
359
+ #pragma GCC diagnostic pop
360
+ #endif
361
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
362
+
363
+ #if defined(__GNUC__)
364
+ #pragma GCC diagnostic push
365
+ #if defined(__clang__)
366
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
367
+ #endif
368
+ #endif
369
+
370
+ uint32_t milliseconds = 0;
371
+ uint32_t microseconds = 0;
372
+ if (suite.timings ().has_timestamps ())
373
+ {
374
+ suite.timings ().compute_elapsed_time (milliseconds, microseconds);
375
+ }
376
+
377
+ char message_time[120] = "";
378
+ if (milliseconds > 0 || microseconds > 0)
379
+ {
380
+ snprintf (message_time, sizeof (message_time),
381
+ ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
382
+ microseconds);
383
+ }
384
+
385
+ // At this point, the buffer may contain output from the test case, which
386
+ // should be displayed.
387
+ if (verbosity_ == verbosity::normal || verbosity_ == verbosity::verbose)
388
+ {
389
+ std::string indent (indent_size, ' ');
390
+
391
+ if (suite.totals ().executed_subtests () > 0)
392
+ {
393
+ printf ("\n");
394
+ }
395
+
396
+ if (suite.totals ().was_successful ()) [[likely]]
397
+ {
398
+ // Successful test suite.
399
+
400
+ char message_totals[300];
401
+ snprintf (message_totals, sizeof (message_totals),
402
+ "(%zu check%s in %zu test case%s)",
403
+ suite.totals ().successful_checks (),
404
+ suite.totals ().successful_checks () == 1 ? "" : "s",
405
+ suite.totals ().executed_subtests (),
406
+ suite.totals ().executed_subtests () == 1 ? "" : "s");
407
+
408
+ if (output_file_ != nullptr)
409
+ {
410
+ write_buffer_to_file_ ();
411
+
412
+ fprintf (output_file_, "✓ %s - passed %s%s\n", suite.name (),
413
+ message_totals, message_time);
414
+ }
415
+
416
+ if (verbosity_ == verbosity::verbose)
417
+ {
418
+ // With verbosity, show full TAP output accumulated in the
419
+ // buffer.
420
+ write_buffer_to_stdout ();
421
+ }
422
+
423
+ printf ("%s✓%s %s - passed %s%s\n", colours_.pass, colours_.none,
424
+ suite.name (), message_totals, message_time);
425
+ }
426
+ else
427
+ {
428
+ // Failed test suite.
429
+
430
+ char message_totals[300];
431
+ snprintf (message_totals, sizeof (message_totals),
432
+ "(%zu check%s passed, %zu "
433
+ "failed, in %zu test case%s)",
434
+ suite.totals ().successful_checks (),
435
+ suite.totals ().successful_checks () == 1 ? "" : "s",
436
+ suite.totals ().failed_checks (),
437
+ suite.totals ().executed_subtests (),
438
+ suite.totals ().executed_subtests () == 1 ? "" : "s");
439
+
440
+ if (output_file_ != nullptr)
441
+ {
442
+ write_buffer_to_file_ ();
443
+
444
+ fprintf (output_file_, "✗ %s - FAILED %s%s\n", suite.name (),
445
+ message_totals, message_time);
446
+ }
447
+
448
+ // Show full TAP output accumulated in the buffer for failed suite
449
+ // cases, as it may contain useful information about the failure.
450
+ write_buffer_to_stdout ();
451
+
452
+ printf ("%s✗%s %s - %sFAILED%s %s%s\n", colours_.fail,
453
+ colours_.none, suite.name (), colours_.fail, colours_.none,
454
+ message_totals, message_time);
455
+ }
456
+ }
457
+
458
+ flush ();
459
+
460
+ // Clear residual content when less verbose.
461
+ buffer_.clear ();
462
+
463
+ add_empty_line_ = true;
464
+
465
+ #if defined(__GNUC__)
466
+ #pragma GCC diagnostic pop
467
+ #endif
468
+ }
469
+
470
+ // --------------------------------------------------------------------------
471
+
472
+ /**
473
+ * @details
474
+ * This method marks the beginning of a test case, setting the internal state
475
+ * to indicate that test output is now within a test case context. If there
476
+ * is pending output and the verbosity level is set to verbose, it ensures
477
+ * that output is properly separated and displayed, adding an empty line if
478
+ * necessary. The output buffer is cleared and the stream is flushed to
479
+ * guarantee that all previous output is visible before the new test case
480
+ * begins. This approach enhances the clarity and organisation of test
481
+ * results across all test cases and folders.
482
+ */
483
+ void
484
+ reporter_human::begin_subtest ([[maybe_unused]] subtest& subtest)
485
+ {
486
+ #if defined(MICRO_OS_PLUS_TRACE) \
487
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
488
+ #if defined(__GNUC__)
489
+ #pragma GCC diagnostic push
490
+ #if defined(__clang__)
491
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
492
+ #endif
493
+ #endif
494
+ trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
495
+ #if defined(__GNUC__)
496
+ #pragma GCC diagnostic pop
497
+ #endif
498
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
499
+
500
+ #if defined(__GNUC__)
501
+ #pragma GCC diagnostic push
502
+ #if defined(__clang__)
503
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
504
+ #endif
505
+ #endif
506
+
507
+ if (!buffer_.empty ())
508
+ {
509
+ // Each suite should start with an empty buffer.
510
+ fprintf (stderr,
511
+ "Buffer not empty at the beginning of a test case:\n%s\n",
512
+ buffer_.c_str ());
513
+ flush ();
514
+ abort ();
515
+ }
516
+
517
+ std::string indent (indent_size * subtest.nesting_depth (), ' ');
518
+
519
+ if (output_file_ != nullptr)
520
+ {
521
+ fprintf (output_file_, "%s• %s\n", indent.c_str (), subtest.name ());
522
+ }
523
+
524
+ if (verbosity_ == verbosity::verbose)
525
+ {
526
+ if (add_empty_line_)
527
+ {
528
+ printf ("\n");
529
+ }
530
+
531
+ printf ("%s• %s\n", indent.c_str (), subtest.name ());
532
+
533
+ add_empty_line_ = false;
534
+ }
535
+
536
+ flush ();
537
+
538
+ #if defined(__GNUC__)
539
+ #pragma GCC diagnostic pop
540
+ #endif
541
+ }
542
+
543
+ /**
544
+ * @details
545
+ * This method marks the end of a test case, summarising its outcome and
546
+ * outputting the results with appropriate formatting and colour coding. If
547
+ * any checks have failed, a failure message is displayed, including the
548
+ * number of successful and failed checks. For passing test cases, a success
549
+ * message is shown with the total number of checks. The output is adjusted
550
+ * according to the verbosity level, and additional spacing is managed for
551
+ * clarity. The output buffer is cleared and the stream is flushed to ensure
552
+ * all results are immediately visible, supporting clear and organised
553
+ * reporting across all test cases and folders.
554
+ */
555
+ void
556
+ reporter_human::end_subtest (subtest& subtest)
557
+ {
558
+ #if defined(MICRO_OS_PLUS_TRACE) \
559
+ && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
560
+ #if defined(__GNUC__)
561
+ #pragma GCC diagnostic push
562
+ #if defined(__clang__)
563
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
564
+ #endif
565
+ #endif
566
+ trace::printf ("%s '%s' i%zu\n", __PRETTY_FUNCTION__, subtest.name (),
567
+ subtest.nesting_depth ());
568
+ #if defined(__GNUC__)
569
+ #pragma GCC diagnostic pop
570
+ #endif
571
+ #endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
572
+
573
+ #if defined(__GNUC__)
574
+ #pragma GCC diagnostic push
575
+ #if defined(__clang__)
576
+ #pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
577
+ #endif
578
+ #endif
579
+
580
+ // At this point, the buffer may contain output from the subtest, which
581
+ // should be displayed.
582
+ if (verbosity_ == verbosity::normal || verbosity_ == verbosity::verbose)
583
+ {
584
+ std::string indent (indent_size * subtest.nesting_depth (), ' ');
585
+ std::string indent2 (indent_size * (subtest.nesting_depth () + 1),
586
+ ' ');
587
+
588
+ if (add_empty_line_)
589
+ {
590
+ printf ("\n");
591
+ }
592
+
593
+ if (subtest.totals ().was_successful ()) [[likely]]
594
+ {
595
+ // Successful subtest.
596
+
597
+ char message_totals[300];
598
+ snprintf (message_totals, sizeof (message_totals),
599
+ "%s - passed (%zu check%s)", subtest.name (),
600
+ subtest.totals ().successful_checks (),
601
+ subtest.totals ().successful_checks () == 1 ? "" : "s");
602
+
603
+ if (output_file_ != nullptr)
604
+ {
605
+ write_buffer_to_file_ ();
606
+
607
+ fprintf (output_file_, "%s✓ %s\n", indent.c_str (),
608
+ message_totals);
609
+ }
610
+
611
+ if (verbosity_ == verbosity::verbose)
612
+ {
613
+ // With verbosity, show full TAP output accumulated in the
614
+ // buffer.
615
+ write_buffer_to_stdout ();
616
+
617
+ printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
618
+ colours_.none, message_totals);
619
+
620
+ add_empty_line_ = true;
621
+ }
622
+ else
623
+ {
624
+ printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
625
+ colours_.none, message_totals);
626
+
627
+ add_empty_line_ = false;
628
+ }
629
+ }
630
+ else
631
+ {
632
+ // Failed subtest.
633
+ char message_totals[300];
634
+ snprintf (message_totals, sizeof (message_totals),
635
+ "(%zu check%s passed, %zu failed)",
636
+ subtest.totals ().successful_checks (),
637
+ subtest.totals ().successful_checks () == 1 ? "" : "s",
638
+ subtest.totals ().failed_checks ());
639
+
640
+ if (output_file_ != nullptr)
641
+ {
642
+ write_buffer_to_file_ ();
643
+
644
+ fprintf (output_file_, "%s✗ %s - %sFAILED%s %s\n",
645
+ indent.c_str (), subtest.name (), colours_.fail,
646
+ colours_.none, message_totals);
647
+ }
648
+
649
+ if (verbosity_ == verbosity::normal)
650
+ {
651
+ if (!add_empty_line_)
652
+ {
653
+ printf ("\n");
654
+ }
655
+
656
+ printf ("%s• %s\n", indent.c_str (), subtest.name ());
657
+ }
658
+
659
+ // Show full output accumulated in the buffer for failed
660
+ // subtests, as it may contain useful information about the
661
+ // failure.
662
+ write_buffer_to_stdout ();
663
+
664
+ printf ("%s%s✗%s %s - %sFAILED%s %s\n", indent.c_str (),
665
+ colours_.fail, colours_.none, subtest.name (),
666
+ colours_.fail, colours_.none, message_totals);
667
+
668
+ add_empty_line_ = true;
669
+ }
670
+ }
671
+
672
+ flush ();
673
+
674
+ // Clear residual content when less verbose.
675
+ buffer_.clear ();
676
+
677
+ #if defined(__GNUC__)
678
+ #pragma GCC diagnostic pop
679
+ #endif
680
+ }
681
+
682
+ // --------------------------------------------------------------------------
683
+
684
+ /**
685
+ * @details
686
+ * Returns an empty string. The human reporter does not prefix comment
687
+ * lines; the `write_info_()` output appears as plain text.
688
+ */
689
+ const char*
690
+ reporter_human::get_comment_prefix (void)
691
+ {
692
+ return "";
693
+ }
694
+
695
+ /**
696
+ * @details
697
+ * This method outputs the prefix for a passing test result, applying the
698
+ * appropriate colour formatting and symbols to clearly indicate success. If
699
+ * the output occurs within a test case, additional indentation is applied
700
+ * for readability. The prefix includes a tick symbol (`✓`) and, if provided,
701
+ * an associated message. Colour formatting is reset after the prefix to
702
+ * maintain consistent output style across all test cases and folders.
703
+ *
704
+ * The prefix/suffix methods help shorten the code
705
+ * generated by the template methods.
706
+ */
707
+ void
708
+ reporter_human::output_pass_prefix_ (std::string& message,
709
+ [[maybe_unused]] subtest& subtest)
710
+ {
711
+ size_t level = subtest.nesting_depth ();
712
+
713
+ *this << indent (level + 1);
714
+ *this << colours_.pass << "✓" << colours_.none << " ";
715
+ if (!message.empty ())
716
+ {
717
+ *this << message.c_str ();
718
+ }
719
+ }
720
+
721
+ /**
722
+ * @details
723
+ * The `endl` function acts as a stream manipulator for the `reporter`,
724
+ * inserting a line ending into the output buffer and flushing the current
725
+ * content if necessary. This ensures that test report output is clearly
726
+ * separated and formatted, improving readability and professionalism in the
727
+ * presentation of test results.
728
+ *
729
+ * Using `endl` in conjunction with the `reporter` output operators
730
+ * provides a familiar and convenient mechanism for managing line breaks,
731
+ * similar to standard C++ stream manipulators.
732
+ *
733
+ * The prefix/suffix methods help shorten the code
734
+ * generated by the template methods.
735
+ */
736
+ void
737
+ reporter_human::output_pass_suffix_ ([[maybe_unused]] subtest& subtest)
738
+ {
739
+ *this << endl;
740
+
741
+ flush ();
742
+ }
743
+
744
+ /**
745
+ * @details
746
+ * This method outputs the prefix for a failing test result, applying the
747
+ * appropriate colour formatting and symbols to clearly indicate failure. If
748
+ * the output occurs within a test case, additional indentation is applied
749
+ * for readability. The prefix includes a cross symbol (`✗`), an optional
750
+ * message, and the label "FAILED". The source location is appended in
751
+ * parentheses, showing the file or folder name and line number where the
752
+ * failure occurred. Colour formatting is reset after the prefix to maintain
753
+ * consistent output style across all test cases and folders.
754
+ */
755
+ void
756
+ reporter_human::output_fail_prefix_ (
757
+ std::string& message, const bool hasExpression,
758
+ const reflection::source_location& location,
759
+ [[maybe_unused]] subtest& subtest)
760
+ {
761
+ #if defined(__GNUC__)
762
+ #pragma GCC diagnostic push
763
+ #if defined(__clang__)
764
+ #pragma clang diagnostic ignored "-Wsign-conversion"
765
+ #elif defined(__GNUC__)
766
+ #pragma GCC diagnostic ignored "-Wnarrowing"
767
+ #pragma GCC diagnostic ignored "-Wsign-conversion"
768
+ #endif
769
+ #endif
770
+
771
+ size_t level = subtest.nesting_depth ();
772
+
773
+ *this << indent (level + 1);
774
+ *this << colours_.fail << "✗" << colours_.none << " ";
775
+ if (!message.empty ())
776
+ {
777
+ *this << message.c_str ();
778
+ *this << " ";
779
+ }
780
+ *this << colours_.fail << "FAILED" << colours_.none;
781
+ *this << " (" << reflection::short_name (location.file_name ()) << ":"
782
+ << type_traits::genuine_integral_value<unsigned int>{
783
+ location.line ()
784
+ };
785
+ if (hasExpression)
786
+ {
787
+ *this << ", ";
788
+ }
789
+
790
+ #if defined(__GNUC__)
791
+ #pragma GCC diagnostic pop
792
+ #endif
793
+ }
794
+
795
+ /**
796
+ * @details
797
+ * This method outputs the suffix for a failing test result by closing the
798
+ * location information, appending an "aborted..." message if the test was
799
+ * aborted, and then adding a newline to the test output. The output stream
800
+ * is flushed to ensure immediate visibility. This approach guarantees that
801
+ * failure results are clearly separated, promptly reported, and easily
802
+ * distinguishable across all test cases and folders.
803
+ */
804
+ void
805
+ reporter_human::output_fail_suffix_ (
806
+ [[maybe_unused]] const reflection::source_location& location, bool abort,
807
+ [[maybe_unused]] subtest& subtest)
808
+ {
809
+ *this << ")";
810
+ if (abort)
811
+ {
812
+ *this << " aborted...";
813
+ }
814
+ *this << endl;
815
+
816
+ flush ();
817
+ }
818
+
819
+ // --------------------------------------------------------------------------
820
+ } // namespace micro_os_plus::micro_test_plus
821
+
822
+ // ----------------------------------------------------------------------------