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
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 {
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;
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 {
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;
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 {
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
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// ----------------------------------------------------------------------------

Generated via doxygen2docusaurus 2.2.2 by Doxygen 1.17.0.