catch_reporter_console.cpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 #include <catch2/reporters/catch_reporter_console.hpp> 9 10 #include <catch2/interfaces/catch_interfaces_config.hpp> 11 #include <catch2/catch_test_spec.hpp> 12 #include <catch2/internal/catch_console_colour.hpp> 13 #include <catch2/internal/catch_string_manip.hpp> 14 #include <catch2/catch_version.hpp> 15 #include <catch2/internal/catch_textflow.hpp> 16 #include <catch2/internal/catch_reusable_string_stream.hpp> 17 #include <catch2/internal/catch_stringref.hpp> 18 #include <catch2/catch_test_case_info.hpp> 19 #include <catch2/internal/catch_console_width.hpp> 20 #include <catch2/reporters/catch_reporter_helpers.hpp> 21 #include <catch2/internal/catch_move_and_forward.hpp> 22 #include <catch2/catch_get_random_seed.hpp> 23 24 #include <cstdio> 25 26 #if defined(_MSC_VER) 27 #pragma warning(push) 28 #pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch 29 // Note that 4062 (not all labels are handled and default is missing) is enabled 30 #endif 31 32 #if defined(__clang__) 33 # pragma clang diagnostic push 34 // For simplicity, benchmarking-only helpers are always enabled 35 # pragma clang diagnostic ignored "-Wunused-function" 36 #endif 37 38 39 40 namespace Catch { 41 42 namespace { 43 44 // Formatter impl for ConsoleReporter 45 class ConsoleAssertionPrinter { 46 public: 47 ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; 48 ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; 49 ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages) 50 : stream(_stream), 51 stats(_stats), 52 result(_stats.assertionResult), 53 colour(Colour::None), 54 messages(_stats.infoMessages), 55 colourImpl(colourImpl_), 56 printInfoMessages(_printInfoMessages) { 57 switch (result.getResultType()) { 58 case ResultWas::Ok: 59 colour = Colour::Success; 60 passOrFail = "PASSED"_sr; 61 //if( result.hasMessage() ) 62 if (messages.size() == 1) 63 messageLabel = "with message"_sr; 64 if (messages.size() > 1) 65 messageLabel = "with messages"_sr; 66 break; 67 case ResultWas::ExpressionFailed: 68 if (result.isOk()) { 69 colour = Colour::Success; 70 passOrFail = "FAILED - but was ok"_sr; 71 } else { 72 colour = Colour::Error; 73 passOrFail = "FAILED"_sr; 74 } 75 if (messages.size() == 1) 76 messageLabel = "with message"_sr; 77 if (messages.size() > 1) 78 messageLabel = "with messages"_sr; 79 break; 80 case ResultWas::ThrewException: 81 colour = Colour::Error; 82 passOrFail = "FAILED"_sr; 83 // todo switch 84 switch (messages.size()) { case 0: 85 messageLabel = "due to unexpected exception with "_sr; 86 break; 87 case 1: 88 messageLabel = "due to unexpected exception with message"_sr; 89 break; 90 default: 91 messageLabel = "due to unexpected exception with messages"_sr; 92 break; 93 } 94 break; 95 case ResultWas::FatalErrorCondition: 96 colour = Colour::Error; 97 passOrFail = "FAILED"_sr; 98 messageLabel = "due to a fatal error condition"_sr; 99 break; 100 case ResultWas::DidntThrowException: 101 colour = Colour::Error; 102 passOrFail = "FAILED"_sr; 103 messageLabel = "because no exception was thrown where one was expected"_sr; 104 break; 105 case ResultWas::Info: 106 messageLabel = "info"_sr; 107 break; 108 case ResultWas::Warning: 109 messageLabel = "warning"_sr; 110 break; 111 case ResultWas::ExplicitFailure: 112 passOrFail = "FAILED"_sr; 113 colour = Colour::Error; 114 if (messages.size() == 1) 115 messageLabel = "explicitly with message"_sr; 116 if (messages.size() > 1) 117 messageLabel = "explicitly with messages"_sr; 118 break; 119 case ResultWas::ExplicitSkip: 120 colour = Colour::Skip; 121 passOrFail = "SKIPPED"_sr; 122 if (messages.size() == 1) 123 messageLabel = "explicitly with message"_sr; 124 if (messages.size() > 1) 125 messageLabel = "explicitly with messages"_sr; 126 break; 127 // These cases are here to prevent compiler warnings 128 case ResultWas::Unknown: 129 case ResultWas::FailureBit: 130 case ResultWas::Exception: 131 passOrFail = "** internal error **"_sr; 132 colour = Colour::Error; 133 break; 134 } 135 } 136 137 void print() const { 138 printSourceInfo(); 139 if (stats.totals.assertions.total() > 0) { 140 printResultType(); 141 printOriginalExpression(); 142 printReconstructedExpression(); 143 } else { 144 stream << '\n'; 145 } 146 printMessage(); 147 } 148 149 private: 150 void printResultType() const { 151 if (!passOrFail.empty()) { 152 stream << colourImpl->guardColour(colour) << passOrFail << ":\n"; 153 } 154 } 155 void printOriginalExpression() const { 156 if (result.hasExpression()) { 157 stream << colourImpl->guardColour( Colour::OriginalExpression ) 158 << " " << result.getExpressionInMacro() << '\n'; 159 } 160 } 161 void printReconstructedExpression() const { 162 if (result.hasExpandedExpression()) { 163 stream << "with expansion:\n"; 164 stream << colourImpl->guardColour( Colour::ReconstructedExpression ) 165 << TextFlow::Column( result.getExpandedExpression() ) 166 .indent( 2 ) 167 << '\n'; 168 } 169 } 170 void printMessage() const { 171 if (!messageLabel.empty()) 172 stream << messageLabel << ':' << '\n'; 173 for (auto const& msg : messages) { 174 // If this assertion is a warning ignore any INFO messages 175 if (printInfoMessages || msg.type != ResultWas::Info) 176 stream << TextFlow::Column(msg.message).indent(2) << '\n'; 177 } 178 } 179 void printSourceInfo() const { 180 stream << colourImpl->guardColour( Colour::FileName ) 181 << result.getSourceInfo() << ": "; 182 } 183 184 std::ostream& stream; 185 AssertionStats const& stats; 186 AssertionResult const& result; 187 Colour::Code colour; 188 StringRef passOrFail; 189 StringRef messageLabel; 190 std::vector<MessageInfo> const& messages; 191 ColourImpl* colourImpl; 192 bool printInfoMessages; 193 }; 194 195 std::size_t makeRatio( std::uint64_t number, std::uint64_t total ) { 196 const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; 197 return (ratio == 0 && number > 0) ? 1 : static_cast<std::size_t>(ratio); 198 } 199 200 std::size_t& 201 findMax( std::size_t& i, std::size_t& j, std::size_t& k, std::size_t& l ) { 202 if (i > j && i > k && i > l) 203 return i; 204 else if (j > k && j > l) 205 return j; 206 else if (k > l) 207 return k; 208 else 209 return l; 210 } 211 212 enum class Justification { Left, Right }; 213 214 struct ColumnInfo { 215 std::string name; 216 std::size_t width; 217 Justification justification; 218 }; 219 struct ColumnBreak {}; 220 struct RowBreak {}; 221 struct OutputFlush {}; 222 223 class Duration { 224 enum class Unit { 225 Auto, 226 Nanoseconds, 227 Microseconds, 228 Milliseconds, 229 Seconds, 230 Minutes 231 }; 232 static const uint64_t s_nanosecondsInAMicrosecond = 1000; 233 static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; 234 static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; 235 static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; 236 237 double m_inNanoseconds; 238 Unit m_units; 239 240 public: 241 explicit Duration(double inNanoseconds, Unit units = Unit::Auto) 242 : m_inNanoseconds(inNanoseconds), 243 m_units(units) { 244 if (m_units == Unit::Auto) { 245 if (m_inNanoseconds < s_nanosecondsInAMicrosecond) 246 m_units = Unit::Nanoseconds; 247 else if (m_inNanoseconds < s_nanosecondsInAMillisecond) 248 m_units = Unit::Microseconds; 249 else if (m_inNanoseconds < s_nanosecondsInASecond) 250 m_units = Unit::Milliseconds; 251 else if (m_inNanoseconds < s_nanosecondsInAMinute) 252 m_units = Unit::Seconds; 253 else 254 m_units = Unit::Minutes; 255 } 256 257 } 258 259 auto value() const -> double { 260 switch (m_units) { 261 case Unit::Microseconds: 262 return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond); 263 case Unit::Milliseconds: 264 return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond); 265 case Unit::Seconds: 266 return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond); 267 case Unit::Minutes: 268 return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute); 269 default: 270 return m_inNanoseconds; 271 } 272 } 273 StringRef unitsAsString() const { 274 switch (m_units) { 275 case Unit::Nanoseconds: 276 return "ns"_sr; 277 case Unit::Microseconds: 278 return "us"_sr; 279 case Unit::Milliseconds: 280 return "ms"_sr; 281 case Unit::Seconds: 282 return "s"_sr; 283 case Unit::Minutes: 284 return "m"_sr; 285 default: 286 return "** internal error **"_sr; 287 } 288 289 } 290 friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { 291 return os << duration.value() << ' ' << duration.unitsAsString(); 292 } 293 }; 294 } // end anon namespace 295 296 class TablePrinter { 297 std::ostream& m_os; 298 std::vector<ColumnInfo> m_columnInfos; 299 ReusableStringStream m_oss; 300 int m_currentColumn = -1; 301 bool m_isOpen = false; 302 303 public: 304 TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos ) 305 : m_os( os ), 306 m_columnInfos( CATCH_MOVE( columnInfos ) ) {} 307 308 auto columnInfos() const -> std::vector<ColumnInfo> const& { 309 return m_columnInfos; 310 } 311 312 void open() { 313 if (!m_isOpen) { 314 m_isOpen = true; 315 *this << RowBreak(); 316 317 TextFlow::Columns headerCols; 318 auto spacer = TextFlow::Spacer(2); 319 for (auto const& info : m_columnInfos) { 320 assert(info.width > 2); 321 headerCols += TextFlow::Column(info.name).width(info.width - 2); 322 headerCols += spacer; 323 } 324 m_os << headerCols << '\n'; 325 326 m_os << lineOfChars('-') << '\n'; 327 } 328 } 329 void close() { 330 if (m_isOpen) { 331 *this << RowBreak(); 332 m_os << '\n' << std::flush; 333 m_isOpen = false; 334 } 335 } 336 337 template<typename T> 338 friend TablePrinter& operator<< (TablePrinter& tp, T const& value) { 339 tp.m_oss << value; 340 return tp; 341 } 342 343 friend TablePrinter& operator<< (TablePrinter& tp, ColumnBreak) { 344 auto colStr = tp.m_oss.str(); 345 const auto strSize = colStr.size(); 346 tp.m_oss.str(""); 347 tp.open(); 348 if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) { 349 tp.m_currentColumn = -1; 350 tp.m_os << '\n'; 351 } 352 tp.m_currentColumn++; 353 354 auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; 355 auto padding = (strSize + 1 < colInfo.width) 356 ? std::string(colInfo.width - (strSize + 1), ' ') 357 : std::string(); 358 if (colInfo.justification == Justification::Left) 359 tp.m_os << colStr << padding << ' '; 360 else 361 tp.m_os << padding << colStr << ' '; 362 return tp; 363 } 364 365 friend TablePrinter& operator<< (TablePrinter& tp, RowBreak) { 366 if (tp.m_currentColumn > 0) { 367 tp.m_os << '\n'; 368 tp.m_currentColumn = -1; 369 } 370 return tp; 371 } 372 373 friend TablePrinter& operator<<(TablePrinter& tp, OutputFlush) { 374 tp.m_os << std::flush; 375 return tp; 376 } 377 }; 378 379 ConsoleReporter::ConsoleReporter(ReporterConfig&& config): 380 StreamingReporterBase( CATCH_MOVE( config ) ), 381 m_tablePrinter(Detail::make_unique<TablePrinter>(m_stream, 382 [&config]() -> std::vector<ColumnInfo> { 383 if (config.fullConfig()->benchmarkNoAnalysis()) 384 { 385 return{ 386 { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, 387 { " samples", 14, Justification::Right }, 388 { " iterations", 14, Justification::Right }, 389 { " mean", 14, Justification::Right } 390 }; 391 } 392 else 393 { 394 return{ 395 { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, 396 { "samples mean std dev", 14, Justification::Right }, 397 { "iterations low mean low std dev", 14, Justification::Right }, 398 { "est run time high mean high std dev", 14, Justification::Right } 399 }; 400 } 401 }())) {} 402 ConsoleReporter::~ConsoleReporter() = default; 403 404 std::string ConsoleReporter::getDescription() { 405 return "Reports test results as plain lines of text"; 406 } 407 408 void ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) { 409 m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; 410 } 411 412 void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { 413 m_stream << "Invalid Filter: " << arg << '\n'; 414 } 415 416 void ConsoleReporter::assertionStarting(AssertionInfo const&) {} 417 418 void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { 419 AssertionResult const& result = _assertionStats.assertionResult; 420 421 bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); 422 423 // Drop out if result was successful but we're not printing them. 424 // TODO: Make configurable whether skips should be printed 425 if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip) 426 return; 427 428 lazyPrint(); 429 430 ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults); 431 printer.print(); 432 m_stream << '\n' << std::flush; 433 } 434 435 void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { 436 m_tablePrinter->close(); 437 m_headerPrinted = false; 438 StreamingReporterBase::sectionStarting(_sectionInfo); 439 } 440 void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { 441 m_tablePrinter->close(); 442 if (_sectionStats.missingAssertions) { 443 lazyPrint(); 444 auto guard = 445 m_colour->guardColour( Colour::ResultError ).engage( m_stream ); 446 if (m_sectionStack.size() > 1) 447 m_stream << "\nNo assertions in section"; 448 else 449 m_stream << "\nNo assertions in test case"; 450 m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" << std::flush; 451 } 452 double dur = _sectionStats.durationInSeconds; 453 if (shouldShowDuration(*m_config, dur)) { 454 m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; 455 } 456 if (m_headerPrinted) { 457 m_headerPrinted = false; 458 } 459 StreamingReporterBase::sectionEnded(_sectionStats); 460 } 461 462 void ConsoleReporter::benchmarkPreparing( StringRef name ) { 463 lazyPrintWithoutClosingBenchmarkTable(); 464 465 auto nameCol = TextFlow::Column( static_cast<std::string>( name ) ) 466 .width( m_tablePrinter->columnInfos()[0].width - 2 ); 467 468 bool firstLine = true; 469 for (auto line : nameCol) { 470 if (!firstLine) 471 (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); 472 else 473 firstLine = false; 474 475 (*m_tablePrinter) << line << ColumnBreak(); 476 } 477 } 478 479 void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { 480 (*m_tablePrinter) << info.samples << ColumnBreak() 481 << info.iterations << ColumnBreak(); 482 if ( !m_config->benchmarkNoAnalysis() ) { 483 ( *m_tablePrinter ) 484 << Duration( info.estimatedDuration ) << ColumnBreak(); 485 } 486 ( *m_tablePrinter ) << OutputFlush{}; 487 } 488 void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { 489 if (m_config->benchmarkNoAnalysis()) 490 { 491 (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); 492 } 493 else 494 { 495 (*m_tablePrinter) << ColumnBreak() 496 << Duration(stats.mean.point.count()) << ColumnBreak() 497 << Duration(stats.mean.lower_bound.count()) << ColumnBreak() 498 << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() 499 << Duration(stats.standardDeviation.point.count()) << ColumnBreak() 500 << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() 501 << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); 502 } 503 } 504 505 void ConsoleReporter::benchmarkFailed( StringRef error ) { 506 auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream ); 507 (*m_tablePrinter) 508 << "Benchmark failed (" << error << ')' 509 << ColumnBreak() << RowBreak(); 510 } 511 512 void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { 513 m_tablePrinter->close(); 514 StreamingReporterBase::testCaseEnded(_testCaseStats); 515 m_headerPrinted = false; 516 } 517 void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { 518 printTotalsDivider(_testRunStats.totals); 519 printTestRunTotals( m_stream, *m_colour, _testRunStats.totals ); 520 m_stream << '\n' << std::flush; 521 StreamingReporterBase::testRunEnded(_testRunStats); 522 } 523 void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { 524 StreamingReporterBase::testRunStarting(_testInfo); 525 if ( m_config->testSpec().hasFilters() ) { 526 m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " 527 << m_config->testSpec() << '\n'; 528 } 529 m_stream << "Randomness seeded to: " << getSeed() << '\n'; 530 } 531 532 void ConsoleReporter::lazyPrint() { 533 534 m_tablePrinter->close(); 535 lazyPrintWithoutClosingBenchmarkTable(); 536 } 537 538 void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { 539 540 if ( !m_testRunInfoPrinted ) { 541 lazyPrintRunInfo(); 542 } 543 if (!m_headerPrinted) { 544 printTestCaseAndSectionHeader(); 545 m_headerPrinted = true; 546 } 547 } 548 void ConsoleReporter::lazyPrintRunInfo() { 549 m_stream << '\n' 550 << lineOfChars( '~' ) << '\n' 551 << m_colour->guardColour( Colour::SecondaryText ) 552 << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() 553 << " host application.\n" 554 << "Run with -? for options\n\n"; 555 556 m_testRunInfoPrinted = true; 557 } 558 void ConsoleReporter::printTestCaseAndSectionHeader() { 559 assert(!m_sectionStack.empty()); 560 printOpenHeader(currentTestCaseInfo->name); 561 562 if (m_sectionStack.size() > 1) { 563 auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); 564 565 auto 566 it = m_sectionStack.begin() + 1, // Skip first section (test case) 567 itEnd = m_sectionStack.end(); 568 for (; it != itEnd; ++it) 569 printHeaderString(it->name, 2); 570 } 571 572 SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; 573 574 575 m_stream << lineOfChars( '-' ) << '\n' 576 << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n' 577 << lineOfChars( '.' ) << "\n\n" 578 << std::flush; 579 } 580 581 void ConsoleReporter::printClosedHeader(std::string const& _name) { 582 printOpenHeader(_name); 583 m_stream << lineOfChars('.') << '\n'; 584 } 585 void ConsoleReporter::printOpenHeader(std::string const& _name) { 586 m_stream << lineOfChars('-') << '\n'; 587 { 588 auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); 589 printHeaderString(_name); 590 } 591 } 592 593 void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { 594 // We want to get a bit fancy with line breaking here, so that subsequent 595 // lines start after ":" if one is present, e.g. 596 // ``` 597 // blablabla: Fancy 598 // linebreaking 599 // ``` 600 // but we also want to avoid problems with overly long indentation causing 601 // the text to take up too many lines, e.g. 602 // ``` 603 // blablabla: F 604 // a 605 // n 606 // c 607 // y 608 // . 609 // . 610 // . 611 // ``` 612 // So we limit the prefix indentation check to first quarter of the possible 613 // width 614 std::size_t idx = _string.find( ": " ); 615 if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) { 616 idx += 2; 617 } else { 618 idx = 0; 619 } 620 m_stream << TextFlow::Column( _string ) 621 .indent( indent + idx ) 622 .initialIndent( indent ) 623 << '\n'; 624 } 625 626 void ConsoleReporter::printTotalsDivider(Totals const& totals) { 627 if (totals.testCases.total() > 0) { 628 std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); 629 std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); 630 std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); 631 std::size_t skippedRatio = makeRatio(totals.testCases.skipped, totals.testCases.total()); 632 while (failedRatio + failedButOkRatio + passedRatio + skippedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) 633 findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)++; 634 while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) 635 findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)--; 636 637 m_stream << m_colour->guardColour( Colour::Error ) 638 << std::string( failedRatio, '=' ) 639 << m_colour->guardColour( Colour::ResultExpectedFailure ) 640 << std::string( failedButOkRatio, '=' ); 641 if ( totals.testCases.allPassed() ) { 642 m_stream << m_colour->guardColour( Colour::ResultSuccess ) 643 << std::string( passedRatio, '=' ); 644 } else { 645 m_stream << m_colour->guardColour( Colour::Success ) 646 << std::string( passedRatio, '=' ); 647 } 648 m_stream << m_colour->guardColour( Colour::Skip ) 649 << std::string( skippedRatio, '=' ); 650 } else { 651 m_stream << m_colour->guardColour( Colour::Warning ) 652 << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' ); 653 } 654 m_stream << '\n'; 655 } 656 657 } // end namespace Catch 658 659 #if defined(_MSC_VER) 660 #pragma warning(pop) 661 #endif 662 663 #if defined(__clang__) 664 # pragma clang diagnostic pop 665 #endif