micro-test-plus 5.0.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
42
43#if __has_include("micro-os-plus/diag/trace.h")
44#include "micro-os-plus/diag/trace.h"
45#endif // __has_include("micro-os-plus/diag/trace.h")
46
47// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
48#include <cinttypes>
49
50#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
51// For isatty() to detect if stdout is a terminal, enabling colour output.
52#include <unistd.h>
53#endif // defined(__APPLE__) || defined(__linux__) || defined(__unix__)
54
55// ----------------------------------------------------------------------------
56
57#if defined(__GNUC__)
58#pragma GCC diagnostic ignored "-Waggregate-return"
59#if defined(__clang__)
60#pragma clang diagnostic ignored "-Wunknown-warning-option"
61#pragma clang diagnostic ignored "-Wc++98-compat"
62#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
63#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
64#endif // defined(__clang__)
65#endif // defined(__GNUC__)
66
67// ============================================================================
68
69#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
70
71// ----------------------------------------------------------------------------
72
74{
75 // --------------------------------------------------------------------------
76
86 std::unique_ptr<std::vector<std::string_view>> argvs)
87 : reporter{ std::move (argvs) }
88 {
89#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
90 trace::printf ("%s\n", __PRETTY_FUNCTION__);
91#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
92
93#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
94 if (isatty (fileno (stdout)))
95 {
97 }
98#endif // defined(__APPLE__) || defined(__linux__) || defined(__unix__)
99 }
100
108 {
109#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
110 trace::printf ("%s\n", __PRETTY_FUNCTION__);
111#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
112 }
113
114 // --------------------------------------------------------------------------
115
123 constexpr size_t indent_size = 4;
124
134 {
135 buffer_.append (m.level * indent_size, ' ');
136 return *this;
137 }
138
139 // --------------------------------------------------------------------------
140
149 void
151 {
152#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
153 trace::printf ("%s\n", __PRETTY_FUNCTION__);
154#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
155
157 {
158 printf ("\n");
159 }
160
161 write_info_ ();
162
163 const char* message = "µTest++ human report";
164 if (output_file_ != nullptr)
165 {
166 fprintf (output_file_, "%s\n", message);
167 }
168
170 {
171 printf ("%s\n", message);
172
173 flush ();
174 }
175
176 add_empty_line_ = true;
177 }
178
188 void
190 {
191#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
192 trace::printf ("%s\n", __PRETTY_FUNCTION__);
193#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
194
196 {
197 if (add_empty_line_)
198 {
199 printf ("\n");
200 }
201
202 size_t total_suites_count = runner.total_suites_count ();
203
204 uint32_t milliseconds = 0;
205 uint32_t microseconds = 0;
206 if (runner.timings ().has_timestamps ())
207 {
208 runner.timings ().compute_elapsed_time (milliseconds,
209 microseconds);
210 }
211
212 char message_totals[300];
213 snprintf (message_totals, sizeof (message_totals),
214 "Total: %zu check%s passed, %zu failed, in %zu test "
215 "case%s, %zu test suite%s",
217 runner.totals ().successful_checks () == 1 ? "" : "s",
220 runner.totals ().executed_subtests () == 1 ? "" : "s",
221 total_suites_count, total_suites_count == 1 ? "" : "s");
222
223 char message_time[120] = "";
224 if (milliseconds > 0 || microseconds > 0)
225 {
226 snprintf (message_time, sizeof (message_time),
227 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
228 microseconds);
229 }
230
231 if (runner.totals ().was_successful ()) [[likely]]
232 {
233 if (output_file_ != nullptr)
234 {
235 fprintf (output_file_, "✓ %s%s\n", message_totals,
236 message_time);
237 }
238
239 printf ("%s✓%s %s%s\n", colours_.pass, colours_.none,
240 message_totals, message_time);
241 }
242 else
243 {
244 if (output_file_ != nullptr)
245 {
246 fprintf (output_file_, "✗ %s%s\n", message_totals,
247 message_time);
248 }
249
250 printf ("%s✗%s %s%s\n", colours_.fail, colours_.none,
251 message_totals, message_time);
252 }
253
254 flush ();
255 }
256 }
257
258 // --------------------------------------------------------------------------
259
270 void
272 {
273#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
274 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
275#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
276
278 {
279 if (add_empty_line_)
280 {
281 printf ("\n");
282 }
283
284 if (output_file_ != nullptr)
285 {
286 fprintf (output_file_, "• %s\n", suite.name ());
287 }
288
289 printf ("• %s\n", suite.name ());
290
291 flush ();
292
293 add_empty_line_ = true;
294 }
295 }
296
309 void
311 {
312#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
313 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
314#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
315
316 uint32_t milliseconds = 0;
317 uint32_t microseconds = 0;
318 if (suite.timings ().has_timestamps ())
319 {
320 suite.timings ().compute_elapsed_time (milliseconds, microseconds);
321 }
322
323 char message_time[120] = "";
324 if (milliseconds > 0 || microseconds > 0)
325 {
326 snprintf (message_time, sizeof (message_time),
327 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
328 microseconds);
329 }
330
331 // At this point, the buffer may contain output from the test case, which
332 // should be displayed.
334 {
335 std::string indent (indent_size, ' ');
336
337 if (suite.totals ().executed_subtests () > 0)
338 {
339 printf ("\n");
340 }
341
342 if (suite.totals ().was_successful ()) [[likely]]
343 {
344 // Successful test suite.
345
346 char message_totals[300];
347 snprintf (message_totals, sizeof (message_totals),
348 "(%zu check%s in %zu test case%s)",
350 suite.totals ().successful_checks () == 1 ? "" : "s",
352 suite.totals ().executed_subtests () == 1 ? "" : "s");
353
354 if (output_file_ != nullptr)
355 {
357
358 fprintf (output_file_, "✓ %s - passed %s%s\n", suite.name (),
359 message_totals, message_time);
360 }
361
363 {
364 // With verbosity, show full TAP output accumulated in the
365 // buffer.
367 }
368
369 printf ("%s✓%s %s - passed %s%s\n", colours_.pass, colours_.none,
370 suite.name (), message_totals, message_time);
371 }
372 else
373 {
374 // Failed test suite.
375
376 char message_totals[300];
377 snprintf (message_totals, sizeof (message_totals),
378 "(%zu check%s passed, %zu "
379 "failed, in %zu test case%s)",
381 suite.totals ().successful_checks () == 1 ? "" : "s",
384 suite.totals ().executed_subtests () == 1 ? "" : "s");
385
386 if (output_file_ != nullptr)
387 {
389
390 fprintf (output_file_, "✗ %s - FAILED %s%s\n", suite.name (),
391 message_totals, message_time);
392 }
393
394 // Show full TAP output accumulated in the buffer for failed suite
395 // cases, as it may contain useful information about the failure.
397
398 printf ("%s✗%s %s - %sFAILED%s %s%s\n", colours_.fail,
399 colours_.none, suite.name (), colours_.fail, colours_.none,
400 message_totals, message_time);
401 }
402 }
403
404 flush ();
405
406 // Clear residual content when less verbose.
407 buffer_.clear ();
408
409 add_empty_line_ = true;
410 }
411
412 // --------------------------------------------------------------------------
413
425 void
427 {
428#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
429 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
430#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
431
432 if (!buffer_.empty ())
433 {
434 // Each suite should start with an empty buffer.
435 fprintf (stderr,
436 "Buffer not empty at the beginning of a test case:\n%s\n",
437 buffer_.c_str ());
438 flush ();
439 abort ();
440 }
441
442 std::string indent (indent_size * subtest.nesting_depth (), ' ');
443
444 if (output_file_ != nullptr)
445 {
446 fprintf (output_file_, "%s• %s\n", indent.c_str (), subtest.name ());
447 }
448
450 {
451 if (add_empty_line_)
452 {
453 printf ("\n");
454 }
455
456 printf ("%s• %s\n", indent.c_str (), subtest.name ());
457
458 add_empty_line_ = false;
459 }
460
461 flush ();
462 }
463
476 void
478 {
479#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
480 trace::printf ("%s '%s' i%zu\n", __PRETTY_FUNCTION__, subtest.name (),
482#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
483
484 // At this point, the buffer may contain output from the subtest, which
485 // should be displayed.
487 {
488 std::string indent (indent_size * subtest.nesting_depth (), ' ');
489 std::string indent2 (indent_size * (subtest.nesting_depth () + 1),
490 ' ');
491
492 if (add_empty_line_)
493 {
494 printf ("\n");
495 }
496
497 if (subtest.totals ().was_successful ()) [[likely]]
498 {
499 // Successful subtest.
500
501 char message_totals[300];
502 snprintf (message_totals, sizeof (message_totals),
503 "%s - passed (%zu check%s)", subtest.name (),
505 subtest.totals ().successful_checks () == 1 ? "" : "s");
506
507 if (output_file_ != nullptr)
508 {
510
511 fprintf (output_file_, "%s✓ %s\n", indent.c_str (),
512 message_totals);
513 }
514
516 {
517 // With verbosity, show full TAP output accumulated in the
518 // buffer.
520
521 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
522 colours_.none, message_totals);
523
524 add_empty_line_ = true;
525 }
526 else
527 {
528 printf ("%s%s✓%s %s\n", indent.c_str (), colours_.pass,
529 colours_.none, message_totals);
530
531 add_empty_line_ = false;
532 }
533 }
534 else
535 {
536 // Failed subtest.
537 char message_totals[300];
538 snprintf (message_totals, sizeof (message_totals),
539 "(%zu check%s passed, %zu failed)",
541 subtest.totals ().successful_checks () == 1 ? "" : "s",
543
544 if (output_file_ != nullptr)
545 {
547
548 fprintf (output_file_, "%s✗ %s - %sFAILED%s %s\n",
549 indent.c_str (), subtest.name (), colours_.fail,
550 colours_.none, message_totals);
551 }
552
554 {
555 if (!add_empty_line_)
556 {
557 printf ("\n");
558 }
559
560 printf ("%s• %s\n", indent.c_str (), subtest.name ());
561 }
562
563 // Show full output accumulated in the buffer for failed
564 // subtests, as it may contain useful information about the
565 // failure.
567
568 printf ("%s%s✗%s %s - %sFAILED%s %s\n", indent.c_str (),
569 colours_.fail, colours_.none, subtest.name (),
570 colours_.fail, colours_.none, message_totals);
571
572 add_empty_line_ = true;
573 }
574 }
575
576 flush ();
577
578 // Clear residual content when less verbose.
579 buffer_.clear ();
580 }
581
582 // --------------------------------------------------------------------------
583
589 const char*
591 {
592 return "";
593 }
594
607 void
609 {
610 size_t level = subtest.nesting_depth ();
611
612 *this << indent (level + 1);
613 *this << colours_.pass << "✓" << colours_.none << " ";
614 if (!message.empty ())
615 {
616 *this << message.c_str ();
617 }
618 }
619
628 void
630 {
631 *this << endl;
632
633 flush ();
634 }
635
647 void
649 std::string& message, const bool has_expression,
651 {
652#if defined(__GNUC__)
653#pragma GCC diagnostic push
654
655#if defined(__clang__)
656#pragma clang diagnostic ignored "-Wsign-conversion"
657#elif defined(__GNUC__)
658#pragma GCC diagnostic ignored "-Wnarrowing"
659#pragma GCC diagnostic ignored "-Wsign-conversion"
660#endif // defined(__clang__)
661#endif // defined(__GNUC__)
662
663 size_t level = subtest.nesting_depth ();
664
665 *this << indent (level + 1);
666 *this << colours_.fail << "✗" << colours_.none << " ";
667 if (!message.empty ())
668 {
669 *this << message.c_str ();
670 *this << " ";
671 }
672 *this << colours_.fail << "FAILED" << colours_.none;
673 *this << " (" << reflection::short_name (location.file_name ()) << ":"
674 << location.line ();
675 if (has_expression)
676 {
677 *this << ", ";
678 }
679 }
680
681#if defined(__GNUC__)
682#pragma GCC diagnostic pop
683#endif // defined(__GNUC__)
684
694 void
696 [[maybe_unused]] const reflection::source_location& location, bool abort,
697 [[maybe_unused]] subtest& subtest)
698 {
699 *this << ")";
700 if (abort)
701 {
702 *this << " aborted...";
703 }
704 *this << endl;
705
706 flush ();
707 }
708
709 // --------------------------------------------------------------------------
710} // namespace micro_os_plus::micro_test_plus
711
712// ----------------------------------------------------------------------------
713
714#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
715
716// ----------------------------------------------------------------------------
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:167
void compute_elapsed_time(uint32_t &milliseconds, uint32_t &microseconds) const
Computes the elapsed time between begin and end timestamps.
Definition timings.cpp:184
Local implementation of source location information for diagnostics.
Definition reflection.h:150
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:80
FILE * output_file_
Optional output file for redirecting test report output.
Definition reporter.h:600
void write_buffer_to_stdout(void)
Output the current buffered content.
Definition reporter.cpp:218
bool add_empty_line_
Controls whether to add an empty line between successful test cases.
Definition reporter.h:581
std::string buffer_
Output accumulation buffer.
Definition reporter.h:561
void write_info_(void)
Appends informational (non-result) text to the output buffer.
Definition reporter.cpp:251
detail::colours colours_
ANSI colour codes for output formatting.
Definition reporter.h:551
void flush(void)
Flush the current buffered content.
Definition reporter.cpp:348
enum verbosity verbosity_
The verbosity level for test reporting.
Definition reporter.h:546
The test runner for the µTest++ framework.
Definition runner.h:144
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:423
A named, runnable test case that lives inside a suite.
Definition test.h:551
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:723
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:187
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:147
size_t level
Number of four-space indentation levels.
Definition reporter.h:148
C++ header file with declarations for the µTest++ test suite.