Do not copy around TestCaseInfo

Now a `TEST_CASE` macro should create a single TestCaseInfo and then
it should never be copied around. This, together with latter changes,
should significantly decrease the number of allocations made before
`main` is even entered.
This commit is contained in:
Martin Hořeňovský
2019-11-04 21:35:57 +01:00
parent 019b0a0fe0
commit 302e2c0b06
33 changed files with 631 additions and 626 deletions

View File

@@ -15,13 +15,15 @@
namespace Catch {
class TestCase;
class TestCaseHandle;
struct TestCaseInfo;
struct ITestCaseRegistry;
struct IExceptionTranslatorRegistry;
struct IExceptionTranslator;
struct IReporterRegistry;
struct IReporterFactory;
struct ITagAliasRegistry;
struct ITestInvoker;
struct IMutableEnumValuesRegistry;
class StartupExceptionRegistry;
@@ -44,7 +46,7 @@ namespace Catch {
virtual ~IMutableRegistryHub();
virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0;
virtual void registerListener( IReporterFactoryPtr const& factory ) = 0;
virtual void registerTest( TestCase const& testInfo ) = 0;
virtual void registerTest(std::unique_ptr<TestCaseInfo>&& testInfo, std::unique_ptr<ITestInvoker>&& invoker) = 0;
virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
virtual void registerStartupException() noexcept = 0;

View File

@@ -76,7 +76,7 @@ namespace Catch {
std::string const& _stdOut,
std::string const& _stdErr,
bool _aborting )
: testInfo( _testInfo ),
: testInfo( &_testInfo ),
totals( _totals ),
stdOut( _stdOut ),
stdErr( _stdErr ),
@@ -141,14 +141,15 @@ namespace Catch {
Catch::cout() << std::endl;
}
void IStreamingReporter::listTests(std::vector<TestCase> const& tests, Config const& config) {
void IStreamingReporter::listTests(std::vector<TestCaseHandle> const& tests, Config const& config) {
if (config.hasTestFilters())
Catch::cout() << "Matching test cases:\n";
else {
Catch::cout() << "All available test cases:\n";
}
for (auto const& testCaseInfo : tests) {
for (auto const& test : tests) {
auto const& testCaseInfo = test.getTestCaseInfo();
Colour::Code colour = testCaseInfo.isHidden()
? Colour::SecondaryText
: Colour::None;

View File

@@ -128,7 +128,7 @@ namespace Catch {
TestCaseStats& operator = ( TestCaseStats && ) = default;
virtual ~TestCaseStats();
TestCaseInfo testInfo;
TestCaseInfo const * testInfo;
Totals totals;
std::string stdOut;
std::string stdErr;
@@ -217,7 +217,7 @@ namespace Catch {
virtual void noMatchingTestCases( std::string const& spec ) = 0;
virtual void reportInvalidArguments(std::string const&) {}
virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
@@ -250,7 +250,7 @@ namespace Catch {
// Listing support
virtual void listReporters(std::vector<ReporterDescription> const& descriptions, Config const& config);
virtual void listTests(std::vector<TestCase> const& tests, Config const& config);
virtual void listTests(std::vector<TestCaseHandle> const& tests, Config const& config);
virtual void listTags(std::vector<TagInfo> const& tags, Config const& config);
};

View File

@@ -19,19 +19,19 @@ namespace Catch {
virtual ~ITestInvoker();
};
class TestCase;
class TestCaseHandle;
struct IConfig;
struct ITestCaseRegistry {
virtual ~ITestCaseRegistry();
virtual std::vector<TestCase> const& getAllTests() const = 0;
virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;
virtual std::vector<TestCaseHandle> const& getAllTests() const = 0;
virtual std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const = 0;
};
bool isThrowSafe( TestCase const& testCase, IConfig const& config );
bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config );
bool matchTest( TestCaseHandle const& testCase, TestSpec const& testSpec, IConfig const& config );
std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config );
std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config );
}

View File

@@ -36,7 +36,7 @@ namespace Catch {
void listTags(IStreamingReporter& reporter, Config const& config) {
TestSpec testSpec = config.testSpec();
std::vector<TestCase> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config);
std::vector<TestCaseHandle> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config);
std::map<std::string, TagInfo> tagCounts;
for (auto const& testCase : matchedTestCases) {

View File

@@ -93,7 +93,7 @@ namespace Catch {
std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
const char* className = class_getName( cls );
getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) );
getMutableRegistryHub().registerTest( makeTestCaseInfo( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) );
noTestMethods++;
}
}

View File

@@ -49,8 +49,8 @@ namespace Catch {
void registerListener( IReporterFactoryPtr const& factory ) override {
m_reporterRegistry.registerListener( factory );
}
void registerTest( TestCase const& testInfo ) override {
m_testCaseRegistry.registerTest( testInfo );
void registerTest( std::unique_ptr<TestCaseInfo>&& testInfo, std::unique_ptr<ITestInvoker>&& invoker ) override {
m_testCaseRegistry.registerTest( std::move(testInfo), std::move(invoker) );
}
void registerTranslator( const IExceptionTranslator* translator ) override {
m_exceptionTranslatorRegistry.registerTranslator( translator );

View File

@@ -93,7 +93,7 @@ namespace Catch {
m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));
}
Totals RunContext::runTest(TestCase const& testCase) {
Totals RunContext::runTest(TestCaseHandle const& testCase) {
Totals prevTotals = m_totals;
std::string redirectedCout;

View File

@@ -44,7 +44,7 @@ namespace Catch {
void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount );
void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount );
Totals runTest(TestCase const& testCase);
Totals runTest(TestCaseHandle const& testCase);
IConfigPtr config() const;
IStreamingReporter& reporter() const;
@@ -133,7 +133,7 @@ namespace Catch {
TestRunInfo m_runInfo;
IMutableContext& m_context;
TestCase const* m_activeTestCase = nullptr;
TestCaseHandle const* m_activeTestCase = nullptr;
ITracker* m_testCaseTracker = nullptr;
Option<AssertionResult> m_lastResult;

View File

@@ -72,7 +72,7 @@ namespace Catch {
if (m_matches.empty() && invalidArgs.empty()) {
for (auto const& test : allTestCases)
if (!test.isHidden())
if (!test.getTestCaseInfo().isHidden())
m_tests.emplace(&test);
} else {
for (auto const& match : m_matches)
@@ -88,7 +88,7 @@ namespace Catch {
if (!m_context.aborting())
totals += m_context.runTest(*testCase);
else
m_context.reporter().skipTest(*testCase);
m_context.reporter().skipTest(testCase->getTestCaseInfo());
}
for (auto const& match : m_matches) {
@@ -108,7 +108,7 @@ namespace Catch {
}
private:
using Tests = std::set<TestCase const*>;
using Tests = std::set<TestCaseHandle const*>;
std::shared_ptr<Config> m_config;
RunContext m_context;
@@ -117,11 +117,11 @@ namespace Catch {
};
void applyFilenamesAsTags(Catch::IConfig const& config) {
auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config));
for (auto& testCase : tests) {
auto tags = testCase.tags;
for (auto const& testCase : getAllTestCasesSorted(config)) {
// Yeah, sue me. This will be removed soon.
auto& testInfo = const_cast<TestCaseInfo&>(testCase.getTestCaseInfo());
std::string filename = testCase.lineInfo.file;
std::string filename = testInfo.lineInfo.file;
auto lastSlash = filename.find_last_of("\\/");
if (lastSlash != std::string::npos) {
filename.erase(0, lastSlash);
@@ -133,8 +133,9 @@ namespace Catch {
filename.erase(lastDot);
}
auto tags = testInfo.tags;
tags.push_back(std::move(filename));
setTags(testCase, tags);
setTags(testInfo, tags);
}
}

View File

@@ -53,10 +53,10 @@ namespace Catch {
}
}
TestCase makeTestCase( ITestInvoker* _testCase,
std::string const& _className,
NameAndTags const& nameAndTags,
SourceLineInfo const& _lineInfo )
std::unique_ptr<TestCaseInfo>
makeTestCaseInfo(std::string const& _className,
NameAndTags const& nameAndTags,
SourceLineInfo const& _lineInfo )
{
bool isHidden = false;
@@ -95,8 +95,8 @@ namespace Catch {
tags.push_back( "." );
}
TestCaseInfo info( static_cast<std::string>(nameAndTags.name), _className, tags, _lineInfo );
return TestCase( _testCase, std::move(info) );
return std::make_unique<TestCaseInfo>(static_cast<std::string>(nameAndTags.name),
_className, tags, _lineInfo);
}
void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) {
@@ -155,26 +155,18 @@ namespace Catch {
}
TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {}
void TestCase::invoke() const {
test->invoke();
bool TestCaseHandle::operator == ( TestCaseHandle const& rhs ) const {
return m_invoker == rhs.m_invoker
&& m_info->name == rhs.m_info->name
&& m_info->className == rhs.m_info->className;
}
bool TestCase::operator == ( TestCase const& other ) const {
return test.get() == other.test.get() &&
name == other.name &&
className == other.className;
bool TestCaseHandle::operator < ( TestCaseHandle const& rhs ) const {
return m_info->name < rhs.m_info->name;
}
bool TestCase::operator < ( TestCase const& other ) const {
return name < other.name;
}
TestCaseInfo const& TestCase::getTestCaseInfo() const
{
return *this;
TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const {
return *m_info;
}
} // end namespace Catch

View File

@@ -24,7 +24,7 @@ namespace Catch {
struct ITestInvoker;
struct TestCaseInfo {
struct TestCaseInfo : NonCopyable {
enum SpecialProperties{
None = 0,
IsHidden = 1 << 1,
@@ -57,24 +57,24 @@ namespace Catch {
SpecialProperties properties;
};
class TestCase : public TestCaseInfo {
class TestCaseHandle {
TestCaseInfo* m_info;
ITestInvoker* m_invoker;
public:
TestCaseHandle(TestCaseInfo* info, ITestInvoker* invoker) :
m_info(info), m_invoker(invoker) {}
TestCase( ITestInvoker* testCase, TestCaseInfo&& info );
void invoke() const;
void invoke() const {
m_invoker->invoke();
}
TestCaseInfo const& getTestCaseInfo() const;
bool operator == ( TestCase const& other ) const;
bool operator < ( TestCase const& other ) const;
private:
std::shared_ptr<ITestInvoker> test;
bool operator== ( TestCaseHandle const& rhs ) const;
bool operator < ( TestCaseHandle const& rhs ) const;
};
TestCase makeTestCase( ITestInvoker* testCase,
std::string const& className,
std::unique_ptr<TestCaseInfo> makeTestCaseInfo( std::string const& className,
NameAndTags const& nameAndTags,
SourceLineInfo const& lineInfo );
}

View File

@@ -19,9 +19,9 @@
namespace Catch {
std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ) {
std::vector<TestCase> sorted = unsortedTestCases;
std::vector<TestCaseHandle> sorted = unsortedTestCases;
switch( config.runOrder() ) {
case RunTests::InLexicographicalOrder:
@@ -38,53 +38,55 @@ namespace Catch {
return sorted;
}
bool isThrowSafe( TestCase const& testCase, IConfig const& config ) {
return !testCase.throws() || config.allowThrows();
bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ) {
return !testCase.getTestCaseInfo().throws() || config.allowThrows();
}
bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {
return testSpec.matches( testCase ) && isThrowSafe( testCase, config );
bool matchTest( TestCaseHandle const& testCase, TestSpec const& testSpec, IConfig const& config ) {
return testSpec.matches( testCase.getTestCaseInfo() ) && isThrowSafe( testCase, config );
}
void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {
std::set<TestCase> seenFunctions;
void enforceNoDuplicateTestCases( std::vector<TestCaseHandle> const& functions ) {
std::set<TestCaseHandle> seenFunctions;
for( auto const& function : functions ) {
auto prev = seenFunctions.insert( function );
CATCH_ENFORCE( prev.second,
"error: TEST_CASE( \"" << function.name << "\" ) already defined.\n"
"error: TEST_CASE( \"" << function.getTestCaseInfo().name << "\" ) already defined.\n"
<< "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
<< "\tRedefined at " << function.getTestCaseInfo().lineInfo );
}
}
std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
std::vector<TestCase> filtered;
std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
std::vector<TestCaseHandle> filtered;
filtered.reserve( testCases.size() );
for (auto const& testCase : testCases) {
if ((!testSpec.hasFilters() && !testCase.isHidden()) ||
if ((!testSpec.hasFilters() && !testCase.getTestCaseInfo().isHidden()) ||
(testSpec.hasFilters() && matchTest(testCase, testSpec, config))) {
filtered.push_back(testCase);
}
}
return filtered;
}
std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {
std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config ) {
return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );
}
void TestRegistry::registerTest( TestCase const& testCase ) {
m_functions.push_back( testCase );
void TestRegistry::registerTest(std::unique_ptr<TestCaseInfo> testInfo, std::unique_ptr<ITestInvoker> testInvoker) {
m_handles.emplace_back(testInfo.get(), testInvoker.get());
m_infos.push_back(std::move(testInfo));
m_invokers.push_back(std::move(testInvoker));
}
std::vector<TestCase> const& TestRegistry::getAllTests() const {
return m_functions;
std::vector<TestCaseHandle> const& TestRegistry::getAllTests() const {
return m_handles;
}
std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const {
std::vector<TestCaseHandle> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const {
if( m_sortedFunctions.empty() )
enforceNoDuplicateTestCases( m_functions );
enforceNoDuplicateTestCases( m_handles );
if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {
m_sortedFunctions = sortTests( config, m_functions );
m_sortedFunctions = sortTests( config, m_handles );
m_currentSortOrder = config.runOrder();
}
return m_sortedFunctions;
@@ -93,8 +95,6 @@ namespace Catch {
///////////////////////////////////////////////////////////////////////////
TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {}
void TestInvokerAsFunction::invoke() const {
m_testAsFunction();
}

View File

@@ -15,44 +15,47 @@
#include <vector>
#include <set>
#include <algorithm>
#include <ios>
namespace Catch {
class TestCase;
class TestCaseHandle;
struct IConfig;
std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases );
std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases );
bool isThrowSafe( TestCase const& testCase, IConfig const& config );
bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config );
bool matchTest( TestCaseHandle const& testCase, TestSpec const& testSpec, IConfig const& config );
void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions );
void enforceNoDuplicateTestCases( std::vector<TestCaseHandle> const& functions );
std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config );
std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config );
class TestRegistry : public ITestCaseRegistry {
public:
virtual ~TestRegistry() = default;
virtual void registerTest( TestCase const& testCase );
virtual void registerTest( std::unique_ptr<TestCaseInfo> testInfo, std::unique_ptr<ITestInvoker> testInvoker );
std::vector<TestCase> const& getAllTests() const override;
std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override;
std::vector<TestCaseHandle> const& getAllTests() const override;
std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const override;
private:
std::vector<TestCase> m_functions;
std::vector<std::unique_ptr<TestCaseInfo>> m_infos;
std::vector<std::unique_ptr<ITestInvoker>> m_invokers;
std::vector<TestCaseHandle> m_handles;
mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder;
mutable std::vector<TestCase> m_sortedFunctions;
mutable std::vector<TestCaseHandle> m_sortedFunctions;
};
///////////////////////////////////////////////////////////////////////////
class TestInvokerAsFunction : public ITestInvoker {
void(*m_testAsFunction)();
class TestInvokerAsFunction final : public ITestInvoker {
using TestType = void(*)();
TestType m_testAsFunction;
public:
TestInvokerAsFunction( void(*testAsFunction)() ) noexcept;
TestInvokerAsFunction(TestType testAsFunction) noexcept:
m_testAsFunction(testAsFunction) {}
void invoke() const override;
};

View File

@@ -12,19 +12,19 @@
namespace Catch {
auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* {
return new(std::nothrow) TestInvokerAsFunction( testAsFunction );
std::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() ) {
return std::make_unique<TestInvokerAsFunction>( testAsFunction );
}
AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {
AutoReg::AutoReg( std::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {
CATCH_TRY {
getMutableRegistryHub()
.registerTest(
makeTestCase(
invoker,
makeTestCaseInfo(
extractClassName( classOrMethod ),
nameAndTags,
lineInfo));
lineInfo),
std::move(invoker));
} CATCH_CATCH_ALL {
// Do not throw when constructing global objects, instead register the exception to be processed later
getMutableRegistryHub().registerStartupException();

View File

@@ -15,6 +15,8 @@
#include "catch_preprocessor.hpp"
#include "catch_meta.hpp"
#include <memory>
namespace Catch {
template<typename C>
@@ -29,11 +31,11 @@ public:
}
};
auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*;
std::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() );
template<typename C>
auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* {
return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod );
std::unique_ptr<ITestInvoker> makeTestInvoker( void (C::*testAsMethod)() ) {
return std::make_unique<TestInvokerAsMethod<C>>( testAsMethod );
}
struct NameAndTags {
@@ -45,7 +47,7 @@ struct NameAndTags {
};
struct AutoReg : NonCopyable {
AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;
AutoReg( std::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;
};
} // end namespace Catch

View File

@@ -84,13 +84,13 @@ namespace Catch {
return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } );
}
TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const
TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const
{
Matches matches( m_filters.size() );
std::transform( m_filters.begin(), m_filters.end(), matches.begin(), [&]( Filter const& filter ){
std::vector<TestCase const*> currentMatches;
std::vector<TestCaseHandle const*> currentMatches;
for( auto const& test : testCases )
if( isThrowSafe( test, config ) && filter.matches( test ) )
if( isThrowSafe( test, config ) && filter.matches( test.getTestCaseInfo() ) )
currentMatches.emplace_back( &test );
return FilterMatch{ filter.name(), currentMatches };
} );

View File

@@ -63,14 +63,14 @@ namespace Catch {
public:
struct FilterMatch {
std::string name;
std::vector<TestCase const*> tests;
std::vector<TestCaseHandle const*> tests;
};
using Matches = std::vector<FilterMatch>;
using vectorStrings = std::vector<std::string>;
bool hasFilters() const;
bool matches( TestCaseInfo const& testCase ) const;
Matches matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const;
Matches matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const;
const vectorStrings & getInvalidArgs() const;
private: