Skip to main content

reporter-human.cpp File

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

Included Headers

#include <micro-os-plus/diag/trace.h> #include "micro-os-plus/micro-test-plus/reporter-human.h" #include "micro-os-plus/micro-test-plus/runner.h" #include "micro-os-plus/micro-test-plus/test.h" #include <cinttypes>

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#include <micro-os-plus/diag/trace.h>
52
56
57// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
58#include <cinttypes>
59
60#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
61// For isatty() to detect if stdout is a terminal, enabling colour output.
62#include <unistd.h>
63#endif
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-compat-pedantic"
73#endif
74#endif
75
76// ============================================================================
77
79{
80 // --------------------------------------------------------------------------
81
91 std::unique_ptr<std::vector<std::string_view>> argvs)
92 : reporter{ std::move (argvs) }
93 {
94#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
95 trace::printf ("%s\n", __PRETTY_FUNCTION__);
96#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
97
98#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
99 if (isatty (fileno (stdout)))
100 {
102 }
103#endif
104 }
105
113 {
114#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
115 trace::printf ("%s\n", __PRETTY_FUNCTION__);
116#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
117 }
118
119 // --------------------------------------------------------------------------
120
128 constexpr size_t indent_size = 4;
129
139 {
140 buffer_.append (m.level * indent_size, ' ');
141 return *this;
142 }
143
144 // --------------------------------------------------------------------------
145
154 void
156 {
157#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
158 trace::printf ("%s\n", __PRETTY_FUNCTION__);
159#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
160
161#if defined(__GNUC__)
162#pragma GCC diagnostic push
163#if defined(__clang__)
164#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
165#endif
166#endif
167
169 {
170 printf ("\n");
171 }
172
173 write_info_ ();
174
175 const char* message = "µTest++ human report";
176 if (output_file_ != nullptr)
177 {
178 fprintf (output_file_, "%s\n", message);
179 }
180
182 {
183 printf ("%s\n", message);
184
185 flush ();
186 }
187
188 add_empty_line_ = true;
189
190#if defined(__GNUC__)
191#pragma GCC diagnostic pop
192#endif
193 }
194
204 void
206 {
207#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
208 trace::printf ("%s\n", __PRETTY_FUNCTION__);
209#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
210
211#if defined(__GNUC__)
212#pragma GCC diagnostic push
213#if defined(__clang__)
214#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
215#endif
216#endif
217
219 {
221 {
222 printf ("\n");
223 }
224
225 size_t total_suites_count = runner.total_suites_count ();
226
227 uint32_t milliseconds = 0;
228 uint32_t microseconds = 0;
230 {
231 runner.timings ().compute_elapsed_time (milliseconds,
232 microseconds);
233 }
234
235 char message_totals[300];
236 snprintf (message_totals, sizeof (message_totals),
237 "Total: %zu check%s passed, %zu failed, in %zu test "
238 "case%s, %zu test suite%s",
240 runner.totals ().successful_checks () == 1 ? "" : "s",
243 runner.totals ().executed_subtests () == 1 ? "" : "s",
244 total_suites_count, total_suites_count == 1 ? "" : "s");
245
246 char message_time[120] = "";
247 if (milliseconds > 0 || microseconds > 0)
248 {
249 snprintf (message_time, sizeof (message_time),
250 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
251 microseconds);
252 }
253
254 if (runner.totals ().was_successful ()) [[likely]]
255 {
256 if (output_file_ != nullptr)
257 {
258 fprintf (output_file_, "✓ %s%s\n", message_totals,
259 message_time);
260 }
261
262 printf ("%s✓%s %s%s\n", colours_.pass, colours_.none,
263 message_totals, message_time);
264 }
265 else
266 {
267 if (output_file_ != nullptr)
268 {
269 fprintf (output_file_, "✗ %s%s\n", message_totals,
270 message_time);
271 }
272
273 printf ("%s✗%s %s%s\n", colours_.fail, colours_.none,
274 message_totals, message_time);
275 }
276
277 flush ();
278 }
279
280#if defined(__GNUC__)
281#pragma GCC diagnostic pop
282#endif
283 }
284
285 // --------------------------------------------------------------------------
286
297 void
299 {
300#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
301#pragma GCC diagnostic push
302#if defined(__clang__)
303#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
304#endif
305 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
306#pragma GCC diagnostic pop
307#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
308
309#if defined(__GNUC__)
310#pragma GCC diagnostic push
311#if defined(__clang__)
312#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
313#endif
314#endif
315
317 {
319 {
320 printf ("\n");
321 }
322
323 if (output_file_ != nullptr)
324 {
325 fprintf (output_file_, "• %s\n", suite.name ());
326 }
327
328 printf ("• %s\n", suite.name ());
329
330 flush ();
331
332 add_empty_line_ = true;
333 }
334
335#if defined(__GNUC__)
336#pragma GCC diagnostic pop
337#endif
338 }
339
352 void
354 {
355#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
356#if defined(__GNUC__)
357#pragma GCC diagnostic push
358#if defined(__clang__)
359#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
360#endif
361#endif
362 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
363#if defined(__GNUC__)
364#pragma GCC diagnostic pop
365#endif
366#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
367
368#if defined(__GNUC__)
369#pragma GCC diagnostic push
370#if defined(__clang__)
371#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
372#endif
373#endif
374
375 uint32_t milliseconds = 0;
376 uint32_t microseconds = 0;
378 {
379 suite.timings ().compute_elapsed_time (milliseconds, microseconds);
380 }
381
382 char message_time[120] = "";
383 if (milliseconds > 0 || microseconds > 0)
384 {
385 snprintf (message_time, sizeof (message_time),
386 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
387 microseconds);
388 }
389
390 // At this point, the buffer may contain output from the test case, which
391 // should be displayed.
393 {
394 std::string indent (indent_size, ' ');
395
396 if (suite.totals ().executed_subtests () > 0)
397 {
398 printf ("\n");
399 }
400
401 if (suite.totals ().was_successful ()) [[likely]]
402 {
403 // Successful test suite.
404
405 char message_totals[300];
406 snprintf (message_totals, sizeof (message_totals),
407 "(%zu check%s in %zu test case%s)",
409 suite.totals ().successful_checks () == 1 ? "" : "s",
411 suite.totals ().executed_subtests () == 1 ? "" : "s");
412
413 if (output_file_ != nullptr)
414 {
416
417 fprintf (output_file_, "✓ %s - passed %s%s\n", suite.name (),
418 message_totals, message_time);
419 }
420
422 {
423 // With verbosity, show full TAP output accumulated in the
424 // buffer.
426 }
427
428 printf ("%s✓%s %s - passed %s%s\n", colours_.pass, colours_.none,
429 suite.name (), message_totals, message_time);
430 }
431 else
432 {
433 // Failed test suite.
434
435 char message_totals[300];
436 snprintf (message_totals, sizeof (message_totals),
437 "(%zu check%s passed, %zu "
438 "failed, in %zu test case%s)",
440 suite.totals ().successful_checks () == 1 ? "" : "s",
443 suite.totals ().executed_subtests () == 1 ? "" : "s");
444
445 if (output_file_ != nullptr)
446 {
448
449 fprintf (output_file_, "✗ %s - FAILED %s%s\n", suite.name (),
450 message_totals, message_time);
451 }
452
453 // Show full TAP output accumulated in the buffer for failed suite
454 // cases, as it may contain useful information about the failure.
456
457 printf ("%s✗%s %s - %sFAILED%s %s%s\n", colours_.fail,
458 colours_.none, suite.name (), colours_.fail, colours_.none,
459 message_totals, message_time);
460 }
461 }
462
463 flush ();
464
465 // Clear residual content when less verbose.
466 buffer_.clear ();
467
468 add_empty_line_ = true;
469
470#if defined(__GNUC__)
471#pragma GCC diagnostic pop
472#endif
473 }
474
475 // --------------------------------------------------------------------------
476
488 void
490 {
491#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
492#if defined(__GNUC__)
493#pragma GCC diagnostic push
494#if defined(__clang__)
495#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
496#endif
497#endif
498 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
499#if defined(__GNUC__)
500#pragma GCC diagnostic pop
501#endif
502#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
503
504#if defined(__GNUC__)
505#pragma GCC diagnostic push
506#if defined(__clang__)
507#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
508#endif
509#endif
510
511 if (!buffer_.empty ())
512 {
513 // Each suite should start with an empty buffer.
514 fprintf (stderr,
515 "Buffer not empty at the beginning of a test case:\n%s\n",
516 buffer_.c_str ());
517 flush ();
518 abort ();
519 }
520
521 std::string indent (indent_size * subtest.nesting_depth (), ' ');
522
523 if (output_file_ != nullptr)
524 {
525 fprintf (output_file_, "%s• %s\n", indent.c_str (), subtest.name ());
526 }
527
529 {
531 {
532 printf ("\n");
533 }
534
535 printf ("%s• %s\n", indent.c_str (), subtest.name ());
536
537 add_empty_line_ = false;
538 }
539
540 flush ();
541
542#if defined(__GNUC__)
543#pragma GCC diagnostic pop
544#endif
545 }
546
559 void
561 {
562#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
563#if defined(__GNUC__)
564#pragma GCC diagnostic push
565#if defined(__clang__)
566#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
567#endif
568#endif
569 trace::printf ("%s '%s' i%zu\n", __PRETTY_FUNCTION__, subtest.name (),
571#if defined(__GNUC__)
572#pragma GCC diagnostic pop
573#endif
574#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
575
576#if defined(__GNUC__)
577#pragma GCC diagnostic push
578#if defined(__clang__)
579#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
580#endif
581#endif
582
583 // At this point, the buffer may contain output from the subtest, which
584 // should be displayed.
586 {
587 std::string indent (indent_size * subtest.nesting_depth (), ' ');
588 std::string indent2 (indent_size * (subtest.nesting_depth () + 1),
589 ' ');
590
592 {
593 printf ("\n");
594 }
595
596 if (subtest.totals ().was_successful ()) [[likely]]
597 {
598 // Successful subtest.
599
600 char message_totals[300];
601 snprintf (message_totals, sizeof (message_totals),
602 "%s - passed (%zu check%s)", subtest.name (),
604 subtest.totals ().successful_checks () == 1 ? "" : "s");
605
606 if (output_file_ != nullptr)
607 {
609
610 fprintf (output_file_, "%s✓ %s\n", indent.c_str (),
611 message_totals);
612 }
613
615 {
616 // With verbosity, show full TAP output accumulated in the
617 // buffer.
619
620 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
621 colours_.none, message_totals);
622
623 add_empty_line_ = true;
624 }
625 else
626 {
627 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
628 colours_.none, message_totals);
629
630 add_empty_line_ = false;
631 }
632 }
633 else
634 {
635 // Failed subtest.
636 char message_totals[300];
637 snprintf (message_totals, sizeof (message_totals),
638 "(%zu check%s passed, %zu failed)",
640 subtest.totals ().successful_checks () == 1 ? "" : "s",
642
643 if (output_file_ != nullptr)
644 {
646
647 fprintf (output_file_, "%s✗ %s - %sFAILED%s %s\n",
648 indent.c_str (), subtest.name (), colours_.fail,
649 colours_.none, message_totals);
650 }
651
653 {
654 if (!add_empty_line_)
655 {
656 printf ("\n");
657 }
658
659 printf ("%s• %s\n", indent.c_str (), subtest.name ());
660 }
661
662 // Show full output accumulated in the buffer for failed
663 // subtests, as it may contain useful information about the
664 // failure.
666
667 printf ("%s%s✗%s %s - %sFAILED%s %s\n", indent.c_str (),
668 colours_.fail, colours_.none, subtest.name (),
669 colours_.fail, colours_.none, message_totals);
670
671 add_empty_line_ = true;
672 }
673 }
674
675 flush ();
676
677 // Clear residual content when less verbose.
678 buffer_.clear ();
679
680#if defined(__GNUC__)
681#pragma GCC diagnostic pop
682#endif
683 }
684
685 // --------------------------------------------------------------------------
686
692 const char*
694 {
695 return "";
696 }
697
710 void
712 {
713 size_t level = subtest.nesting_depth ();
714
715 *this << indent (level + 1);
716 *this << colours_.pass << "✓" << colours_.none << " ";
717 if (!message.empty ())
718 {
719 *this << message.c_str ();
720 }
721 }
722
731 void
733 {
734 *this << endl;
735
736 flush ();
737 }
738
750 void
752 std::string& message, const bool has_expression,
754 {
755#if defined(__GNUC__)
756#pragma GCC diagnostic push
757#if defined(__clang__)
758#pragma clang diagnostic ignored "-Wsign-conversion"
759#elif defined(__GNUC__)
760#pragma GCC diagnostic ignored "-Wnarrowing"
761#pragma GCC diagnostic ignored "-Wsign-conversion"
762#endif
763#endif
764
765 size_t level = subtest.nesting_depth ();
766
767 *this << indent (level + 1);
768 *this << colours_.fail << "✗" << colours_.none << " ";
769 if (!message.empty ())
770 {
771 *this << message.c_str ();
772 *this << " ";
773 }
774 *this << colours_.fail << "FAILED" << colours_.none;
775 *this << " (" << reflection::short_name (location.file_name ()) << ":"
776 << location.line ();
777 if (has_expression)
778 {
779 *this << ", ";
780 }
781
782#if defined(__GNUC__)
783#pragma GCC diagnostic pop
784#endif
785 }
786
796 void
798 [[maybe_unused]] const reflection::source_location& location, bool abort,
799 [[maybe_unused]] subtest& subtest)
800 {
801 *this << ")";
802 if (abort)
803 {
804 *this << " aborted...";
805 }
806 *this << endl;
807
808 flush ();
809 }
810
811 // --------------------------------------------------------------------------
812} // namespace micro_os_plus::micro_test_plus
813
814// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.1 by Doxygen 1.17.0.