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// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
40#include <cinttypes>
41
42#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
43// For isatty() to detect if stdout is a terminal, enabling colour output.
44#include <unistd.h>
45#endif
46
47#if defined(MICRO_OS_PLUS_INCLUDE_CONFIG_H)
48#include <micro-os-plus/config.h>
49#endif // MICRO_OS_PLUS_INCLUDE_CONFIG_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// ----------------------------------------------------------------------------
60
61#if defined(__GNUC__)
62#pragma GCC diagnostic ignored "-Waggregate-return"
63#if defined(__clang__)
64#pragma clang diagnostic ignored "-Wunknown-warning-option"
65#pragma clang diagnostic ignored "-Wc++98-compat"
66#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
67#endif
68#endif
69
70// ============================================================================
71
73{
74 // --------------------------------------------------------------------------
75
85 std::unique_ptr<std::vector<std::string_view>> argvs)
86 : reporter{ std::move (argvs) }
87 {
88#if defined(MICRO_OS_PLUS_TRACE) \
89 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
90 trace::printf ("%s\n", __PRETTY_FUNCTION__);
91#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
92
93#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
94 if (isatty (fileno (stdout)))
95 {
97 }
98#endif
99 }
100
108 {
109#if defined(MICRO_OS_PLUS_TRACE) \
110 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS)
111 trace::printf ("%s\n", __PRETTY_FUNCTION__);
112#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS_CONSTRUCTORS
113 }
114
115 // --------------------------------------------------------------------------
116
124 constexpr size_t indent_size = 4;
125
135 {
136 buffer_.append (m.level * indent_size, ' ');
137 return *this;
138 }
139
140 // --------------------------------------------------------------------------
141
150 void
152 {
153#if defined(MICRO_OS_PLUS_TRACE) \
154 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
155 trace::printf ("%s\n", __PRETTY_FUNCTION__);
156#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
157
158#if defined(__GNUC__)
159#pragma GCC diagnostic push
160#if defined(__clang__)
161#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
162#endif
163#endif
164
166 {
167 printf ("\n");
168 }
169
170 write_info_ ();
171
172 const char* message = "µTest++ human report";
173 if (output_file_ != nullptr)
174 {
175 fprintf (output_file_, "%s\n", message);
176 }
177
179 {
180 printf ("%s\n", message);
181
182 flush ();
183 }
184
185 add_empty_line_ = true;
186
187#if defined(__GNUC__)
188#pragma GCC diagnostic pop
189#endif
190 }
191
201 void
203 {
204#if defined(MICRO_OS_PLUS_TRACE) \
205 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
206 trace::printf ("%s\n", __PRETTY_FUNCTION__);
207#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
208
209#if defined(__GNUC__)
210#pragma GCC diagnostic push
211#if defined(__clang__)
212#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
213#endif
214#endif
215
217 {
219 {
220 printf ("\n");
221 }
222
223 size_t total_suites_count = runner.total_suites_count ();
224
225 uint32_t milliseconds = 0;
226 uint32_t microseconds = 0;
228 {
229 runner.timings ().compute_elapsed_time (milliseconds,
230 microseconds);
231 }
232
233 char message_totals[300];
234 snprintf (message_totals, sizeof (message_totals),
235 "Total: %zu check%s passed, %zu failed, in %zu test "
236 "case%s, %zu test suite%s",
238 runner.totals ().successful_checks () == 1 ? "" : "s",
241 runner.totals ().executed_subtests () == 1 ? "" : "s",
242 total_suites_count, total_suites_count == 1 ? "" : "s");
243
244 char message_time[120] = "";
245 if (milliseconds > 0 || microseconds > 0)
246 {
247 snprintf (message_time, sizeof (message_time),
248 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
249 microseconds);
250 }
251
252 if (runner.totals ().was_successful ()) [[likely]]
253 {
254 if (output_file_ != nullptr)
255 {
256 fprintf (output_file_, "✓ %s%s\n", message_totals,
257 message_time);
258 }
259
260 printf ("%s✓%s %s%s\n", colours_.pass, colours_.none,
261 message_totals, message_time);
262 }
263 else
264 {
265 if (output_file_ != nullptr)
266 {
267 fprintf (output_file_, "✗ %s%s\n", message_totals,
268 message_time);
269 }
270
271 printf ("%s✗%s %s%s\n", colours_.fail, colours_.none,
272 message_totals, message_time);
273 }
274
275 flush ();
276 }
277
278#if defined(__GNUC__)
279#pragma GCC diagnostic pop
280#endif
281 }
282
283 // --------------------------------------------------------------------------
284
295 void
297 {
298#if defined(MICRO_OS_PLUS_TRACE) \
299 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
300#pragma GCC diagnostic push
301#if defined(__clang__)
302#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
303#endif
304 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
305#pragma GCC diagnostic pop
306#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
307
308#if defined(__GNUC__)
309#pragma GCC diagnostic push
310#if defined(__clang__)
311#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
312#endif
313#endif
314
316 {
318 {
319 printf ("\n");
320 }
321
322 if (output_file_ != nullptr)
323 {
324 fprintf (output_file_, "• %s\n", suite.name ());
325 }
326
327 printf ("• %s\n", suite.name ());
328
329 flush ();
330
331 add_empty_line_ = true;
332 }
333
334#if defined(__GNUC__)
335#pragma GCC diagnostic pop
336#endif
337 }
338
351 void
353 {
354#if defined(MICRO_OS_PLUS_TRACE) \
355 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
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_TRACE_MICRO_TEST_PLUS
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_TRACE) \
492 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
493#if defined(__GNUC__)
494#pragma GCC diagnostic push
495#if defined(__clang__)
496#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
497#endif
498#endif
499 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
500#if defined(__GNUC__)
501#pragma GCC diagnostic pop
502#endif
503#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
504
505#if defined(__GNUC__)
506#pragma GCC diagnostic push
507#if defined(__clang__)
508#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
509#endif
510#endif
511
512 if (!buffer_.empty ())
513 {
514 // Each suite should start with an empty buffer.
515 fprintf (stderr,
516 "Buffer not empty at the beginning of a test case:\n%s\n",
517 buffer_.c_str ());
518 flush ();
519 abort ();
520 }
521
522 std::string indent (indent_size * subtest.nesting_depth (), ' ');
523
524 if (output_file_ != nullptr)
525 {
526 fprintf (output_file_, "%s• %s\n", indent.c_str (), subtest.name ());
527 }
528
530 {
532 {
533 printf ("\n");
534 }
535
536 printf ("%s• %s\n", indent.c_str (), subtest.name ());
537
538 add_empty_line_ = false;
539 }
540
541 flush ();
542
543#if defined(__GNUC__)
544#pragma GCC diagnostic pop
545#endif
546 }
547
560 void
562 {
563#if defined(MICRO_OS_PLUS_TRACE) \
564 && defined(MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS)
565#if defined(__GNUC__)
566#pragma GCC diagnostic push
567#if defined(__clang__)
568#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
569#endif
570#endif
571 trace::printf ("%s '%s' i%zu\n", __PRETTY_FUNCTION__, subtest.name (),
573#if defined(__GNUC__)
574#pragma GCC diagnostic pop
575#endif
576#endif // MICRO_OS_PLUS_TRACE_MICRO_TEST_PLUS
577
578#if defined(__GNUC__)
579#pragma GCC diagnostic push
580#if defined(__clang__)
581#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
582#endif
583#endif
584
585 // At this point, the buffer may contain output from the subtest, which
586 // should be displayed.
588 {
589 std::string indent (indent_size * subtest.nesting_depth (), ' ');
590 std::string indent2 (indent_size * (subtest.nesting_depth () + 1),
591 ' ');
592
594 {
595 printf ("\n");
596 }
597
598 if (subtest.totals ().was_successful ()) [[likely]]
599 {
600 // Successful subtest.
601
602 char message_totals[300];
603 snprintf (message_totals, sizeof (message_totals),
604 "%s - passed (%zu check%s)", subtest.name (),
606 subtest.totals ().successful_checks () == 1 ? "" : "s");
607
608 if (output_file_ != nullptr)
609 {
611
612 fprintf (output_file_, "%s✓ %s\n", indent.c_str (),
613 message_totals);
614 }
615
617 {
618 // With verbosity, show full TAP output accumulated in the
619 // buffer.
621
622 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
623 colours_.none, message_totals);
624
625 add_empty_line_ = true;
626 }
627 else
628 {
629 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
630 colours_.none, message_totals);
631
632 add_empty_line_ = false;
633 }
634 }
635 else
636 {
637 // Failed subtest.
638 char message_totals[300];
639 snprintf (message_totals, sizeof (message_totals),
640 "(%zu check%s passed, %zu failed)",
642 subtest.totals ().successful_checks () == 1 ? "" : "s",
644
645 if (output_file_ != nullptr)
646 {
648
649 fprintf (output_file_, "%s✗ %s - %sFAILED%s %s\n",
650 indent.c_str (), subtest.name (), colours_.fail,
651 colours_.none, message_totals);
652 }
653
655 {
656 if (!add_empty_line_)
657 {
658 printf ("\n");
659 }
660
661 printf ("%s• %s\n", indent.c_str (), subtest.name ());
662 }
663
664 // Show full output accumulated in the buffer for failed
665 // subtests, as it may contain useful information about the
666 // failure.
668
669 printf ("%s%s✗%s %s - %sFAILED%s %s\n", indent.c_str (),
670 colours_.fail, colours_.none, subtest.name (),
671 colours_.fail, colours_.none, message_totals);
672
673 add_empty_line_ = true;
674 }
675 }
676
677 flush ();
678
679 // Clear residual content when less verbose.
680 buffer_.clear ();
681
682#if defined(__GNUC__)
683#pragma GCC diagnostic pop
684#endif
685 }
686
687 // --------------------------------------------------------------------------
688
694 const char*
696 {
697 return "";
698 }
699
712 void
714 {
715 size_t level = subtest.nesting_depth ();
716
717 *this << indent (level + 1);
718 *this << colours_.pass << "✓" << colours_.none << " ";
719 if (!message.empty ())
720 {
721 *this << message.c_str ();
722 }
723 }
724
733 void
735 {
736 *this << endl;
737
738 flush ();
739 }
740
752 void
754 std::string& message, const bool has_expression,
756 {
757#if defined(__GNUC__)
758#pragma GCC diagnostic push
759#if defined(__clang__)
760#pragma clang diagnostic ignored "-Wsign-conversion"
761#elif defined(__GNUC__)
762#pragma GCC diagnostic ignored "-Wnarrowing"
763#pragma GCC diagnostic ignored "-Wsign-conversion"
764#endif
765#endif
766
767 size_t level = subtest.nesting_depth ();
768
769 *this << indent (level + 1);
770 *this << colours_.fail << "✗" << colours_.none << " ";
771 if (!message.empty ())
772 {
773 *this << message.c_str ();
774 *this << " ";
775 }
776 *this << colours_.fail << "FAILED" << colours_.none;
777 *this << " (" << reflection::short_name (location.file_name ()) << ":"
778 << location.line ();
779 if (has_expression)
780 {
781 *this << ", ";
782 }
783
784#if defined(__GNUC__)
785#pragma GCC diagnostic pop
786#endif
787 }
788
798 void
800 [[maybe_unused]] const reflection::source_location& location, bool abort,
801 [[maybe_unused]] subtest& subtest)
802 {
803 *this << ")";
804 if (abort)
805 {
806 *this << " aborted...";
807 }
808 *this << endl;
809
810 flush ();
811 }
812
813 // --------------------------------------------------------------------------
814} // namespace micro_os_plus::micro_test_plus
815
816// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.0 by Doxygen 1.17.0.