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 }
testRunStarting(TestRunInfo const & _testInfo)444 void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) {
445 StreamingReporterBase::testRunStarting(_testInfo);
446 printTestFilters();
447 }
448
lazyPrint()449 void ConsoleReporter::lazyPrint() {
450
451 m_tablePrinter->close();
452 lazyPrintWithoutClosingBenchmarkTable();
453 }
454
lazyPrintWithoutClosingBenchmarkTable()455 void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {
456
457 if (!currentTestRunInfo.used)
458 lazyPrintRunInfo();
459 if (!currentGroupInfo.used)
460 lazyPrintGroupInfo();
461
462 if (!m_headerPrinted) {
463 printTestCaseAndSectionHeader();
464 m_headerPrinted = true;
465 }
466 }
lazyPrintRunInfo()467 void ConsoleReporter::lazyPrintRunInfo() {
468 stream << '\n' << getLineOfChars<'~'>() << '\n';
469 Colour colour(Colour::SecondaryText);
470 stream << currentTestRunInfo->name
471 << " is a Catch v" << libraryVersion() << " host application.\n"
472 << "Run with -? for options\n\n";
473
474 if (m_config->rngSeed() != 0)
475 stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
476
477 currentTestRunInfo.used = true;
478 }
lazyPrintGroupInfo()479 void ConsoleReporter::lazyPrintGroupInfo() {
480 if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {
481 printClosedHeader("Group: " + currentGroupInfo->name);
482 currentGroupInfo.used = true;
483 }
484 }
printTestCaseAndSectionHeader()485 void ConsoleReporter::printTestCaseAndSectionHeader() {
486 assert(!m_sectionStack.empty());
487 printOpenHeader(currentTestCaseInfo->name);
488
489 if (m_sectionStack.size() > 1) {
490 Colour colourGuard(Colour::Headers);
491
492 auto
493 it = m_sectionStack.begin() + 1, // Skip first section (test case)
494 itEnd = m_sectionStack.end();
495 for (; it != itEnd; ++it)
496 printHeaderString(it->name, 2);
497 }
498
499 SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;
500
501 if (!lineInfo.empty()) {
502 stream << getLineOfChars<'-'>() << '\n';
503 Colour colourGuard(Colour::FileName);
504 stream << lineInfo << '\n';
505 }
506 stream << getLineOfChars<'.'>() << '\n' << std::endl;
507 }
508
printClosedHeader(std::string const & _name)509 void ConsoleReporter::printClosedHeader(std::string const& _name) {
510 printOpenHeader(_name);
511 stream << getLineOfChars<'.'>() << '\n';
512 }
printOpenHeader(std::string const & _name)513 void ConsoleReporter::printOpenHeader(std::string const& _name) {
514 stream << getLineOfChars<'-'>() << '\n';
515 {
516 Colour colourGuard(Colour::Headers);
517 printHeaderString(_name);
518 }
519 }
520
521 // if string has a : in first line will set indent to follow it on
522 // subsequent lines
printHeaderString(std::string const & _string,std::size_t indent)523 void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {
524 std::size_t i = _string.find(": ");
525 if (i != std::string::npos)
526 i += 2;
527 else
528 i = 0;
529 stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n';
530 }
531
532 struct SummaryColumn {
533
SummaryColumnCatch::SummaryColumn534 SummaryColumn( std::string _label, Colour::Code _colour )
535 : label( std::move( _label ) ),
536 colour( _colour ) {}
addRowCatch::SummaryColumn537 SummaryColumn addRow( std::size_t count ) {
538 ReusableStringStream rss;
539 rss << count;
540 std::string row = rss.str();
541 for (auto& oldRow : rows) {
542 while (oldRow.size() < row.size())
543 oldRow = ' ' + oldRow;
544 while (oldRow.size() > row.size())
545 row = ' ' + row;
546 }
547 rows.push_back(row);
548 return *this;
549 }
550
551 std::string label;
552 Colour::Code colour;
553 std::vector<std::string> rows;
554
555 };
556
printTotals(Totals const & totals)557 void ConsoleReporter::printTotals( Totals const& totals ) {
558 if (totals.testCases.total() == 0) {
559 stream << Colour(Colour::Warning) << "No tests ran\n";
560 } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {
561 stream << Colour(Colour::ResultSuccess) << "All tests passed";
562 stream << " ("
563 << pluralise(totals.assertions.passed, "assertion") << " in "
564 << pluralise(totals.testCases.passed, "test case") << ')'
565 << '\n';
566 } else {
567
568 std::vector<SummaryColumn> columns;
569 columns.push_back(SummaryColumn("", Colour::None)
570 .addRow(totals.testCases.total())
571 .addRow(totals.assertions.total()));
572 columns.push_back(SummaryColumn("passed", Colour::Success)
573 .addRow(totals.testCases.passed)
574 .addRow(totals.assertions.passed));
575 columns.push_back(SummaryColumn("failed", Colour::ResultError)
576 .addRow(totals.testCases.failed)
577 .addRow(totals.assertions.failed));
578 columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure)
579 .addRow(totals.testCases.failedButOk)
580 .addRow(totals.assertions.failedButOk));
581
582 printSummaryRow("test cases", columns, 0);
583 printSummaryRow("assertions", columns, 1);
584 }
585 }
printSummaryRow(std::string const & label,std::vector<SummaryColumn> const & cols,std::size_t row)586 void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) {
587 for (auto col : cols) {
588 std::string value = col.rows[row];
589 if (col.label.empty()) {
590 stream << label << ": ";
591 if (value != "0")
592 stream << value;
593 else
594 stream << Colour(Colour::Warning) << "- none -";
595 } else if (value != "0") {
596 stream << Colour(Colour::LightGrey) << " | ";
597 stream << Colour(col.colour)
598 << value << ' ' << col.label;
599 }
600 }
601 stream << '\n';
602 }
603
printTotalsDivider(Totals const & totals)604 void ConsoleReporter::printTotalsDivider(Totals const& totals) {
605 if (totals.testCases.total() > 0) {
606 std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());
607 std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());
608 std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());
609 while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)
610 findMax(failedRatio, failedButOkRatio, passedRatio)++;
611 while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)
612 findMax(failedRatio, failedButOkRatio, passedRatio)--;
613
614 stream << Colour(Colour::Error) << std::string(failedRatio, '=');
615 stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');
616 if (totals.testCases.allPassed())
617 stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');
618 else
619 stream << Colour(Colour::Success) << std::string(passedRatio, '=');
620 } else {
621 stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');
622 }
623 stream << '\n';
624 }
printSummaryDivider()625 void ConsoleReporter::printSummaryDivider() {
626 stream << getLineOfChars<'-'>() << '\n';
627 }
628
printTestFilters()629 void ConsoleReporter::printTestFilters() {
630 if (m_config->testSpec().hasFilters())
631 stream << Colour(Colour::BrightYellow) << "Filters: " << serializeFilters( m_config->getTestsOrTags() ) << '\n';
632 }
633
634 CATCH_REGISTER_REPORTER("console", ConsoleReporter)
635
636 } // end namespace Catch
637
638 #if defined(_MSC_VER)
639 #pragma warning(pop)
640 #endif
641