micro-test-plus 4.1.1
µ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#if defined(MICRO_OS_PLUS_TRACE)
50#include <micro-os-plus/diag/trace.h>
51#endif // MICRO_OS_PLUS_TRACE
52
56
57// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
58#include <cinttypes>
59
60// ----------------------------------------------------------------------------
61
62#if defined(__GNUC__)
63#pragma GCC diagnostic ignored "-Waggregate-return"
64#if defined(__clang__)
65#pragma clang diagnostic ignored "-Wunknown-warning-option"
66#pragma clang diagnostic ignored "-Wc++98-compat"
67#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
68#endif
69#endif
70
71// =============================================================================
72
74{
75 // --------------------------------------------------------------------------
76
84 std::unique_ptr<std::vector<std::string_view>> argvs)
85 : reporter{ std::move (argvs) }
86 {
87#if defined(MICRO_OS_PLUS_TRACE) \
88 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
89 trace::printf ("%s\n", __PRETTY_FUNCTION__);
90#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
91 }
92
100 {
101#if defined(MICRO_OS_PLUS_TRACE) \
102 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
103 trace::printf ("%s\n", __PRETTY_FUNCTION__);
104#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
105 }
106
107 // --------------------------------------------------------------------------
108
109 constexpr size_t indent_size = 4;
110
120 {
121 buffer_.append (m.level * indent_size, ' ');
122 return *this;
123 }
124
125 // --------------------------------------------------------------------------
126
136 void
138 {
139#if defined(MICRO_OS_PLUS_TRACE) \
140 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
141 trace::printf ("%s\n", __PRETTY_FUNCTION__);
142#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
143
144#if defined(__GNUC__)
145#pragma GCC diagnostic push
146#if defined(__clang__)
147#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
148#endif
149#endif
150
152 {
153 printf ("\n");
154 }
155
156 write_info_ ();
157
158 const char* message = "TAP version 14";
159 if (output_file_ != nullptr)
160 {
161 fprintf (output_file_, "%s\n", message);
162 }
163
165 {
166 printf ("%s\n", message);
167
168 flush ();
169 }
170
171 add_empty_line_ = false;
172
173#if defined(__GNUC__)
174#pragma GCC diagnostic pop
175#endif
176 }
177
187 void
189 {
190#if defined(MICRO_OS_PLUS_TRACE) \
191 && 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
195#if defined(__GNUC__)
196#pragma GCC diagnostic push
197#if defined(__clang__)
198#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
199#endif
200#endif
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, microseconds);
209 }
210
211 char message_summary[32];
212 snprintf (message_summary, sizeof (message_summary), "1..%zu",
213 total_suites_count);
214
215 char message_totals[160];
216 snprintf (message_totals, sizeof (message_totals),
217 "total: %zu check%s passed, %zu failed, in %zu test "
218 "case%s, %zu test suite%s",
220 runner.totals ().successful_checks () == 1 ? "" : "s",
223 runner.totals ().executed_subtests () == 1 ? "" : "s",
224 total_suites_count, total_suites_count == 1 ? "" : "s");
225
226 char message_time[120] = "";
227 if (milliseconds > 0 || microseconds > 0)
228 {
229 snprintf (message_time, sizeof (message_time),
230 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
231 microseconds);
232 }
233
234 if (output_file_ != nullptr)
235 {
236 fprintf (output_file_, "%s\n# { %s%s }\n", message_summary,
237 message_totals, message_time);
238 }
239
241 {
242 if (add_empty_line_)
243 {
244 printf ("\n");
245 }
246
248 {
249 printf ("%s\n", message_summary);
250 }
251 else
252 {
253 // With quiet verbosity, there are no ok/not ok lines, so the TAP
254 // plan should look like a skipped test.
255 printf ("1..0\n");
256 }
257
258 printf ("# { %s%s }\n", message_totals, message_time);
259
260 flush ();
261 }
262
263#if defined(__GNUC__)
264#pragma GCC diagnostic pop
265#endif
266 }
267
268 // --------------------------------------------------------------------------
269
278 void
280 {
281#if defined(MICRO_OS_PLUS_TRACE) \
282 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
283#pragma GCC diagnostic push
284#if defined(__clang__)
285#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
286#endif
287 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
288#pragma GCC diagnostic pop
289#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
290
291#if defined(__GNUC__)
292#pragma GCC diagnostic push
293#if defined(__clang__)
294#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
295#endif
296#endif
297
298 char message_subtest[120];
299 snprintf (message_subtest, sizeof (message_subtest), "# Subtest: %s",
300 suite.name ());
301
302 if (output_file_ != nullptr)
303 {
304 fprintf (output_file_, "%s\n", message_subtest);
305 }
306
308 {
309 if (add_empty_line_)
310 {
311 printf ("\n");
312 }
313
314 printf ("%s\n", message_subtest);
315
316 flush ();
317
318 add_empty_line_ = true;
319 }
320
321#if defined(__GNUC__)
322#pragma GCC diagnostic pop
323#endif
324 }
325
335 void
337 {
338#if defined(MICRO_OS_PLUS_TRACE) \
339 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
340#pragma GCC diagnostic push
341#if defined(__clang__)
342#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
343#endif
344 trace::printf (
345 "%s '%s' +%zu -%zu in xc%zu, xs%zu | cs%zu\n", __PRETTY_FUNCTION__,
350
351#pragma GCC diagnostic pop
352#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
353
354#if defined(__GNUC__)
355#pragma GCC diagnostic push
356#if defined(__clang__)
357#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
358#endif
359#endif
360
361 uint32_t milliseconds = 0;
362 uint32_t microseconds = 0;
363 if (suite.timings ().has_timestamps ())
364 {
365 suite.timings ().compute_elapsed_time (milliseconds, microseconds);
366 }
367
368 std::string indent (indent_size, ' ');
369
370 char message_summary[40];
371 snprintf (message_summary, sizeof (message_summary), "%s1..%zu",
373 char message_totals[120];
374 if (suite.totals ().was_successful ()) [[likely]]
375 {
376 snprintf (message_totals, sizeof (message_totals),
377 "ok %zu - %s # { passed, %zu check%s in %zu "
378 "test case%s",
379 suite.own_index (), suite.name (),
381 suite.totals ().successful_checks () == 1 ? "" : "s",
383 suite.totals ().executed_subtests () == 1 ? "" : "s");
384 }
385 else
386 {
387 snprintf (message_totals, sizeof (message_totals),
388 "not ok %zu - %s # { FAILED, %zu check%s "
389 "passed, %zu failed, in %zu test case%s",
390 suite.own_index (), suite.name (),
392 suite.totals ().successful_checks () == 1 ? "" : "s",
395 suite.totals ().executed_subtests () == 1 ? "" : "s");
396 }
397
398 char message_time[120] = "";
399 if (milliseconds > 0 || microseconds > 0)
400 {
401 snprintf (message_time, sizeof (message_time),
402 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
403 microseconds);
404 }
405
406 if (output_file_ != nullptr)
407 {
409
410 fprintf (output_file_, "%s\n%s%s }\n", message_summary, message_totals,
411 message_time);
412 }
413
414 // At this point, the buffer may contain output from the test case, which
415 // should be displayed.
417 {
419 {
420 printf ("\n");
421 }
422
423 if (suite.totals ().was_successful ()) [[likely]]
424 {
425 // Successful test suite.
426
428 {
429 // With verbosity, show full TAP output accumulated in the
430 // buffer.
432 }
433
434 printf ("%s\n%s%s }\n", message_summary, message_totals,
435 message_time);
436 }
437 else
438 {
439 // Failed test suite.
440
441 // Show full TAP output accumulated in the buffer for failed suite
442 // cases, as it may contain useful information about the failure.
444
445 printf ("%s\n%s%s }\n", message_summary, message_totals,
446 message_time);
447 }
448
449 flush ();
450 }
451
452 buffer_.clear ();
453
454 add_empty_line_ = true;
455
456#if defined(__GNUC__)
457#pragma GCC diagnostic pop
458#endif
459 }
460
461 // --------------------------------------------------------------------------
462
471 void
473 {
474#if defined(MICRO_OS_PLUS_TRACE) \
475 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
476#pragma GCC diagnostic push
477#if defined(__clang__)
478#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
479#endif
480 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
481#pragma GCC diagnostic pop
482#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
483
484#if defined(__GNUC__)
485#pragma GCC diagnostic push
486#if defined(__clang__)
487#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
488#endif
489#endif
490
491 if (!buffer_.empty ())
492 {
493 // Each suite should start with an empty buffer.
495 flush ();
496 abort ();
497 }
498
499 std::string indent (indent_size * subtest.nesting_depth (), ' ');
500
501 char message_subtest[120];
502 snprintf (message_subtest, sizeof (message_subtest), "%s# Subtest: %s",
503 indent.c_str (), subtest.name ());
504
505 if (output_file_ != nullptr)
506 {
507 fprintf (output_file_, "%s\n", message_subtest);
508 }
509
511 {
512 if (add_empty_line_)
513 {
514 printf ("\n");
515 }
516
517 printf ("%s\n", message_subtest);
518
519 flush ();
520
521 add_empty_line_ = false;
522 }
523
524#if defined(__GNUC__)
525#pragma GCC diagnostic pop
526#endif
527 }
528
538 void
540 {
541#if defined(MICRO_OS_PLUS_TRACE) \
542 && defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
543#pragma GCC diagnostic push
544#if defined(__clang__)
545#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
546#endif
547 trace::printf ("%s '%s' i%zu +%zu -%zu in xc%zu, xs%zu | cs%zu\n",
548 __PRETTY_FUNCTION__, subtest.name (),
555#pragma GCC diagnostic pop
556#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
557
558#if defined(__GNUC__)
559#pragma GCC diagnostic push
560#if defined(__clang__)
561#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
562#endif
563#endif
564
565 std::string indent (indent_size * subtest.nesting_depth (), ' ');
566 std::string indent2 (indent_size * (subtest.nesting_depth () + 1), ' ');
567
568 char message_summary[40];
569 snprintf (message_summary, sizeof (message_summary), "%s1..%zu",
570 indent2.c_str (),
573
574 char message_totals[120];
575 if (subtest.totals ().was_successful ()) [[likely]]
576 {
577 snprintf (message_totals, sizeof (message_totals),
578 "%sok %zu - %s # { passed, %zu check%s }", indent.c_str (),
581 subtest.totals ().successful_checks () == 1 ? "" : "s");
582 }
583 else
584 {
585 snprintf (message_totals, sizeof (message_totals),
586 "%snot ok %zu - %s # { FAILED, %zu check%s "
587 "passed, %zu failed }",
588 indent.c_str (), subtest.own_index (), subtest.name (),
590 subtest.totals ().successful_checks () == 1 ? "" : "s",
592 }
593
594 if (output_file_ != nullptr)
595 {
597
598 fprintf (output_file_, "%s\n%s\n", message_summary, message_totals);
599 }
600
601 // At this point, the buffer may contain output from the subtest, which
602 // should be displayed.
604 {
605 if (add_empty_line_)
606 {
607 printf ("\n");
608 }
609
610 if (subtest.totals ().was_successful ()) [[likely]]
611 {
612 // Successful subtest.
614 {
615 // With verbosity, show full TAP output accumulated in the
616 // buffer.
618
619 printf ("%s\n", message_summary);
620 }
621 else
622 {
623 // Without verbosity, show only the summary line
624 // and count only subtests, not checks, as the TAP output is
625 // not shown.
626 printf ("%s1..%zu\n", indent2.c_str (),
628 }
629
630 printf ("%s\n", message_totals);
631 }
632 else
633 {
634 // Failed subtest.
635
636 // Show full TAP output accumulated in the buffer for failed
637 // subtests, as it may contain useful information about the
638 // failure.
640
641 printf ("%s\n%s\n", message_summary, message_totals);
642 }
643
644 flush ();
645 }
646
647 buffer_.clear ();
648
649 add_empty_line_ = true;
650
651#if defined(__GNUC__)
652#pragma GCC diagnostic pop
653#endif
654 }
655
656 // --------------------------------------------------------------------------
657
664 const char*
666 {
667 return "# ";
668 }
669
682 void
684 {
685 size_t level = subtest.nesting_depth ();
686 *this << indent (level + 1) << "ok "
687 << static_cast<int> (subtest.current_subtest_index ()) << " - ";
688 if (!message.empty ())
689 {
690 *this << message.c_str ();
691 }
692 }
693
702 void
704 {
705 *this << endl;
706
707 flush ();
708 }
709
719 void
721 std::string& message, const bool has_expression,
722 [[maybe_unused]] const reflection::source_location& location,
724 {
725 size_t level = subtest.nesting_depth ();
726 *this << indent (level + 1) << "not ok "
727 << static_cast<int> (subtest.current_subtest_index ());
728
729 if (!message.empty ())
730 {
731 *this << " - " << message.c_str ();
732 }
733 *this << endl;
734
735 // https://testanything.org/tap-version-14-specification.html
736 // 2-space indentation for YAML diagnostics.
737 *this << indent (level + 1) << " ---";
738 if (has_expression)
739 {
740 *this << endl;
741 *this << indent (level + 1) << " condition: ";
742 }
743 }
744
751 void
753 const reflection::source_location& location, bool abort,
755 {
756 size_t level = subtest.nesting_depth ();
757 if (abort)
758 {
759 *this << " aborted...";
760 }
761 *this << endl;
762
763 // https://testanything.org/tap-version-14-specification.html
764 // 2-space indentation for YAML diagnostics.
765
766 *this << indent (level + 1) << " at:" << endl;
767 *this << indent (level + 1)
768 << " filename: " << reflection::short_name (location.file_name ())
769 << endl;
770 *this << indent (level + 1) << " line: " << location.line () << endl;
771
772 *this << indent (level + 1) << " ..." << endl;
773
774 flush ();
775 }
776
777 // --------------------------------------------------------------------------
778} // namespace micro_os_plus::micro_test_plus
779
780// ----------------------------------------------------------------------------
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: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
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++ 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.