/ externals / catch / src / catch2 / reporters / catch_reporter_helpers.cpp
catch_reporter_helpers.cpp
  1  
  2  //              Copyright Catch2 Authors
  3  // Distributed under the Boost Software License, Version 1.0.
  4  //   (See accompanying file LICENSE.txt or copy at
  5  //        https://www.boost.org/LICENSE_1_0.txt)
  6  
  7  // SPDX-License-Identifier: BSL-1.0
  8  
  9  #include <catch2/reporters/catch_reporter_helpers.hpp>
 10  #include <catch2/interfaces/catch_interfaces_config.hpp>
 11  #include <catch2/internal/catch_console_width.hpp>
 12  #include <catch2/internal/catch_errno_guard.hpp>
 13  #include <catch2/internal/catch_textflow.hpp>
 14  #include <catch2/internal/catch_reusable_string_stream.hpp>
 15  #include <catch2/internal/catch_string_manip.hpp>
 16  #include <catch2/internal/catch_console_colour.hpp>
 17  #include <catch2/catch_tostring.hpp>
 18  #include <catch2/catch_test_case_info.hpp>
 19  
 20  #include <algorithm>
 21  #include <cfloat>
 22  #include <cstdio>
 23  #include <ostream>
 24  #include <iomanip>
 25  
 26  namespace Catch {
 27  
 28      namespace {
 29          void listTestNamesOnly(std::ostream& out,
 30                                 std::vector<TestCaseHandle> const& tests) {
 31              for (auto const& test : tests) {
 32                  auto const& testCaseInfo = test.getTestCaseInfo();
 33  
 34                  if (startsWith(testCaseInfo.name, '#')) {
 35                      out << '"' << testCaseInfo.name << '"';
 36                  } else {
 37                      out << testCaseInfo.name;
 38                  }
 39  
 40                  out << '\n';
 41              }
 42              out << std::flush;
 43          }
 44      } // end unnamed namespace
 45  
 46  
 47      // Because formatting using c++ streams is stateful, drop down to C is
 48      // required Alternatively we could use stringstream, but its performance
 49      // is... not good.
 50      std::string getFormattedDuration( double duration ) {
 51          // Max exponent + 1 is required to represent the whole part
 52          // + 1 for decimal point
 53          // + 3 for the 3 decimal places
 54          // + 1 for null terminator
 55          const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;
 56          char buffer[maxDoubleSize];
 57  
 58          // Save previous errno, to prevent sprintf from overwriting it
 59          ErrnoGuard guard;
 60  #ifdef _MSC_VER
 61          size_t printedLength = static_cast<size_t>(
 62              sprintf_s( buffer, "%.3f", duration ) );
 63  #else
 64          size_t printedLength = static_cast<size_t>(
 65              std::snprintf( buffer, maxDoubleSize, "%.3f", duration ) );
 66  #endif
 67          return std::string( buffer, printedLength );
 68      }
 69  
 70      bool shouldShowDuration( IConfig const& config, double duration ) {
 71          if ( config.showDurations() == ShowDurations::Always ) {
 72              return true;
 73          }
 74          if ( config.showDurations() == ShowDurations::Never ) {
 75              return false;
 76          }
 77          const double min = config.minDuration();
 78          return min >= 0 && duration >= min;
 79      }
 80  
 81      std::string serializeFilters( std::vector<std::string> const& filters ) {
 82          // We add a ' ' separator between each filter
 83          size_t serialized_size = filters.size() - 1;
 84          for (auto const& filter : filters) {
 85              serialized_size += filter.size();
 86          }
 87  
 88          std::string serialized;
 89          serialized.reserve(serialized_size);
 90          bool first = true;
 91  
 92          for (auto const& filter : filters) {
 93              if (!first) {
 94                  serialized.push_back(' ');
 95              }
 96              first = false;
 97              serialized.append(filter);
 98          }
 99  
100          return serialized;
101      }
102  
103      std::ostream& operator<<( std::ostream& out, lineOfChars value ) {
104          for ( size_t idx = 0; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1; ++idx ) {
105              out.put( value.c );
106          }
107          return out;
108      }
109  
110      void
111      defaultListReporters( std::ostream& out,
112                            std::vector<ReporterDescription> const& descriptions,
113                            Verbosity verbosity ) {
114          out << "Available reporters:\n";
115          const auto maxNameLen =
116              std::max_element( descriptions.begin(),
117                                descriptions.end(),
118                                []( ReporterDescription const& lhs,
119                                    ReporterDescription const& rhs ) {
120                                    return lhs.name.size() < rhs.name.size();
121                                } )
122                  ->name.size();
123  
124          for ( auto const& desc : descriptions ) {
125              if ( verbosity == Verbosity::Quiet ) {
126                  out << TextFlow::Column( desc.name )
127                             .indent( 2 )
128                             .width( 5 + maxNameLen )
129                      << '\n';
130              } else {
131                  out << TextFlow::Column( desc.name + ':' )
132                                 .indent( 2 )
133                                 .width( 5 + maxNameLen ) +
134                             TextFlow::Column( desc.description )
135                                 .initialIndent( 0 )
136                                 .indent( 2 )
137                                 .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 )
138                      << '\n';
139              }
140          }
141          out << '\n' << std::flush;
142      }
143  
144      void defaultListListeners( std::ostream& out,
145                                 std::vector<ListenerDescription> const& descriptions ) {
146          out << "Registered listeners:\n";
147  
148          if(descriptions.empty()) {
149              return;
150          }
151  
152          const auto maxNameLen =
153              std::max_element( descriptions.begin(),
154                                descriptions.end(),
155                                []( ListenerDescription const& lhs,
156                                    ListenerDescription const& rhs ) {
157                                    return lhs.name.size() < rhs.name.size();
158                                } )
159                  ->name.size();
160  
161          for ( auto const& desc : descriptions ) {
162              out << TextFlow::Column( static_cast<std::string>( desc.name ) +
163                                       ':' )
164                             .indent( 2 )
165                             .width( maxNameLen + 5 ) +
166                         TextFlow::Column( desc.description )
167                             .initialIndent( 0 )
168                             .indent( 2 )
169                             .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 )
170                  << '\n';
171          }
172  
173          out << '\n' << std::flush;
174      }
175  
176      void defaultListTags( std::ostream& out,
177                            std::vector<TagInfo> const& tags,
178                            bool isFiltered ) {
179          if ( isFiltered ) {
180              out << "Tags for matching test cases:\n";
181          } else {
182              out << "All available tags:\n";
183          }
184  
185          for ( auto const& tagCount : tags ) {
186              ReusableStringStream rss;
187              rss << "  " << std::setw( 2 ) << tagCount.count << "  ";
188              auto str = rss.str();
189              auto wrapper = TextFlow::Column( tagCount.all() )
190                                 .initialIndent( 0 )
191                                 .indent( str.size() )
192                                 .width( CATCH_CONFIG_CONSOLE_WIDTH - 10 );
193              out << str << wrapper << '\n';
194          }
195          out << pluralise(tags.size(), "tag"_sr) << "\n\n" << std::flush;
196      }
197  
198      void defaultListTests(std::ostream& out, ColourImpl* streamColour, std::vector<TestCaseHandle> const& tests, bool isFiltered, Verbosity verbosity) {
199          // We special case this to provide the equivalent of old
200          // `--list-test-names-only`, which could then be used by the
201          // `--input-file` option.
202          if (verbosity == Verbosity::Quiet) {
203              listTestNamesOnly(out, tests);
204              return;
205          }
206  
207          if (isFiltered) {
208              out << "Matching test cases:\n";
209          } else {
210              out << "All available test cases:\n";
211          }
212  
213          for (auto const& test : tests) {
214              auto const& testCaseInfo = test.getTestCaseInfo();
215              Colour::Code colour = testCaseInfo.isHidden()
216                  ? Colour::SecondaryText
217                  : Colour::None;
218              auto colourGuard = streamColour->guardColour( colour ).engage( out );
219  
220              out << TextFlow::Column(testCaseInfo.name).indent(2) << '\n';
221              if (verbosity >= Verbosity::High) {
222                  out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\n';
223              }
224              if (!testCaseInfo.tags.empty() &&
225                  verbosity > Verbosity::Quiet) {
226                  out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n';
227              }
228          }
229  
230          if (isFiltered) {
231              out << pluralise(tests.size(), "matching test case"_sr);
232          } else {
233              out << pluralise(tests.size(), "test case"_sr);
234          }
235          out << "\n\n" << std::flush;
236      }
237  
238      namespace {
239          class SummaryColumn {
240          public:
241              SummaryColumn( std::string suffix, Colour::Code colour ):
242                  m_suffix( CATCH_MOVE( suffix ) ), m_colour( colour ) {}
243  
244              SummaryColumn&& addRow( std::uint64_t count ) && {
245                  std::string row = std::to_string(count);
246                  auto const new_width = std::max( m_width, row.size() );
247                  if ( new_width > m_width ) {
248                      for ( auto& oldRow : m_rows ) {
249                          oldRow.insert( 0, new_width - m_width, ' ' );
250                      }
251                  } else {
252                      row.insert( 0, m_width - row.size(), ' ' );
253                  }
254                  m_width = new_width;
255                  m_rows.push_back( row );
256                  return std::move( *this );
257              }
258  
259              std::string const& getSuffix() const { return m_suffix; }
260              Colour::Code getColour() const { return m_colour; }
261              std::string const& getRow( std::size_t index ) const {
262                  return m_rows[index];
263              }
264  
265          private:
266              std::string m_suffix;
267              Colour::Code m_colour;
268              std::size_t m_width = 0;
269              std::vector<std::string> m_rows;
270          };
271  
272          void printSummaryRow( std::ostream& stream,
273                                ColourImpl& colour,
274                                StringRef label,
275                                std::vector<SummaryColumn> const& cols,
276                                std::size_t row ) {
277              for ( auto const& col : cols ) {
278                  auto const& value = col.getRow( row );
279                  auto const& suffix = col.getSuffix();
280                  if ( suffix.empty() ) {
281                      stream << label << ": ";
282                      if ( value != "0" ) {
283                          stream << value;
284                      } else {
285                          stream << colour.guardColour( Colour::Warning )
286                                 << "- none -";
287                      }
288                  } else if ( value != "0" ) {
289                      stream << colour.guardColour( Colour::LightGrey ) << " | "
290                             << colour.guardColour( col.getColour() ) << value
291                             << ' ' << suffix;
292                  }
293              }
294              stream << '\n';
295          }
296      } // namespace
297  
298      void printTestRunTotals( std::ostream& stream,
299                               ColourImpl& streamColour,
300                               Totals const& totals ) {
301          if ( totals.testCases.total() == 0 ) {
302              stream << streamColour.guardColour( Colour::Warning )
303                     << "No tests ran\n";
304              return;
305          }
306  
307          if ( totals.assertions.total() > 0 && totals.testCases.allPassed() ) {
308              stream << streamColour.guardColour( Colour::ResultSuccess )
309                     << "All tests passed";
310              stream << " ("
311                     << pluralise( totals.assertions.passed, "assertion"_sr )
312                     << " in "
313                     << pluralise( totals.testCases.passed, "test case"_sr )
314                     << ')' << '\n';
315              return;
316          }
317  
318          std::vector<SummaryColumn> columns;
319          // Don't include "skipped assertions" in total count
320          const auto totalAssertionCount =
321              totals.assertions.total() - totals.assertions.skipped;
322          columns.push_back( SummaryColumn( "", Colour::None )
323                                 .addRow( totals.testCases.total() )
324                                 .addRow( totalAssertionCount ) );
325          columns.push_back( SummaryColumn( "passed", Colour::Success )
326                                 .addRow( totals.testCases.passed )
327                                 .addRow( totals.assertions.passed ) );
328          columns.push_back( SummaryColumn( "failed", Colour::ResultError )
329                                 .addRow( totals.testCases.failed )
330                                 .addRow( totals.assertions.failed ) );
331          columns.push_back( SummaryColumn( "skipped", Colour::Skip )
332                                 .addRow( totals.testCases.skipped )
333                                 // Don't print "skipped assertions"
334                                 .addRow( 0 ) );
335          columns.push_back(
336              SummaryColumn( "failed as expected", Colour::ResultExpectedFailure )
337                  .addRow( totals.testCases.failedButOk )
338                  .addRow( totals.assertions.failedButOk ) );
339          printSummaryRow( stream, streamColour, "test cases"_sr, columns, 0 );
340          printSummaryRow( stream, streamColour, "assertions"_sr, columns, 1 );
341      }
342  
343  } // namespace Catch