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