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 - 43, 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 double dur = _sectionStats.durationInSeconds;
422 if (shouldShowDuration(*m_config, dur)) {
423 stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl;
424 }
425 if (m_headerPrinted) {
426 m_headerPrinted = false;
427 }
428 StreamingReporterBase::sectionEnded(_sectionStats);
429 }
430
431 #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)
benchmarkPreparing(std::string const & name)432 void ConsoleReporter::benchmarkPreparing(std::string const& name) {
433 lazyPrintWithoutClosingBenchmarkTable();
434
435 auto nameCol = Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2));
436
437 bool firstLine = true;
438 for (auto line : nameCol) {
439 if (!firstLine)
440 (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();
441 else
442 firstLine = false;
443
444 (*m_tablePrinter) << line << ColumnBreak();
445 }
446 }
447
benchmarkStarting(BenchmarkInfo const & info)448 void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {
449 (*m_tablePrinter) << info.samples << ColumnBreak()
450 << info.iterations << ColumnBreak();
451 if (!m_config->benchmarkNoAnalysis())
452 (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak();
453 }
benchmarkEnded(BenchmarkStats<> const & stats)454 void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) {
455 if (m_config->benchmarkNoAnalysis())
456 {
457 (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak();
458 }
459 else
460 {
461 (*m_tablePrinter) << ColumnBreak()
462 << Duration(stats.mean.point.count()) << ColumnBreak()
463 << Duration(stats.mean.lower_bound.count()) << ColumnBreak()
464 << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak()
465 << Duration(stats.standardDeviation.point.count()) << ColumnBreak()
466 << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak()
467 << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak();
468 }
469 }
470
benchmarkFailed(std::string const & error)471 void ConsoleReporter::benchmarkFailed(std::string const& error) {
472 Colour colour(Colour::Red);
473 (*m_tablePrinter)
474 << "Benchmark failed (" << error << ')'
475 << ColumnBreak() << RowBreak();
476 }
477 #endif // CATCH_CONFIG_ENABLE_BENCHMARKING
478
testCaseEnded(TestCaseStats const & _testCaseStats)479 void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {
480 m_tablePrinter->close();
481 StreamingReporterBase::testCaseEnded(_testCaseStats);
482 m_headerPrinted = false;
483 }
testGroupEnded(TestGroupStats const & _testGroupStats)484 void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) {
485 if (currentGroupInfo.used) {
486 printSummaryDivider();
487 stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
488 printTotals(_testGroupStats.totals);
489 stream << '\n' << std::endl;
490 }
491 StreamingReporterBase::testGroupEnded(_testGroupStats);
492 }
testRunEnded(TestRunStats const & _testRunStats)493 void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {
494 printTotalsDivider(_testRunStats.totals);
495 printTotals(_testRunStats.totals);
496 stream << std::endl;
497 StreamingReporterBase::testRunEnded(_testRunStats);
498 }
testRunStarting(TestRunInfo const & _testInfo)499 void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) {
500 StreamingReporterBase::testRunStarting(_testInfo);
501 printTestFilters();
502 }
503
lazyPrint()504 void ConsoleReporter::lazyPrint() {
505
506 m_tablePrinter->close();
507 lazyPrintWithoutClosingBenchmarkTable();
508 }
509
lazyPrintWithoutClosingBenchmarkTable()510 void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
511
512 if (!currentTestRunInfo.used)
513 lazyPrintRunInfo();
514 if (!currentGroupInfo.used)
515 lazyPrintGroupInfo();
516
517 if (!m_headerPrinted) {
518 printTestCaseAndSectionHeader();
519 m_headerPrinted = true;
520 }
521 }
lazyPrintRunInfo()522 void ConsoleReporter::lazyPrintRunInfo() {
523 stream << '\n' << getLineOfChars<'~'>() << '\n';
524 Colour colour(Colour::SecondaryText);
525 stream << currentTestRunInfo->name
526 << " is a Catch v" << libraryVersion() << " host application.\n"
527 << "Run with -? for options\n\n";
528
529 if (m_config->rngSeed() != 0)
530 stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
531
532 currentTestRunInfo.used = true;
533 }
lazyPrintGroupInfo()534 void ConsoleReporter::lazyPrintGroupInfo() {
535 if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {
536 printClosedHeader("Group: " + currentGroupInfo->name);
537 currentGroupInfo.used = true;
538 }
539 }
printTestCaseAndSectionHeader()540 void ConsoleReporter::printTestCaseAndSectionHeader() {
541 assert(!m_sectionStack.empty());
542 printOpenHeader(currentTestCaseInfo->name);
543
544 if (m_sectionStack.size() > 1) {
545 Colour colourGuard(Colour::Headers);
546
547 auto
548 it = m_sectionStack.begin() + 1, // Skip first section (test case)
549 itEnd = m_sectionStack.end();
550 for (; it != itEnd; ++it)
551 printHeaderString(it->name, 2);
552 }
553
554 SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
555
556
557 stream << getLineOfChars<'-'>() << '\n';
558 Colour colourGuard(Colour::FileName);
559 stream << lineInfo << '\n';
560 stream << getLineOfChars<'.'>() << '\n' << std::endl;
561 }
562
printClosedHeader(std::string const & _name)563 void ConsoleReporter::printClosedHeader(std::string const& _name) {
564 printOpenHeader(_name);
565 stream << getLineOfChars<'.'>() << '\n';
566 }
printOpenHeader(std::string const & _name)567 void ConsoleReporter::printOpenHeader(std::string const& _name) {
568 stream << getLineOfChars<'-'>() << '\n';
569 {
570 Colour colourGuard(Colour::Headers);
571 printHeaderString(_name);
572 }
573 }
574
575 // if string has a : in first line will set indent to follow it on
576 // subsequent lines
printHeaderString(std::string const & _string,std::size_t indent)577 void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {
578 std::size_t i = _string.find(": ");
579 if (i != std::string::npos)
580 i += 2;
581 else
582 i = 0;
583 stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n';
584 }
585
586 struct SummaryColumn {
587
SummaryColumnCatch::SummaryColumn588 SummaryColumn( std::string _label, Colour::Code _colour )
589 : label( std::move( _label ) ),
590 colour( _colour ) {}
addRowCatch::SummaryColumn591 SummaryColumn addRow( std::size_t count ) {
592 ReusableStringStream rss;
593 rss << count;
594 std::string row = rss.str();
595 for (auto& oldRow : rows) {
596 while (oldRow.size() < row.size())
597 oldRow = ' ' + oldRow;
598 while (oldRow.size() > row.size())
599 row = ' ' + row;
600 }
601 rows.push_back(row);
602 return *this;
603 }
604
605 std::string label;
606 Colour::Code colour;
607 std::vector<std::string> rows;
608
609 };
610
printTotals(Totals const & totals)611 void ConsoleReporter::printTotals( Totals const& totals ) {
612 if (totals.testCases.total() == 0) {
613 stream << Colour(Colour::Warning) << "No tests ran\n";
614 } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {
615 stream << Colour(Colour::ResultSuccess) << "All tests passed";
616 stream << " ("
617 << pluralise(totals.assertions.passed, "assertion") << " in "
618 << pluralise(totals.testCases.passed, "test case") << ')'
619 << '\n';
620 } else {
621
622 std::vector<SummaryColumn> columns;
623 columns.push_back(SummaryColumn("", Colour::None)
624 .addRow(totals.testCases.total())
625 .addRow(totals.assertions.total()));
626 columns.push_back(SummaryColumn("passed", Colour::Success)
627 .addRow(totals.testCases.passed)
628 .addRow(totals.assertions.passed));
629 columns.push_back(SummaryColumn("failed", Colour::ResultError)
630 .addRow(totals.testCases.failed)
631 .addRow(totals.assertions.failed));
632 columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure)
633 .addRow(totals.testCases.failedButOk)
634 .addRow(totals.assertions.failedButOk));
635
636 printSummaryRow("test cases", columns, 0);
637 printSummaryRow("assertions", columns, 1);
638 }
639 }
printSummaryRow(std::string const & label,std::vector<SummaryColumn> const & cols,std::size_t row)640 void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) {
641 for (auto col : cols) {
642 std::string value = col.rows[row];
643 if (col.label.empty()) {
644 stream << label << ": ";
645 if (value != "0")
646 stream << value;
647 else
648 stream << Colour(Colour::Warning) << "- none -";
649 } else if (value != "0") {
650 stream << Colour(Colour::LightGrey) << " | ";
651 stream << Colour(col.colour)
652 << value << ' ' << col.label;
653 }
654 }
655 stream << '\n';
656 }
657
printTotalsDivider(Totals const & totals)658 void ConsoleReporter::printTotalsDivider(Totals const& totals) {
659 if (totals.testCases.total() > 0) {
660 std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());
661 std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());
662 std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());
663 while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)
664 findMax(failedRatio, failedButOkRatio, passedRatio)++;
665 while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
666 findMax(failedRatio, failedButOkRatio, passedRatio)--;
667
668 stream << Colour(Colour::Error) << std::string(failedRatio, '=');
669 stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');
670 if (totals.testCases.allPassed())
671 stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');
672 else
673 stream << Colour(Colour::Success) << std::string(passedRatio, '=');
674 } else {
675 stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');
676 }
677 stream << '\n';
678 }
printSummaryDivider()679 void ConsoleReporter::printSummaryDivider() {
680 stream << getLineOfChars<'-'>() << '\n';
681 }
682
printTestFilters()683 void ConsoleReporter::printTestFilters() {
684 if (m_config->testSpec().hasFilters()) {
685 Colour guard(Colour::BrightYellow);
686 stream << "Filters: " << serializeFilters(m_config->getTestsOrTags()) << '\n';
687 }
688 }
689
690 CATCH_REGISTER_REPORTER("console", ConsoleReporter)
691
692 } // end namespace Catch
693
694 #if defined(_MSC_VER)
695 #pragma warning(pop)
696 #endif
697
698 #if defined(__clang__)
699 # pragma clang diagnostic pop
700 #endif
701