Skip to main content

reporter-human.cpp File

C++ source file with implementations for the µTest++ human test reporter 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...

Variables Index

constexpr size_tindent_size = 4

Number of spaces per indentation level. More...

Description

C++ source file with implementations for the µTest++ human test reporter methods.

This source file contains the implementations for reporter_human, the default concrete implementation of the reporter abstract interface. It formats and presents test results using printf-based standard output, accumulating output in an internal string buffer and supporting colour-coded diagnostics and multiple verbosity levels.

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
36
37// ----------------------------------------------------------------------------
38
39#if __has_include(<micro-os-plus/project-config.h>)
40#include <micro-os-plus/project-config.h>
41#elif __has_include(<micro-os-plus/config.h>)
42#pragma message \
43 "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"
44#include <micro-os-plus/config.h>
45#endif // __has_include(<micro-os-plus/project-config.h>)
46
47#if __has_include(<micro-os-plus/micro-test-plus-defines.h>)
48#include <micro-os-plus/micro-test-plus-defines.h>
49#endif // __has_include(<micro-os-plus/micro-test-plus-defines.h>)
50
51#if defined(MICRO_OS_PLUS_TRACE)
52#include <micro-os-plus/diag/trace.h>
53#endif // MICRO_OS_PLUS_TRACE
54
58
59// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
60#include <cinttypes>
61
62#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
63// For isatty() to detect if stdout is a terminal, enabling colour output.
64#include <unistd.h>
65#endif
66
67// ----------------------------------------------------------------------------
68
69#if defined(__GNUC__)
70#pragma GCC diagnostic ignored "-Waggregate-return"
71#if defined(__clang__)
72#pragma clang diagnostic ignored "-Wunknown-warning-option"
73#pragma clang diagnostic ignored "-Wc++98-compat"
74#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
75#endif
76#endif
77
78// ============================================================================
79
81{
82 // --------------------------------------------------------------------------
83
93 std::unique_ptr<std::vector<std::string_view>> argvs)
94 : reporter{ std::move (argvs) }
95 {
96#if defined(MICRO_OS_PLUS_TRACE) \
97 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
98 trace::printf ("%s\n", __PRETTY_FUNCTION__);
99#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
100
101#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
102 if (isatty (fileno (stdout)))
103 {
105 }
106#endif
107 }
108
116 {
117#if defined(MICRO_OS_PLUS_TRACE) \
118 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
119 trace::printf ("%s\n", __PRETTY_FUNCTION__);
120#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
121 }
122
123 // --------------------------------------------------------------------------
124
132 constexpr size_t indent_size = 4;
133
143 {
144 buffer_.append (m.level * indent_size, ' ');
145 return *this;
146 }
147
148 // --------------------------------------------------------------------------
149
158 void
160 {
161#if defined(MICRO_OS_PLUS_TRACE) \
162 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
163 trace::printf ("%s\n", __PRETTY_FUNCTION__);
164#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
165
166#if defined(__GNUC__)
167#pragma GCC diagnostic push
168#if defined(__clang__)
169#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
170#endif
171#endif
172
174 {
175 printf ("\n");
176 }
177
178 write_info_ ();
179
180 const char* message = "µTest++ human report";
181 if (output_file_ != nullptr)
182 {
183 fprintf (output_file_, "%s\n", message);
184 }
185
187 {
188 printf ("%s\n", message);
189
190 flush ();
191 }
192
193 add_empty_line_ = true;
194
195#if defined(__GNUC__)
196#pragma GCC diagnostic pop
197#endif
198 }
199
209 void
211 {
212#if defined(MICRO_OS_PLUS_TRACE) \
213 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
214 trace::printf ("%s\n", __PRETTY_FUNCTION__);
215#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
216
217#if defined(__GNUC__)
218#pragma GCC diagnostic push
219#if defined(__clang__)
220#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
221#endif
222#endif
223
225 {
227 {
228 printf ("\n");
229 }
230
231 size_t total_suites_count = runner.total_suites_count ();
232
233 uint32_t milliseconds = 0;
234 uint32_t microseconds = 0;
236 {
237 runner.timings ().compute_elapsed_time (milliseconds,
238 microseconds);
239 }
240
241 char message_totals[300];
242 snprintf (message_totals, sizeof (message_totals),
243 "Total: %zu check%s passed, %zu failed, in %zu test "
244 "case%s, %zu test suite%s",
246 runner.totals ().successful_checks () == 1 ? "" : "s",
249 runner.totals ().executed_subtests () == 1 ? "" : "s",
250 total_suites_count, total_suites_count == 1 ? "" : "s");
251
252 char message_time[120] = "";
253 if (milliseconds > 0 || microseconds > 0)
254 {
255 snprintf (message_time, sizeof (message_time),
256 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
257 microseconds);
258 }
259
260 if (runner.totals ().was_successful ()) [[likely]]
261 {
262 if (output_file_ != nullptr)
263 {
264 fprintf (output_file_, "✓ %s%s\n", message_totals,
265 message_time);
266 }
267
268 printf ("%s✓%s %s%s\n", colours_.pass, colours_.none,
269 message_totals, message_time);
270 }
271 else
272 {
273 if (output_file_ != nullptr)
274 {
275 fprintf (output_file_, "✗ %s%s\n", message_totals,
276 message_time);
277 }
278
279 printf ("%s✗%s %s%s\n", colours_.fail, colours_.none,
280 message_totals, message_time);
281 }
282
283 flush ();
284 }
285
286#if defined(__GNUC__)
287#pragma GCC diagnostic pop
288#endif
289 }
290
291 // --------------------------------------------------------------------------
292
303 void
305 {
306#if defined(MICRO_OS_PLUS_TRACE) \
307 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
308#pragma GCC diagnostic push
309#if defined(__clang__)
310#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
311#endif
312 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
313#pragma GCC diagnostic pop
314#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
315
316#if defined(__GNUC__)
317#pragma GCC diagnostic push
318#if defined(__clang__)
319#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
320#endif
321#endif
322
324 {
326 {
327 printf ("\n");
328 }
329
330 if (output_file_ != nullptr)
331 {
332 fprintf (output_file_, "• %s\n", suite.name ());
333 }
334
335 printf ("• %s\n", suite.name ());
336
337 flush ();
338
339 add_empty_line_ = true;
340 }
341
342#if defined(__GNUC__)
343#pragma GCC diagnostic pop
344#endif
345 }
346
359 void
361 {
362#if defined(MICRO_OS_PLUS_TRACE) \
363 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
364#if defined(__GNUC__)
365#pragma GCC diagnostic push
366#if defined(__clang__)
367#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
368#endif
369#endif
370 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
371#if defined(__GNUC__)
372#pragma GCC diagnostic pop
373#endif
374#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
375
376#if defined(__GNUC__)
377#pragma GCC diagnostic push
378#if defined(__clang__)
379#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
380#endif
381#endif
382
383 uint32_t milliseconds = 0;
384 uint32_t microseconds = 0;
386 {
387 suite.timings ().compute_elapsed_time (milliseconds, microseconds);
388 }
389
390 char message_time[120] = "";
391 if (milliseconds > 0 || microseconds > 0)
392 {
393 snprintf (message_time, sizeof (message_time),
394 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
395 microseconds);
396 }
397
398 // At this point, the buffer may contain output from the test case, which
399 // should be displayed.
401 {
402 std::string indent (indent_size, ' ');
403
404 if (suite.totals ().executed_subtests () > 0)
405 {
406 printf ("\n");
407 }
408
409 if (suite.totals ().was_successful ()) [[likely]]
410 {
411 // Successful test suite.
412
413 char message_totals[300];
414 snprintf (message_totals, sizeof (message_totals),
415 "(%zu check%s in %zu test case%s)",
417 suite.totals ().successful_checks () == 1 ? "" : "s",
419 suite.totals ().executed_subtests () == 1 ? "" : "s");
420
421 if (output_file_ != nullptr)
422 {
424
425 fprintf (output_file_, "✓ %s - passed %s%s\n", suite.name (),
426 message_totals, message_time);
427 }
428
430 {
431 // With verbosity, show full TAP output accumulated in the
432 // buffer.
434 }
435
436 printf ("%s✓%s %s - passed %s%s\n", colours_.pass, colours_.none,
437 suite.name (), message_totals, message_time);
438 }
439 else
440 {
441 // Failed test suite.
442
443 char message_totals[300];
444 snprintf (message_totals, sizeof (message_totals),
445 "(%zu check%s passed, %zu "
446 "failed, in %zu test case%s)",
448 suite.totals ().successful_checks () == 1 ? "" : "s",
451 suite.totals ().executed_subtests () == 1 ? "" : "s");
452
453 if (output_file_ != nullptr)
454 {
456
457 fprintf (output_file_, "✗ %s - FAILED %s%s\n", suite.name (),
458 message_totals, message_time);
459 }
460
461 // Show full TAP output accumulated in the buffer for failed suite
462 // cases, as it may contain useful information about the failure.
464
465 printf ("%s✗%s %s - %sFAILED%s %s%s\n", colours_.fail,
466 colours_.none, suite.name (), colours_.fail, colours_.none,
467 message_totals, message_time);
468 }
469 }
470
471 flush ();
472
473 // Clear residual content when less verbose.
474 buffer_.clear ();
475
476 add_empty_line_ = true;
477
478#if defined(__GNUC__)
479#pragma GCC diagnostic pop
480#endif
481 }
482
483 // --------------------------------------------------------------------------
484
496 void
498 {
499#if defined(MICRO_OS_PLUS_TRACE) \
500 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
501#if defined(__GNUC__)
502#pragma GCC diagnostic push
503#if defined(__clang__)
504#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
505#endif
506#endif
507 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
508#if defined(__GNUC__)
509#pragma GCC diagnostic pop
510#endif
511#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
512
513#if defined(__GNUC__)
514#pragma GCC diagnostic push
515#if defined(__clang__)
516#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
517#endif
518#endif
519
520 if (!buffer_.empty ())
521 {
522 // Each suite should start with an empty buffer.
523 fprintf (stderr,
524 "Buffer not empty at the beginning of a test case:\n%s\n",
525 buffer_.c_str ());
526 flush ();
527 abort ();
528 }
529
530 std::string indent (indent_size * subtest.nesting_depth (), ' ');
531
532 if (output_file_ != nullptr)
533 {
534 fprintf (output_file_, "%s• %s\n", indent.c_str (), subtest.name ());
535 }
536
538 {
540 {
541 printf ("\n");
542 }
543
544 printf ("%s• %s\n", indent.c_str (), subtest.name ());
545
546 add_empty_line_ = false;
547 }
548
549 flush ();
550
551#if defined(__GNUC__)
552#pragma GCC diagnostic pop
553#endif
554 }
555
568 void
570 {
571#if defined(MICRO_OS_PLUS_TRACE) \
572 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
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 trace::printf ("%s '%s' i%zu\n", __PRETTY_FUNCTION__, subtest.name (),
581#if defined(__GNUC__)
582#pragma GCC diagnostic pop
583#endif
584#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
585
586#if defined(__GNUC__)
587#pragma GCC diagnostic push
588#if defined(__clang__)
589#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
590#endif
591#endif
592
593 // At this point, the buffer may contain output from the subtest, which
594 // should be displayed.
596 {
597 std::string indent (indent_size * subtest.nesting_depth (), ' ');
598 std::string indent2 (indent_size * (subtest.nesting_depth () + 1),
599 ' ');
600
602 {
603 printf ("\n");
604 }
605
606 if (subtest.totals ().was_successful ()) [[likely]]
607 {
608 // Successful subtest.
609
610 char message_totals[300];
611 snprintf (message_totals, sizeof (message_totals),
612 "%s - passed (%zu check%s)", subtest.name (),
614 subtest.totals ().successful_checks () == 1 ? "" : "s");
615
616 if (output_file_ != nullptr)
617 {
619
620 fprintf (output_file_, "%s✓ %s\n", indent.c_str (),
621 message_totals);
622 }
623
625 {
626 // With verbosity, show full TAP output accumulated in the
627 // buffer.
629
630 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
631 colours_.none, message_totals);
632
633 add_empty_line_ = true;
634 }
635 else
636 {
637 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
638 colours_.none, message_totals);
639
640 add_empty_line_ = false;
641 }
642 }
643 else
644 {
645 // Failed subtest.
646 char message_totals[300];
647 snprintf (message_totals, sizeof (message_totals),
648 "(%zu check%s passed, %zu failed)",
650 subtest.totals ().successful_checks () == 1 ? "" : "s",
652
653 if (output_file_ != nullptr)
654 {
656
657 fprintf (output_file_, "%s✗ %s - %sFAILED%s %s\n",
658 indent.c_str (), subtest.name (), colours_.fail,
659 colours_.none, message_totals);
660 }
661
663 {
664 if (!add_empty_line_)
665 {
666 printf ("\n");
667 }
668
669 printf ("%s• %s\n", indent.c_str (), subtest.name ());
670 }
671
672 // Show full output accumulated in the buffer for failed
673 // subtests, as it may contain useful information about the
674 // failure.
676
677 printf ("%s%s✗%s %s - %sFAILED%s %s\n", indent.c_str (),
678 colours_.fail, colours_.none, subtest.name (),
679 colours_.fail, colours_.none, message_totals);
680
681 add_empty_line_ = true;
682 }
683 }
684
685 flush ();
686
687 // Clear residual content when less verbose.
688 buffer_.clear ();
689
690#if defined(__GNUC__)
691#pragma GCC diagnostic pop
692#endif
693 }
694
695 // --------------------------------------------------------------------------
696
702 const char*
704 {
705 return "";
706 }
707
720 void
722 {
723 size_t level = subtest.nesting_depth ();
724
725 *this << indent (level + 1);
726 *this << colours_.pass << "✓" << colours_.none << " ";
727 if (!message.empty ())
728 {
729 *this << message.c_str ();
730 }
731 }
732
741 void
743 {
744 *this << endl;
745
746 flush ();
747 }
748
760 void
762 std::string& message, const bool has_expression,
764 {
765#if defined(__GNUC__)
766#pragma GCC diagnostic push
767#if defined(__clang__)
768#pragma clang diagnostic ignored "-Wsign-conversion"
769#elif defined(__GNUC__)
770#pragma GCC diagnostic ignored "-Wnarrowing"
771#pragma GCC diagnostic ignored "-Wsign-conversion"
772#endif
773#endif
774
775 size_t level = subtest.nesting_depth ();
776
777 *this << indent (level + 1);
778 *this << colours_.fail << "✗" << colours_.none << " ";
779 if (!message.empty ())
780 {
781 *this << message.c_str ();
782 *this << " ";
783 }
784 *this << colours_.fail << "FAILED" << colours_.none;
785 *this << " (" << reflection::short_name (location.file_name ()) << ":"
786 << location.line ();
787 if (has_expression)
788 {
789 *this << ", ";
790 }
791
792#if defined(__GNUC__)
793#pragma GCC diagnostic pop
794#endif
795 }
796
806 void
808 [[maybe_unused]] const reflection::source_location& location, bool abort,
809 [[maybe_unused]] subtest& subtest)
810 {
811 *this << ")";
812 if (abort)
813 {
814 *this << " aborted...";
815 }
816 *this << endl;
817
818 flush ();
819 }
820
821 // --------------------------------------------------------------------------
822} // namespace micro_os_plus::micro_test_plus
823
824// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.0 by Doxygen 1.17.0.