From da0edcbe257c9b5f19e69906ce1b10f4dac4fa98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 4 Jun 2017 21:39:27 +0200 Subject: [PATCH] Collect startup exceptions instead of throwing them Previously, some errors in Catch configuration would cause exceptions to be thrown before main was even entered. This leads to call to `std::terminate`, which is not a particularly nice way of ending the binary. Now these exceptions are registered with a global collector and used once Catch enters main. They can also be optionally ignored, if user supplies his own main and opts not to check them (or ignored them intentionally). Closes #921 --- CMakeLists.txt | 2 ++ docs/own-main.md | 20 +++++++++++-- include/catch_session.hpp | 15 +++++++++- include/internal/catch_common.h | 6 ++-- include/internal/catch_impl.hpp | 1 + .../internal/catch_interfaces_registry_hub.h | 5 ++++ include/internal/catch_registry_hub.hpp | 9 +++++- .../catch_startup_exception_registry.h | 27 +++++++++++++++++ .../catch_startup_exception_registry.hpp | 24 +++++++++++++++ include/internal/catch_tag_alias_registry.hpp | 29 ++++++++++++++----- include/internal/catch_test_case_info.hpp | 15 +++++++--- 11 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 include/internal/catch_startup_exception_registry.h create mode 100644 include/internal/catch_startup_exception_registry.hpp 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,