/ externals / catch / src / catch2 / reporters / catch_reporter_console.cpp
catch_reporter_console.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  #include <catch2/reporters/catch_reporter_console.hpp>
  9  
 10  #include <catch2/interfaces/catch_interfaces_config.hpp>
 11  #include <catch2/catch_test_spec.hpp>
 12  #include <catch2/internal/catch_console_colour.hpp>
 13  #include <catch2/internal/catch_string_manip.hpp>
 14  #include <catch2/catch_version.hpp>
 15  #include <catch2/internal/catch_textflow.hpp>
 16  #include <catch2/internal/catch_reusable_string_stream.hpp>
 17  #include <catch2/internal/catch_stringref.hpp>
 18  #include <catch2/catch_test_case_info.hpp>
 19  #include <catch2/internal/catch_console_width.hpp>
 20  #include <catch2/reporters/catch_reporter_helpers.hpp>
 21  #include <catch2/internal/catch_move_and_forward.hpp>
 22  #include <catch2/catch_get_random_seed.hpp>
 23  
 24  #include <cstdio>
 25  
 26  #if defined(_MSC_VER)
 27  #pragma warning(push)
 28  #pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
 29   // Note that 4062 (not all labels are handled and default is missing) is enabled
 30  #endif
 31  
 32  #if defined(__clang__)
 33  #  pragma clang diagnostic push
 34  // For simplicity, benchmarking-only helpers are always enabled
 35  #  pragma clang diagnostic ignored "-Wunused-function"
 36  #endif
 37  
 38  
 39  
 40  namespace Catch {
 41  
 42  namespace {
 43  
 44  // Formatter impl for ConsoleReporter
 45  class ConsoleAssertionPrinter {
 46  public:
 47      ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete;
 48      ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete;
 49      ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages)
 50          : stream(_stream),
 51          stats(_stats),
 52          result(_stats.assertionResult),
 53          colour(Colour::None),
 54          messages(_stats.infoMessages),
 55          colourImpl(colourImpl_),
 56          printInfoMessages(_printInfoMessages) {
 57          switch (result.getResultType()) {
 58          case ResultWas::Ok:
 59              colour = Colour::Success;
 60              passOrFail = "PASSED"_sr;
 61              //if( result.hasMessage() )
 62              if (messages.size() == 1)
 63                  messageLabel = "with message"_sr;
 64              if (messages.size() > 1)
 65                  messageLabel = "with messages"_sr;
 66              break;
 67          case ResultWas::ExpressionFailed:
 68              if (result.isOk()) {
 69                  colour = Colour::Success;
 70                  passOrFail = "FAILED - but was ok"_sr;
 71              } else {
 72                  colour = Colour::Error;
 73                  passOrFail = "FAILED"_sr;
 74              }
 75              if (messages.size() == 1)
 76                  messageLabel = "with message"_sr;
 77              if (messages.size() > 1)
 78                  messageLabel = "with messages"_sr;
 79              break;
 80          case ResultWas::ThrewException:
 81              colour = Colour::Error;
 82              passOrFail = "FAILED"_sr;
 83              // todo switch
 84              switch (messages.size()) { case 0:
 85                  messageLabel = "due to unexpected exception with "_sr;
 86                  break;
 87              case 1:
 88                  messageLabel = "due to unexpected exception with message"_sr;
 89                  break;
 90              default:
 91                  messageLabel = "due to unexpected exception with messages"_sr;
 92                  break;
 93              }
 94              break;
 95          case ResultWas::FatalErrorCondition:
 96              colour = Colour::Error;
 97              passOrFail = "FAILED"_sr;
 98              messageLabel = "due to a fatal error condition"_sr;
 99              break;
100          case ResultWas::DidntThrowException:
101              colour = Colour::Error;
102              passOrFail = "FAILED"_sr;
103              messageLabel = "because no exception was thrown where one was expected"_sr;
104              break;
105          case ResultWas::Info:
106              messageLabel = "info"_sr;
107              break;
108          case ResultWas::Warning:
109              messageLabel = "warning"_sr;
110              break;
111          case ResultWas::ExplicitFailure:
112              passOrFail = "FAILED"_sr;
113              colour = Colour::Error;
114              if (messages.size() == 1)
115                  messageLabel = "explicitly with message"_sr;
116              if (messages.size() > 1)
117                  messageLabel = "explicitly with messages"_sr;
118              break;
119          case ResultWas::ExplicitSkip:
120              colour = Colour::Skip;
121              passOrFail = "SKIPPED"_sr;
122              if (messages.size() == 1)
123                  messageLabel = "explicitly with message"_sr;
124              if (messages.size() > 1)
125                  messageLabel = "explicitly with messages"_sr;
126              break;
127              // These cases are here to prevent compiler warnings
128          case ResultWas::Unknown:
129          case ResultWas::FailureBit:
130          case ResultWas::Exception:
131              passOrFail = "** internal error **"_sr;
132              colour = Colour::Error;
133              break;
134          }
135      }
136  
137      void print() const {
138          printSourceInfo();
139          if (stats.totals.assertions.total() > 0) {
140              printResultType();
141              printOriginalExpression();
142              printReconstructedExpression();
143          } else {
144              stream << '\n';
145          }
146          printMessage();
147      }
148  
149  private:
150      void printResultType() const {
151          if (!passOrFail.empty()) {
152              stream << colourImpl->guardColour(colour) << passOrFail << ":\n";
153          }
154      }
155      void printOriginalExpression() const {
156          if (result.hasExpression()) {
157              stream << colourImpl->guardColour( Colour::OriginalExpression )
158                     << "  " << result.getExpressionInMacro() << '\n';
159          }
160      }
161      void printReconstructedExpression() const {
162          if (result.hasExpandedExpression()) {
163              stream << "with expansion:\n";
164              stream << colourImpl->guardColour( Colour::ReconstructedExpression )
165                     << TextFlow::Column( result.getExpandedExpression() )
166                            .indent( 2 )
167                     << '\n';
168          }
169      }
170      void printMessage() const {
171          if (!messageLabel.empty())
172              stream << messageLabel << ':' << '\n';
173          for (auto const& msg : messages) {
174              // If this assertion is a warning ignore any INFO messages
175              if (printInfoMessages || msg.type != ResultWas::Info)
176                  stream << TextFlow::Column(msg.message).indent(2) << '\n';
177          }
178      }
179      void printSourceInfo() const {
180          stream << colourImpl->guardColour( Colour::FileName )
181                 << result.getSourceInfo() << ": ";
182      }
183  
184      std::ostream& stream;
185      AssertionStats const& stats;
186      AssertionResult const& result;
187      Colour::Code colour;
188      StringRef passOrFail;
189      StringRef messageLabel;
190      std::vector<MessageInfo> const& messages;
191      ColourImpl* colourImpl;
192      bool printInfoMessages;
193  };
194  
195  std::size_t makeRatio( std::uint64_t number, std::uint64_t total ) {
196      const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;
197      return (ratio == 0 && number > 0) ? 1 : static_cast<std::size_t>(ratio);
198  }
199  
200  std::size_t&
201  findMax( std::size_t& i, std::size_t& j, std::size_t& k, std::size_t& l ) {
202      if (i > j && i > k && i > l)
203          return i;
204      else if (j > k && j > l)
205          return j;
206      else if (k > l)
207          return k;
208      else
209          return l;
210  }
211  
212  enum class Justification { Left, Right };
213  
214  struct ColumnInfo {
215      std::string name;
216      std::size_t width;
217      Justification justification;
218  };
219  struct ColumnBreak {};
220  struct RowBreak {};
221  struct OutputFlush {};
222  
223  class Duration {
224      enum class Unit {
225          Auto,
226          Nanoseconds,
227          Microseconds,
228          Milliseconds,
229          Seconds,
230          Minutes
231      };
232      static const uint64_t s_nanosecondsInAMicrosecond = 1000;
233      static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;
234      static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;
235      static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;
236  
237      double m_inNanoseconds;
238      Unit m_units;
239  
240  public:
241      explicit Duration(double inNanoseconds, Unit units = Unit::Auto)
242          : m_inNanoseconds(inNanoseconds),
243          m_units(units) {
244          if (m_units == Unit::Auto) {
245              if (m_inNanoseconds < s_nanosecondsInAMicrosecond)
246                  m_units = Unit::Nanoseconds;
247              else if (m_inNanoseconds < s_nanosecondsInAMillisecond)
248                  m_units = Unit::Microseconds;
249              else if (m_inNanoseconds < s_nanosecondsInASecond)
250                  m_units = Unit::Milliseconds;
251              else if (m_inNanoseconds < s_nanosecondsInAMinute)
252                  m_units = Unit::Seconds;
253              else
254                  m_units = Unit::Minutes;
255          }
256  
257      }
258  
259      auto value() const -> double {
260          switch (m_units) {
261          case Unit::Microseconds:
262              return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);
263          case Unit::Milliseconds:
264              return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);
265          case Unit::Seconds:
266              return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);
267          case Unit::Minutes:
268              return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);
269          default:
270              return m_inNanoseconds;
271          }
272      }
273      StringRef unitsAsString() const {
274          switch (m_units) {
275          case Unit::Nanoseconds:
276              return "ns"_sr;
277          case Unit::Microseconds:
278              return "us"_sr;
279          case Unit::Milliseconds:
280              return "ms"_sr;
281          case Unit::Seconds:
282              return "s"_sr;
283          case Unit::Minutes:
284              return "m"_sr;
285          default:
286              return "** internal error **"_sr;
287          }
288  
289      }
290      friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {
291          return os << duration.value() << ' ' << duration.unitsAsString();
292      }
293  };
294  } // end anon namespace
295  
296  class TablePrinter {
297      std::ostream& m_os;
298      std::vector<ColumnInfo> m_columnInfos;
299      ReusableStringStream m_oss;
300      int m_currentColumn = -1;
301      bool m_isOpen = false;
302  
303  public:
304      TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos )
305      :   m_os( os ),
306          m_columnInfos( CATCH_MOVE( columnInfos ) ) {}
307  
308      auto columnInfos() const -> std::vector<ColumnInfo> const& {
309          return m_columnInfos;
310      }
311  
312      void open() {
313          if (!m_isOpen) {
314              m_isOpen = true;
315              *this << RowBreak();
316  
317  			TextFlow::Columns headerCols;
318  			auto spacer = TextFlow::Spacer(2);
319  			for (auto const& info : m_columnInfos) {
320                  assert(info.width > 2);
321  				headerCols += TextFlow::Column(info.name).width(info.width - 2);
322  				headerCols += spacer;
323  			}
324  			m_os << headerCols << '\n';
325  
326              m_os << lineOfChars('-') << '\n';
327          }
328      }
329      void close() {
330          if (m_isOpen) {
331              *this << RowBreak();
332              m_os << '\n' << std::flush;
333              m_isOpen = false;
334          }
335      }
336  
337      template<typename T>
338      friend TablePrinter& operator<< (TablePrinter& tp, T const& value) {
339          tp.m_oss << value;
340          return tp;
341      }
342  
343      friend TablePrinter& operator<< (TablePrinter& tp, ColumnBreak) {
344          auto colStr = tp.m_oss.str();
345          const auto strSize = colStr.size();
346          tp.m_oss.str("");
347          tp.open();
348          if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {
349              tp.m_currentColumn = -1;
350              tp.m_os << '\n';
351          }
352          tp.m_currentColumn++;
353  
354          auto colInfo = tp.m_columnInfos[tp.m_currentColumn];
355          auto padding = (strSize + 1 < colInfo.width)
356              ? std::string(colInfo.width - (strSize + 1), ' ')
357              : std::string();
358          if (colInfo.justification == Justification::Left)
359              tp.m_os << colStr << padding << ' ';
360          else
361              tp.m_os << padding << colStr << ' ';
362          return tp;
363      }
364  
365      friend TablePrinter& operator<< (TablePrinter& tp, RowBreak) {
366          if (tp.m_currentColumn > 0) {
367              tp.m_os << '\n';
368              tp.m_currentColumn = -1;
369          }
370          return tp;
371      }
372  
373      friend TablePrinter& operator<<(TablePrinter& tp, OutputFlush) {
374          tp.m_os << std::flush;
375          return tp;
376      }
377  };
378  
379  ConsoleReporter::ConsoleReporter(ReporterConfig&& config):
380      StreamingReporterBase( CATCH_MOVE( config ) ),
381      m_tablePrinter(Detail::make_unique<TablePrinter>(m_stream,
382          [&config]() -> std::vector<ColumnInfo> {
383          if (config.fullConfig()->benchmarkNoAnalysis())
384          {
385              return{
386                  { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left },
387                  { "     samples", 14, Justification::Right },
388                  { "  iterations", 14, Justification::Right },
389                  { "        mean", 14, Justification::Right }
390              };
391          }
392          else
393          {
394              return{
395                  { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left },
396                  { "samples      mean       std dev", 14, Justification::Right },
397                  { "iterations   low mean   low std dev", 14, Justification::Right },
398                  { "est run time high mean  high std dev", 14, Justification::Right }
399              };
400          }
401      }())) {}
402  ConsoleReporter::~ConsoleReporter() = default;
403  
404  std::string ConsoleReporter::getDescription() {
405      return "Reports test results as plain lines of text";
406  }
407  
408  void ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) {
409      m_stream << "No test cases matched '" << unmatchedSpec << "'\n";
410  }
411  
412  void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) {
413      m_stream << "Invalid Filter: " << arg << '\n';
414  }
415  
416  void ConsoleReporter::assertionStarting(AssertionInfo const&) {}
417  
418  void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {
419      AssertionResult const& result = _assertionStats.assertionResult;
420  
421      bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
422  
423      // Drop out if result was successful but we're not printing them.
424      // TODO: Make configurable whether skips should be printed
425      if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip)
426          return;
427  
428      lazyPrint();
429  
430      ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults);
431      printer.print();
432      m_stream << '\n' << std::flush;
433  }
434  
435  void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) {
436      m_tablePrinter->close();
437      m_headerPrinted = false;
438      StreamingReporterBase::sectionStarting(_sectionInfo);
439  }
440  void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {
441      m_tablePrinter->close();
442      if (_sectionStats.missingAssertions) {
443          lazyPrint();
444          auto guard =
445              m_colour->guardColour( Colour::ResultError ).engage( m_stream );
446          if (m_sectionStack.size() > 1)
447              m_stream << "\nNo assertions in section";
448          else
449              m_stream << "\nNo assertions in test case";
450          m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" << std::flush;
451      }
452      double dur = _sectionStats.durationInSeconds;
453      if (shouldShowDuration(*m_config, dur)) {
454          m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush;
455      }
456      if (m_headerPrinted) {
457          m_headerPrinted = false;
458      }
459      StreamingReporterBase::sectionEnded(_sectionStats);
460  }
461  
462  void ConsoleReporter::benchmarkPreparing( StringRef name ) {
463  	lazyPrintWithoutClosingBenchmarkTable();
464  
465  	auto nameCol = TextFlow::Column( static_cast<std::string>( name ) )
466                         .width( m_tablePrinter->columnInfos()[0].width - 2 );
467  
468  	bool firstLine = true;
469  	for (auto line : nameCol) {
470  		if (!firstLine)
471  			(*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();
472  		else
473  			firstLine = false;
474  
475  		(*m_tablePrinter) << line << ColumnBreak();
476  	}
477  }
478  
479  void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {
480      (*m_tablePrinter) << info.samples << ColumnBreak()
481          << info.iterations << ColumnBreak();
482      if ( !m_config->benchmarkNoAnalysis() ) {
483          ( *m_tablePrinter )
484              << Duration( info.estimatedDuration ) << ColumnBreak();
485      }
486      ( *m_tablePrinter ) << OutputFlush{};
487  }
488  void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) {
489      if (m_config->benchmarkNoAnalysis())
490      {
491          (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak();
492      }
493      else
494      {
495          (*m_tablePrinter) << ColumnBreak()
496              << Duration(stats.mean.point.count()) << ColumnBreak()
497              << Duration(stats.mean.lower_bound.count()) << ColumnBreak()
498              << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak()
499              << Duration(stats.standardDeviation.point.count()) << ColumnBreak()
500              << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak()
501              << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak();
502      }
503  }
504  
505  void ConsoleReporter::benchmarkFailed( StringRef error ) {
506      auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream );
507      (*m_tablePrinter)
508          << "Benchmark failed (" << error << ')'
509          << ColumnBreak() << RowBreak();
510  }
511  
512  void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {
513      m_tablePrinter->close();
514      StreamingReporterBase::testCaseEnded(_testCaseStats);
515      m_headerPrinted = false;
516  }
517  void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {
518      printTotalsDivider(_testRunStats.totals);
519      printTestRunTotals( m_stream, *m_colour, _testRunStats.totals );
520      m_stream << '\n' << std::flush;
521      StreamingReporterBase::testRunEnded(_testRunStats);
522  }
523  void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) {
524      StreamingReporterBase::testRunStarting(_testInfo);
525      if ( m_config->testSpec().hasFilters() ) {
526          m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: "
527                   << m_config->testSpec() << '\n';
528      }
529      m_stream << "Randomness seeded to: " << getSeed() << '\n';
530  }
531  
532  void ConsoleReporter::lazyPrint() {
533  
534      m_tablePrinter->close();
535      lazyPrintWithoutClosingBenchmarkTable();
536  }
537  
538  void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
539  
540      if ( !m_testRunInfoPrinted ) {
541          lazyPrintRunInfo();
542      }
543      if (!m_headerPrinted) {
544          printTestCaseAndSectionHeader();
545          m_headerPrinted = true;
546      }
547  }
548  void ConsoleReporter::lazyPrintRunInfo() {
549      m_stream << '\n'
550               << lineOfChars( '~' ) << '\n'
551               << m_colour->guardColour( Colour::SecondaryText )
552               << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion()
553               << " host application.\n"
554               << "Run with -? for options\n\n";
555  
556      m_testRunInfoPrinted = true;
557  }
558  void ConsoleReporter::printTestCaseAndSectionHeader() {
559      assert(!m_sectionStack.empty());
560      printOpenHeader(currentTestCaseInfo->name);
561  
562      if (m_sectionStack.size() > 1) {
563          auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream );
564  
565          auto
566              it = m_sectionStack.begin() + 1, // Skip first section (test case)
567              itEnd = m_sectionStack.end();
568          for (; it != itEnd; ++it)
569              printHeaderString(it->name, 2);
570      }
571  
572      SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
573  
574  
575      m_stream << lineOfChars( '-' ) << '\n'
576               << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n'
577               << lineOfChars( '.' ) << "\n\n"
578               << std::flush;
579  }
580  
581  void ConsoleReporter::printClosedHeader(std::string const& _name) {
582      printOpenHeader(_name);
583      m_stream << lineOfChars('.') << '\n';
584  }
585  void ConsoleReporter::printOpenHeader(std::string const& _name) {
586      m_stream << lineOfChars('-') << '\n';
587      {
588          auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream );
589          printHeaderString(_name);
590      }
591  }
592  
593  void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {
594      // We want to get a bit fancy with line breaking here, so that subsequent
595      // lines start after ":" if one is present, e.g.
596      // ```
597      // blablabla: Fancy
598      //            linebreaking
599      // ```
600      // but we also want to avoid problems with overly long indentation causing
601      // the text to take up too many lines, e.g.
602      // ```
603      // blablabla: F
604      //            a
605      //            n
606      //            c
607      //            y
608      //            .
609      //            .
610      //            .
611      // ```
612      // So we limit the prefix indentation check to first quarter of the possible
613      // width
614      std::size_t idx = _string.find( ": " );
615      if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) {
616          idx += 2;
617      } else {
618          idx = 0;
619      }
620      m_stream << TextFlow::Column( _string )
621                    .indent( indent + idx )
622                    .initialIndent( indent )
623             << '\n';
624  }
625  
626  void ConsoleReporter::printTotalsDivider(Totals const& totals) {
627      if (totals.testCases.total() > 0) {
628          std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());
629          std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());
630          std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());
631          std::size_t skippedRatio = makeRatio(totals.testCases.skipped, totals.testCases.total());
632          while (failedRatio + failedButOkRatio + passedRatio + skippedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)
633              findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)++;
634          while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
635              findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)--;
636  
637          m_stream << m_colour->guardColour( Colour::Error )
638                   << std::string( failedRatio, '=' )
639                   << m_colour->guardColour( Colour::ResultExpectedFailure )
640                   << std::string( failedButOkRatio, '=' );
641          if ( totals.testCases.allPassed() ) {
642              m_stream << m_colour->guardColour( Colour::ResultSuccess )
643                       << std::string( passedRatio, '=' );
644          } else {
645              m_stream << m_colour->guardColour( Colour::Success )
646                       << std::string( passedRatio, '=' );
647          }
648          m_stream << m_colour->guardColour( Colour::Skip )
649                   << std::string( skippedRatio, '=' );
650      } else {
651          m_stream << m_colour->guardColour( Colour::Warning )
652                   << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' );
653      }
654      m_stream << '\n';
655  }
656  
657  } // end namespace Catch
658  
659  #if defined(_MSC_VER)
660  #pragma warning(pop)
661  #endif
662  
663  #if defined(__clang__)
664  #  pragma clang diagnostic pop
665  #endif