diff --git a/include/catch.hpp b/include/catch.hpp index 447e3b29..b05c485b 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -32,6 +32,7 @@ #include "internal/catch_approx.hpp" #include "internal/catch_matchers.hpp" #include "internal/catch_compiler_capabilities.h" +#include "internal/catch_interfaces_tag_alias_registry.h" // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections diff --git a/include/catch_runner.hpp b/include/catch_runner.hpp index 3e1e42b2..42069b9f 100644 --- a/include/catch_runner.hpp +++ b/include/catch_runner.hpp @@ -41,7 +41,7 @@ namespace Catch { TestSpec testSpec = m_config->testSpec(); if( !testSpec.hasFilters() ) - testSpec = TestSpecParser().parse( "~[.]" ).testSpec(); // All not hidden tests + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests std::vector testCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); diff --git a/include/internal/catch_config.hpp b/include/internal/catch_config.hpp index ab1d2aa8..1217b9a9 100644 --- a/include/internal/catch_config.hpp +++ b/include/internal/catch_config.hpp @@ -84,7 +84,7 @@ namespace Catch { m_os( std::cout.rdbuf() ) { if( !data.testsOrTags.empty() ) { - TestSpecParser parser; + TestSpecParser parser( ITagAliasRegistry::get() ); for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) parser.parse( data.testsOrTags[i] ); m_testSpec = parser.testSpec(); diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp index 1d621823..cc03ae08 100644 --- a/include/internal/catch_impl.hpp +++ b/include/internal/catch_impl.hpp @@ -34,6 +34,7 @@ #include "catch_debugger.hpp" #include "catch_tostring.hpp" #include "catch_result_builder.hpp" +#include "catch_tag_alias_registry.hpp" #include "../reporters/catch_reporter_xml.hpp" #include "../reporters/catch_reporter_junit.hpp" diff --git a/include/internal/catch_interfaces_tag_alias_registry.h b/include/internal/catch_interfaces_tag_alias_registry.h new file mode 100644 index 00000000..cd6ac51d --- /dev/null +++ b/include/internal/catch_interfaces_tag_alias_registry.h @@ -0,0 +1,26 @@ +/* + * Created by Phil on 27/6/2014. + * Copyright 2014 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_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include "catch_tag_alias.h" +#include "catch_option.hpp" + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED diff --git a/include/internal/catch_list.hpp b/include/internal/catch_list.hpp index fb640da5..6a75b7de 100644 --- a/include/internal/catch_list.hpp +++ b/include/internal/catch_list.hpp @@ -26,7 +26,7 @@ namespace Catch { std::cout << "Matching test cases:\n"; else { std::cout << "All available test cases:\n"; - testSpec = TestSpecParser().parse( "*" ).testSpec(); + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; @@ -61,7 +61,7 @@ namespace Catch { inline std::size_t listTestsNamesOnly( Config const& config ) { TestSpec testSpec = config.testSpec(); if( !config.testSpec().hasFilters() ) - testSpec = TestSpecParser().parse( "*" ).testSpec(); + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; std::vector matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); @@ -99,7 +99,7 @@ namespace Catch { std::cout << "Tags for matching test cases:\n"; else { std::cout << "All available tags:\n"; - testSpec = TestSpecParser().parse( "*" ).testSpec(); + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; diff --git a/include/internal/catch_tag_alias.h b/include/internal/catch_tag_alias.h new file mode 100644 index 00000000..6dde74ad --- /dev/null +++ b/include/internal/catch_tag_alias.h @@ -0,0 +1,32 @@ +/* + * Created by Phil on 27/6/2014. + * Copyright 2014 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_TAG_ALIAS_H_INCLUDED +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include "catch_common.h" + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } diff --git a/include/internal/catch_tag_alias_registry.h b/include/internal/catch_tag_alias_registry.h new file mode 100644 index 00000000..98c796e1 --- /dev/null +++ b/include/internal/catch_tag_alias_registry.h @@ -0,0 +1,31 @@ +/* + * Created by Phil on 27/6/2014. + * Copyright 2014 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_TAG_ALIAS_REGISTRY_H_INCLUDED +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include "catch_interfaces_tag_alias_registry.h" + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED diff --git a/include/internal/catch_tag_alias_registry.hpp b/include/internal/catch_tag_alias_registry.hpp new file mode 100644 index 00000000..fea7ce9c --- /dev/null +++ b/include/internal/catch_tag_alias_registry.hpp @@ -0,0 +1,83 @@ +/* + * Created by Phil on 27/6/2014. + * Copyright 2014 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_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +#include "catch_tag_alias_registry.h" +#include "catch_console_colour.hpp" + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + std::cerr << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED diff --git a/include/internal/catch_test_spec_parser.hpp b/include/internal/catch_test_spec_parser.hpp index 6fc356e8..157893f5 100644 --- a/include/internal/catch_test_spec_parser.hpp +++ b/include/internal/catch_test_spec_parser.hpp @@ -14,6 +14,7 @@ #endif #include "catch_test_spec.hpp" +#include "catch_interfaces_tag_alias_registry.h" namespace Catch { @@ -25,13 +26,16 @@ namespace Catch { std::string m_arg; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; + ITagAliasRegistry const& m_tagAliases; public: - TestSpecParser parse( std::string const& arg ) { + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { m_mode = None; m_exclusion = false; m_start = std::string::npos; - m_arg = arg; + m_arg = m_tagAliases.expandAliases( arg ); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) @@ -100,7 +104,7 @@ namespace Catch { } }; inline TestSpec parseTestSpec( std::string const& arg ) { - return TestSpecParser().parse( arg ).testSpec(); + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } } // namespace Catch diff --git a/projects/SelfTest/TagAliasTests.cpp b/projects/SelfTest/TagAliasTests.cpp new file mode 100644 index 00000000..b95ad009 --- /dev/null +++ b/projects/SelfTest/TagAliasTests.cpp @@ -0,0 +1,39 @@ +/* + * Created by Phil on 27/06/2014. + * Copyright 2014 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.hpp" +#include "internal/catch_tag_alias_registry.h" + +TEST_CASE( "Tag alias can be registered against tag patterns", "" ) { + + Catch::TagAliasRegistry registry; + + registry.add( "[@zzz]", "[one][two]", Catch::SourceLineInfo( "file", 2 ) ); + + SECTION( "The same tag alias can only be registered once", "" ) { + + try { + registry.add( "[@zzz]", "[one][two]", Catch::SourceLineInfo( "file", 10 ) ); + FAIL( "expected exception" ); + } + catch( std::exception& ex ) { + std::string what = ex.what(); + CHECK_THAT( what, Contains( "[@zzz]" ) ); + CHECK_THAT( what, Contains( "file" ) ); + CHECK_THAT( what, Contains( "2" ) ); + CHECK_THAT( what, Contains( "10" ) ); + } + } + + SECTION( "Tag aliases must be of the form [@name]", "" ) { + CHECK_THROWS( registry.add( "[no ampersat]", "", Catch::SourceLineInfo( "file", 3 ) ) ); + CHECK_THROWS( registry.add( "[the @ is not at the start]", "", Catch::SourceLineInfo( "file", 3 ) ) ); + CHECK_THROWS( registry.add( "@no square bracket at start]", "", Catch::SourceLineInfo( "file", 3 ) ) ); + CHECK_THROWS( registry.add( "[@no square bracket at end", "", Catch::SourceLineInfo( "file", 3 ) ) ); + } +} diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index 5046cafc..edc8aa1f 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -9,11 +9,17 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" +// Some example tag aliases +CATCH_REGISTER_TAG_ALIAS( "[@nhf]", "[failing]~[.]" ) +CATCH_REGISTER_TAG_ALIAS( "[@tricky]", "[tricky]~[.]" ) + + #ifdef __clang__ #pragma clang diagnostic ignored "-Wpadded" #pragma clang diagnostic ignored "-Wweak-vtables" #endif + template void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { Catch::Clara::CommandLine parser = Catch::makeCommandLineParser(); diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj b/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj index a7a3bc04..9ad4cda4 100644 --- a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj +++ b/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 2656C2211925E7330040DB02 /* catch_test_spec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2656C2201925E7330040DB02 /* catch_test_spec.cpp */; }; 266B06B816F3A60A004ED264 /* VariadicMacrosTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 266B06B616F3A60A004ED264 /* VariadicMacrosTests.cpp */; }; 266ECD74170F3C620030D735 /* BDDTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 266ECD73170F3C620030D735 /* BDDTests.cpp */; }; + 26711C8F195D465C0033EDA2 /* TagAliasTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26711C8D195D465C0033EDA2 /* TagAliasTests.cpp */; }; 26847E5F16BBADB40043B9C1 /* catch_message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26847E5D16BBADB40043B9C1 /* catch_message.cpp */; }; 26948286179A9AB900ED166E /* SectionTrackerTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26948284179A9AB900ED166E /* SectionTrackerTests.cpp */; }; 2694A1FD16A0000E004816E3 /* catch_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2694A1FB16A0000E004816E3 /* catch_text.cpp */; }; @@ -75,6 +76,11 @@ 266ECD73170F3C620030D735 /* BDDTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BDDTests.cpp; path = ../../../SelfTest/BDDTests.cpp; sourceTree = ""; }; 266ECD8C1713614B0030D735 /* catch_legacy_reporter_adapter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_legacy_reporter_adapter.hpp; sourceTree = ""; }; 266ECD8D1713614B0030D735 /* catch_legacy_reporter_adapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = catch_legacy_reporter_adapter.h; sourceTree = ""; }; + 26711C8D195D465C0033EDA2 /* TagAliasTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TagAliasTests.cpp; path = ../../../SelfTest/TagAliasTests.cpp; sourceTree = ""; }; + 26711C90195D46CD0033EDA2 /* catch_interfaces_tag_alias_registry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_interfaces_tag_alias_registry.h; sourceTree = ""; }; + 26711C91195D47820033EDA2 /* catch_tag_alias.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_tag_alias.h; sourceTree = ""; }; + 26711C92195D48F60033EDA2 /* catch_tag_alias_registry.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_tag_alias_registry.hpp; sourceTree = ""; }; + 26711C94195D4B120033EDA2 /* catch_tag_alias_registry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_tag_alias_registry.h; sourceTree = ""; }; 26759472171C72A400A84BD1 /* catch_sfinae.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = catch_sfinae.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; 26759473171C74C200A84BD1 /* catch_compiler_capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = catch_compiler_capabilities.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 26847E5B16BBAB790043B9C1 /* catch_message.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_message.h; sourceTree = ""; }; @@ -195,6 +201,7 @@ children = ( 26948284179A9AB900ED166E /* SectionTrackerTests.cpp */, 26E1B7D119213BC900812682 /* CmdLineTests.cpp */, + 26711C8D195D465C0033EDA2 /* TagAliasTests.cpp */, ); name = "Introspective Tests"; sourceTree = ""; @@ -341,6 +348,7 @@ 4A084F1C15DACEEA0027E631 /* catch_test_case_info.hpp */, 26847E5C16BBACB60043B9C1 /* catch_message.hpp */, 2627F7061935B55F009BCE2D /* catch_result_builder.hpp */, + 26711C92195D48F60033EDA2 /* catch_tag_alias_registry.hpp */, ); name = impl; sourceTree = ""; @@ -389,6 +397,8 @@ 4A7ADB4314F631E10094FE10 /* catch_totals.hpp */, 4AB77CB71553B72B00857BF0 /* catch_section_info.hpp */, 26948287179EF7F900ED166E /* catch_test_case_tracker.hpp */, + 26711C91195D47820033EDA2 /* catch_tag_alias.h */, + 26711C94195D4B120033EDA2 /* catch_tag_alias_registry.h */, ); name = "Test execution"; sourceTree = ""; @@ -414,6 +424,7 @@ 4A6D0C57149B3E3D00DB3EAA /* catch_interfaces_testcase.h */, 4AFC661D157E96A7009D58CF /* catch_interfaces_config.h */, 4A90B59B15D0F61A00EF71BC /* catch_interfaces_generators.h */, + 26711C90195D46CD0033EDA2 /* catch_interfaces_tag_alias_registry.h */, ); name = Interfaces; sourceTree = ""; @@ -527,6 +538,7 @@ 4A45DA2D16161FA2004F8D6B /* catch_interfaces_capture.cpp in Sources */, 4A45DA3116161FFC004F8D6B /* catch_interfaces_reporter.cpp in Sources */, 4A45DA3316162047004F8D6B /* catch_interfaces_exception.cpp in Sources */, + 26711C8F195D465C0033EDA2 /* TagAliasTests.cpp in Sources */, 4A45DA3516162071004F8D6B /* catch_interfaces_runner.cpp in Sources */, 4AB3D99D1616216500C9A0F8 /* catch_interfaces_testcase.cpp in Sources */, 4AB3D9A01616219100C9A0F8 /* catch_interfaces_config.cpp in Sources */,