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
This commit is contained in:
Martin Hořeňovský 2017-06-04 21:39:27 +02:00
parent 0020747420
commit da0edcbe25
11 changed files with 135 additions and 18 deletions

View File

@ -176,6 +176,8 @@ set(INTERNAL_HEADERS
${HEADER_DIR}/internal/catch_section.hpp ${HEADER_DIR}/internal/catch_section.hpp
${HEADER_DIR}/internal/catch_section_info.h ${HEADER_DIR}/internal/catch_section_info.h
${HEADER_DIR}/internal/catch_section_info.hpp ${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.h
${HEADER_DIR}/internal/catch_stream.hpp ${HEADER_DIR}/internal/catch_stream.hpp
${HEADER_DIR}/internal/catch_streambuf.h ${HEADER_DIR}/internal/catch_streambuf.h

View File

@ -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 #define CATCH_CONFIG_RUNNER
#include "catch.hpp" #include "catch.hpp"
int main( int argc, char* argv[] ) int main( int argc, char* argv[] ) {
{
// global setup... // global setup...
int result = Catch::Session().run( argc, argv ); int result = Catch::Session().run( argc, argv );
@ -42,7 +41,22 @@ int main( int argc, char* argv[] )
// writing to session.configData() here sets defaults // writing to session.configData() here sets defaults
// this is the preferred way to set them // 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 ); int returnCode = session.applyCommandLine( argc, argv );
if( returnCode != 0 ) // Indicates a command line error if( returnCode != 0 ) // Indicates a command line error
return returnCode; return returnCode;

View File

@ -15,6 +15,7 @@
#include "internal/catch_version.h" #include "internal/catch_version.h"
#include "internal/catch_text.h" #include "internal/catch_text.h"
#include "internal/catch_interfaces_reporter.h" #include "internal/catch_interfaces_reporter.h"
#include "internal/catch_startup_exception_registry.h"
#include <fstream> #include <fstream>
#include <stdlib.h> #include <stdlib.h>
@ -146,7 +147,19 @@ namespace Catch {
} }
int run( int argc, char const* const* const argv ) { 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 ); int returnCode = applyCommandLine( argc, argv );
if( returnCode == 0 ) if( returnCode == 0 )
returnCode = run(); returnCode = run();

View File

@ -120,10 +120,12 @@ namespace Catch {
#define CATCH_INTERNAL_LINEINFO \ #define CATCH_INTERNAL_LINEINFO \
::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
#define CATCH_PREPARE_EXCEPTION( type, msg ) \
type( static_cast<std::ostringstream&&>( std::ostringstream() << msg ).str() )
#define CATCH_INTERNAL_ERROR( msg ) \ #define CATCH_INTERNAL_ERROR( msg ) \
throw std::logic_error( static_cast<std::ostringstream&&>( 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 ) \ #define CATCH_ERROR( msg ) \
throw std::domain_error( static_cast<std::ostringstream&&>( std::ostringstream() << msg ).str() ) throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg )
#define CATCH_ENFORCE( condition, msg ) \ #define CATCH_ENFORCE( condition, msg ) \
do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false)

View File

@ -36,6 +36,7 @@
#include "catch_tag_alias_registry.hpp" #include "catch_tag_alias_registry.hpp"
#include "catch_test_case_tracker.hpp" #include "catch_test_case_tracker.hpp"
#include "catch_matchers_string.hpp" #include "catch_matchers_string.hpp"
#include "catch_startup_exception_registry.hpp"
#include "../reporters/catch_reporter_multi.hpp" #include "../reporters/catch_reporter_multi.hpp"
#include "../reporters/catch_reporter_xml.hpp" #include "../reporters/catch_reporter_xml.hpp"

View File

@ -22,6 +22,7 @@ namespace Catch {
struct IReporterRegistry; struct IReporterRegistry;
struct IReporterFactory; struct IReporterFactory;
struct ITagAliasRegistry; struct ITagAliasRegistry;
class StartupExceptionRegistry;
using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;
@ -33,6 +34,9 @@ namespace Catch {
virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;
virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0;
}; };
struct IMutableRegistryHub { struct IMutableRegistryHub {
@ -42,6 +46,7 @@ namespace Catch {
virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0;
virtual void registerTranslator( const IExceptionTranslator* translator ) = 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 registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;
virtual void registerStartupException( std::exception_ptr const& exception ) = 0;
}; };
IRegistryHub& getRegistryHub(); IRegistryHub& getRegistryHub();

View File

@ -14,6 +14,7 @@
#include "catch_reporter_registry.hpp" #include "catch_reporter_registry.hpp"
#include "catch_exception_translator_registry.hpp" #include "catch_exception_translator_registry.hpp"
#include "catch_tag_alias_registry.h" #include "catch_tag_alias_registry.h"
#include "catch_startup_exception_registry.h"
namespace Catch { namespace Catch {
@ -39,7 +40,9 @@ namespace Catch {
virtual ITagAliasRegistry const& getTagAliasRegistry() const override { virtual ITagAliasRegistry const& getTagAliasRegistry() const override {
return m_tagAliasRegistry; return m_tagAliasRegistry;
} }
virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const override {
return m_exceptionRegistry;
}
public: // IMutableRegistryHub public: // IMutableRegistryHub
virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { 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 { virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {
m_tagAliasRegistry.add( alias, tag, lineInfo ); m_tagAliasRegistry.add( alias, tag, lineInfo );
} }
virtual void registerStartupException( std::exception_ptr const& exception ) override {
m_exceptionRegistry.add(exception);
}
private: private:
TestRegistry m_testCaseRegistry; TestRegistry m_testCaseRegistry;
ReporterRegistry m_reporterRegistry; ReporterRegistry m_reporterRegistry;
ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
TagAliasRegistry m_tagAliasRegistry; TagAliasRegistry m_tagAliasRegistry;
StartupExceptionRegistry m_exceptionRegistry;
}; };
// Single, global, instance // Single, global, instance

View File

@ -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 <vector>
#include <exception>
namespace Catch {
class StartupExceptionRegistry {
public:
void add(std::exception_ptr const& exception);
std::vector<std::exception_ptr> const& getExceptions() const;
private:
std::vector<std::exception_ptr> m_exceptions;
};
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_STARTUP_EXCEPTION_REGISTRY_H_INCLUDED

View File

@ -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<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const {
return m_exceptions;
}
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED

View File

@ -39,14 +39,29 @@ namespace Catch {
} }
void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { 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, ']' ), if (!m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second) {
"error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); getMutableRegistryHub().registerStartupException(
std::make_exception_ptr(
CATCH_ENFORCE( m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second, CATCH_PREPARE_EXCEPTION(std::domain_error,
"error: tag alias, '" << alias << "' already registered.\n" "error: tag alias, '" << alias << "' already registered.\n"
<< "\tFirst seen at: " << find(alias)->lineInfo << "\n" << "\tFirst seen at: " << find(alias)->lineInfo << "\n"
<< "\tRedefined at: " << lineInfo ); << "\tRedefined at: " << lineInfo)
)
);
}
} }
ITagAliasRegistry::~ITagAliasRegistry() {} ITagAliasRegistry::~ITagAliasRegistry() {}

View File

@ -14,6 +14,7 @@
#include "catch_common.h" #include "catch_common.h"
#include <cctype> #include <cctype>
#include <exception>
namespace Catch { namespace Catch {
@ -37,10 +38,16 @@ namespace Catch {
return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] );
} }
inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
CATCH_ENFORCE( !isReservedTag( tag ), // Do not throw when constructing global objects, instead register the exception to be processed later
"Tag name: [" << tag << "] is not allowed.\n" if (isReservedTag(tag)) {
<< "Tag names starting with non alpha-numeric characters are reserved\n" getMutableRegistryHub().registerStartupException(
<< _lineInfo ); 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, TestCase makeTestCase( ITestCase* _testCase,