• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Created by Phil on 5/12/2012.
3  *  Copyright 2012 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 
9 #include "catch_reporter_console.h"
10 
11 #include "../internal/catch_reporter_registrars.hpp"
12 #include "internal/catch_console_colour.h"
13 #include "../internal/catch_version.h"
14 #include "../internal/catch_text.h"
15 #include "../internal/catch_stringref.h"
16 
17 #include <cfloat>
18 #include <cstdio>
19 
20 #if defined(_MSC_VER)
21 #pragma warning(push)
22 #pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
23  // Note that 4062 (not all labels are handled
24  // and default is missing) is enabled
25 #endif
26 
27 
28 namespace Catch {
29 
30 namespace {
31 
32 // Formatter impl for ConsoleReporter
33 class ConsoleAssertionPrinter {
34 public:
35     ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete;
36     ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete;
ConsoleAssertionPrinter(std::ostream & _stream,AssertionStats const & _stats,bool _printInfoMessages)37     ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)
38         : stream(_stream),
39         stats(_stats),
40         result(_stats.assertionResult),
41         colour(Colour::None),
42         message(result.getMessage()),
43         messages(_stats.infoMessages),
44         printInfoMessages(_printInfoMessages) {
45         switch (result.getResultType()) {
46         case ResultWas::Ok:
47             colour = Colour::Success;
48             passOrFail = "PASSED";
49             //if( result.hasMessage() )
50             if (_stats.infoMessages.size() == 1)
51                 messageLabel = "with message";
52             if (_stats.infoMessages.size() > 1)
53                 messageLabel = "with messages";
54             break;
55         case ResultWas::ExpressionFailed:
56             if (result.isOk()) {
57                 colour = Colour::Success;
58                 passOrFail = "FAILED - but was ok";
59             } else {
60                 colour = Colour::Error;
61                 passOrFail = "FAILED";
62             }
63             if (_stats.infoMessages.size() == 1)
64                 messageLabel = "with message";
65             if (_stats.infoMessages.size() > 1)
66                 messageLabel = "with messages";
67             break;
68         case ResultWas::ThrewException:
69             colour = Colour::Error;
70             passOrFail = "FAILED";
71             messageLabel = "due to unexpected exception with ";
72             if (_stats.infoMessages.size() == 1)
73                 messageLabel += "message";
74             if (_stats.infoMessages.size() > 1)
75                 messageLabel += "messages";
76             break;
77         case ResultWas::FatalErrorCondition:
78             colour = Colour::Error;
79             passOrFail = "FAILED";
80             messageLabel = "due to a fatal error condition";
81             break;
82         case ResultWas::DidntThrowException:
83             colour = Colour::Error;
84             passOrFail = "FAILED";
85             messageLabel = "because no exception was thrown where one was expected";
86             break;
87         case ResultWas::Info:
88             messageLabel = "info";
89             break;
90         case ResultWas::Warning:
91             messageLabel = "warning";
92             break;
93         case ResultWas::ExplicitFailure:
94             passOrFail = "FAILED";
95             colour = Colour::Error;
96             if (_stats.infoMessages.size() == 1)
97                 messageLabel = "explicitly with message";
98             if (_stats.infoMessages.size() > 1)
99                 messageLabel = "explicitly with messages";
100             break;
101             // These cases are here to prevent compiler warnings
102         case ResultWas::Unknown:
103         case ResultWas::FailureBit:
104         case ResultWas::Exception:
105             passOrFail = "** internal error **";
106             colour = Colour::Error;
107             break;
108         }
109     }
110 
print() const111     void print() const {
112         printSourceInfo();
113         if (stats.totals.assertions.total() > 0) {
114             printResultType();
115             printOriginalExpression();
116             printReconstructedExpression();
117         } else {
118             stream << '\n';
119         }
120         printMessage();
121     }
122 
123 private:
printResultType() const124     void printResultType() const {
125         if (!passOrFail.empty()) {
126             Colour colourGuard(colour);
127             stream << passOrFail << ":\n";
128         }
129     }
printOriginalExpression() const130     void printOriginalExpression() const {
131         if (result.hasExpression()) {
132             Colour colourGuard(Colour::OriginalExpression);
133             stream << "  ";
134             stream << result.getExpressionInMacro();
135             stream << '\n';
136         }
137     }
printReconstructedExpression() const138     void printReconstructedExpression() const {
139         if (result.hasExpandedExpression()) {
140             stream << "with expansion:\n";
141             Colour colourGuard(Colour::ReconstructedExpression);
142             stream << Column(result.getExpandedExpression()).indent(2) << '\n';
143         }
144     }
printMessage() const145     void printMessage() const {
146         if (!messageLabel.empty())
147             stream << messageLabel << ':' << '\n';
148         for (auto const& msg : messages) {
149             // If this assertion is a warning ignore any INFO messages
150             if (printInfoMessages || msg.type != ResultWas::Info)
151                 stream << Column(msg.message).indent(2) << '\n';
152         }
153     }
printSourceInfo() const154     void printSourceInfo() const {
155         Colour colourGuard(Colour::FileName);
156         stream << result.getSourceInfo() << ": ";
157     }
158 
159     std::ostream& stream;
160     AssertionStats const& stats;
161     AssertionResult const& result;
162     Colour::Code colour;
163     std::string passOrFail;
164     std::string messageLabel;
165     std::string message;
166     std::vector<MessageInfo> messages;
167     bool printInfoMessages;
168 };
169 
makeRatio(std::size_t number,std::size_t total)170 std::size_t makeRatio(std::size_t number, std::size_t total) {
171     std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;
172     return (ratio == 0 && number > 0) ? 1 : ratio;
173 }
174 
findMax(std::size_t & i,std::size_t & j,std::size_t & k)175 std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) {
176     if (i > j && i > k)
177         return i;
178     else if (j > k)
179         return j;
180     else
181         return k;
182 }
183 
184 struct ColumnInfo {
185     enum Justification { Left, Right };
186     std::string name;
187     int width;
188     Justification justification;
189 };
190 struct ColumnBreak {};
191 struct RowBreak {};
192 
193 class Duration {
194     enum class Unit {
195         Auto,
196         Nanoseconds,
197         Microseconds,
198         Milliseconds,
199         Seconds,
200         Minutes
201     };
202     static const uint64_t s_nanosecondsInAMicrosecond = 1000;
203     static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;
204     static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;
205     static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;
206 
207     uint64_t m_inNanoseconds;
208     Unit m_units;
209 
210 public:
Duration(uint64_t inNanoseconds,Unit units=Unit::Auto)211     explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto)
212         : m_inNanoseconds(inNanoseconds),
213         m_units(units) {
214         if (m_units == Unit::Auto) {
215             if (m_inNanoseconds < s_nanosecondsInAMicrosecond)
216                 m_units = Unit::Nanoseconds;
217             else if (m_inNanoseconds < s_nanosecondsInAMillisecond)
218                 m_units = Unit::Microseconds;
219             else if (m_inNanoseconds < s_nanosecondsInASecond)
220                 m_units = Unit::Milliseconds;
221             else if (m_inNanoseconds < s_nanosecondsInAMinute)
222                 m_units = Unit::Seconds;
223             else
224                 m_units = Unit::Minutes;
225         }
226 
227     }
228 
value() const229     auto value() const -> double {
230         switch (m_units) {
231         case Unit::Microseconds:
232             return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);
233         case Unit::Milliseconds:
234             return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);
235         case Unit::Seconds:
236             return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);
237         case Unit::Minutes:
238             return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);
239         default:
240             return static_cast<double>(m_inNanoseconds);
241         }
242     }
unitsAsString() const243     auto unitsAsString() const -> std::string {
244         switch (m_units) {
245         case Unit::Nanoseconds:
246             return "ns";
247         case Unit::Microseconds:
248             return "us";
249         case Unit::Milliseconds:
250             return "ms";
251         case Unit::Seconds:
252             return "s";
253         case Unit::Minutes:
254             return "m";
255         default:
256             return "** internal error **";
257         }
258 
259     }
operator <<(std::ostream & os,Duration const & duration)260     friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {
261         return os << duration.value() << " " << duration.unitsAsString();
262     }
263 };
264 } // end anon namespace
265 
266 class TablePrinter {
267     std::ostream& m_os;
268     std::vector<ColumnInfo> m_columnInfos;
269     std::ostringstream m_oss;
270     int m_currentColumn = -1;
271     bool m_isOpen = false;
272 
273 public:
TablePrinter(std::ostream & os,std::vector<ColumnInfo> columnInfos)274     TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos )
275     :   m_os( os ),
276         m_columnInfos( std::move( columnInfos ) ) {}
277 
columnInfos() const278     auto columnInfos() const -> std::vector<ColumnInfo> const& {
279         return m_columnInfos;
280     }
281 
open()282     void open() {
283         if (!m_isOpen) {
284             m_isOpen = true;
285             *this << RowBreak();
286             for (auto const& info : m_columnInfos)
287                 *this << info.name << ColumnBreak();
288             *this << RowBreak();
289             m_os << Catch::getLineOfChars<'-'>() << "\n";
290         }
291     }
close()292     void close() {
293         if (m_isOpen) {
294             *this << RowBreak();
295             m_os << std::endl;
296             m_isOpen = false;
297         }
298     }
299 
300     template<typename T>
operator <<(TablePrinter & tp,T const & value)301     friend TablePrinter& operator << (TablePrinter& tp, T const& value) {
302         tp.m_oss << value;
303         return tp;
304     }
305 
operator <<(TablePrinter & tp,ColumnBreak)306     friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) {
307         auto colStr = tp.m_oss.str();
308         // This takes account of utf8 encodings
309         auto strSize = Catch::StringRef(colStr).numberOfCharacters();
310         tp.m_oss.str("");
311         tp.open();
312         if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {
313             tp.m_currentColumn = -1;
314             tp.m_os << "\n";
315         }
316         tp.m_currentColumn++;
317 
318         auto colInfo = tp.m_columnInfos[tp.m_currentColumn];
319         auto padding = (strSize + 2 < static_cast<std::size_t>(colInfo.width))
320             ? std::string(colInfo.width - (strSize + 2), ' ')
321             : std::string();
322         if (colInfo.justification == ColumnInfo::Left)
323             tp.m_os << colStr << padding << " ";
324         else
325             tp.m_os << padding << colStr << " ";
326         return tp;
327     }
328 
operator <<(TablePrinter & tp,RowBreak)329     friend TablePrinter& operator << (TablePrinter& tp, RowBreak) {
330         if (tp.m_currentColumn > 0) {
331             tp.m_os << "\n";
332             tp.m_currentColumn = -1;
333         }
334         return tp;
335     }
336 };
337 
ConsoleReporter(ReporterConfig const & config)338 ConsoleReporter::ConsoleReporter(ReporterConfig const& config)
339     : StreamingReporterBase(config),
340     m_tablePrinter(new TablePrinter(config.stream(),
341     {
342         { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left },
343         { "iters", 8, ColumnInfo::Right },
344         { "elapsed ns", 14, ColumnInfo::Right },
345         { "average", 14, ColumnInfo::Right }
346     })) {}
347 ConsoleReporter::~ConsoleReporter() = default;
348 
getDescription()349 std::string ConsoleReporter::getDescription() {
350     return "Reports test results as plain lines of text";
351 }
352 
noMatchingTestCases(std::string const & spec)353 void ConsoleReporter::noMatchingTestCases(std::string const& spec) {
354     stream << "No test cases matched '" << spec << '\'' << std::endl;
355 }
356 
assertionStarting(AssertionInfo const &)357 void ConsoleReporter::assertionStarting(AssertionInfo const&) {}
358 
assertionEnded(AssertionStats const & _assertionStats)359 bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {
360     AssertionResult const& result = _assertionStats.assertionResult;
361 
362     bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();
363 
364     // Drop out if result was successful but we're not printing them.
365     if (!includeResults && result.getResultType() != ResultWas::Warning)
366         return false;
367 
368     lazyPrint();
369 
370     ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults);
371     printer.print();
372     stream << std::endl;
373     return true;
374 }
375 
sectionStarting(SectionInfo const & _sectionInfo)376 void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) {
377     m_headerPrinted = false;
378     StreamingReporterBase::sectionStarting(_sectionInfo);
379 }
sectionEnded(SectionStats const & _sectionStats)380 void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {
381     m_tablePrinter->close();
382     if (_sectionStats.missingAssertions) {
383         lazyPrint();
384         Colour colour(Colour::ResultError);
385         if (m_sectionStack.size() > 1)
386             stream << "\nNo assertions in section";
387         else
388             stream << "\nNo assertions in test case";
389         stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
390     }
391     if (m_config->showDurations() == ShowDurations::Always) {
392         stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl;
393     }
394     if (m_headerPrinted) {
395         m_headerPrinted = false;
396     }
397     StreamingReporterBase::sectionEnded(_sectionStats);
398 }
399 
400 
benchmarkStarting(BenchmarkInfo const & info)401 void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {
402     lazyPrintWithoutClosingBenchmarkTable();
403 
404     auto nameCol = Column( info.name ).width( static_cast<std::size_t>( m_tablePrinter->columnInfos()[0].width - 2 ) );
405 
406     bool firstLine = true;
407     for (auto line : nameCol) {
408         if (!firstLine)
409             (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();
410         else
411             firstLine = false;
412 
413         (*m_tablePrinter) << line << ColumnBreak();
414     }
415 }
benchmarkEnded(BenchmarkStats const & stats)416 void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) {
417     Duration average(stats.elapsedTimeInNanoseconds / stats.iterations);
418     (*m_tablePrinter)
419         << stats.iterations << ColumnBreak()
420         << stats.elapsedTimeInNanoseconds << ColumnBreak()
421         << average << ColumnBreak();
422 }
423 
testCaseEnded(TestCaseStats const & _testCaseStats)424 void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {
425     m_tablePrinter->close();
426     StreamingReporterBase::testCaseEnded(_testCaseStats);
427     m_headerPrinted = false;
428 }
testGroupEnded(TestGroupStats const & _testGroupStats)429 void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) {
430     if (currentGroupInfo.used) {
431         printSummaryDivider();
432         stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
433         printTotals(_testGroupStats.totals);
434         stream << '\n' << std::endl;
435     }
436     StreamingReporterBase::testGroupEnded(_testGroupStats);
437 }
testRunEnded(TestRunStats const & _testRunStats)438 void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {
439     printTotalsDivider(_testRunStats.totals);
440     printTotals(_testRunStats.totals);
441     stream << std::endl;
442     StreamingReporterBase::testRunEnded(_testRunStats);
443 }
444 
lazyPrint()445 void ConsoleReporter::lazyPrint() {
446 
447     m_tablePrinter->close();
448     lazyPrintWithoutClosingBenchmarkTable();
449 }
450 
lazyPrintWithoutClosingBenchmarkTable()451 void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
452 
453     if (!currentTestRunInfo.used)
454         lazyPrintRunInfo();
455     if (!currentGroupInfo.used)
456         lazyPrintGroupInfo();
457 
458     if (!m_headerPrinted) {
459         printTestCaseAndSectionHeader();
460         m_headerPrinted = true;
461     }
462 }
lazyPrintRunInfo()463 void ConsoleReporter::lazyPrintRunInfo() {
464     stream << '\n' << getLineOfChars<'~'>() << '\n';
465     Colour colour(Colour::SecondaryText);
466     stream << currentTestRunInfo->name
467         << " is a Catch v" << libraryVersion() << " host application.\n"
468         << "Run with -? for options\n\n";
469 
470     if (m_config->rngSeed() != 0)
471         stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
472 
473     currentTestRunInfo.used = true;
474 }
lazyPrintGroupInfo()475 void ConsoleReporter::lazyPrintGroupInfo() {
476     if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {
477         printClosedHeader("Group: " + currentGroupInfo->name);
478         currentGroupInfo.used = true;
479     }
480 }
printTestCaseAndSectionHeader()481 void ConsoleReporter::printTestCaseAndSectionHeader() {
482     assert(!m_sectionStack.empty());
483     printOpenHeader(currentTestCaseInfo->name);
484 
485     if (m_sectionStack.size() > 1) {
486         Colour colourGuard(Colour::Headers);
487 
488         auto
489             it = m_sectionStack.begin() + 1, // Skip first section (test case)
490             itEnd = m_sectionStack.end();
491         for (; it != itEnd; ++it)
492             printHeaderString(it->name, 2);
493     }
494 
495     SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
496 
497     if (!lineInfo.empty()) {
498         stream << getLineOfChars<'-'>() << '\n';
499         Colour colourGuard(Colour::FileName);
500         stream << lineInfo << '\n';
501     }
502     stream << getLineOfChars<'.'>() << '\n' << std::endl;
503 }
504 
printClosedHeader(std::string const & _name)505 void ConsoleReporter::printClosedHeader(std::string const& _name) {
506     printOpenHeader(_name);
507     stream << getLineOfChars<'.'>() << '\n';
508 }
printOpenHeader(std::string const & _name)509 void ConsoleReporter::printOpenHeader(std::string const& _name) {
510     stream << getLineOfChars<'-'>() << '\n';
511     {
512         Colour colourGuard(Colour::Headers);
513         printHeaderString(_name);
514     }
515 }
516 
517 // if string has a : in first line will set indent to follow it on
518 // subsequent lines
printHeaderString(std::string const & _string,std::size_t indent)519 void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {
520     std::size_t i = _string.find(": ");
521     if (i != std::string::npos)
522         i += 2;
523     else
524         i = 0;
525     stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n';
526 }
527 
528 struct SummaryColumn {
529 
SummaryColumnCatch::SummaryColumn530     SummaryColumn( std::string _label, Colour::Code _colour )
531     :   label( std::move( _label ) ),
532         colour( _colour ) {}
addRowCatch::SummaryColumn533     SummaryColumn addRow( std::size_t count ) {
534         ReusableStringStream rss;
535         rss << count;
536         std::string row = rss.str();
537         for (auto& oldRow : rows) {
538             while (oldRow.size() < row.size())
539                 oldRow = ' ' + oldRow;
540             while (oldRow.size() > row.size())
541                 row = ' ' + row;
542         }
543         rows.push_back(row);
544         return *this;
545     }
546 
547     std::string label;
548     Colour::Code colour;
549     std::vector<std::string> rows;
550 
551 };
552 
printTotals(Totals const & totals)553 void ConsoleReporter::printTotals( Totals const& totals ) {
554     if (totals.testCases.total() == 0) {
555         stream << Colour(Colour::Warning) << "No tests ran\n";
556     } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {
557         stream << Colour(Colour::ResultSuccess) << "All tests passed";
558         stream << " ("
559             << pluralise(totals.assertions.passed, "assertion") << " in "
560             << pluralise(totals.testCases.passed, "test case") << ')'
561             << '\n';
562     } else {
563 
564         std::vector<SummaryColumn> columns;
565         columns.push_back(SummaryColumn("", Colour::None)
566                           .addRow(totals.testCases.total())
567                           .addRow(totals.assertions.total()));
568         columns.push_back(SummaryColumn("passed", Colour::Success)
569                           .addRow(totals.testCases.passed)
570                           .addRow(totals.assertions.passed));
571         columns.push_back(SummaryColumn("failed", Colour::ResultError)
572                           .addRow(totals.testCases.failed)
573                           .addRow(totals.assertions.failed));
574         columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure)
575                           .addRow(totals.testCases.failedButOk)
576                           .addRow(totals.assertions.failedButOk));
577 
578         printSummaryRow("test cases", columns, 0);
579         printSummaryRow("assertions", columns, 1);
580     }
581 }
printSummaryRow(std::string const & label,std::vector<SummaryColumn> const & cols,std::size_t row)582 void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) {
583     for (auto col : cols) {
584         std::string value = col.rows[row];
585         if (col.label.empty()) {
586             stream << label << ": ";
587             if (value != "0")
588                 stream << value;
589             else
590                 stream << Colour(Colour::Warning) << "- none -";
591         } else if (value != "0") {
592             stream << Colour(Colour::LightGrey) << " | ";
593             stream << Colour(col.colour)
594                 << value << ' ' << col.label;
595         }
596     }
597     stream << '\n';
598 }
599 
printTotalsDivider(Totals const & totals)600 void ConsoleReporter::printTotalsDivider(Totals const& totals) {
601     if (totals.testCases.total() > 0) {
602         std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());
603         std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());
604         std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());
605         while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)
606             findMax(failedRatio, failedButOkRatio, passedRatio)++;
607         while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
608             findMax(failedRatio, failedButOkRatio, passedRatio)--;
609 
610         stream << Colour(Colour::Error) << std::string(failedRatio, '=');
611         stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');
612         if (totals.testCases.allPassed())
613             stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');
614         else
615             stream << Colour(Colour::Success) << std::string(passedRatio, '=');
616     } else {
617         stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');
618     }
619     stream << '\n';
620 }
printSummaryDivider()621 void ConsoleReporter::printSummaryDivider() {
622     stream << getLineOfChars<'-'>() << '\n';
623 }
624 
625 CATCH_REGISTER_REPORTER("console", ConsoleReporter)
626 
627 } // end namespace Catch
628 
629 #if defined(_MSC_VER)
630 #pragma warning(pop)
631 #endif
632