Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/extras/catch_amalgamated.cpp
diff options
context:
space:
mode:
authorLeitwolf112026-09-28 01:10:14 +0300
committerLeitwolf112026-09-28 01:10:14 +0300
commit97328fefe9650c84b594f1cea212c48562e1745b (patch)
tree8905f56f9a2f337637d6ed9c86675b52829239b5 /extras/catch_amalgamated.cpp
parentfbd2ec1c12c3ea39df0b7af01b6ab11360302299 (diff)
downloadcatch3-97328fefe9650c84b594f1cea212c48562e1745b.tar.gz
Harden Catch3 exit guards and pin build workflows
Diffstat (limited to 'extras/catch_amalgamated.cpp')
-rw-r--r--extras/catch_amalgamated.cpp525
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 );
}