Skip to main content

reporter-human.cpp File

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

namespacedetail

Internal implementation details for the µTest++ framework. More...

Description​

C++ source file with implementations for the µTest++ human-readable reporter member functions.

This source file contains the implementations for reporter_human, the concrete implementation of the reporter abstract interface that presents test results in a human-readable form on the 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::detail namespace, since the reporters are internal to the framework.

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
12// ----------------------------------------------------------------------------
13
32
33// ----------------------------------------------------------------------------
34
39
40#if __has_include("micro-os-plus/diag/trace.h")
41#include "micro-os-plus/diag/trace.h"
42#endif // __has_include("micro-os-plus/diag/trace.h")
43
44#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
45// For isatty() to detect if stdout is a terminal, enabling colour output.
46#include <unistd.h>
47#endif // defined(__APPLE__) || defined(__linux__) || defined(__unix__)
48
49// ----------------------------------------------------------------------------
50
51#if defined(__GNUC__)
52#pragma GCC diagnostic ignored "-Waggregate-return"
53#if defined(__clang__)
54#pragma clang diagnostic ignored "-Wunknown-warning-option"
55#pragma clang diagnostic ignored "-Wc++98-compat"
56#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
57#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
58#endif // defined(__clang__)
59#endif // defined(__GNUC__)
60
61// ============================================================================
62
63#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
64
65// ----------------------------------------------------------------------------
66
68{
69 // --------------------------------------------------------------------------
70
81 : reporter{ opts }
82 {
83#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
84 trace::printf ("%s\n", __PRETTY_FUNCTION__);
85#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
86
87 bool use_colours = (opts.colour == colour_mode::always);
88
89#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
90 if (opts.colour == colour_mode::automatic && isatty (fileno (stdout)))
91 {
92 use_colours = true;
93 }
94#endif // defined(__APPLE__) || defined(__linux__) || defined(__unix__)
95
96 if (use_colours)
97 {
99 }
100 }
101
109 {
110#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
111 trace::printf ("%s\n", __PRETTY_FUNCTION__);
112#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
113 }
114
115 // --------------------------------------------------------------------------
116
126 void
128 {
129#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
130 trace::printf ("%s\n", __PRETTY_FUNCTION__);
131#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
132
133 const bool to_console = route (record_kind::total).console;
134
135 if (to_console)
136 {
137 write_console ("\n");
138 }
139
141
142 write_line (to_console, "µTest++ human-readable report\n");
143
145
146 add_empty_line_ = true;
147 }
148
160 void
162 {
163#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
164 trace::printf ("%s\n", __PRETTY_FUNCTION__);
165#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
166
167 const bool to_console = route (record_kind::total).console;
168
169 if (to_console && add_empty_line_)
170 {
171 write_console ("\n");
172 }
173
174 char message_totals[totals_size_];
175 format_session_totals (runner, message_totals);
176
177 char message_time[elapsed_time_size_];
179
180 if (runner.totals ().passed ()) [[likely]]
181 {
182 write_line (to_console, "%s✓%s Total: %s%s\n", colours_.pass,
183 colours_.none, message_totals, message_time);
184 }
185 else
186 {
187 write_line (to_console, "%s✗%s Total: %s%s\n", colours_.fail,
188 colours_.none, message_totals, message_time);
189 }
190
193 }
194
195 // --------------------------------------------------------------------------
196
207 void
209 {
210#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
211 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
212#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
213
214 const bool to_console = route (record_kind::suite_heading).console;
215
216 if (to_console && add_empty_line_)
217 {
218 write_console ("\n");
219 }
220
221 write_line (to_console, "• %s\n", suite.name ());
222
224
225 if (to_console)
226 {
227 add_empty_line_ = true;
228 }
229
230 enter_suite (suite, to_console);
231 }
232
254 void
256 {
257#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
258 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
259#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
260
261 const bool to_console = route (record_kind::suite_result).console;
262 const bool passed = suite.totals ().passed ();
263
264 char message_time[elapsed_time_size_];
265 format_elapsed_time (suite.stopwatch_, message_time);
266
267 char message_totals[totals_size_];
268 format_suite_totals (suite, message_totals);
269
270 // At this point, the buffer may contain output from the test case, which
271 // should be displayed.
272 const std::string_view lines = console_lines (!passed);
273
275
276 if (to_console)
277 {
278 if (suite.totals ().executed_test_cases () > 0)
279 {
280 write_console ("\n");
281 }
282
283 write_console_text (lines);
284 }
285 else if (!lines.empty ())
286 {
287 // The failure of the suite body, with quiet verbosity.
289 {
290 write_console ("\n");
291 }
292
293 display_open_names (true);
294
295 write_console_text (lines);
296 }
297
298 if (passed) [[likely]]
299 {
300 // Successful test suite.
301
302 write_line (to_console, "%s✓%s %s - passed (%s)%s\n", colours_.pass,
303 colours_.none, suite.name (), message_totals,
304 message_time);
305 }
306 else
307 {
308 // Failed test suite.
309
310 write_line (to_console, "%s✗%s %s - %sFAILED%s (%s)%s\n",
312 colours_.none, message_totals, message_time);
313 }
314
316
317 // Clear residual content when less verbose.
318 clear_buffer ();
319
320 add_empty_line_ = true;
321
322 current_suite_ = nullptr;
323 }
324
333 void
335 {
336#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
337 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
338#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
339
340 const bool to_console = route (record_kind::skipped).console;
341
342 if (to_console && add_empty_line_)
343 {
344 write_console ("\n");
345 }
346
347 write_line (to_console, "• %s - skipped (filtered)\n", suite.name ());
348
350
351 if (to_console)
352 {
353 add_empty_line_ = true;
354 }
355 }
356
365 void
366 reporter_human::fail_suite (const std::string& message,
367 [[maybe_unused]] const suite& suite)
368 {
369#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
370 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
371#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
372
373 const size_t begin = buffer_.size ();
374
375 *this << indent (1) << colours_.fail << "✗" << colours_.none << " "
376 << message << " " << colours_.fail << "FAILED" << colours_.none
377 << endl;
378
379 mark_failure (begin);
380 }
381
382 // --------------------------------------------------------------------------
383
397 void
399 {
400#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
401 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, test_case.name ());
402#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
403
405
406 const bool to_console = route (record_kind::test_case_heading).console;
407
408 open_test_cases_.push_back ({ &test_case, to_console });
409
410 std::string indent (indent_size_ * test_case.nesting_depth (), ' ');
411
412 if (to_console && add_empty_line_)
413 {
414 write_console ("\n");
415 }
416
417 write_line (to_console, "%s• %s\n", indent.c_str (), test_case.name ());
418
419 if (to_console)
420 {
421 add_empty_line_ = false;
422 }
423
425 }
426
451 void
453 {
454#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
455 trace::printf ("%s '%s' i%" MICRO_OS_PLUS_MICRO_TEST_PLUS_PRI_SIZE "\n",
456 __PRETTY_FUNCTION__, test_case.name (),
458#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
459
460 const bool to_console = route (record_kind::test_case_result).console;
461 const bool passed = test_case.totals ().passed ();
462
463 std::string indent (indent_size_ * test_case.nesting_depth (), ' ');
464
465 // The name is not included, since its length is not bounded;
466 // it is passed to each write.
467 char message_totals[totals_size_];
468 format_test_case_totals (test_case, message_totals);
469
470 // At this point, the buffer may contain output from the test case, which
471 // should be displayed.
472 const std::string_view lines = console_lines (!passed);
473
475
476 const bool shows = to_console || !lines.empty ();
477 if (shows)
478 {
480 {
481 write_console ("\n");
482 }
483
484 // The names of this test case and of the outer ones, whose names
485 // may have been displayed already, by `commit_buffer()` or by a
486 // failed sibling; for a passed test case, only the outer ones.
487 display_open_names (!passed);
488
489 // With verbosity, the full output accumulated in the buffer;
490 // for a failed test case, as it may contain useful information
491 // about the failure.
492 write_console_text (lines);
493 }
494
495 if (passed) [[likely]]
496 {
497 // Successful test case.
498
499 write_line (to_console, "%s%s✓%s %s - passed (%s)\n", indent.c_str (),
501 message_totals);
502 }
503 else
504 {
505 // Failed test case.
506
507 write_line (to_console, "%s%s✗%s %s - %sFAILED%s (%s)\n",
508 indent.c_str (), colours_.fail, colours_.none,
510 message_totals);
511 }
512
513 if (shows)
514 {
517 }
518
520
521 // Clear residual content when less verbose.
522 clear_buffer ();
523
524 if (!open_test_cases_.empty ())
525 {
526 open_test_cases_.pop_back ();
527 }
528 }
529
530 // --------------------------------------------------------------------------
531
537 const char*
538 reporter_human::comment_prefix (void) const noexcept
539 {
540 return "";
541 }
542
553 void
554 reporter_human::output_pass_prefix (const std::string& message,
555 const test_case& test_case)
556 {
557 size_t level = test_case.nesting_depth ();
558
559 *this << indent (level + 1);
560 *this << colours_.pass << "✓" << colours_.none << " ";
561 if (!message.empty ())
562 {
563 *this << message;
564 }
565 }
566
577 void
579 [[maybe_unused]] const test_case& test_case)
580 {
581 *this << endl;
582 }
583
594 void
596 const std::string& message, const bool has_expression,
597 const reflection::source_location& location, const test_case& test_case)
598 {
599#if defined(__GNUC__)
600#pragma GCC diagnostic push
601
602#if defined(__clang__)
603#pragma clang diagnostic ignored "-Wsign-conversion"
604#elif defined(__GNUC__)
605#pragma GCC diagnostic ignored "-Wnarrowing"
606#pragma GCC diagnostic ignored "-Wsign-conversion"
607#endif // defined(__clang__)
608#endif // defined(__GNUC__)
609
610 size_t level = test_case.nesting_depth ();
611
612 *this << indent (level + 1);
613 *this << colours_.fail << "✗" << colours_.none << " ";
614 if (!message.empty ())
615 {
616 *this << message;
617 *this << " ";
618 }
619 *this << colours_.fail << "FAILED" << colours_.none;
620 *this << " (" << utility::extract_file_name (location.file_name ()) << ":"
621 << location.line ();
622 if (has_expression)
623 {
624 *this << ", ";
625 }
626 }
627
628#if defined(__GNUC__)
629#pragma GCC diagnostic pop
630#endif // defined(__GNUC__)
631
645 void
647 [[maybe_unused]] const reflection::source_location& location,
648 check_kind kind, [[maybe_unused]] const test_case& test_case)
649 {
650 *this << ")";
651 if (kind == check_kind::requirement)
652 {
653#if defined(__cpp_exceptions)
654 *this << " - test case stopped";
655#else
656 *this << " - test execution aborted";
657#endif // defined(__cpp_exceptions)
658 }
659 *this << endl;
660 }
661
676 void
678 {
679#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
680 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, test_case.name ());
681#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
682
684
685 const bool to_console = route (record_kind::skipped).console;
686
687 std::string indent (indent_size_ * test_case.nesting_depth (), ' ');
688
689 if (to_console)
690 {
692 {
693 write_console ("\n");
694 }
695
696 display_open_names (true);
697 }
698
699 write_line (to_console, "%s• %s - skipped (filtered)\n", indent.c_str (),
700 test_case.name ());
701
702 if (to_console)
703 {
704 // As for a passed test case.
706 }
707
709 }
710
717 void
719 [[maybe_unused]] const test_case& test_case)
720 {
721 }
722
750 void
752 [[maybe_unused]] bool is_requirement)
753 {
754#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
755 trace::printf ("%s\n", __PRETTY_FUNCTION__);
756#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
757
758 const bool to_console = route (record_kind::abort).console;
759
760 // The type of `line()` depends on the toolchain (`uint_least32_t`
761 // for `std::source_location`); widen it implicitly, without a cast
762 // that would be useless on some platforms.
763 const unsigned long line = location.line ();
764 const char* file_name = utility::extract_file_name (location.file_name ());
765
766 const std::string_view lines = console_lines (true);
767
769
770 if (to_console)
771 {
772 // Whether headings or results are displayed before the final line.
773 bool has_results = !lines.empty ()
774 || (current_suite_ != nullptr && !suite_displayed_);
775 for (const open_test_case& open : open_test_cases_)
776 {
777 has_results = has_results || !open.name_displayed;
778 }
779
781 {
782 write_console ("\n");
783 }
784
785 display_open_names (true);
786
787 write_console_text (lines);
788
789 // Separate the final line by an empty line, as the summary, unless
790 // it was already written above.
791 if (has_results || !add_empty_line_)
792 {
793 write_console ("\n");
794 }
795 }
796
797 write_line (to_console, "%s✗%s Test execution aborted at %s:%lu\n",
798 colours_.fail, colours_.none, file_name, line);
799
801
803
804 clear_buffer ();
805
806 // Should the abort handler throw, nothing may refer to the objects
807 // that are unwound.
808 open_test_cases_.clear ();
809 current_suite_ = nullptr;
810 }
811
828 void
830 {
832
833 const std::string_view lines = console_lines (buffer_has_failures_);
834 if (!lines.empty ())
835 {
837 {
838 // Verbose, the headings were displayed.
840 {
841 write_console ("\n");
842 add_empty_line_ = false;
843 }
844
845 write_console_text (lines);
846 }
847 else
848 {
850 {
851 write_console ("\n");
852 }
853
854 display_open_names (true);
855
856 write_console_text (lines);
857
858 add_empty_line_ = true;
859 }
860 }
861
863
864 clear_buffer ();
865 }
866
875 void
877 {
878 if (!suite_displayed_ && current_suite_ != nullptr)
879 {
880 if (!add_empty_line_)
881 {
882 write_console ("\n");
883 }
884
885 write_console ("• %s\n\n", current_suite_->name ());
886
887 suite_displayed_ = true;
888 add_empty_line_ = true;
889 }
890
891 display_open_test_cases (include_last);
892 }
893
900 void
902 bool is_first)
903 {
904 if (is_first && !add_empty_line_)
905 {
906 write_console ("\n");
907 }
908
909 std::string indent (indent_size_ * open.test->nesting_depth (), ' ');
910 write_console ("%s• %s\n", indent.c_str (), open.test->name ());
911 }
912
913 // --------------------------------------------------------------------------
914} // namespace micro_os_plus::micro_test_plus::detail
915
916// ----------------------------------------------------------------------------
917
918#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
919
920// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.8 by Doxygen 1.18.0.