diff --git a/CMakeLists.txt b/CMakeLists.txt index bda66bc4..9c2fda01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT DEFINED PROJECT_NAME) set(NOT_SUBPROJECT ON) endif() -project(Catch2 LANGUAGES CXX VERSION 2.9.1) +project(Catch2 LANGUAGES CXX VERSION 2.9.2) if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR) message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt") diff --git a/README.md b/README.md index e22a82a5..98a95034 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ [![Build Status](https://travis-ci.org/catchorg/Catch2.svg?branch=master)](https://travis-ci.org/catchorg/Catch2) [![Build status](https://ci.appveyor.com/api/projects/status/github/catchorg/Catch2?svg=true)](https://ci.appveyor.com/project/catchorg/catch2) [![codecov](https://codecov.io/gh/catchorg/Catch2/branch/master/graph/badge.svg)](https://codecov.io/gh/catchorg/Catch2) -[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/5icuqPwk9miJLAL1) +[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/8YrGVqYqqSC4Sc5R) [![Join the chat in Discord: https://discord.gg/4CWS9zD](https://img.shields.io/badge/Discord-Chat!-brightgreen.svg)](https://discord.gg/4CWS9zD) -The latest version of the single header can be downloaded directly using this link +The latest version of the single header can be downloaded directly using this link ## Catch2 is released! diff --git a/docs/release-notes.md b/docs/release-notes.md index 3b67e147..3e760c7c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,7 @@ # Release notes **Contents**
+[2.9.2](#292)
[2.9.1](#291)
[2.9.0](#290)
[2.8.0](#280)
@@ -26,6 +27,42 @@ [Older versions](#older-versions)
[Even Older versions](#even-older-versions)
+ +## 2.9.2 + +### Fixes +* `ChunkGenerator` can now be used with chunks of size 0 (#1671) +* Nested subsections are now run properly when specific section is run via the `-c` argument (#1670, #1673) +* Catch2 now consistently uses `_WIN32` to detect Windows platform (#1676) +* `TEMPLATE_LIST_TEST_CASE` now support non-default constructible type lists (#1697) +* Fixed a crash in the XMLReporter when a benchmark throws exception during warmup (#1706) +* Fixed a possible infinite loop in CompactReporter (#1715) +* Fixed `-w NoTests` returning 0 even when no tests were matched (#1449, #1683, #1684) +* Fixed matcher compilation under Obj-C++ (#1661) + +### Improvements +* `RepeatGenerator` and `FixedValuesGenerator` now fail to compile when used with `bool` (#1692) + * Previously they would fail at runtime. +* Catch2 now supports Android's debug logging for its debug output (#1710) +* Catch2 now detects and configures itself for the RTX platform (#1693) + * You still need to pass `--benchmark-no-analysis` if you are using benchmarking under RTX +* Removed a "storage class is not first" warning when compiling Catch2 with PGI compiler (#1717) + +### Miscellaneous +* Documentation now contains indication when a specific feature was introduced (#1695) + * These start with Catch2 v2.3.0, (a bit over a year ago). + * `docs/contributing.md` has been updated to provide contributors guidance on how to add these to newly written documentation +* Various other documentation improvements + * ToC fixes + * Documented `--order` and `--rng-seed` command line options + * Benchmarking documentation now clearly states that it requires opt-in + * Documented `CATCH_CONFIG_CPP17_OPTIONAL` and `CATCH_CONFIG_CPP17_BYTE` macros + * Properly documented built-in vector matchers + * Improved `*_THROWS_MATCHES` documentation a bit +* CMake config file is now arch-independent even if `CMAKE_SIZEOF_VOID_P` is in CMake cache (#1660) +* `CatchAddTests` now properly escapes `[` and `]` in test names (#1634, #1698) + + ## 2.9.1 ### Fixes diff --git a/include/catch.hpp b/include/catch.hpp index 148d0430..dba005ad 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -11,7 +11,7 @@ #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 9 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_PATCH 2 #ifdef __clang__ # pragma clang system_header diff --git a/include/internal/catch_version.cpp b/include/internal/catch_version.cpp index 44b529e5..03fded91 100644 --- a/include/internal/catch_version.cpp +++ b/include/internal/catch_version.cpp @@ -37,7 +37,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 9, 1, "", 0 ); + static Version version( 2, 9, 2, "", 0 ); return version; } diff --git a/single_include/catch2/catch.hpp b/single_include/catch2/catch.hpp index 303f664f..5feb2a4b 100644 --- a/single_include/catch2/catch.hpp +++ b/single_include/catch2/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v2.9.1 - * Generated: 2019-06-17 11:59:24.363643 + * Catch v2.9.2 + * Generated: 2019-08-08 13:35:12.279703 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. @@ -15,7 +15,7 @@ #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 9 -#define CATCH_VERSION_PATCH 1 +#define CATCH_VERSION_PATCH 2 #ifdef __clang__ # pragma clang system_header @@ -275,6 +275,17 @@ namespace Catch { #define CATCH_INTERNAL_CONFIG_COUNTER #endif +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + //////////////////////////////////////////////////////////////////////////////// // Check if string_view is available and usable // The check is split apart to work around v140 (VS2015) preprocessor issue... @@ -292,6 +303,14 @@ namespace Catch { # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) #endif // __has_include +//////////////////////////////////////////////////////////////////////////////// +// Check if byte is available and usable +#if defined(__has_include) +# if __has_include() && defined(CATCH_CPP17_OR_GREATER) +# define CATCH_INTERNAL_CONFIG_CPP17_BYTE +# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // __has_include + //////////////////////////////////////////////////////////////////////////////// // Check if variant is available and usable #if defined(__has_include) @@ -346,6 +365,10 @@ namespace Catch { # define CATCH_CONFIG_CPP17_VARIANT #endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif @@ -362,7 +385,7 @@ namespace Catch { # define CATCH_CONFIG_POLYFILL_ISNAN #endif -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) # define CATCH_CONFIG_USE_ASYNC #endif @@ -515,6 +538,7 @@ namespace Catch { virtual std::vector 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 filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); @@ -1153,7 +1177,7 @@ struct AutoReg : NonCopyable { } \ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ - using TestInit = decltype(convert(TmplList {})); \ + using TestInit = decltype(convert(std::declval())); \ TestInit t; \ t.reg_tests(); \ return 0; \ @@ -1279,7 +1303,7 @@ struct AutoReg : NonCopyable { }\ };\ static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ - using TestInit = decltype(convert(TmplList {}));\ + using TestInit = decltype(convert(std::declval()));\ TestInit t;\ t.reg_tests();\ return 0;\ @@ -1679,6 +1703,12 @@ namespace Catch { } }; +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) template<> struct StringMaker { static std::string convert(int value); @@ -3180,6 +3210,15 @@ namespace Matchers { virtual bool match( ObjectT const& arg ) const = 0; }; +#if defined(__OBJC__) + // Hack to fix Catch GH issue #1661. Could use id for generic Object support. + // use of const for Object pointers is very uncommon and under ARC it causes some kind of signature mismatch that breaks compilation + template<> + struct MatcherMethod { + virtual bool match( NSString* arg ) const = 0; + }; +#endif + #ifdef __clang__ # pragma clang diagnostic pop #endif @@ -3829,6 +3868,9 @@ namespace Generators { template class FixedValuesGenerator final : public IGenerator { + static_assert(!std::is_same::value, + "ValuesGenerator does not support bools because of std::vector" + "specialization, use SingleValue Generator instead."); std::vector m_values; size_t m_idx = 0; public: @@ -4048,6 +4090,9 @@ namespace Generators { template class RepeatGenerator : public IGenerator { + static_assert(!std::is_same::value, + "RepeatGenerator currently does not support bools" + "because of std::vector specialization"); GeneratorWrapper m_generator; mutable std::vector m_returned; size_t m_target_repeats; @@ -4162,12 +4207,14 @@ namespace Generators { m_chunk_size(size), m_generator(std::move(generator)) { m_chunk.reserve(m_chunk_size); - m_chunk.push_back(m_generator.get()); - for (size_t i = 1; i < m_chunk_size; ++i) { - if (!m_generator.next()) { - Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); - } + if (m_chunk_size != 0) { m_chunk.push_back(m_generator.get()); + for (size_t i = 1; i < m_chunk_size; ++i) { + if (!m_generator.next()) { + Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); + } + m_chunk.push_back(m_generator.get()); + } } } std::vector const& get() const override { @@ -4711,7 +4758,7 @@ namespace Catch { arcSafeRelease( m_substr ); } - bool match( NSString* const& str ) const override { + bool match( NSString* str ) const override { return false; } @@ -4721,7 +4768,7 @@ namespace Catch { struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* const& str ) const override { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } @@ -4734,7 +4781,7 @@ namespace Catch { struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* const& str ) const override { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } @@ -4747,7 +4794,7 @@ namespace Catch { struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* const& str ) const override { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } @@ -4759,7 +4806,7 @@ namespace Catch { struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} - bool match( NSString* const& str ) const override { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } @@ -4867,17 +4914,23 @@ namespace Catch namespace Catch { + struct IConfig; + class TestSpec { - struct Pattern { + class Pattern { + public: + explicit Pattern( std::string const& name ); virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + std::string const m_name; }; using PatternPtr = std::shared_ptr; class NamePattern : public Pattern { public: - NamePattern( std::string const& name ); - virtual ~NamePattern(); + explicit NamePattern( std::string const& name, std::string const& filterString ); bool matches( TestCaseInfo const& testCase ) const override; private: WildcardPattern m_wildcardPattern; @@ -4885,8 +4938,7 @@ namespace Catch { class TagPattern : public Pattern { public: - TagPattern( std::string const& tag ); - virtual ~TagPattern(); + explicit TagPattern( std::string const& tag, std::string const& filterString ); bool matches( TestCaseInfo const& testCase ) const override; private: std::string m_tag; @@ -4894,8 +4946,7 @@ namespace Catch { class ExcludedPattern : public Pattern { public: - ExcludedPattern( PatternPtr const& underlyingPattern ); - virtual ~ExcludedPattern(); + explicit ExcludedPattern( PatternPtr const& underlyingPattern ); bool matches( TestCaseInfo const& testCase ) const override; private: PatternPtr m_underlyingPattern; @@ -4905,11 +4956,19 @@ namespace Catch { std::vector m_patterns; bool matches( TestCaseInfo const& testCase ) const; + std::string name() const; }; public: + struct FilterMatch { + std::string name; + std::vector tests; + }; + using Matches = std::vector; + bool hasFilters() const; bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector const& testCases, IConfig const& config ) const; private: std::vector m_filters; @@ -4949,8 +5008,10 @@ namespace Catch { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; Mode m_mode = None; bool m_exclusion = false; - std::size_t m_start = std::string::npos, m_pos = 0; + std::size_t m_pos = 0; std::string m_arg; + std::string m_substring; + std::string m_patternName; std::vector m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; @@ -4964,26 +5025,32 @@ namespace Catch { private: void visitChar( char c ); - void startNewMode( Mode mode, std::size_t start ); + void startNewMode( Mode mode ); + bool processNoneChar( char c ); + void processNameChar( char c ); + bool processOtherChar( char c ); + void endMode(); void escape(); - std::string subString() const; + bool isControlChar( char c ) const; template void addPattern() { - std::string token = subString(); + std::string token = m_patternName; for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) - token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + token = token.substr( 0, m_escapeChars[i] - i ) + token.substr( m_escapeChars[i] -i +1 ); m_escapeChars.clear(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { - TestSpec::PatternPtr pattern = std::make_shared( token ); + TestSpec::PatternPtr pattern = std::make_shared( token, m_substring ); if( m_exclusion ) pattern = std::make_shared( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } + m_substring.clear(); + m_patternName.clear(); m_exclusion = false; m_mode = None; } @@ -6126,6 +6193,7 @@ namespace Catch { void testRunEnded(TestRunStats const& testRunStats) override; #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; void benchmarkStarting(BenchmarkInfo const&) override; void benchmarkEnded(BenchmarkStats<> const&) override; void benchmarkFailed(std::string const&) override; @@ -9919,7 +9987,16 @@ namespace Catch { } // end catch_debug_console.h -#ifdef CATCH_PLATFORM_WINDOWS +#if defined(__ANDROID__) +#include + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + __android_log_print( ANDROID_LOG_DEBUG, "Catch", text.c_str() ); + } + } + +#elif defined(CATCH_PLATFORM_WINDOWS) namespace Catch { void writeToDebugConsole( std::string const& text ) { @@ -10373,7 +10450,7 @@ namespace Catch { // 32kb for the alternate stack seems to be sufficient. However, this value // is experimentally determined, so that's not guaranteed. - constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; + static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, @@ -10811,9 +10888,18 @@ namespace Catch { } std::string TagInfo::all() const { - std::string out; - for( auto const& spelling : spellings ) - out += "[" + spelling + "]"; + size_t size = 0; + for (auto const& spelling : spellings) { + // Add 2 for the brackes + size += spelling.size() + 2; + } + + std::string out; out.reserve(size); + for (auto const& spelling : spellings) { + out += '['; + out += spelling; + out += ']'; + } return out; } @@ -11660,6 +11746,8 @@ namespace Catch { struct IConfig; std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); void enforceNoDuplicateTestCases( std::vector const& functions ); @@ -12503,7 +12591,7 @@ namespace Catch { void libIdentify(); int applyCommandLine( int argc, char const * const * argv ); - #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) int applyCommandLine( int argc, wchar_t const * const * argv ); #endif @@ -12570,6 +12658,8 @@ namespace Catch { // end catch_version.h #include #include +#include +#include namespace Catch { @@ -12603,45 +12693,53 @@ namespace Catch { return ret; } - Catch::Totals runTests(std::shared_ptr const& config) { - auto reporter = makeReporter(config); + class TestGroup { + public: + explicit TestGroup(std::shared_ptr const& config) + : m_config{config} + , m_context{config, makeReporter(config)} + { + auto const& allTestCases = getAllTestCasesSorted(*m_config); + m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config); - RunContext context(config, std::move(reporter)); - - Totals totals; - - context.testGroupStarting(config->name(), 1, 1); - - TestSpec testSpec = config->testSpec(); - - auto const& allTestCases = getAllTestCasesSorted(*config); - for (auto const& testCase : allTestCases) { - bool matching = (!testSpec.hasFilters() && !testCase.isHidden()) || - (testSpec.hasFilters() && matchTest(testCase, testSpec, *config)); - - if (!context.aborting() && matching) - totals += context.runTest(testCase); - else - context.reporter().skipTest(testCase); + if (m_matches.empty()) { + for (auto const& test : allTestCases) + if (!test.isHidden()) + m_tests.emplace(&test); + } else { + for (auto const& match : m_matches) + m_tests.insert(match.tests.begin(), match.tests.end()); + } } - if (config->warnAboutNoTests() && totals.testCases.total() == 0) { - ReusableStringStream testConfig; - - bool first = true; - for (const auto& input : config->getTestsOrTags()) { - if (!first) { testConfig << ' '; } - first = false; - testConfig << input; + Totals execute() { + Totals totals; + m_context.testGroupStarting(m_config->name(), 1, 1); + for (auto const& testCase : m_tests) { + if (!m_context.aborting()) + totals += m_context.runTest(*testCase); + else + m_context.reporter().skipTest(*testCase); } - context.reporter().noMatchingTestCases(testConfig.str()); - totals.error = -1; + for (auto const& match : m_matches) { + if (match.tests.empty()) { + m_context.reporter().noMatchingTestCases(match.name); + totals.error = -1; + } + } + m_context.testGroupEnded(m_config->name(), totals, 1, 1); + return totals; } - context.testGroupEnded(config->name(), totals, 1, 1); - return totals; - } + private: + using Tests = std::set; + + std::shared_ptr m_config; + RunContext m_context; + Tests m_tests; + TestSpec::Matches m_matches; + }; void applyFilenamesAsTags(Catch::IConfig const& config) { auto& tests = const_cast&>(getAllTestCasesSorted(config)); @@ -12741,7 +12839,7 @@ namespace Catch { return 0; } -#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { char **utf8Argv = new char *[ argc ]; @@ -12818,7 +12916,12 @@ namespace Catch { if( Option listed = list( m_config ) ) return static_cast( *listed ); - auto totals = runTests( m_config ); + TestGroup tests { m_config }; + auto const totals = tests.execute(); + + if( m_config->warnAboutNoTests() && totals.error == -1 ) + return 2; + // Note that on unices only the lower 8 bits are usually used, clamping // the return value to 255 prevents false negative when some multiple // of 256 tests has failed @@ -13529,8 +13632,13 @@ namespace Catch { } return sorted; } + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ) { + return !testCase.throws() || config.allowThrows(); + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { - return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + return testSpec.matches( testCase ) && isThrowSafe( testCase, config ); } void enforceNoDuplicateTestCases( std::vector const& functions ) { @@ -13733,7 +13841,7 @@ namespace TestCaseTracking { m_runState = CompletedSuccessfully; break; case ExecutingChildren: - if( m_children.empty() || m_children.back()->isComplete() ) + if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) ) m_runState = CompletedSuccessfully; break; @@ -13876,47 +13984,77 @@ namespace Catch { namespace Catch { - TestSpec::Pattern::~Pattern() = default; - TestSpec::NamePattern::~NamePattern() = default; - TestSpec::TagPattern::~TagPattern() = default; - TestSpec::ExcludedPattern::~ExcludedPattern() = default; - - TestSpec::NamePattern::NamePattern( std::string const& name ) - : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + TestSpec::Pattern::Pattern( std::string const& name ) + : m_name( name ) {} + + TestSpec::Pattern::~Pattern() = default; + + std::string const& TestSpec::Pattern::name() const { + return m_name; + } + + TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) + : Pattern( filterString ) + , m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { return m_wildcardPattern.matches( toLower( testCase.name ) ); } - TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) + : Pattern( filterString ) + , m_tag( toLower( tag ) ) + {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { return std::find(begin(testCase.lcaseTags), end(testCase.lcaseTags), m_tag) != end(testCase.lcaseTags); } - TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} - bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) + : Pattern( underlyingPattern->name() ) + , m_underlyingPattern( underlyingPattern ) + {} + + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { + return !m_underlyingPattern->matches( testCase ); + } bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( auto const& pattern : m_patterns ) { - if( !pattern->matches( testCase ) ) - return false; - } - return true; + return std::all_of( m_patterns.begin(), m_patterns.end(), [&]( PatternPtr const& p ){ return p->matches( testCase ); } ); + } + + std::string TestSpec::Filter::name() const { + std::string name; + for( auto const& p : m_patterns ) + name += p->name(); + return name; } bool TestSpec::hasFilters() const { return !m_filters.empty(); } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( auto const& filter : m_filters ) - if( filter.matches( testCase ) ) - return true; - return false; + return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } ); } + + TestSpec::Matches TestSpec::matchesByFilter( std::vector 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 currentMatches; + for( auto const& test : testCases ) + if( isThrowSafe( test, config ) && filter.matches( test ) ) + currentMatches.emplace_back( &test ); + return FilterMatch{ filter.name(), currentMatches }; + } ); + return matches; + } + } // end catch_test_spec.cpp // start catch_test_spec_parser.cpp @@ -13928,64 +14066,125 @@ namespace Catch { TestSpecParser& TestSpecParser::parse( std::string const& arg ) { m_mode = None; m_exclusion = false; - m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); m_escapeChars.clear(); + m_substring.reserve(m_arg.size()); + m_patternName.reserve(m_arg.size()); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); - if( m_mode == Name ) - addPattern(); + endMode(); return *this; } TestSpec TestSpecParser::testSpec() { addFilter(); return m_testSpec; } - void TestSpecParser::visitChar( char c ) { - if( m_mode == None ) { - switch( c ) { - case ' ': return; - case '~': m_exclusion = true; return; - case '[': return startNewMode( Tag, ++m_pos ); - case '"': return startNewMode( QuotedName, ++m_pos ); - case '\\': return escape(); - default: startNewMode( Name, m_pos ); break; - } + if( c == ',' ) { + endMode(); + addFilter(); + return; } - if( m_mode == Name ) { - if( c == ',' ) { - addPattern(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern(); - startNewMode( Tag, ++m_pos ); - } - else if( c == '\\' ) - escape(); + + switch( m_mode ) { + case None: + if( processNoneChar( c ) ) + return; + break; + case Name: + processNameChar( c ); + break; + case EscapedName: + endMode(); + break; + default: + case Tag: + case QuotedName: + if( processOtherChar( c ) ) + return; + break; } - else if( m_mode == EscapedName ) - m_mode = Name; - else if( m_mode == QuotedName && c == '"' ) - addPattern(); - else if( m_mode == Tag && c == ']' ) - addPattern(); + + m_substring += c; + if( !isControlChar( c ) ) + m_patternName += c; } - void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + // Two of the processing methods return true to signal the caller to return + // without adding the given character to the current pattern strings + bool TestSpecParser::processNoneChar( char c ) { + switch( c ) { + case ' ': + return true; + case '~': + m_exclusion = true; + return false; + case '[': + startNewMode( Tag ); + return false; + case '"': + startNewMode( QuotedName ); + return false; + case '\\': + escape(); + return true; + default: + startNewMode( Name ); + return false; + } + } + void TestSpecParser::processNameChar( char c ) { + if( c == '[' ) { + if( m_substring == "exclude:" ) + m_exclusion = true; + else + endMode(); + startNewMode( Tag ); + } + } + bool TestSpecParser::processOtherChar( char c ) { + if( !isControlChar( c ) ) + return false; + m_substring += c; + endMode(); + return true; + } + void TestSpecParser::startNewMode( Mode mode ) { m_mode = mode; - m_start = start; + } + void TestSpecParser::endMode() { + switch( m_mode ) { + case Name: + case QuotedName: + return addPattern(); + case Tag: + return addPattern(); + case EscapedName: + return startNewMode( Name ); + case None: + default: + return startNewMode( None ); + } } void TestSpecParser::escape() { - if( m_mode == None ) - m_start = m_pos; m_mode = EscapedName; m_escapeChars.push_back( m_pos ); } - std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + bool TestSpecParser::isControlChar( char c ) const { + switch( m_mode ) { + default: + return false; + case None: + return c == '~'; + case Name: + return c == '['; + case EscapedName: + return true; + case QuotedName: + return c == '"'; + case Tag: + return c == '[' || c == ']'; + } + } void TestSpecParser::addFilter() { if( !m_currentFilter.m_patterns.empty() ) { @@ -14225,6 +14424,13 @@ std::string StringMaker::convert(wchar_t * str) { } #endif +#if defined(CATCH_CONFIG_CPP17_BYTE) +#include +std::string StringMaker::convert(std::byte value) { + return ::Catch::Detail::stringify(std::to_integer(value)); +} +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + std::string StringMaker::convert(int value) { return ::Catch::Detail::stringify(static_cast(value)); } @@ -14414,7 +14620,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 9, 1, "", 0 ); + static Version version( 2, 9, 2, "", 0 ); return version; } @@ -15001,24 +15207,25 @@ private: if (itMessage == messages.end()) return; - // using messages.end() directly yields (or auto) compilation error: - std::vector::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast(std::distance(itMessage, itEnd)); + const auto itEnd = messages.cend(); + const auto N = static_cast(std::distance(itMessage, itEnd)); { Colour colourGuard(colour); stream << " with " << pluralise(N, "message") << ':'; } - for (; itMessage != itEnd; ) { + while (itMessage != itEnd) { // If this assertion is a warning ignore any INFO messages if (printInfoMessages || itMessage->type != ResultWas::Info) { - stream << " '" << itMessage->message << '\''; - if (++itMessage != itEnd) { + printMessage(); + if (itMessage != itEnd) { Colour colourGuard(dimColour()); stream << " and"; } + continue; } + ++itMessage; } } @@ -16355,10 +16562,13 @@ namespace Catch { } #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) - void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + void XmlReporter::benchmarkPreparing(std::string const& name) { m_xml.startElement("BenchmarkResults") - .writeAttribute("name", info.name) - .writeAttribute("samples", info.samples) + .writeAttribute("name", name); + } + + void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + m_xml.writeAttribute("samples", info.samples) .writeAttribute("resamples", info.resamples) .writeAttribute("iterations", info.iterations) .writeAttribute("clockResolution", static_cast(info.clockResolution))