diff options
| author | Leitwolf11 | 2026-09-28 01:10:14 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-09-28 01:10:14 +0300 |
| commit | 97328fefe9650c84b594f1cea212c48562e1745b (patch) | |
| tree | 8905f56f9a2f337637d6ed9c86675b52829239b5 /extras/catch_amalgamated.cpp | |
| parent | fbd2ec1c12c3ea39df0b7af01b6ab11360302299 (diff) | |
| download | catch3-97328fefe9650c84b594f1cea212c48562e1745b.tar.gz | |
Harden Catch3 exit guards and pin build workflows
Diffstat (limited to 'extras/catch_amalgamated.cpp')
| -rw-r--r-- | extras/catch_amalgamated.cpp | 525 |
1 files changed, 359 insertions, 166 deletions
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp index 73ef255d..c6a8de36 100644 --- a/extras/catch_amalgamated.cpp +++ b/extras/catch_amalgamated.cpp @@ -7,7 +7,7 @@ // SPDX-License-Identifier: BSL-1.0 // Catch v3.16.0 -// Generated: 2026-08-25 09:29:23.172704 +// Generated: 2026-09-28 00:12:28.744397 // ---------------------------------------------------------- // This file is an amalgamation of multiple different files. // You probably shouldn't edit it directly. @@ -1248,11 +1248,22 @@ namespace Catch { #include <cassert> +#include <cerrno> #include <cstdio> #include <cstdlib> +#include <cstring> #include <exception> #include <iomanip> -#include <set> + +#if defined( _WIN32 ) +# include <fcntl.h> +# include <io.h> +# include <sys/stat.h> +#else +# include <fcntl.h> +# include <sys/stat.h> +# include <unistd.h> +#endif namespace Catch { @@ -1307,24 +1318,32 @@ namespace Catch { assert( m_config->testSpec().getInvalidSpecs().empty() && "Invalid test specs should be handled before running tests" ); + // Note that we are working with pointers into this vector + // of sorted test cases, so sorting/unique-ing the pointers + // keeps the user-specified order. auto const& allTestCases = getAllTestCasesSorted(*m_config); auto const& testSpec = m_config->testSpec(); + m_tests.reserve( allTestCases.size() ); if ( !testSpec.hasFilters() ) { for ( auto const& test : allTestCases ) { if ( !test.getTestCaseInfo().isHidden() ) { - m_tests.emplace( &test ); + m_tests.push_back( &test ); } } } else { - m_matches = - testSpec.matchesByFilter( allTestCases, *m_config ); + m_matches = testSpec.matchesByFilter( allTestCases, *m_config ); for ( auto const& match : m_matches ) { - m_tests.insert( match.tests.begin(), + m_tests.insert( m_tests.end(), + match.tests.begin(), match.tests.end() ); } + std::sort( m_tests.begin(), m_tests.end() ); + m_tests.erase( + std::unique( m_tests.begin(), m_tests.end() ), + m_tests.end() ); } - m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex()); + m_tests = createShard(CATCH_MOVE(m_tests), m_config->shardCount(), m_config->shardIndex()); } Totals execute() { @@ -1355,7 +1374,7 @@ namespace Catch { IEventListener* m_reporter; Config const* m_config; RunContext m_context; - std::set<TestCaseHandle const*> m_tests; + std::vector<TestCaseHandle const*> m_tests; TestSpec::Matches m_matches; bool m_unmatchedTestSpecs = false; }; @@ -1364,29 +1383,93 @@ namespace Catch { getMutableRegistryHub().getMutableTestCaseRegistry().enableFilenameTags(); } - // Creates empty file at path. The path must be writable, we do not - // try to create directories in path because that's hard in C++14. + std::FILE* openGuardFile( std::string const& guardFilePath, + int& error ) { +#if defined( _MSC_VER ) + std::FILE* file = nullptr; + error = fopen_s( &file, guardFilePath.c_str(), "w" ); + return error ? nullptr : file; +#elif defined( _WIN32 ) + const int descriptor = _open( guardFilePath.c_str(), + _O_WRONLY | _O_CREAT | _O_TRUNC | + _O_BINARY | _O_NOINHERIT, + _S_IREAD | _S_IWRITE ); + if ( descriptor == -1 ) { + error = errno; + return nullptr; + } + std::FILE* const file = _fdopen( descriptor, "w" ); + if ( file == nullptr ) { + error = errno; + _close( descriptor ); + } + return file; +#else + int flags = O_WRONLY | O_CREAT | O_TRUNC; +# if defined( O_NONBLOCK ) + flags |= O_NONBLOCK; +# endif +# if defined( O_CLOEXEC ) + flags |= O_CLOEXEC; +# endif +# if defined( O_NOFOLLOW ) + // Do not let a stale guard-file symlink redirect this write. + flags |= O_NOFOLLOW; +# endif + const int descriptor = + ::open( guardFilePath.c_str(), flags, S_IRUSR | S_IWUSR ); + if ( descriptor == -1 ) { + error = errno; + return nullptr; + } + + struct stat fileStatus = {}; + if ( ::fstat( descriptor, &fileStatus ) != 0 ) { + error = errno; + ::close( descriptor ); + return nullptr; + } + if ( !S_ISREG( fileStatus.st_mode ) ) { + error = EINVAL; + ::close( descriptor ); + return nullptr; + } + // The creation mode is already owner-only; also tighten an + // existing regular guard file before it is reused. + if ( ::fchmod( descriptor, S_IRUSR | S_IWUSR ) != 0 ) { + error = errno; + ::close( descriptor ); + return nullptr; + } + + std::FILE* const file = ::fdopen( descriptor, "w" ); + if ( file == nullptr ) { + error = errno; + ::close( descriptor ); + } + return file; +#endif + } + + // Creates an owner-only guard file. The path must be writable; we do + // not create its parent directories. void setUpGuardFile( std::string const& guardFilePath ) { if ( !guardFilePath.empty() ) { -#if defined( _MSC_VER ) - std::FILE* file = nullptr; - if ( fopen_s( &file, guardFilePath.c_str(), "w" ) ) { - char msgBuffer[100]; - const auto err = errno; + int err = 0; + std::FILE* const file = openGuardFile( guardFilePath, err ); + if ( file == nullptr ) { + char msgBuffer[100] = {}; std::string errMsg; +#if defined( _MSC_VER ) if ( !strerror_s( msgBuffer, err ) ) { errMsg = msgBuffer; } else { errMsg = "Could not translate errno to a string"; } - #else - std::FILE* file = std::fopen( guardFilePath.c_str(), "w" ); - if ( !file ) { - const auto err = errno; - const char* errMsg = std::strerror( err ); + const char* const message = std::strerror( err ); + errMsg = message ? message : "Unknown error"; #endif - CATCH_RUNTIME_ERROR( "Could not open the exit guard file '" << guardFilePath << "' because '" << errMsg << "' (" << err << ')' ); @@ -1668,6 +1751,7 @@ namespace Catch { #include <cassert> #include <cctype> #include <algorithm> +#include <utility> namespace Catch { @@ -1701,32 +1785,27 @@ namespace Catch { return tcp != TestCaseProperties::None; } - TestCaseProperties parseSpecialTag( StringRef tag ) { - if( !tag.empty() && tag[0] == '.' ) - return TestCaseProperties::IsHidden; - else if( tag == "!throws"_sr ) - return TestCaseProperties::Throws; - else if( tag == "!shouldfail"_sr ) - return TestCaseProperties::ShouldFail; - else if( tag == "!mayfail"_sr ) - return TestCaseProperties::MayFail; - else if( tag == "!nonportable"_sr ) - return TestCaseProperties::NonPortable; - else if( tag == "!benchmark"_sr ) - return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden; - else - return TestCaseProperties::None; - } - bool isReservedTag( StringRef tag ) { - return parseSpecialTag( tag ) == TestCaseProperties::None - && tag.size() > 0 - && !std::isalnum( static_cast<unsigned char>(tag[0]) ); - } - void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) { - CATCH_ENFORCE( !isReservedTag(tag), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alphanumeric characters are reserved\n" - << _lineInfo ); + TestCaseProperties propertiesFromTag( StringRef tag, SourceLineInfo const& _lineInfo ) { + if ( tag.empty() ) { return TestCaseProperties::None; } + // All tags starting with alphanumeric characters are normal. + if ( std::isalnum( tag[0] ) ) { return TestCaseProperties::None; } + + // Explicitly check known special tags, and error out if it is not one of them + if ( tag[0] == '.' ) { return TestCaseProperties::IsHidden; } + else if ( tag == "!throws"_sr ) { return TestCaseProperties::Throws; } + else if ( tag == "!shouldfail"_sr ) { return TestCaseProperties::ShouldFail; } + else if ( tag == "!mayfail"_sr ) { return TestCaseProperties::MayFail; } + else if ( tag == "!nonportable"_sr ) { return TestCaseProperties::NonPortable; } + else if ( tag == "!benchmark"_sr ) { + return TestCaseProperties::Benchmark | + TestCaseProperties::IsHidden; + } else { + CATCH_RUNTIME_ERROR( + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non-alphanumeric " + "characters are reserved.\n" + << _lineInfo ); + } } std::string makeDefaultName() { @@ -1788,6 +1867,18 @@ namespace Catch { auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file); backingTags.reserve(requiredSize); + // Since there is usually only small number of tags on a test case, + // the geometric growth on vector does not get time to properly + // amortize the reallocations. But we can save a lot of time by + // pre-allocating space for all tags. + if ( !originalTags.empty() ) { + size_t tagCount = 1; // 1 for filename tag + for ( size_t i = 0; i < originalTags.size(); ++i ) { + if ( originalTags[i] == '[' ) { ++tagCount; } + } + tags.reserve( tagCount ); + } + // We cannot copy the tags directly, as we need to normalize // some tags, so that [.foo] is copied as [.][foo]. size_t tagStart = 0; @@ -1823,8 +1914,7 @@ namespace Catch { << _nameAndTags.name << "' at " << _lineInfo ); - enforceNotReservedTag(tagStr, lineInfo); - properties |= parseSpecialTag(tagStr); + properties |= propertiesFromTag(tagStr, _lineInfo); // When copying a tag to the backing storage, we need to // check if it is a merged hide tag, such as [.foo], and // if it is, we need to handle it as if it was [foo]. @@ -1847,10 +1937,21 @@ namespace Catch { internalAppendTag("."_sr); } - // Sort and prepare tags - std::sort(begin(tags), end(tags)); - tags.erase(std::unique(begin(tags), end(tags)), - end(tags)); + // Tests usually have small number of tags, so we explicitly handle + // the 1 and 2 tags cases for better performance in low-opt builds. + switch ( tags.size() ) { + case 1: + break; + case 2: + if ( tags[0] == tags[1] ) { tags.pop_back(); } + else if ( tags[1] < tags[0] ) { std::swap( tags[0], tags[1] ); } + break; + default: + std::sort( begin( tags ), end( tags ) ); + tags.erase( std::unique( begin( tags ), end( tags ) ), + end( tags ) ); + } + } bool TestCaseInfo::isHidden() const { @@ -1913,6 +2014,12 @@ namespace Catch { return lhs.tags < rhs.tags; } + bool operator==( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) { + return lhs.name == rhs.name + && lhs.className == rhs.className + && lhs.tags == rhs.tags; + } + } // end namespace Catch @@ -1937,7 +2044,7 @@ namespace Catch { TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) : Pattern( filterString ) - , m_wildcardPattern( toLower( name ), CaseSensitive::No ) + , m_wildcardPattern( name, CaseSensitive::No ) {} bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { @@ -2018,12 +2125,14 @@ namespace Catch { matches.reserve( m_filters.size() ); for ( auto const& filter : m_filters ) { std::vector<TestCaseHandle const*> currentMatches; - for ( auto const& test : testCases ) + for ( auto const& test : testCases ) { if ( isThrowSafe( test, config ) && - filter.matches( test.getTestCaseInfo() ) ) + filter.matches( test.getTestCaseInfo() ) ) { currentMatches.emplace_back( &test ); - matches.push_back( - FilterMatch{ extractFilterName( filter ), currentMatches } ); + } + } + matches.push_back( FilterMatch{ extractFilterName( filter ), + CATCH_MOVE( currentMatches ) } ); } return matches; } @@ -2131,26 +2240,40 @@ namespace Detail { } std::string formatTimeT(std::time_t time) { -#ifdef _MSC_VER std::tm timeInfo = {}; +#ifdef _MSC_VER const auto err = gmtime_s( &timeInfo, &time ); if ( err ) { return "gmtime from provided timepoint has failed. This " "happens e.g. with pre-1970 dates using Microsoft libc"; } +#elif defined( __MINGW32__ ) + if ( gmtime_s( &timeInfo, &time ) != 0 ) { + return "gmtime from provided timepoint has failed"; + } +#elif defined( CATCH_PLATFORM_PLAYSTATION ) + gmtime_s( &time, &timeInfo ); +#elif defined( __IAR_SYSTEMS_ICC__ ) + // This toolchain does not provide gmtime_r. Copy the result immediately + // so the formatter does not retain the C library's static buffer. + const std::tm* const converted = std::gmtime( &time ); + if ( converted == nullptr ) { + return "gmtime from provided timepoint has failed"; + } + timeInfo = *converted; #else - std::tm* timeInfo = std::gmtime( &time ); + if ( gmtime_r( &time, &timeInfo ) == nullptr ) { + return "gmtime from provided timepoint has failed"; + } #endif auto const timeStampSize = sizeof( "2017-01-16T17:06:45Z" ); - char timeStamp[timeStampSize]; + char timeStamp[timeStampSize] = {}; const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; -#ifdef _MSC_VER - std::strftime( timeStamp, timeStampSize, fmt, &timeInfo ); -#else - std::strftime( timeStamp, timeStampSize, fmt, timeInfo ); -#endif + if ( std::strftime( timeStamp, timeStampSize, fmt, &timeInfo ) == 0 ) { + return "gmtime from provided timepoint has failed"; + } return std::string( timeStamp, timeStampSize - 1 ); } @@ -7082,7 +7205,6 @@ namespace Catch { #include <ostream> #include <cstring> -#include <cctype> #include <vector> namespace Catch { @@ -7112,9 +7234,6 @@ namespace Catch { toLowerInPlace( lc ); return lc; } - char toLower(char c) { - return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); - } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; @@ -7327,7 +7446,6 @@ namespace Catch { #include <algorithm> -#include <set> namespace Catch { @@ -7337,23 +7455,39 @@ namespace Catch { static void enforceNoDuplicateTestCases( std::vector<TestCaseHandle> const& tests ) { - auto testInfoCmp = []( TestCaseInfo const* lhs, - TestCaseInfo const* rhs ) { - return *lhs < *rhs; - }; - std::set<TestCaseInfo const*, decltype( testInfoCmp )&> seenTests( - testInfoCmp ); + // Flat set instead of std::set for performance. + std::vector<TestCaseInfo const*> seenTests; + seenTests.reserve( tests.size() ); for ( auto const& test : tests ) { - const auto infoPtr = &test.getTestCaseInfo(); - const auto prev = seenTests.insert( infoPtr ); - CATCH_ENFORCE( prev.second, - "error: test case \"" - << infoPtr->name << "\", with tags \"" - << infoPtr->tagsAsString() - << "\" already defined.\n" - << "\tFirst seen at " - << ( *prev.first )->lineInfo << "\n" - << "\tRedefined at " << infoPtr->lineInfo ); + seenTests.push_back( &test.getTestCaseInfo() ); + } + // We want to keep order of ~equal~ tests + // stable_sort so that duplicates are reported with the + // registration order preserved ("first seen" really is first) + std::stable_sort( + seenTests.begin(), + seenTests.end(), + []( TestCaseInfo const* lhs, TestCaseInfo const* rhs ) { + return *lhs < *rhs; + } + ); + const auto duplicate = std::adjacent_find( + seenTests.begin(), + seenTests.end(), + []( TestCaseInfo const* lhs, TestCaseInfo const* rhs ) { + return *lhs == *rhs; + } + ); + + if ( duplicate != seenTests.end() ) { + const auto infoPtr = *std::next( duplicate ); + CATCH_ERROR( "error: test case \"" + << infoPtr->name << "\", with tags \"" + << infoPtr->tagsAsString() + << "\" already defined.\n" + << "\tFirst seen at " + << ( *duplicate )->lineInfo << "\n" + << "\tRedefined at " << infoPtr->lineInfo ); } } @@ -7430,7 +7564,7 @@ namespace Catch { filtered.push_back(testCase); } } - return createShard(filtered, config.shardCount(), config.shardIndex()); + return createShard(CATCH_MOVE(filtered), config.shardCount(), config.shardIndex()); } std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config ) { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); @@ -7634,7 +7768,7 @@ namespace TestCaseTracking { bool SectionTracker::isFilteredImpl() const { // TBD: This is currently _very_ similar to the block in `isComplete`. - // Is this neccessarily that way, or just accident of current semantics? + // Is this necessarily that way, or just accident of current semantics? const size_t filterIndex = m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth; @@ -7810,6 +7944,15 @@ namespace Catch { return Detail::make_unique<TestInvokerAsFunction>( testAsFunction ); } + AutoReg::AutoReg( void ( *testAsFunction )(), + SourceLineInfo const& lineInfo, + StringRef classOrMethod, + NameAndTags const& nameAndTags ) noexcept: + AutoReg( makeTestInvoker( testAsFunction ), + lineInfo, + classOrMethod, + nameAndTags ) {} + AutoReg::AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { CATCH_TRY { getMutableRegistryHub() @@ -8087,6 +8230,15 @@ namespace { namespace Catch { namespace TextFlow { void AnsiSkippingString::preprocessString() { + // If there are no potential escapes, we can use fast path + if ( m_string.find( '\033' ) == std::string::npos ) { + size_t size = 0; + for ( const char c : m_string ) { + size += !isUtf8ContinuationByte( c ); + } + m_size = size; + return; + } for ( auto it = m_string.begin(); it != m_string.end(); ) { // try to read through an ansi sequence while ( it != m_string.end() && *it == '\033' && @@ -8099,8 +8251,10 @@ namespace Catch { if ( cursor == m_string.end() || *cursor != 'm' ) { break; } - // 'm' -> 0xff - *cursor = AnsiSkippingString::sentinel; + // record the escape sequence's byte range + m_escapes.push_back( + { std::distance( m_string.begin(), it ), + std::distance( m_string.begin(), cursor + 1 ) } ); // if we've read an ansi sequence, set the iterator and // return to the top of the loop it = cursor + 1; @@ -8128,79 +8282,102 @@ namespace Catch { } AnsiSkippingString::const_iterator AnsiSkippingString::begin() const { - return const_iterator( m_string ); + return const_iterator( *this ); } AnsiSkippingString::const_iterator AnsiSkippingString::end() const { - return const_iterator( m_string, const_iterator::EndTag{} ); + return const_iterator( *this, const_iterator::EndTag{} ); + } + + std::string AnsiSkippingString::substring( const_iterator first, + const_iterator last ) const { + std::string ret; + appendSubstringTo( ret, first, last ); + return ret; } - std::string AnsiSkippingString::substring( const_iterator begin, - const_iterator end ) const { + void + AnsiSkippingString::appendSubstringTo( std::string& out, + const_iterator first, + const_iterator last ) const { // There's one caveat here to an otherwise simple substring: when // making a begin iterator we might have skipped ansi sequences at - // the start. If `begin` here is a begin iterator, skipped over + // the start. If `first` here is a begin iterator, skipped over // initial ansi sequences, we'll use the true beginning of the - // string. Lastly: We need to transform any chars we replaced with - // 0xff back to 'm' - auto str = std::string( begin == this->begin() ? m_string.begin() - : begin.m_it, - end.m_it ); - std::transform( str.begin(), str.end(), str.begin(), []( char c ) { - return c == AnsiSkippingString::sentinel ? 'm' : c; - } ); - return str; - } - - void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() { - // check if we've landed on an ansi sequence, and if so read through - // it - while ( m_it != m_string->end() && *m_it == '\033' && - m_it + 1 != m_string->end() && *( m_it + 1 ) == '[' ) { - auto cursor = m_it + 2; - while ( cursor != m_string->end() && - ( isdigit( *cursor ) || *cursor == ';' ) ) { - ++cursor; - } - if ( cursor == m_string->end() || - *cursor != AnsiSkippingString::sentinel ) { + // string. + if ( hasEscapes() && first == begin() ) { + out.append( m_string.begin(), last.m_it ); + } else { + out.append( first.m_it, last.m_it ); + } + } + + void AnsiSkippingString::const_iterator::jumpForwardOverEscapes() { + // Escapes can only start with '\033' + if ( m_it == m_string->m_string.end() || *m_it != '\033' ) { + return; + } + // If we are at '\033', check if we are at an actual escape + auto current_pos = std::distance( m_string->m_string.begin(), m_it ); + + auto const& escapes = m_string->m_escapes; + auto candidate = + std::lower_bound( escapes.begin(), + escapes.end(), + current_pos, + []( AnsiSkippingString::EscapeRange const& r, + std::ptrdiff_t o ) { return r.start < o; } ); + // Escapes can be consecutive, we need to jump over all of them + while ( candidate != escapes.end() && candidate->start == current_pos ) { + current_pos = candidate->end; + ++candidate; + } + m_it = m_string->m_string.begin() + current_pos; + } + + void AnsiSkippingString::const_iterator::jumpBackOverEscapes() { + auto strBegin = m_string->m_string.begin(); + // Escapes only end with 'm' + while ( *m_it == 'm' ) { + // If we are at 'm', check if we are at an actual escape + const auto current_pos = std::distance( strBegin, m_it ); + + auto const& escapes = m_string->m_escapes; + auto range = std::lower_bound( + escapes.begin(), + escapes.end(), + current_pos + 1, // to half-open range + []( AnsiSkippingString::EscapeRange const& r, + std::ptrdiff_t o ) { return r.end < o; } ); + if ( range == escapes.end() || range->end != current_pos + 1 ) { break; } - // if we've read an ansi sequence, set the iterator and - // return to the top of the loop - m_it = cursor + 1; + assert( range->start != 0 && + "Trying to go back with begin iterator" ); + m_it = strBegin + range->start - 1; } } + void AnsiSkippingString::const_iterator::advance() { - assert( m_it != m_string->end() ); + auto strEnd = m_string->m_string.end(); + assert( m_it != strEnd ); m_it++; // Skip UTF-8 continuation bytes - while ( m_it != m_string->end() && + while ( m_it != strEnd && isUtf8ContinuationByte( *m_it ) ) { m_it++; } - tryParseAnsiEscapes(); + if ( m_string->hasEscapes() ) { jumpForwardOverEscapes(); } } void AnsiSkippingString::const_iterator::unadvance() { - assert( m_it != m_string->begin() ); + auto strBegin = m_string->m_string.begin(); + assert( m_it != strBegin ); m_it--; - // if *m_it is 0xff, scan back to the \033 and then m_it-- once more - // (and repeat check) - while ( *m_it == AnsiSkippingString::sentinel ) { - while ( *m_it != '\033' ) { - assert( m_it != m_string->begin() ); - m_it--; - } - // if this happens, we must have been a begin iterator that had - // skipped over ansi sequences at the start of a string - assert( m_it != m_string->begin() ); - assert( *m_it == '\033' ); - m_it--; - } + if ( m_string->hasEscapes() ) { jumpBackOverEscapes(); } // Skip back over UTF-8 continuation bytes to the leading byte - while ( m_it != m_string->begin() && + while ( m_it != strBegin && isUtf8ContinuationByte( *m_it ) ) { m_it--; } @@ -8278,10 +8455,8 @@ namespace Catch { AnsiSkippingString::const_iterator end ) const { std::string ret; const auto desired_indent = indentSize(); - // ret.reserve( desired_indent + (end - start) + m_addHyphen ); ret.append( desired_indent, ' ' ); - // ret.append( start, end ); - ret += m_column.m_string.substring( start, end ); + m_column.m_string.appendSubstringTo( ret, start, end ); if ( m_addHyphen ) { ret.push_back( '-' ); } return ret; @@ -8473,35 +8648,53 @@ namespace Catch { WildcardPattern::WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ) : m_caseSensitivity( caseSensitivity ), - m_pattern( normaliseString( pattern ) ) + m_pattern( trim(pattern) ) { if( startsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 1 ); - m_wildcard = WildcardAtStart; + m_pattern = m_pattern.erase( 0, 1 ); // effectively pop_front + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtStart ); } if( endsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_pattern.pop_back(); m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); } } - bool WildcardPattern::matches( std::string const& str ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_pattern == normaliseString( str ); - case WildcardAtStart: - return endsWith( normaliseString( str ), m_pattern ); - case WildcardAtEnd: - return startsWith( normaliseString( str ), m_pattern ); - case WildcardAtBothEnds: - return contains( normaliseString( str ), m_pattern ); - default: - CATCH_INTERNAL_ERROR( "Unknown enum" ); - } - } + bool WildcardPattern::matches( StringRef input ) const { + input = trim( input ); + if ( input.size() < m_pattern.size() ) { return false; } + + auto casedMatch = [&]( StringRef lhs, StringRef rhs ) { + if ( m_caseSensitivity == CaseSensitive::Yes ) { + return lhs == rhs; + } else { + Detail::CaseInsensitiveEqualTo eq; + return eq( lhs, rhs ); + } + }; - std::string WildcardPattern::normaliseString( std::string const& str ) const { - return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str ); + switch ( m_wildcard ) { + case NoWildcard: + return casedMatch(input, m_pattern); + case WildcardAtStart: + return casedMatch( + input.substr( input.size() - m_pattern.size(), input.size() ), + m_pattern ); + case WildcardAtEnd: + return casedMatch( input.substr( 0, m_pattern.size() ), m_pattern ); + case WildcardAtBothEnds: { + // TBD: This has terrible performance, but it is also the least + // common use case. Once the StringContains matcher gets + // efficient implementation, we should reuse it here. + const size_t lastOffset = input.size() - m_pattern.size(); + for ( size_t offset = 0; offset <= lastOffset; ++offset ) { + if ( casedMatch( input.substr(offset, m_pattern.size()), m_pattern ) ) { return true; } + } + return false; + } + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } } } @@ -10366,7 +10559,7 @@ namespace Catch { if ( m_sectionStack.empty() ) { if ( !m_rootSection ) { m_rootSection = - Detail::make_unique<SectionNode>( incompleteStats ); + Detail::make_unique<SectionNode>( CATCH_MOVE( incompleteStats ) ); } node = m_rootSection.get(); } else { @@ -10376,7 +10569,7 @@ namespace Catch { BySectionInfo( sectionInfo ) ); if ( it == parentNode.childSections.end() ) { auto newNode = - Detail::make_unique<SectionNode>( incompleteStats ); + Detail::make_unique<SectionNode>( CATCH_MOVE( incompleteStats ) ); node = newNode.get(); parentNode.childSections.push_back( CATCH_MOVE( newNode ) ); } else { @@ -10756,7 +10949,7 @@ namespace Catch { row.insert( 0, m_width - row.size(), ' ' ); } m_width = new_width; - m_rows.push_back( row ); + m_rows.push_back( CATCH_MOVE( row ) ); return std::move( *this ); } |