micro-test-plus 5.0.0
µTest++ Testing Framework
Loading...
Searching...
No Matches
reporter-tap.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
34
35// ----------------------------------------------------------------------------
36
37#if __has_include(<micro-os-plus/project-config.h>)
38#include <micro-os-plus/project-config.h>
39#elif __has_include(<micro-os-plus/config.h>)
40#pragma message \
41 "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"
42#include <micro-os-plus/config.h>
43#endif // __has_include(<micro-os-plus/project-config.h>)
44
45#if __has_include(<micro-os-plus/micro-test-plus-defines.h>)
46#include <micro-os-plus/micro-test-plus-defines.h>
47#endif // __has_include(<micro-os-plus/micro-test-plus-defines.h>)
48
49#include <micro-os-plus/diag/trace.h>
50
54
55// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
56#include <cinttypes>
57
58// ----------------------------------------------------------------------------
59
60#if defined(__GNUC__)
61#pragma GCC diagnostic ignored "-Waggregate-return"
62#if defined(__clang__)
63#pragma clang diagnostic ignored "-Wunknown-warning-option"
64#pragma clang diagnostic ignored "-Wc++98-compat"
65#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
66#endif
67#endif
68
69// =============================================================================
70
72{
73 // --------------------------------------------------------------------------
74
82 std::unique_ptr<std::vector<std::string_view>> argvs)
83 : reporter{ std::move (argvs) }
84 {
85#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
86 trace::printf ("%s\n", __PRETTY_FUNCTION__);
87#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
88 }
89
97 {
98#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
99 trace::printf ("%s\n", __PRETTY_FUNCTION__);
100#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
101 }
102
103 // --------------------------------------------------------------------------
104
105 constexpr size_t indent_size = 4;
106
116 {
117 buffer_.append (m.level * indent_size, ' ');
118 return *this;
119 }
120
121 // --------------------------------------------------------------------------
122
132 void
134 {
135#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
136 trace::printf ("%s\n", __PRETTY_FUNCTION__);
137#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
138
139#if defined(__GNUC__)
140#pragma GCC diagnostic push
141#if defined(__clang__)
142#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
143#endif
144#endif
145
147 {
148 printf ("\n");
149 }
150
151 write_info_ ();
152
153 const char* message = "TAP version 14";
154 if (output_file_ != nullptr)
155 {
156 fprintf (output_file_, "%s\n", message);
157 }
158
160 {
161 printf ("%s\n", message);
162
163 flush ();
164 }
165
166 add_empty_line_ = false;
167
168#if defined(__GNUC__)
169#pragma GCC diagnostic pop
170#endif
171 }
172
182 void
184 {
185#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
186 trace::printf ("%s\n", __PRETTY_FUNCTION__);
187#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
188
189#if defined(__GNUC__)
190#pragma GCC diagnostic push
191#if defined(__clang__)
192#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
193#endif
194#endif
195
196 size_t total_suites_count = runner.total_suites_count ();
197
198 uint32_t milliseconds = 0;
199 uint32_t microseconds = 0;
200 if (runner.timings ().has_timestamps ())
201 {
202 runner.timings ().compute_elapsed_time (milliseconds, microseconds);
203 }
204
205 char message_summary[32];
206 snprintf (message_summary, sizeof (message_summary), "1..%zu",
207 total_suites_count);
208
209 char message_totals[160];
210 snprintf (message_totals, sizeof (message_totals),
211 "total: %zu check%s passed, %zu failed, in %zu test "
212 "case%s, %zu test suite%s",
214 runner.totals ().successful_checks () == 1 ? "" : "s",
217 runner.totals ().executed_subtests () == 1 ? "" : "s",
218 total_suites_count, total_suites_count == 1 ? "" : "s");
219
220 char message_time[120] = "";
221 if (milliseconds > 0 || microseconds > 0)
222 {
223 snprintf (message_time, sizeof (message_time),
224 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
225 microseconds);
226 }
227
228 if (output_file_ != nullptr)
229 {
230 fprintf (output_file_, "%s\n# { %s%s }\n", message_summary,
231 message_totals, message_time);
232 }
233
235 {
236 if (add_empty_line_)
237 {
238 printf ("\n");
239 }
240
242 {
243 printf ("%s\n", message_summary);
244 }
245 else
246 {
247 // With quiet verbosity, there are no ok/not ok lines, so the TAP
248 // plan should look like a skipped test.
249 printf ("1..0\n");
250 }
251
252 printf ("# { %s%s }\n", message_totals, message_time);
253
254 flush ();
255 }
256
257#if defined(__GNUC__)
258#pragma GCC diagnostic pop
259#endif
260 }
261
262 // --------------------------------------------------------------------------
263
272 void
274 {
275#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
276#pragma GCC diagnostic push
277#if defined(__clang__)
278#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
279#endif
280 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
281#pragma GCC diagnostic pop
282#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
283
284#if defined(__GNUC__)
285#pragma GCC diagnostic push
286#if defined(__clang__)
287#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
288#endif
289#endif
290
291 char message_subtest[120];
292 snprintf (message_subtest, sizeof (message_subtest), "# Subtest: %s",
293 suite.name ());
294
295 if (output_file_ != nullptr)
296 {
297 fprintf (output_file_, "%s\n", message_subtest);
298 }
299
301 {
302 if (add_empty_line_)
303 {
304 printf ("\n");
305 }
306
307 printf ("%s\n", message_subtest);
308
309 flush ();
310
311 add_empty_line_ = true;
312 }
313
314#if defined(__GNUC__)
315#pragma GCC diagnostic pop
316#endif
317 }
318
328 void
330 {
331#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
332#pragma GCC diagnostic push
333#if defined(__clang__)
334#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
335#endif
336 trace::printf (
337 "%s '%s' +%zu -%zu in xc%zu, xs%zu | cs%zu\n", __PRETTY_FUNCTION__,
342
343#pragma GCC diagnostic pop
344#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
345
346#if defined(__GNUC__)
347#pragma GCC diagnostic push
348#if defined(__clang__)
349#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
350#endif
351#endif
352
353 uint32_t milliseconds = 0;
354 uint32_t microseconds = 0;
355 if (suite.timings ().has_timestamps ())
356 {
357 suite.timings ().compute_elapsed_time (milliseconds, microseconds);
358 }
359
360 std::string indent (indent_size, ' ');
361
362 char message_summary[40];
363 snprintf (message_summary, sizeof (message_summary), "%s1..%zu",
365 char message_totals[120];
366 if (suite.totals ().was_successful ()) [[likely]]
367 {
368 snprintf (message_totals, sizeof (message_totals),
369 "ok %zu - %s # { passed, %zu check%s in %zu "
370 "test case%s",
371 suite.own_index (), suite.name (),
373 suite.totals ().successful_checks () == 1 ? "" : "s",
375 suite.totals ().executed_subtests () == 1 ? "" : "s");
376 }
377 else
378 {
379 snprintf (message_totals, sizeof (message_totals),
380 "not ok %zu - %s # { FAILED, %zu check%s "
381 "passed, %zu failed, in %zu test case%s",
382 suite.own_index (), suite.name (),
384 suite.totals ().successful_checks () == 1 ? "" : "s",
387 suite.totals ().executed_subtests () == 1 ? "" : "s");
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 if (output_file_ != nullptr)
399 {
401
402 fprintf (output_file_, "%s\n%s%s }\n", message_summary, message_totals,
403 message_time);
404 }
405
406 // At this point, the buffer may contain output from the test case, which
407 // should be displayed.
409 {
411 {
412 printf ("\n");
413 }
414
415 if (suite.totals ().was_successful ()) [[likely]]
416 {
417 // Successful test suite.
418
420 {
421 // With verbosity, show full TAP output accumulated in the
422 // buffer.
424 }
425
426 printf ("%s\n%s%s }\n", message_summary, message_totals,
427 message_time);
428 }
429 else
430 {
431 // Failed test suite.
432
433 // Show full TAP output accumulated in the buffer for failed suite
434 // cases, as it may contain useful information about the failure.
436
437 printf ("%s\n%s%s }\n", message_summary, message_totals,
438 message_time);
439 }
440
441 flush ();
442 }
443
444 buffer_.clear ();
445
446 add_empty_line_ = true;
447
448#if defined(__GNUC__)
449#pragma GCC diagnostic pop
450#endif
451 }
452
453 // --------------------------------------------------------------------------
454
463 void
465 {
466#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
467#pragma GCC diagnostic push
468#if defined(__clang__)
469#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
470#endif
471 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
472#pragma GCC diagnostic pop
473#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
474
475#if defined(__GNUC__)
476#pragma GCC diagnostic push
477#if defined(__clang__)
478#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
479#endif
480#endif
481
482 if (!buffer_.empty ())
483 {
484 // Each suite should start with an empty buffer.
486 flush ();
487 abort ();
488 }
489
490 std::string indent (indent_size * subtest.nesting_depth (), ' ');
491
492 char message_subtest[120];
493 snprintf (message_subtest, sizeof (message_subtest), "%s# Subtest: %s",
494 indent.c_str (), subtest.name ());
495
496 if (output_file_ != nullptr)
497 {
498 fprintf (output_file_, "%s\n", message_subtest);
499 }
500
502 {
503 if (add_empty_line_)
504 {
505 printf ("\n");
506 }
507
508 printf ("%s\n", message_subtest);
509
510 flush ();
511
512 add_empty_line_ = false;
513 }
514
515#if defined(__GNUC__)
516#pragma GCC diagnostic pop
517#endif
518 }
519
529 void
531 {
532#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
533#pragma GCC diagnostic push
534#if defined(__clang__)
535#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
536#endif
537 trace::printf ("%s '%s' i%zu +%zu -%zu in xc%zu, xs%zu | cs%zu\n",
538 __PRETTY_FUNCTION__, subtest.name (),
545#pragma GCC diagnostic pop
546#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
547
548#if defined(__GNUC__)
549#pragma GCC diagnostic push
550#if defined(__clang__)
551#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
552#endif
553#endif
554
555 std::string indent (indent_size * subtest.nesting_depth (), ' ');
556 std::string indent2 (indent_size * (subtest.nesting_depth () + 1), ' ');
557
558 char message_summary[40];
559 snprintf (message_summary, sizeof (message_summary), "%s1..%zu",
560 indent2.c_str (),
563
564 char message_totals[120];
565 if (subtest.totals ().was_successful ()) [[likely]]
566 {
567 snprintf (message_totals, sizeof (message_totals),
568 "%sok %zu - %s # { passed, %zu check%s }", indent.c_str (),
571 subtest.totals ().successful_checks () == 1 ? "" : "s");
572 }
573 else
574 {
575 snprintf (message_totals, sizeof (message_totals),
576 "%snot ok %zu - %s # { FAILED, %zu check%s "
577 "passed, %zu failed }",
578 indent.c_str (), subtest.own_index (), subtest.name (),
580 subtest.totals ().successful_checks () == 1 ? "" : "s",
582 }
583
584 if (output_file_ != nullptr)
585 {
587
588 fprintf (output_file_, "%s\n%s\n", message_summary, message_totals);
589 }
590
591 // At this point, the buffer may contain output from the subtest, which
592 // should be displayed.
594 {
595 if (add_empty_line_)
596 {
597 printf ("\n");
598 }
599
600 if (subtest.totals ().was_successful ()) [[likely]]
601 {
602 // Successful subtest.
604 {
605 // With verbosity, show full TAP output accumulated in the
606 // buffer.
608
609 printf ("%s\n", message_summary);
610 }
611 else
612 {
613 // Without verbosity, show only the summary line
614 // and count only subtests, not checks, as the TAP output is
615 // not shown.
616 printf ("%s1..%zu\n", indent2.c_str (),
618 }
619
620 printf ("%s\n", message_totals);
621 }
622 else
623 {
624 // Failed subtest.
625
626 // Show full TAP output accumulated in the buffer for failed
627 // subtests, as it may contain useful information about the
628 // failure.
630
631 printf ("%s\n%s\n", message_summary, message_totals);
632 }
633
634 flush ();
635 }
636
637 buffer_.clear ();
638
639 add_empty_line_ = true;
640
641#if defined(__GNUC__)
642#pragma GCC diagnostic pop
643#endif
644 }
645
646 // --------------------------------------------------------------------------
647
654 const char*
656 {
657 return "# ";
658 }
659
672 void
674 {
675 size_t level = subtest.nesting_depth ();
676 *this << indent (level + 1) << "ok "
677 << static_cast<int> (subtest.current_subtest_index ()) << " - ";
678 if (!message.empty ())
679 {
680 *this << message.c_str ();
681 }
682 }
683
692 void
694 {
695 *this << endl;
696
697 flush ();
698 }
699
709 void
711 std::string& message, const bool has_expression,
712 [[maybe_unused]] const reflection::source_location& location,
714 {
715 size_t level = subtest.nesting_depth ();
716 *this << indent (level + 1) << "not ok "
717 << static_cast<int> (subtest.current_subtest_index ());
718
719 if (!message.empty ())
720 {
721 *this << " - " << message.c_str ();
722 }
723 *this << endl;
724
725 // https://testanything.org/tap-version-14-specification.html
726 // 2-space indentation for YAML diagnostics.
727 *this << indent (level + 1) << " ---";
728 if (has_expression)
729 {
730 *this << endl;
731 *this << indent (level + 1) << " condition: ";
732 }
733 }
734
741 void
743 const reflection::source_location& location, bool abort,
745 {
746 size_t level = subtest.nesting_depth ();
747 if (abort)
748 {
749 *this << " aborted...";
750 }
751 *this << endl;
752
753 // https://testanything.org/tap-version-14-specification.html
754 // 2-space indentation for YAML diagnostics.
755
756 *this << indent (level + 1) << " at:" << endl;
757 *this << indent (level + 1)
758 << " filename: " << reflection::short_name (location.file_name ())
759 << endl;
760 *this << indent (level + 1) << " line: " << location.line () << endl;
761
762 *this << indent (level + 1) << " ..." << endl;
763
764 flush ();
765 }
766
767 // --------------------------------------------------------------------------
768} // namespace micro_os_plus::micro_test_plus
769
770// ----------------------------------------------------------------------------
size_t current_subtest_index() const noexcept
Returns the index of the most recently created child subtest.
size_t own_index() const noexcept
Returns the positional index of this object within its parent.
size_t children_subtests_count(void) const noexcept
Returns the number of direct child subtests owned by this node.
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.
size_t executed_checks() const noexcept
Returns the total number of checks executed.
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.
void begin_session(runner &runner) override
Mark the beginning of a test session.
virtual void begin_subtest(subtest &subtest) override
Mark the beginning of a subtest.
reporter_tap & operator<<(detail::indent_t m)
Output operator for the indent_t manipulator.
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.
virtual void end_subtest(subtest &subtest) override
Mark the end of a subtest.
virtual void end_suite(suite &suite) override
Mark the end of a test suite.
void output_fail_suffix_(const reflection::source_location &location, bool abort, subtest &subtest) override
Outputs the suffix for a failing condition.
void end_session(runner &runner) override
Mark the end of a test session.
reporter_tap(std::unique_ptr< std::vector< std::string_view > > argvs)
Constructor for the reporter_tap class.
virtual const char * get_comment_prefix(void) override
Returns the TAP comment prefix string "# ".
void output_pass_prefix_(std::string &message, subtest &subtest) override
Outputs the prefix for a passing condition.
void output_pass_suffix_(subtest &subtest) override
Outputs the suffix for a passing condition.
virtual void begin_suite(suite &suite) override
Mark the beginning of a test suite.
~reporter_tap() override
Destructor for the reporter_tap 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
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++ TAP 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.