micro-test-plus 4.1.1
µTest++ Testing Framework
Loading...
Searching...
No Matches
reporter-human.cpp
Go to the documentation of this file.
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
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 {
226 if (add_empty_line_)
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;
235 if (runner.timings ().has_timestamps ())
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 {
325 if (add_empty_line_)
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;
385 if (suite.timings ().has_timestamps ())
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 {
539 if (add_empty_line_)
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
601 if (add_empty_line_)
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// ----------------------------------------------------------------------------
size_t executed_subtests() const noexcept
Returns the number of subtests that were executed.
size_t successful_checks() const noexcept
Returns the number of checks that passed.
bool was_successful(void) const noexcept
Checks whether all executed checks were successful.
size_t failed_checks() const noexcept
Returns the number of checks that failed.
const char * name(void) const noexcept
Gets the node name.
runner_totals & totals() noexcept
Gets the totals for the test.
bool has_timestamps(void) const noexcept
Returns true if both begin and end timestamps are available.
Definition timings.cpp:175
void compute_elapsed_time(uint32_t &milliseconds, uint32_t &microseconds) const
Computes the elapsed time between begin and end timestamps.
Definition timings.cpp:192
Local implementation of source location information for diagnostics.
Definition reflection.h:138
constexpr auto file_name(void) const noexcept
Retrieve the file name associated with this source location.
constexpr auto line(void) const noexcept
Retrieve the line number associated with this source location.
virtual void end_subtest(subtest &subtest) override
Mark the end of a subtest.
virtual void begin_suite(suite &suite) override
Mark the beginning of a suite.
void output_fail_prefix_(std::string &message, const bool has_expression, const reflection::source_location &location, subtest &subtest) override
Outputs the prefix for a failing condition.
void end_session(runner &runner) override
Mark the end of a test session.
reporter_human(std::unique_ptr< std::vector< std::string_view > > argvs)
Constructor for the reporter_human class.
virtual void begin_subtest(subtest &subtest) override
Mark the beginning of a subtest.
reporter_human & operator<<(detail::indent_t m)
Output operator for the indent_t manipulator.
void begin_session(runner &runner) override
Mark the beginning of a test session.
virtual void end_suite(suite &suite) override
Mark the end of a test suite.
void output_pass_suffix_(subtest &subtest) override
Outputs the suffix for a passing condition.
virtual const char * get_comment_prefix(void) override
Returns an empty comment prefix string.
void output_pass_prefix_(std::string &message, subtest &subtest) override
Outputs the prefix for a passing condition.
void output_fail_suffix_(const reflection::source_location &location, bool abort, subtest &subtest) override
Outputs the suffix for a failing condition.
~reporter_human() override
Destructor for the reporter_human class.
reporter(std::unique_ptr< std::vector< std::string_view > > argvs)
Constructor for the reporter class.
Definition reporter.cpp:87
FILE * output_file_
Optional output file for redirecting test report output.
Definition reporter.h:591
void write_buffer_to_stdout(void)
Output the current buffered content.
Definition reporter.cpp:245
bool add_empty_line_
Controls whether to add an empty line between successful test cases.
Definition reporter.h:572
std::string buffer_
Output accumulation buffer.
Definition reporter.h:552
void write_info_(void)
Appends informational (non-result) text to the output buffer.
Definition reporter.cpp:278
detail::colours colours_
ANSI colour codes for output formatting.
Definition reporter.h:542
void flush(void)
Flush the current buffered content.
Definition reporter.cpp:373
enum verbosity verbosity_
The verbosity level for test reporting.
Definition reporter.h:537
The test runner for the µTest++ framework.
Definition runner.h:111
detail::timestamps & timings() noexcept
Gets the timings for this runner.
virtual size_t total_suites_count(void) const noexcept
Returns the total count of registered test suites.
Definition runner.cpp:479
A named, runnable test case that lives inside a suite.
Definition test.h:542
size_t nesting_depth() const noexcept
Returns the nesting depth of this subtest.
A named, runnable test suite registered with the test runner.
Definition test.h:714
detail::timestamps & timings() noexcept
Gets the timings for this suite.
const char * short_name(const char *name) noexcept
Extract a short type or function name from a fully qualified name.
Primary namespace for the µTest++ testing framework.
constexpr size_t indent_size
Number of spaces per indentation level.
detail::indent_t indent(size_t level)
Factory function that creates an indent_t manipulator.
reporter & endl(reporter &stream)
Output stream manipulator for ending a line in test reports.
Definition reporter.cpp:214
C++ header file with declarations for the µTest++ human test reporter.
C++ header file with declarations for the µTest++ test runner.
Parameterised stream manipulator for outputting indentation.
Definition reporter.h:138
size_t level
Number of four-space indentation levels.
Definition reporter.h:139
C++ header file with declarations for the µTest++ test suite.