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