diff --git a/CMakeLists.txt b/CMakeLists.txt index dfea23cb..e581becc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,8 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_section.hpp ${HEADER_DIR}/internal/catch_section_info.h ${HEADER_DIR}/internal/catch_section_info.hpp + ${HEADER_DIR}/internal/catch_startup_exception_registry.h + ${HEADER_DIR}/internal/catch_startup_exception_registry.hpp ${HEADER_DIR}/internal/catch_stream.h ${HEADER_DIR}/internal/catch_stream.hpp ${HEADER_DIR}/internal/catch_streambuf.h diff --git a/docs/own-main.md b/docs/own-main.md index 67e551f3..91a79b26 100644 --- a/docs/own-main.md +++ b/docs/own-main.md @@ -16,8 +16,7 @@ If you just need to have code that executes before and/ or after Catch this is t #define CATCH_CONFIG_RUNNER #include "catch.hpp" -int main( int argc, char* argv[] ) -{ +int main( int argc, char* argv[] ) { // global setup... int result = Catch::Session().run( argc, argv ); @@ -42,7 +41,22 @@ int main( int argc, char* argv[] ) // writing to session.configData() here sets defaults // this is the preferred way to set them - + + // Verify that all tests, aliases, etc registered properly + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch (std::exception const& ex) { + Catch::cerr() << ex.what(); + } + } + // Indicate that an error occured before main + return 1; + } + int returnCode = session.applyCommandLine( argc, argv ); if( returnCode != 0 ) // Indicates a command line error return returnCode; diff --git a/include/catch_session.hpp b/include/catch_session.hpp index dc356614..4fcdfafe 100644 --- a/include/catch_session.hpp +++ b/include/catch_session.hpp @@ -15,6 +15,7 @@ #include "internal/catch_version.h" #include "internal/catch_text.h" #include "internal/catch_interfaces_reporter.h" +#include "internal/catch_startup_exception_registry.h" #include #include @@ -146,7 +147,19 @@ namespace Catch { } int run( int argc, char const* const* const argv ) { - + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + Catch::cerr() << "Errors occured during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << ex.what() << '\n'; + } + } + return 1; + } int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); diff --git a/include/internal/catch_common.h b/include/internal/catch_common.h index 630a65c9..87b7020f 100644 --- a/include/internal/catch_common.h +++ b/include/internal/catch_common.h @@ -120,10 +120,12 @@ namespace Catch { #define CATCH_INTERNAL_LINEINFO \ ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( static_cast( std::ostringstream() << msg ).str() ) #define CATCH_INTERNAL_ERROR( msg ) \ - throw std::logic_error( static_cast( std::ostringstream() << CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg ).str() ) + throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); #define CATCH_ERROR( msg ) \ - throw std::domain_error( static_cast( std::ostringstream() << msg ).str() ) + throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) #define CATCH_ENFORCE( condition, msg ) \ do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp index 978ca1a4..eb35ed11 100644 --- a/include/internal/catch_impl.hpp +++ b/include/internal/catch_impl.hpp @@ -36,6 +36,7 @@ #include "catch_tag_alias_registry.hpp" #include "catch_test_case_tracker.hpp" #include "catch_matchers_string.hpp" +#include "catch_startup_exception_registry.hpp" #include "../reporters/catch_reporter_multi.hpp" #include "../reporters/catch_reporter_xml.hpp" diff --git a/include/internal/catch_interfaces_registry_hub.h b/include/internal/catch_interfaces_registry_hub.h index fb7d87cf..8934d008 100644 --- a/include/internal/catch_interfaces_registry_hub.h +++ b/include/internal/catch_interfaces_registry_hub.h @@ -22,6 +22,7 @@ namespace Catch { struct IReporterRegistry; struct IReporterFactory; struct ITagAliasRegistry; + class StartupExceptionRegistry; using IReporterFactoryPtr = std::shared_ptr; @@ -33,6 +34,9 @@ namespace Catch { virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; }; struct IMutableRegistryHub { @@ -42,6 +46,7 @@ namespace Catch { virtual void registerTest( TestCase const& testInfo ) = 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( std::exception_ptr const& exception ) = 0; }; IRegistryHub& getRegistryHub(); diff --git a/include/internal/catch_registry_hub.hpp b/include/internal/catch_registry_hub.hpp index d51306fd..5e83203f 100644 --- a/include/internal/catch_registry_hub.hpp +++ b/include/internal/catch_registry_hub.hpp @@ -14,6 +14,7 @@ #include "catch_reporter_registry.hpp" #include "catch_exception_translator_registry.hpp" #include "catch_tag_alias_registry.h" +#include "catch_startup_exception_registry.h" namespace Catch { @@ -39,7 +40,9 @@ namespace Catch { virtual ITagAliasRegistry const& getTagAliasRegistry() const override { return m_tagAliasRegistry; } - + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } public: // IMutableRegistryHub virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { @@ -57,12 +60,16 @@ namespace Catch { virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { m_tagAliasRegistry.add( alias, tag, lineInfo ); } + virtual void registerStartupException( std::exception_ptr const& exception ) override { + m_exceptionRegistry.add(exception); + } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; }; // Single, global, instance diff --git a/include/internal/catch_startup_exception_registry.h b/include/internal/catch_startup_exception_registry.h new file mode 100644 index 00000000..ece5e6ca --- /dev/null +++ b/include/internal/catch_startup_exception_registry.h @@ -0,0 +1,27 @@ +/* + * Created by Martin on 04/06/2017. + * Copyright 2017 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_STARTUP_EXCEPTION_REGISTRY_H_INCLUDED +#define TWOBLUECUBES_CATCH_STARTUP_EXCEPTION_REGISTRY_H_INCLUDED + + +#include +#include + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception); + std::vector const& getExceptions() const; + private: + std::vector m_exceptions; + }; + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_STARTUP_EXCEPTION_REGISTRY_H_INCLUDED diff --git a/include/internal/catch_startup_exception_registry.hpp b/include/internal/catch_startup_exception_registry.hpp new file mode 100644 index 00000000..caf5d894 --- /dev/null +++ b/include/internal/catch_startup_exception_registry.hpp @@ -0,0 +1,24 @@ +/* + * Created by Martin on 04/06/2017. + * Copyright 2017 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_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED + +#include "catch_startup_exception_registry.h" + +namespace Catch { + void StartupExceptionRegistry::add( std::exception_ptr const& exception ) { + m_exceptions.push_back(exception); + } + + std::vector const& StartupExceptionRegistry::getExceptions() const { + return m_exceptions; + } + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED diff --git a/include/internal/catch_tag_alias_registry.hpp b/include/internal/catch_tag_alias_registry.hpp index fef96c35..a078e3ce 100644 --- a/include/internal/catch_tag_alias_registry.hpp +++ b/include/internal/catch_tag_alias_registry.hpp @@ -39,14 +39,29 @@ namespace Catch { } void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + // Do not throw when constructing global objects, instead register the exception to be processed later + if (!(startsWith( alias, "[@") && endsWith(alias, ']'))) { + getMutableRegistryHub().registerStartupException( + std::make_exception_ptr( + CATCH_PREPARE_EXCEPTION( std::domain_error, + "error: tag alias, '" + << alias + << "' is not of the form [@alias name].\n" + << lineInfo) + ) + ); + } - CATCH_ENFORCE( startsWith( alias, "[@" ) && endsWith( alias, ']' ), - "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); - - CATCH_ENFORCE( m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second, - "error: tag alias, '" << alias << "' already registered.\n" - << "\tFirst seen at: " << find(alias)->lineInfo << "\n" - << "\tRedefined at: " << lineInfo ); + if (!m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second) { + getMutableRegistryHub().registerStartupException( + std::make_exception_ptr( + CATCH_PREPARE_EXCEPTION(std::domain_error, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo) + ) + ); + } } ITagAliasRegistry::~ITagAliasRegistry() {} diff --git a/include/internal/catch_test_case_info.hpp b/include/internal/catch_test_case_info.hpp index 32c0e6e9..0af74698 100644 --- a/include/internal/catch_test_case_info.hpp +++ b/include/internal/catch_test_case_info.hpp @@ -14,6 +14,7 @@ #include "catch_common.h" #include +#include namespace Catch { @@ -37,10 +38,16 @@ namespace Catch { return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { - CATCH_ENFORCE( !isReservedTag( tag ), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n" - << _lineInfo ); + // Do not throw when constructing global objects, instead register the exception to be processed later + if (isReservedTag(tag)) { + getMutableRegistryHub().registerStartupException( + std::make_exception_ptr( + CATCH_PREPARE_EXCEPTION(std::domain_error, "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo) + ) + ); + } } TestCase makeTestCase( ITestCase* _testCase,