micro-test-plus 5.0.0
µ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#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 {
220 if (add_empty_line_)
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;
229 if (runner.timings ().has_timestamps ())
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 {
318 if (add_empty_line_)
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;
377 if (suite.timings ().has_timestamps ())
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 {
530 if (add_empty_line_)
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
591 if (add_empty_line_)
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// ----------------------------------------------------------------------------
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:85
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:241
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:274
detail::colours colours_
ANSI colour codes for output formatting.
Definition reporter.h:542
void flush(void)
Flush the current buffered content.
Definition reporter.cpp:371
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:470
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:210
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.