diff --git a/.gitignore b/.gitignore index dd12a74ba6..66d7f93d44 100644 --- a/.gitignore +++ b/.gitignore @@ -23,9 +23,13 @@ projects/Generated DerivedData *.xccheckout Build -.idea -.vs +/build/ +/.idea/ +/.vs/ +/.vscode/ cmake-build-* benchmark-dir .conan/test_package/build bazel-* +/.cache/ +/compile_commands.json diff --git a/docs/reporters.md b/docs/reporters.md index a33e55bf11..f0c88a25df 100644 --- a/docs/reporters.md +++ b/docs/reporters.md @@ -18,6 +18,7 @@ There are four reporters built in to the single include: * `console` writes as lines of text, formatted to a typical terminal width, with colours if a capable terminal is detected. * `compact` similar to `console` but optimised for minimal output - each entry on one line +* `gtest` also designed for console output, but mimics the output of Google Test framework. See below for details. * `junit` writes xml that corresponds to Ant's [junitreport](http://help.catchsoftware.com/display/ET/JUnit+Format) target. Useful for build systems that understand Junit. Because of the way the junit format is structured the run must complete before anything is written. * `xml` writes an xml format tailored to Catch. Unlike `junit` this is a streaming format so results are delivered progressively. @@ -35,6 +36,26 @@ You see what reporters are available from the command line by running with `--li By default all these reports are written to stdout, but can be redirected to a file with [`-o` or `--out`](command-line.md#sending-output-to-a-file) +### gtest reporter + +The `gtest` reporter is much more verbose than `console` but it allows you to: +* See how each of your tests is run - it feels good to see a lot of green lines +* See what test is being run now - useful when some tests are slow +* See the progress is going - useful when the whole test suit is slow +* See the output of each test nested inside its scope - useful when tests print something to stdout +* Feel comfortable if you are used to Google Test + +The reporter is optimized for test cases that do not have nested sections in them. A _test case_ from Catch2 is thought of as a _test case_ from gtest and each _section_ of the test case is thought of as a _test_ from gtest. The latter is referred to as simply a "test" in the output. It is assumed that code outside sections is only used for set up and tear down and is unlikely to fail the test case. + +The reporter also works perfectly fine when there are nested sections, but we need to know the general definition of a "test" to understand how it works: a test is a single run of a test case. For instance: +* If there are no sections in the test case, Catch2 runs the test case once and thus there is only one test. +* If there are `N` non-nested sections in the test case, Catch2 runs it `N` times and thus there is `N` tests. +* If there are nested sections in the test case, Catch2 runs it for every leaf section and thus the number of tests depends on the structure of the sections. + +Each time Catch2 enters a section the reporter prints the `[ RUN ]` line to the output. With the exception of the root section, which Catch2 implicitly creates for the body of the test case itself. It is only printed once per test case to make the output a little more compact and similar to what you'd expect. You can set verbosity to high to remove this exception and fully see how the sections stack is formed during the execution. + +Note that since Catch2 is much more flexible with its sections than Google Test with its test cases and tests, there is no single "true" way to define how the output of the reporter should look. Especially if there are nested sections. To make things even more complicated, Catch2 runner does not know the structure of the sections before they are actually executed, and thus we cannot analyze this structure beforehand to e.g. collapse the output for entering multiple nested sections. + ## Writing your own reporter You can write your own custom reporter and register it with Catch. diff --git a/include/internal/catch_string_manip.cpp b/include/internal/catch_string_manip.cpp index accb3498e2..2694cf2f3e 100644 --- a/include/internal/catch_string_manip.cpp +++ b/include/internal/catch_string_manip.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Catch { @@ -20,6 +21,9 @@ namespace Catch { char toLowerCh(char c) { return static_cast( std::tolower( static_cast(c) ) ); } + char toUpperCh(char c) { + return static_cast( std::toupper( static_cast(c) ) ); + } } bool startsWith( std::string const& s, std::string const& prefix ) { @@ -40,11 +44,19 @@ namespace Catch { void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } + void toUpperInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toUpperCh ); + } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } + std::string toUpper( std::string const& s ) { + std::string lc = s; + toUpperInPlace( lc ); + return lc; + } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); @@ -99,6 +111,12 @@ namespace Catch { m_label( label ) {} + std::string pluralise::str() const { + std::stringstream ss; + ss << *this; + return ss.str(); + } + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << ' ' << pluraliser.m_label; if( pluraliser.m_count != 1 ) diff --git a/include/internal/catch_string_manip.h b/include/internal/catch_string_manip.h index cdb7be5fee..023f84d8f0 100644 --- a/include/internal/catch_string_manip.h +++ b/include/internal/catch_string_manip.h @@ -21,7 +21,9 @@ namespace Catch { bool endsWith( std::string const& s, char suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); + void toUpperInPlace( std::string& s ); std::string toLower( std::string const& s ); + std::string toUpper( std::string const& s ); //! Returns a new string without whitespace at the start/end std::string trim( std::string const& str ); //! Returns a substring of the original ref without whitespace. Beware lifetimes! @@ -34,6 +36,8 @@ namespace Catch { struct pluralise { pluralise( std::size_t count, std::string const& label ); + std::string str() const; + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); std::size_t m_count; diff --git a/include/reporters/catch_reporter_bases.cpp b/include/reporters/catch_reporter_bases.cpp index 0578e8abd2..132cca1cf5 100644 --- a/include/reporters/catch_reporter_bases.cpp +++ b/include/reporters/catch_reporter_bases.cpp @@ -8,6 +8,7 @@ #include "../internal/catch_interfaces_reporter.h" #include "../internal/catch_errno_guard.h" +#include "../internal/catch_text.h" #include "catch_reporter_bases.hpp" #include @@ -80,5 +81,125 @@ namespace Catch { return false; } + ConsoleAssertionPrinter::ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void ConsoleAssertionPrinter::print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + + void ConsoleAssertionPrinter::printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void ConsoleAssertionPrinter::printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void ConsoleAssertionPrinter::printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void ConsoleAssertionPrinter::printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void ConsoleAssertionPrinter::printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } } // end namespace Catch diff --git a/include/reporters/catch_reporter_bases.hpp b/include/reporters/catch_reporter_bases.hpp index f62e14305f..b9c045013d 100644 --- a/include/reporters/catch_reporter_bases.hpp +++ b/include/reporters/catch_reporter_bases.hpp @@ -8,6 +8,7 @@ #ifndef TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED +#include "../internal/catch_console_colour.h" #include "../internal/catch_enforce.h" #include "../internal/catch_interfaces_reporter.h" @@ -280,6 +281,33 @@ namespace Catch { bool assertionEnded(AssertionStats const&) override; }; + // Formatter impl for ConsoleReporter + class ConsoleAssertionPrinter { + public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages); + + void print() const; + + private: + void printResultType() const; + void printOriginalExpression() const; + void printReconstructedExpression() const; + void printMessage() const; + void printSourceInfo() const; + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + } // end namespace Catch #endif // TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED \ No newline at end of file diff --git a/include/reporters/catch_reporter_console.cpp b/include/reporters/catch_reporter_console.cpp index 4f74ccb320..d15909743f 100644 --- a/include/reporters/catch_reporter_console.cpp +++ b/include/reporters/catch_reporter_console.cpp @@ -35,144 +35,6 @@ namespace Catch { namespace { -// Formatter impl for ConsoleReporter -class ConsoleAssertionPrinter { -public: - ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) - : stream(_stream), - stats(_stats), - result(_stats.assertionResult), - colour(Colour::None), - message(result.getMessage()), - messages(_stats.infoMessages), - printInfoMessages(_printInfoMessages) { - switch (result.getResultType()) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"; - //if( result.hasMessage() ) - if (_stats.infoMessages.size() == 1) - messageLabel = "with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "with messages"; - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"; - } else { - colour = Colour::Error; - passOrFail = "FAILED"; - } - if (_stats.infoMessages.size() == 1) - messageLabel = "with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "with messages"; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with "; - if (_stats.infoMessages.size() == 1) - messageLabel += "message"; - if (_stats.infoMessages.size() > 1) - messageLabel += "messages"; - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to a fatal error condition"; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "because no exception was thrown where one was expected"; - break; - case ResultWas::Info: - messageLabel = "info"; - break; - case ResultWas::Warning: - messageLabel = "warning"; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; - colour = Colour::Error; - if (_stats.infoMessages.size() == 1) - messageLabel = "explicitly with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "explicitly with messages"; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"; - colour = Colour::Error; - break; - } - } - - void print() const { - printSourceInfo(); - if (stats.totals.assertions.total() > 0) { - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } else { - stream << '\n'; - } - printMessage(); - } - -private: - void printResultType() const { - if (!passOrFail.empty()) { - Colour colourGuard(colour); - stream << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if (result.hasExpression()) { - Colour colourGuard(Colour::OriginalExpression); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; - } - } - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - stream << "with expansion:\n"; - Colour colourGuard(Colour::ReconstructedExpression); - stream << Column(result.getExpandedExpression()).indent(2) << '\n'; - } - } - void printMessage() const { - if (!messageLabel.empty()) - stream << messageLabel << ':' << '\n'; - for (auto const& msg : messages) { - // If this assertion is a warning ignore any INFO messages - if (printInfoMessages || msg.type != ResultWas::Info) - stream << Column(msg.message).indent(2) << '\n'; - } - } - void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ": "; - } - - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector messages; - bool printInfoMessages; -}; - std::size_t makeRatio(std::size_t number, std::size_t total) { std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; return (ratio == 0 && number > 0) ? 1 : ratio; diff --git a/include/reporters/catch_reporter_gtest.cpp b/include/reporters/catch_reporter_gtest.cpp new file mode 100644 index 0000000000..6b728601b5 --- /dev/null +++ b/include/reporters/catch_reporter_gtest.cpp @@ -0,0 +1,264 @@ +/* + * Created by Phil on 5/12/2012. + * Copyright 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ + +#include "catch_reporter_gtest.h" + +#include "../internal/catch_console_colour.h" +#include "../internal/catch_reporter_registrars.hpp" +#include "../internal/catch_stringref.h" +#include "../internal/catch_text.h" +#include "../internal/catch_version.h" + +#include +#include +#include + +#if defined( _MSC_VER ) +# pragma warning( push ) +# pragma warning( \ + disable : 4061 ) // Not all labels are EXPLICITLY handled in switch +// Note that 4062 (not all labels are handled and default is missing) is enabled +#endif + +#if defined( __clang__ ) +# pragma clang diagnostic push +// For simplicity, benchmarking-only helpers are always enabled +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +namespace Catch { + + namespace { + + struct Coloured { + std::string text; + Colour::Code code; + }; + + std::ostream& operator<<( std::ostream& os, Coloured coloured ) { + Colour colourGuard( coloured.code ); + return os << coloured.text; + } + + } // namespace + + GTestReporter::GTestReporter( ReporterConfig const& config ): + StreamingReporterBase( config ) {} + + GTestReporter::~GTestReporter() = default; + + std::string GTestReporter::getDescription() { + return "Reports each test and section as it runs, similar to Google " + "Test. Entering the root section corresponding to the test body " + "is not reported for each subsection by default, set verbosity " + "to high to change this."; + } + + std::set GTestReporter::getSupportedVerbosities() { + return { Verbosity::Normal, Verbosity::High }; + } + + void GTestReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void GTestReporter::reportInvalidArguments( std::string const& arg ) { + stream << "Invalid Filter: " << arg << std::endl; + } + + void GTestReporter::testRunStarting( TestRunInfo const& _testInfo ) { + StreamingReporterBase::testRunStarting( _testInfo ); + printTestFilters(); + m_runStats.timer.start(); + stream << Coloured{ "[==========] ", Colour::Green } << "Running " + << _testInfo.name << "\n"; + } + + void GTestReporter::testCaseStarting( TestCaseInfo const& _testInfo ) { + StreamingReporterBase::testCaseStarting( _testInfo ); + m_testCaseStats = {}; + m_testCaseStats.timer.start(); + stream << Coloured{ "[----------] ", Colour::Green } << _testInfo.name + << "\n"; + if ( shouldPrintRootSectionOnlyOnce() ) { + stream << Coloured{ "[ RUN ] ", Colour::Green } + << _testInfo.name << "\n"; + } + } + + void GTestReporter::sectionStarting( SectionInfo const& _sectionInfo ) { + StreamingReporterBase::sectionStarting( _sectionInfo ); + m_prevNestedSectionAssertionFailures = 0; + if ( m_sectionStack.size() > 1 || !shouldPrintRootSectionOnlyOnce() ) { + stream << Coloured{ "[ RUN ] ", Colour::Green } + << formatFullSectionName() << "\n"; + } + } + + void GTestReporter::assertionStarting( AssertionInfo const& ) {} + + bool + GTestReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = + m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if ( !includeResults && result.getResultType() != ResultWas::Warning ) + return false; + + ConsoleAssertionPrinter printer( + stream, _assertionStats, includeResults ); + printer.print(); + stream << std::endl; + return true; + } + + void GTestReporter::sectionEnded( SectionStats const& _sectionStats ) { + if ( _sectionStats.missingAssertions ) { + Colour colour( Colour::ResultError ); + if ( m_sectionStack.size() > 1 ) + stream << "No assertions in section\n"; + else + stream << "No assertions in test case\n"; + } + const auto sectionName = formatFullSectionName(); + if ( m_sectionStack.size() > 1 || !shouldPrintRootSectionOnlyOnce() ) { + stream << ( _sectionStats.assertions.allOk() + ? Coloured{ "[ OK ] ", Colour::Green } + : Coloured{ "[ FAILED ] ", Colour::Red } ) + << sectionName << " (" + << formatDuration( _sectionStats.durationInSeconds ) + << ")\n"; + } + if ( m_sectionStack.size() == 1 ) { + if ( _sectionStats.assertions.allPassed() ) { + m_testCaseStats.tests.passed++; + } else if ( _sectionStats.assertions.allOk() ) { + m_testCaseStats.tests.failedButOk++; + } else { + m_testCaseStats.tests.failed++; + } + } + if ( _sectionStats.assertions.failed > + m_prevNestedSectionAssertionFailures ) { + if ( !m_failedSectionsSet.count( sectionName ) ) { + m_failedSections.emplace_back( sectionName ); + m_failedSectionsSet.insert( sectionName ); + } + } + m_prevNestedSectionAssertionFailures = _sectionStats.assertions.failed; + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + void GTestReporter::testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_runStats.tests += m_testCaseStats.tests; + if ( shouldPrintRootSectionOnlyOnce() ) { + stream << ( _testCaseStats.totals.testCases.allOk() + ? Coloured{ "[ OK ] ", Colour::Green } + : Coloured{ "[ FAILED ] ", Colour::Red } ) + << _testCaseStats.testInfo.name << "\n"; + } + stream << Coloured{ "[----------] ", Colour::Green } + << pluralise( m_testCaseStats.tests.total(), "test" ) << " from " + << _testCaseStats.testInfo.name << " (" + << formatDuration( m_testCaseStats.timer.getElapsedSeconds() ) + << " total)\n"; + if ( m_testCaseStats.tests.failedButOk > 0 ) { + stream << Coloured{ "[ EXPECTED ] ", Colour::Yellow } + << pluralise( m_testCaseStats.tests.failedButOk, "test" ) + << " failed as expected\n"; + } + if ( m_testCaseStats.tests.failed > 0 ) { + stream << Coloured{ "[ FAILED ] ", Colour::Red } + << pluralise( m_testCaseStats.tests.failed, "test" ) + << " failed\n"; + } + stream << "\n"; + } + + void GTestReporter::testRunEnded( TestRunStats const& _testRunStats ) { + StreamingReporterBase::testRunEnded( _testRunStats ); + stream << Coloured{ "[==========] ", Colour::Green } + << pluralise( m_runStats.tests.total(), "test" ) << " from " + << pluralise( _testRunStats.totals.testCases.total(), + "test case" ) + << " run (" + << formatDuration( m_runStats.timer.getElapsedSeconds() ) + << " total)\n"; + stream << Coloured{ "[ PASSED ] ", Colour::Green } + << pluralise( m_runStats.tests.passed, "test" ) << " passed\n"; + if ( m_runStats.tests.failedButOk > 0 ) { + stream << Coloured{ "[ EXPECTED ] ", Colour::Yellow } + << pluralise( m_runStats.tests.failedButOk, "test" ) + << " failed as expected\n"; + } + if ( m_runStats.tests.failed > 0 ) { + stream << Coloured{ "[ FAILED ] ", Colour::Red } + << pluralise( m_runStats.tests.failed, "test" ) << " from " + << pluralise( _testRunStats.totals.testCases.failed, + "test case" ) + << " failed in " + << pluralise( m_failedSections.size(), "section" ) << ":\n"; + for ( const auto& section : m_failedSections ) { + stream << Coloured{ "[ FAILED ] ", Colour::Red } << section + << "\n"; + } + stream << "\n " + << toUpper( + pluralise( m_runStats.tests.failed, "failed test" ) + .str() ) + << "\n"; + } + } + + bool GTestReporter::shouldPrintRootSectionOnlyOnce() const { + return m_config->verbosity() <= Verbosity::Normal; + } + + std::string GTestReporter::formatFullSectionName() { + std::stringstream ss; + bool need_separator = false; + for ( const auto& section : m_sectionStack ) { + if ( need_separator ) { + ss << " / "; + } else { + need_separator = true; + } + ss << trim( section.name ); + } + return ss.str(); + } + + void GTestReporter::printTestFilters() { + if ( m_config->testSpec().hasFilters() ) { + Colour guard( Colour::Yellow ); + stream << "Filters: " + << serializeFilters( m_config->getTestsOrTags() ) << '\n'; + } + } + + std::string GTestReporter::formatDuration( double seconds ) { + return std::to_string( + static_cast( std::round( seconds * 1000 ) ) ) + + " ms"; + } + + CATCH_REGISTER_REPORTER( "gtest", GTestReporter ) + +} // end namespace Catch + +#if defined( _MSC_VER ) +# pragma warning( pop ) +#endif + +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif diff --git a/include/reporters/catch_reporter_gtest.h b/include/reporters/catch_reporter_gtest.h new file mode 100644 index 0000000000..c88c180625 --- /dev/null +++ b/include/reporters/catch_reporter_gtest.h @@ -0,0 +1,73 @@ +/* + * Created by Phil on 5/12/2012. + * Copyright 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CATCH_REPORTER_CONSOLE_H_INCLUDED +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_H_INCLUDED + +#include "catch_reporter_bases.hpp" +#include "internal/catch_timer.h" + +#include + +#if defined( _MSC_VER ) +# pragma warning( push ) +# pragma warning( \ + disable : 4061 ) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + struct GTestReporter : StreamingReporterBase { + GTestReporter( ReporterConfig const& config ); + ~GTestReporter() override; + static std::string getDescription(); + static std::set getSupportedVerbosities(); + + void noMatchingTestCases( std::string const& spec ) override; + + void reportInvalidArguments( std::string const& arg ) override; + + void testRunStarting( TestRunInfo const& _testRunInfo ) override; + void testCaseStarting( TestCaseInfo const& _testInfo ) override; + void sectionStarting( SectionInfo const& _sectionInfo ) override; + void assertionStarting( AssertionInfo const& ) override; + + bool assertionEnded( AssertionStats const& _assertionStats ) override; + void sectionEnded( SectionStats const& _sectionStats ) override; + void testCaseEnded( TestCaseStats const& _testCaseStats ) override; + void testRunEnded( TestRunStats const& _testRunStats ) override; + + private: + bool shouldPrintRootSectionOnlyOnce() const; + std::string formatFullSectionName(); + + void printTestFilters(); + + static std::string formatDuration( double seconds ) ; + + private: + struct Stats { + Timer timer; + // By "test" here we mean one pass through a test case. Each leaf + // section corresponds to a test. + Counts tests; + }; + Stats m_runStats; + Stats m_testCaseStats; + std::size_t m_prevNestedSectionAssertionFailures; + std::vector m_failedSections; + std::unordered_set m_failedSectionsSet; + }; + +} // end namespace Catch + +#if defined( _MSC_VER ) +# pragma warning( pop ) +#endif + +#endif // TWOBLUECUBES_CATCH_REPORTER_CONSOLE_H_INCLUDED \ No newline at end of file diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index aee85974a0..d43f2ecdf3 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -279,6 +279,7 @@ set(REPORTER_HEADERS ${HEADER_DIR}/reporters/catch_reporter_bases.hpp ${HEADER_DIR}/reporters/catch_reporter_compact.h ${HEADER_DIR}/reporters/catch_reporter_console.h + ${HEADER_DIR}/reporters/catch_reporter_gtest.h ${HEADER_DIR}/reporters/catch_reporter_junit.h ${HEADER_DIR}/reporters/catch_reporter_listening.h ${HEADER_DIR}/reporters/catch_reporter_tap.hpp @@ -290,6 +291,7 @@ set(REPORTER_SOURCES ${HEADER_DIR}/reporters/catch_reporter_bases.cpp ${HEADER_DIR}/reporters/catch_reporter_compact.cpp ${HEADER_DIR}/reporters/catch_reporter_console.cpp + ${HEADER_DIR}/reporters/catch_reporter_gtest.cpp ${HEADER_DIR}/reporters/catch_reporter_junit.cpp ${HEADER_DIR}/reporters/catch_reporter_listening.cpp ${HEADER_DIR}/reporters/catch_reporter_xml.cpp