/* * Created by Phil on 27/11/2013. * Copyright 2013 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_BASES_HPP_INCLUDED #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #include "../internal/catch_interfaces_reporter.h" #include "../internal/catch_errno_guard.hpp" #include #include #include #include #include namespace Catch { namespace { // Because formatting using c++ streams is stateful, drop down to C is required // Alternatively we could use stringstream, but its performance is... not good. std::string getFormattedDuration( double duration ) { // Max exponent + 1 is required to represent the whole part // + 1 for decimal point // + 3 for the 3 decimal places // + 1 for null terminator const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; char buffer[maxDoubleSize]; // Save previous errno, to prevent sprintf from overwriting it ErrnoGuard guard; #ifdef _MSC_VER sprintf_s(buffer, "%.3f", duration); #else sprintf(buffer, "%.3f", duration); #endif return std::string(buffer); } } struct StreamingReporterBase : IStreamingReporter { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; CATCH_ENFORCE( getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); } virtual ReporterPreferences getPreferences() const override { return m_reporterPrefs; } virtual std::set const& getSupportedVerbosities() const override { static std::set supported{ Verbosity::Normal }; return supported; } virtual ~StreamingReporterBase() override; virtual void noMatchingTestCases( std::string const& ) override {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) override { currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) override { currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) override { currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) override { m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) override { m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) override { currentTestCaseInfo.reset(); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) override { currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) override { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } virtual void skipTest( TestCaseInfo const& ) override { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } IConfigPtr m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : IStreamingReporter { template struct Node { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} using ChildNodes = std::vector>; T value; ChildNodes children; }; struct SectionNode { explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} virtual ~SectionNode(); bool operator == ( SectionNode const& other ) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } bool operator == ( std::shared_ptr const& other ) const { return operator==( *other ); } SectionStats stats; using ChildSections = std::vector>; using Assertions = std::vector; ChildSections childSections; Assertions assertions; std::string stdOut; std::string stdErr; }; struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( std::shared_ptr const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; using TestCaseNode = Node; using TestGroupNode = Node; using TestRunNode = Node; CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; CATCH_ENFORCE( getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); } ~CumulativeReporterBase(); virtual ReporterPreferences getPreferences() const override { return m_reporterPrefs; } virtual std::set const& getSupportedVerbosities() const override { static std::set supported{ Verbosity::Normal }; return supported; } virtual void testRunStarting( TestRunInfo const& ) override {} virtual void testGroupStarting( GroupInfo const& ) override {} virtual void testCaseStarting( TestCaseInfo const& ) override {} virtual void sectionStarting( SectionInfo const& sectionInfo ) override { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); std::shared_ptr node; if( m_sectionStack.empty() ) { if( !m_rootSection ) m_rootSection = std::make_shared( incompleteStats ); node = m_rootSection; } else { SectionNode& parentNode = *m_sectionStack.back(); SectionNode::ChildSections::const_iterator it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if( it == parentNode.childSections.end() ) { node = std::make_shared( incompleteStats ); parentNode.childSections.push_back( node ); } else node = *it; } m_sectionStack.push_back( node ); m_deepestSection = node; } virtual void assertionStarting( AssertionInfo const& ) override {} virtual bool assertionEnded( AssertionStats const& assertionStats ) override { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); // AssertionResult holds a pointer to a temporary DecomposedExpression, // which getExpandedExpression() calls to build the expression string. // Our section stack copy of the assertionResult will likely outlive the // temporary, so it must be expanded or discarded now to avoid calling // a destroyed object later. prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) override { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) override { auto node = std::make_shared( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); m_testCases.push_back( node ); m_rootSection.reset(); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) override { auto node = std::make_shared( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) override { auto node = std::make_shared( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); testRunEndedCumulative(); } virtual void testRunEndedCumulative() = 0; virtual void skipTest( TestCaseInfo const& ) override {} virtual void prepareExpandedExpression( AssertionResult& result ) const { if( result.isOk() ) result.discardDecomposedExpression(); else result.expandDecomposedExpression(); } IConfigPtr m_config; std::ostream& stream; std::vector m_assertions; std::vector>> m_sections; std::vector> m_testCases; std::vector> m_testGroups; std::vector> m_testRuns; std::shared_ptr m_rootSection; std::shared_ptr m_deepestSection; std::vector> m_sectionStack; ReporterPreferences m_reporterPrefs; }; template char const* getLineOfChars() { static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; if( !*line ) { std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; } return line; } struct TestEventListenerBase : StreamingReporterBase { TestEventListenerBase( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual void assertionStarting( AssertionInfo const& ) override {} virtual bool assertionEnded( AssertionStats const& ) override { return false; } }; } // end namespace Catch #endif // TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED