micro-test-plus 5.0.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
40
41#if __has_include("micro-os-plus/diag/trace.h")
42#include "micro-os-plus/diag/trace.h"
43#endif // __has_include("micro-os-plus/diag/trace.h")
44
45// For the PRIu32 macro used in snprintf() formatting of uint32_t values.
46#include <cinttypes>
47
48// ----------------------------------------------------------------------------
49
50#if defined(__GNUC__)
51#pragma GCC diagnostic ignored "-Waggregate-return"
52#if defined(__clang__)
53#pragma clang diagnostic ignored "-Wunknown-warning-option"
54#pragma clang diagnostic ignored "-Wc++98-compat"
55#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
56#pragma clang diagnostic ignored "-Wunsafe-buffer-usage-in-libc-call"
57#endif // defined(__clang__)
58#endif // defined(__GNUC__)
59
60// =============================================================================
61
62#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
63
64// ----------------------------------------------------------------------------
65
67{
68 // --------------------------------------------------------------------------
69
77 std::unique_ptr<std::vector<std::string_view>> argvs)
78 : reporter{ std::move (argvs) }
79 {
80#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
81 trace::printf ("%s\n", __PRETTY_FUNCTION__);
82#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
83 }
84
92 {
93#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED)
94 trace::printf ("%s\n", __PRETTY_FUNCTION__);
95#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_CONSTRUCTORS_ENABLED
96 }
97
98 // --------------------------------------------------------------------------
99
100 constexpr size_t indent_size = 4;
101
111 {
112 buffer_.append (m.level * indent_size, ' ');
113 return *this;
114 }
115
116 // --------------------------------------------------------------------------
117
127 void
129 {
130#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
131 trace::printf ("%s\n", __PRETTY_FUNCTION__);
132#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
133
135 {
136 printf ("\n");
137 }
138
139 write_info_ ();
140
141 const char* message = "TAP version 14";
142 if (output_file_ != nullptr)
143 {
144 fprintf (output_file_, "%s\n", message);
145 }
146
148 {
149 printf ("%s\n", message);
150
151 flush ();
152 }
153
154 add_empty_line_ = false;
155 }
156
166 void
168 {
169#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
170 trace::printf ("%s\n", __PRETTY_FUNCTION__);
171#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
172
173 size_t total_suites_count = runner.total_suites_count ();
174
175 uint32_t milliseconds = 0;
176 uint32_t microseconds = 0;
177 if (runner.timings ().has_timestamps ())
178 {
179 runner.timings ().compute_elapsed_time (milliseconds, microseconds);
180 }
181
182 char message_summary[32];
183 snprintf (message_summary, sizeof (message_summary), "1..%zu",
184 total_suites_count);
185
186 char message_totals[160];
187 snprintf (message_totals, sizeof (message_totals),
188 "total: %zu check%s passed, %zu failed, in %zu test "
189 "case%s, %zu test suite%s",
191 runner.totals ().successful_checks () == 1 ? "" : "s",
194 runner.totals ().executed_subtests () == 1 ? "" : "s",
195 total_suites_count, total_suites_count == 1 ? "" : "s");
196
197 char message_time[120] = "";
198 if (milliseconds > 0 || microseconds > 0)
199 {
200 snprintf (message_time, sizeof (message_time),
201 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
202 microseconds);
203 }
204
205 if (output_file_ != nullptr)
206 {
207 fprintf (output_file_, "%s\n# { %s%s }\n", message_summary,
208 message_totals, message_time);
209 }
210
212 {
213 if (add_empty_line_)
214 {
215 printf ("\n");
216 }
217
219 {
220 printf ("%s\n", message_summary);
221 }
222 else
223 {
224 // With quiet verbosity, there are no ok/not ok lines, so the TAP
225 // plan should look like a skipped test.
226 printf ("1..0\n");
227 }
228
229 printf ("# { %s%s }\n", message_totals, message_time);
230
231 flush ();
232 }
233 }
234
235 // --------------------------------------------------------------------------
236
245 void
247 {
248#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
249 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, suite.name ());
250#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
251
252 char message_subtest[120];
253 snprintf (message_subtest, sizeof (message_subtest), "# Subtest: %s",
254 suite.name ());
255
256 if (output_file_ != nullptr)
257 {
258 fprintf (output_file_, "%s\n", message_subtest);
259 }
260
262 {
263 if (add_empty_line_)
264 {
265 printf ("\n");
266 }
267
268 printf ("%s\n", message_subtest);
269
270 flush ();
271
272 add_empty_line_ = true;
273 }
274 }
275
285 void
287 {
288#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
289 trace::printf (
290 "%s '%s' +%zu -%zu in xc%zu, xs%zu | cs%zu\n", __PRETTY_FUNCTION__,
295#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
296
297 uint32_t milliseconds = 0;
298 uint32_t microseconds = 0;
299 if (suite.timings ().has_timestamps ())
300 {
301 suite.timings ().compute_elapsed_time (milliseconds, microseconds);
302 }
303
304 std::string indent (indent_size, ' ');
305
306 char message_summary[40];
307 snprintf (message_summary, sizeof (message_summary), "%s1..%zu",
309 char message_totals[120];
310 if (suite.totals ().was_successful ()) [[likely]]
311 {
312 snprintf (message_totals, sizeof (message_totals),
313 "ok %zu - %s # { passed, %zu check%s in %zu "
314 "test case%s",
315 suite.own_index (), suite.name (),
317 suite.totals ().successful_checks () == 1 ? "" : "s",
319 suite.totals ().executed_subtests () == 1 ? "" : "s");
320 }
321 else
322 {
323 snprintf (message_totals, sizeof (message_totals),
324 "not ok %zu - %s # { FAILED, %zu check%s "
325 "passed, %zu failed, in %zu test case%s",
326 suite.own_index (), suite.name (),
328 suite.totals ().successful_checks () == 1 ? "" : "s",
331 suite.totals ().executed_subtests () == 1 ? "" : "s");
332 }
333
334 char message_time[120] = "";
335 if (milliseconds > 0 || microseconds > 0)
336 {
337 snprintf (message_time, sizeof (message_time),
338 ", time: %" PRIu32 ".%03" PRIu32 " ms", milliseconds,
339 microseconds);
340 }
341
342 if (output_file_ != nullptr)
343 {
345
346 fprintf (output_file_, "%s\n%s%s }\n", message_summary, message_totals,
347 message_time);
348 }
349
350 // At this point, the buffer may contain output from the test case, which
351 // should be displayed.
353 {
355 {
356 printf ("\n");
357 }
358
359 if (suite.totals ().was_successful ()) [[likely]]
360 {
361 // Successful test suite.
362
364 {
365 // With verbosity, show full TAP output accumulated in the
366 // buffer.
368 }
369
370 printf ("%s\n%s%s }\n", message_summary, message_totals,
371 message_time);
372 }
373 else
374 {
375 // Failed test suite.
376
377 // Show full TAP output accumulated in the buffer for failed suite
378 // cases, as it may contain useful information about the failure.
380
381 printf ("%s\n%s%s }\n", message_summary, message_totals,
382 message_time);
383 }
384
385 flush ();
386 }
387
388 buffer_.clear ();
389
390 add_empty_line_ = true;
391 }
392
393 // --------------------------------------------------------------------------
394
403 void
405 {
406#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
407 trace::printf ("%s '%s'\n", __PRETTY_FUNCTION__, subtest.name ());
408#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
409
410 if (!buffer_.empty ())
411 {
412 // Each suite should start with an empty buffer.
414 flush ();
415 abort ();
416 }
417
418 std::string indent (indent_size * subtest.nesting_depth (), ' ');
419
420 char message_subtest[120];
421 snprintf (message_subtest, sizeof (message_subtest), "%s# Subtest: %s",
422 indent.c_str (), subtest.name ());
423
424 if (output_file_ != nullptr)
425 {
426 fprintf (output_file_, "%s\n", message_subtest);
427 }
428
430 {
431 if (add_empty_line_)
432 {
433 printf ("\n");
434 }
435
436 printf ("%s\n", message_subtest);
437
438 flush ();
439
440 add_empty_line_ = false;
441 }
442 }
443
453 void
455 {
456#if defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED)
457 trace::printf ("%s '%s' i%zu +%zu -%zu in xc%zu, xs%zu | cs%zu\n",
458 __PRETTY_FUNCTION__, subtest.name (),
465#endif // MICRO_OS_PLUS_MICRO_TEST_PLUS_TRACE_ENABLED
466
467 std::string indent (indent_size * subtest.nesting_depth (), ' ');
468 std::string indent2 (indent_size * (subtest.nesting_depth () + 1), ' ');
469
470 char message_summary[40];
471 snprintf (message_summary, sizeof (message_summary), "%s1..%zu",
472 indent2.c_str (),
475
476 char message_totals[120];
477 if (subtest.totals ().was_successful ()) [[likely]]
478 {
479 snprintf (message_totals, sizeof (message_totals),
480 "%sok %zu - %s # { passed, %zu check%s }", indent.c_str (),
483 subtest.totals ().successful_checks () == 1 ? "" : "s");
484 }
485 else
486 {
487 snprintf (message_totals, sizeof (message_totals),
488 "%snot ok %zu - %s # { FAILED, %zu check%s "
489 "passed, %zu failed }",
490 indent.c_str (), subtest.own_index (), subtest.name (),
492 subtest.totals ().successful_checks () == 1 ? "" : "s",
494 }
495
496 if (output_file_ != nullptr)
497 {
499
500 fprintf (output_file_, "%s\n%s\n", message_summary, message_totals);
501 }
502
503 // At this point, the buffer may contain output from the subtest, which
504 // should be displayed.
506 {
507 if (add_empty_line_)
508 {
509 printf ("\n");
510 }
511
512 if (subtest.totals ().was_successful ()) [[likely]]
513 {
514 // Successful subtest.
516 {
517 // With verbosity, show full TAP output accumulated in the
518 // buffer.
520
521 printf ("%s\n", message_summary);
522 }
523 else
524 {
525 // Without verbosity, show only the summary line
526 // and count only subtests, not checks, as the TAP output is
527 // not shown.
528 printf ("%s1..%zu\n", indent2.c_str (),
530 }
531
532 printf ("%s\n", message_totals);
533 }
534 else
535 {
536 // Failed subtest.
537
538 // Show full TAP output accumulated in the buffer for failed
539 // subtests, as it may contain useful information about the
540 // failure.
542
543 printf ("%s\n%s\n", message_summary, message_totals);
544 }
545
546 flush ();
547 }
548
549 buffer_.clear ();
550
551 add_empty_line_ = true;
552 }
553
554 // --------------------------------------------------------------------------
555
562 const char*
564 {
565 return "# ";
566 }
567
580 void
582 {
583 size_t level = subtest.nesting_depth ();
584 *this << indent (level + 1) << "ok "
585 << static_cast<int> (subtest.current_subtest_index ()) << " - ";
586 if (!message.empty ())
587 {
588 *this << message.c_str ();
589 }
590 }
591
600 void
602 {
603 *this << endl;
604
605 flush ();
606 }
607
617 void
619 std::string& message, const bool has_expression,
620 [[maybe_unused]] const reflection::source_location& location,
622 {
623 size_t level = subtest.nesting_depth ();
624 *this << indent (level + 1) << "not ok "
625 << static_cast<int> (subtest.current_subtest_index ());
626
627 if (!message.empty ())
628 {
629 *this << " - " << message.c_str ();
630 }
631 *this << endl;
632
633 // https://testanything.org/tap-version-14-specification.html
634 // 2-space indentation for YAML diagnostics.
635 *this << indent (level + 1) << " ---";
636 if (has_expression)
637 {
638 *this << endl;
639 *this << indent (level + 1) << " condition: ";
640 }
641 }
642
649 void
651 const reflection::source_location& location, bool abort,
653 {
654 size_t level = subtest.nesting_depth ();
655 if (abort)
656 {
657 *this << " aborted...";
658 }
659 *this << endl;
660
661 // https://testanything.org/tap-version-14-specification.html
662 // 2-space indentation for YAML diagnostics.
663
664 *this << indent (level + 1) << " at:" << endl;
665 *this << indent (level + 1)
666 << " filename: " << reflection::short_name (location.file_name ())
667 << endl;
668 *this << indent (level + 1) << " line: " << location.line () << endl;
669
670 *this << indent (level + 1) << " ..." << endl;
671
672 flush ();
673 }
674
675 // --------------------------------------------------------------------------
676} // namespace micro_os_plus::micro_test_plus
677
678// ----------------------------------------------------------------------------
679
680#endif // defined(MICRO_OS_PLUS_MICRO_TEST_PLUS_ENABLED)
681
682// ----------------------------------------------------------------------------
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: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.
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: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
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++ TAP 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.