Store tags in one big pre-allocated string and only work with refs

This should decrease the number of allocations before main is entered
significantly, but complicates the code somewhat in return.

Assuming I used `massif` right, doing just `SelfTest --list-tests`
went from 929 allocations at "Remove gcc-4.9 from the travis builds"
(2 commits up), to 614 allocations with this commit.
This commit is contained in:
Martin Hořeňovský 2019-11-07 12:39:07 +01:00
parent 302e2c0b06
commit d36c15c3ca
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
18 changed files with 296 additions and 187 deletions

View File

@ -21,6 +21,10 @@ std::string ws(int const level) {
return std::string( 2 * level, ' ' ); return std::string( 2 * level, ' ' );
} }
std::ostream& operator<<(std::ostream& out, Catch::Tag t) {
return out << "original: " << t.original << "lower cased: " << t.lowerCased;
}
template< typename T > template< typename T >
std::ostream& operator<<( std::ostream& os, std::vector<T> const& v ) { std::ostream& operator<<( std::ostream& os, std::vector<T> const& v ) {
os << "{ "; os << "{ ";
@ -119,31 +123,36 @@ void print( std::ostream& os, int const level, std::string const& title, Catch::
os << ws(level+1) << "- aborting: " << info.aborting << "\n"; os << ws(level+1) << "- aborting: " << info.aborting << "\n";
} }
// struct TestCaseInfo { // struct Tag {
// enum SpecialProperties{ // StringRef original, lowerCased;
// None = 0, // };
// IsHidden = 1 << 1,
// ShouldFail = 1 << 2,
// MayFail = 1 << 3,
// Throws = 1 << 4,
// NonPortable = 1 << 5,
// Benchmark = 1 << 6
// };
// //
// bool isHidden() const;
// bool throws() const;
// bool okToFail() const;
// bool expectedToFail() const;
// //
// std::string tagsAsString() const; // enum class TestCaseProperties : uint8_t {
// None = 0,
// IsHidden = 1 << 1,
// ShouldFail = 1 << 2,
// MayFail = 1 << 3,
// Throws = 1 << 4,
// NonPortable = 1 << 5,
// Benchmark = 1 << 6
// };
// //
// std::string name; //
// std::string className; // struct TestCaseInfo : NonCopyable {
// std::vector<std::string> tags; //
// std::vector<std::string> lcaseTags; // bool isHidden() const;
// SourceLineInfo lineInfo; // bool throws() const;
// SpecialProperties properties; // bool okToFail() const;
// }; // bool expectedToFail() const;
//
//
// std::string name;
// std::string className;
// std::vector<Tag> tags;
// SourceLineInfo lineInfo;
// TestCaseProperties properties = TestCaseProperties::None;
// };
void print( std::ostream& os, int const level, std::string const& title, Catch::TestCaseInfo const& info ) { void print( std::ostream& os, int const level, std::string const& title, Catch::TestCaseInfo const& info ) {
os << ws(level ) << title << ":\n" os << ws(level ) << title << ":\n"
@ -154,10 +163,9 @@ void print( std::ostream& os, int const level, std::string const& title, Catch::
<< ws(level+1) << "- tagsAsString(): '" << info.tagsAsString() << "'\n" << ws(level+1) << "- tagsAsString(): '" << info.tagsAsString() << "'\n"
<< ws(level+1) << "- name: '" << info.name << "'\n" << ws(level+1) << "- name: '" << info.name << "'\n"
<< ws(level+1) << "- className: '" << info.className << "'\n" << ws(level+1) << "- className: '" << info.className << "'\n"
<< ws(level+1) << "- tags: " << info.tags << "\n" << ws(level+1) << "- tags: " << info.tags << "\n";
<< ws(level+1) << "- lcaseTags: " << info.lcaseTags << "\n";
print( os, level+1 , "- lineInfo", info.lineInfo ); print( os, level+1 , "- lineInfo", info.lineInfo );
os << ws(level+1) << "- properties (flags): 0x" << std::hex << info.properties << std::dec << "\n"; os << ws(level+1) << "- properties (flags): 0x" << std::hex << static_cast<uint32_t>(info.properties) << std::dec << "\n";
} }
// struct TestCaseStats { // struct TestCaseStats {

View File

@ -9,10 +9,12 @@
#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED #define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED
#include <vector> #include <vector>
#include <memory>
namespace Catch { namespace Catch {
class TestSpec; class TestSpec;
struct TestCaseInfo;
struct ITestInvoker { struct ITestInvoker {
virtual void invoke () const = 0; virtual void invoke () const = 0;
@ -24,6 +26,7 @@ namespace Catch {
struct ITestCaseRegistry { struct ITestCaseRegistry {
virtual ~ITestCaseRegistry(); virtual ~ITestCaseRegistry();
virtual std::vector<std::unique_ptr<TestCaseInfo>> const& getAllInfos() const = 0;
virtual std::vector<TestCaseHandle> const& getAllTests() const = 0; virtual std::vector<TestCaseHandle> const& getAllTests() const = 0;
virtual std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const = 0; virtual std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const = 0;
}; };

View File

@ -38,14 +38,13 @@ namespace Catch {
TestSpec testSpec = config.testSpec(); TestSpec testSpec = config.testSpec();
std::vector<TestCaseHandle> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); std::vector<TestCaseHandle> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config);
std::map<std::string, TagInfo> tagCounts; std::map<StringRef, TagInfo> tagCounts;
for (auto const& testCase : matchedTestCases) { for (auto const& testCase : matchedTestCases) {
for (auto const& tagName : testCase.getTestCaseInfo().tags) { for (auto const& tagName : testCase.getTestCaseInfo().tags) {
std::string lcaseTagName = toLower(tagName); auto it = tagCounts.find(tagName.lowerCased);
auto countIt = tagCounts.find(lcaseTagName); if (it == tagCounts.end())
if (countIt == tagCounts.end()) it = tagCounts.insert(std::make_pair(tagName.lowerCased, TagInfo())).first;
countIt = tagCounts.insert(std::make_pair(lcaseTagName, TagInfo())).first; it->second.add(tagName.original);
countIt->second.add(tagName);
} }
} }
@ -71,16 +70,16 @@ namespace Catch {
} // end anonymous namespace } // end anonymous namespace
void TagInfo::add( std::string const& spelling ) { void TagInfo::add( StringRef spelling ) {
++count; ++count;
spellings.insert( spelling ); spellings.insert( spelling );
} }
std::string TagInfo::all() const { std::string TagInfo::all() const {
size_t size = 0; // 2 per tag for brackets '[' and ']'
size_t size = spellings.size() * 2;
for (auto const& spelling : spellings) { for (auto const& spelling : spellings) {
// Add 2 for the brackes size += spelling.size();
size += spelling.size() + 2;
} }
std::string out; out.reserve(size); std::string out; out.reserve(size);

View File

@ -23,10 +23,10 @@ namespace Catch {
}; };
struct TagInfo { struct TagInfo {
void add(std::string const& spelling); void add(StringRef spelling);
std::string all() const; std::string all() const;
std::set<std::string> spellings; std::set<StringRef> spellings;
std::size_t count = 0; std::size_t count = 0;
}; };

View File

@ -116,26 +116,9 @@ namespace Catch {
TestSpec::Matches m_matches; TestSpec::Matches m_matches;
}; };
void applyFilenamesAsTags(Catch::IConfig const& config) { void applyFilenamesAsTags() {
for (auto const& testCase : getAllTestCasesSorted(config)) { for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) {
// Yeah, sue me. This will be removed soon. testInfo->addFilenameTag();
auto& testInfo = const_cast<TestCaseInfo&>(testCase.getTestCaseInfo());
std::string filename = testInfo.lineInfo.file;
auto lastSlash = filename.find_last_of("\\/");
if (lastSlash != std::string::npos) {
filename.erase(0, lastSlash);
filename[0] = '#';
}
auto lastDot = filename.find_last_of('.');
if (lastDot != std::string::npos) {
filename.erase(lastDot);
}
auto tags = testInfo.tags;
tags.push_back(std::move(filename));
setTags(testInfo, tags);
} }
} }
@ -285,8 +268,9 @@ namespace Catch {
seedRng( *m_config ); seedRng( *m_config );
if( m_configData.filenamesAsTags ) if (m_configData.filenamesAsTags) {
applyFilenamesAsTags( *m_config ); applyFilenamesAsTags();
}
// Create reporter(s) so we can route listings through them // Create reporter(s) so we can route listings through them
auto reporter = makeReporter(m_config); auto reporter = makeReporter(m_config);

View File

@ -38,6 +38,13 @@ namespace Catch {
&& (std::memcmp( m_start, other.m_start, m_size ) == 0); && (std::memcmp( m_start, other.m_start, m_size ) == 0);
} }
bool StringRef::operator<(StringRef const& rhs) const noexcept {
if (m_size < rhs.m_size) {
return strncmp(m_start, rhs.m_start, m_size) <= 0;
}
return strncmp(m_start, rhs.m_start, rhs.m_size) < 0;
}
auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& {
return os.write(str.data(), str.size()); return os.write(str.data(), str.size());
} }

View File

@ -58,6 +58,8 @@ namespace Catch {
return m_start[index]; return m_start[index];
} }
bool operator<(StringRef const& rhs) const noexcept;
public: // named queries public: // named queries
constexpr auto empty() const noexcept -> bool { constexpr auto empty() const noexcept -> bool {
return m_size == 0; return m_size == 0;
@ -91,7 +93,6 @@ namespace Catch {
auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;
auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;
constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {
return StringRef( rawChars, size ); return StringRef( rawChars, size );
} }

View File

@ -20,27 +20,58 @@
namespace Catch { namespace Catch {
namespace { namespace {
TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { using TCP_underlying_type = uint8_t;
if( startsWith( tag, '.' ) || static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type),
tag == "!hide" ) "The size of the TestCaseProperties is different from the assumed size");
return TestCaseInfo::IsHidden;
else if( tag == "!throws" ) TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {
return TestCaseInfo::Throws; return static_cast<TestCaseProperties>(
else if( tag == "!shouldfail" ) static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
return TestCaseInfo::ShouldFail; );
else if( tag == "!mayfail" ) }
return TestCaseInfo::MayFail;
else if( tag == "!nonportable" ) TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {
return TestCaseInfo::NonPortable; lhs = static_cast<TestCaseProperties>(
else if( tag == "!benchmark" ) static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); );
return lhs;
}
TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {
return static_cast<TestCaseProperties>(
static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs)
);
}
bool applies(TestCaseProperties tcp) {
static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0,
"TestCaseProperties::None must be equal to 0");
return tcp != TestCaseProperties::None;
}
TestCaseProperties parseSpecialTag( StringRef tag ) {
if( (!tag.empty() && tag[0] == '.')
|| tag == "!hide"_sr )
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 static_cast<TestCaseProperties>(TestCaseProperties::Benchmark | TestCaseProperties::IsHidden );
else else
return TestCaseInfo::None; return TestCaseProperties::None;
} }
bool isReservedTag( std::string const& tag ) { bool isReservedTag( StringRef tag ) {
return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) ); return parseSpecialTag( tag ) == TestCaseProperties::None
&& tag.size() > 0
&& !std::isalnum( static_cast<unsigned char>(tag[0]) );
} }
void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) {
CATCH_ENFORCE( !isReservedTag(tag), CATCH_ENFORCE( !isReservedTag(tag),
"Tag name: [" << tag << "] is not allowed.\n" "Tag name: [" << tag << "] is not allowed.\n"
<< "Tag names starting with non alphanumeric characters are reserved\n" << "Tag names starting with non alphanumeric characters are reserved\n"
@ -51,90 +82,117 @@ namespace Catch {
static size_t counter = 0; static size_t counter = 0;
return "Anonymous test case " + std::to_string(++counter); return "Anonymous test case " + std::to_string(++counter);
} }
StringRef extractFilenamePart(StringRef filename) {
size_t lastDot = filename.size();
while (lastDot > 0 && filename[lastDot - 1] != '.') {
--lastDot;
}
--lastDot;
size_t nameStart = lastDot;
while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') {
--nameStart;
}
return filename.substr(nameStart, lastDot - nameStart);
}
// Returns the upper bound on size of extra tags ([#file]+[.])
size_t sizeOfExtraTags(StringRef filepath) {
// [.] is 3, [#] is another 3
const size_t extras = 3 + 3;
return extractFilenamePart(filepath).size() + extras;
}
} }
std::unique_ptr<TestCaseInfo> std::unique_ptr<TestCaseInfo>
makeTestCaseInfo(std::string const& _className, makeTestCaseInfo(std::string const& _className,
NameAndTags const& nameAndTags, NameAndTags const& nameAndTags,
SourceLineInfo const& _lineInfo ) SourceLineInfo const& _lineInfo ) {
{ return std::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo);
bool isHidden = false;
// Parse out tags
std::vector<std::string> tags;
std::string tag;
bool inTag = false;
for (char c : nameAndTags.tags) {
if( !inTag ) {
if (c == '[') {
inTag = true;
}
} else {
if( c == ']' ) {
TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
if( ( prop & TestCaseInfo::IsHidden ) != 0 )
isHidden = true;
else if( prop == TestCaseInfo::None )
enforceNotReservedTag( tag, _lineInfo );
// Merged hide tags like `[.approvals]` should be added as
// `[.][approvals]`. The `[.]` is added at later point, so
// we only strip the prefix
if (startsWith(tag, '.') && tag.size() > 1) {
tag.erase(0, 1);
}
tags.push_back( tag );
tag.clear();
inTag = false;
}
else
tag += c;
}
}
if( isHidden ) {
tags.push_back( "." );
}
return std::make_unique<TestCaseInfo>(static_cast<std::string>(nameAndTags.name),
_className, tags, _lineInfo);
} }
void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { TestCaseInfo::TestCaseInfo(std::string const& _className,
std::sort(begin(tags), end(tags)); NameAndTags const& _nameAndTags,
tags.erase(std::unique(begin(tags), end(tags)), end(tags)); SourceLineInfo const& _lineInfo):
testCaseInfo.lcaseTags.clear(); name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ),
for( auto const& tag : tags ) {
std::string lcaseTag = toLower( tag );
testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
testCaseInfo.lcaseTags.push_back( lcaseTag );
}
testCaseInfo.tags = std::move(tags);
}
TestCaseInfo::TestCaseInfo( std::string const& _name,
std::string const& _className,
std::vector<std::string> const& _tags,
SourceLineInfo const& _lineInfo )
: name( _name.empty() ? makeDefaultName() : _name ),
className( _className ), className( _className ),
lineInfo( _lineInfo ), lineInfo( _lineInfo )
properties( None )
{ {
setTags( *this, _tags ); StringRef originalTags = _nameAndTags.tags;
// We need to reserve enough space to store all of the tags
// (including optional hidden tag and filename tag)
auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file);
backingTags.reserve(requiredSize);
backingLCaseTags.reserve(requiredSize);
// We cannot copy the tags directly, as we need to normalize
// some tags, so that [.foo] is copied as [.][foo].
size_t tagStart = 0;
size_t tagEnd = 0;
bool inTag = false;
for (size_t idx = 0; idx < originalTags.size(); ++idx) {
auto c = originalTags[idx];
if (c == '[') {
assert(!inTag);
inTag = true;
tagStart = idx;
}
if (c == ']') {
assert(inTag);
inTag = false;
tagEnd = idx;
assert(tagStart < tagEnd);
// We need to check the tag for special meanings, copy
// it over to backing storage and actually reference the
// backing storage in the saved tags
StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1);
enforceNotReservedTag(tagStr, lineInfo);
properties |= parseSpecialTag(tagStr);
// 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].
if (tagStr.size() > 1 && tagStr[0] == '.') {
tagStr = tagStr.substr(1, tagStr.size() - 1);
}
// We skip over dealing with the [.] tag, as we will add
// it later unconditionally and then sort and unique all
// the tags.
internalAppendTag(tagStr);
}
(void)inTag; // Silence "set-but-unused" warning in release mode.
}
// Add [.] if relevant
if (isHidden()) {
internalAppendTag("."_sr);
}
// Sort and prepare tags
toLowerInPlace(backingLCaseTags);
std::sort(begin(tags), end(tags), [](Tag lhs, Tag rhs) { return lhs.lowerCased < rhs.lowerCased; });
tags.erase(std::unique(begin(tags), end(tags), [](Tag lhs, Tag rhs) {return lhs.lowerCased == rhs.lowerCased; }),
end(tags));
} }
bool TestCaseInfo::isHidden() const { bool TestCaseInfo::isHidden() const {
return ( properties & IsHidden ) != 0; return applies( properties & TestCaseProperties::IsHidden );
} }
bool TestCaseInfo::throws() const { bool TestCaseInfo::throws() const {
return ( properties & Throws ) != 0; return applies( properties & TestCaseProperties::Throws );
} }
bool TestCaseInfo::okToFail() const { bool TestCaseInfo::okToFail() const {
return ( properties & (ShouldFail | MayFail ) ) != 0; return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) );
} }
bool TestCaseInfo::expectedToFail() const { bool TestCaseInfo::expectedToFail() const {
return ( properties & (ShouldFail ) ) != 0; return applies( properties & (TestCaseProperties::ShouldFail) );
}
void TestCaseInfo::addFilenameTag() {
std::string combined("#");
combined += extractFilenamePart(lineInfo.file);
internalAppendTag(combined);
} }
std::string TestCaseInfo::tagsAsString() const { std::string TestCaseInfo::tagsAsString() const {
@ -142,18 +200,33 @@ namespace Catch {
// '[' and ']' per tag // '[' and ']' per tag
std::size_t full_size = 2 * tags.size(); std::size_t full_size = 2 * tags.size();
for (const auto& tag : tags) { for (const auto& tag : tags) {
full_size += tag.size(); full_size += tag.original.size();
} }
ret.reserve(full_size); ret.reserve(full_size);
for (const auto& tag : tags) { for (const auto& tag : tags) {
ret.push_back('['); ret.push_back('[');
ret.append(tag); ret += tag.original;
ret.push_back(']'); ret.push_back(']');
} }
return ret; return ret;
} }
void TestCaseInfo::internalAppendTag(StringRef tagStr) {
backingTags += '[';
const auto backingStart = backingTags.size();
backingTags += tagStr;
const auto backingEnd = backingTags.size();
backingTags += ']';
backingLCaseTags += '[';
// We append the tag to the lower-case backing storage as-is,
// because we will perform the lower casing later, in bulk
backingLCaseTags += tagStr;
backingLCaseTags += ']';
tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart),
StringRef(backingLCaseTags.c_str() + backingStart, backingEnd - backingStart));
}
bool TestCaseHandle::operator == ( TestCaseHandle const& rhs ) const { bool TestCaseHandle::operator == ( TestCaseHandle const& rhs ) const {
return m_invoker == rhs.m_invoker return m_invoker == rhs.m_invoker

View File

@ -9,6 +9,7 @@
#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED
#include "catch_common.h" #include "catch_common.h"
#include "catch_stringref.h"
#include "catch_test_registry.h" #include "catch_test_registry.h"
#include <string> #include <string>
@ -22,39 +23,54 @@
namespace Catch { namespace Catch {
struct Tag {
Tag(StringRef original_, StringRef lowerCased_):
original(original_), lowerCased(lowerCased_)
{}
StringRef original, lowerCased;
};
struct ITestInvoker; struct ITestInvoker;
enum class TestCaseProperties : uint8_t {
None = 0,
IsHidden = 1 << 1,
ShouldFail = 1 << 2,
MayFail = 1 << 3,
Throws = 1 << 4,
NonPortable = 1 << 5,
Benchmark = 1 << 6
};
struct TestCaseInfo : NonCopyable { struct TestCaseInfo : NonCopyable {
enum SpecialProperties{
None = 0,
IsHidden = 1 << 1,
ShouldFail = 1 << 2,
MayFail = 1 << 3,
Throws = 1 << 4,
NonPortable = 1 << 5,
Benchmark = 1 << 6
};
TestCaseInfo( std::string const& _name, TestCaseInfo(std::string const& _className,
std::string const& _className, NameAndTags const& _tags,
std::vector<std::string> const& _tags, SourceLineInfo const& _lineInfo);
SourceLineInfo const& _lineInfo );
friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags );
bool isHidden() const; bool isHidden() const;
bool throws() const; bool throws() const;
bool okToFail() const; bool okToFail() const;
bool expectedToFail() const; bool expectedToFail() const;
// Adds the tag(s) with test's filename (for the -# flag)
void addFilenameTag();
std::string tagsAsString() const; std::string tagsAsString() const;
std::string name; std::string name;
std::string className; std::string className;
std::vector<std::string> tags; private:
std::vector<std::string> lcaseTags; std::string backingTags, backingLCaseTags;
// Internally we copy tags to the backing storage and then add
// refs to this storage to the tags vector.
void internalAppendTag(StringRef tagString);
public:
std::vector<Tag> tags;
SourceLineInfo lineInfo; SourceLineInfo lineInfo;
SpecialProperties properties; TestCaseProperties properties = TestCaseProperties::None;
}; };
class TestCaseHandle { class TestCaseHandle {

View File

@ -78,6 +78,10 @@ namespace Catch {
m_invokers.push_back(std::move(testInvoker)); m_invokers.push_back(std::move(testInvoker));
} }
std::vector<std::unique_ptr<TestCaseInfo>> const& TestRegistry::getAllInfos() const {
return m_infos;
}
std::vector<TestCaseHandle> const& TestRegistry::getAllTests() const { std::vector<TestCaseHandle> const& TestRegistry::getAllTests() const {
return m_handles; return m_handles;
} }

View File

@ -37,6 +37,7 @@ namespace Catch {
virtual void registerTest( std::unique_ptr<TestCaseInfo> testInfo, std::unique_ptr<ITestInvoker> testInvoker ); virtual void registerTest( std::unique_ptr<TestCaseInfo> testInfo, std::unique_ptr<ITestInvoker> testInvoker );
std::vector<std::unique_ptr<TestCaseInfo>> const& getAllInfos() const override;
std::vector<TestCaseHandle> const& getAllTests() const override; std::vector<TestCaseHandle> const& getAllTests() const override;
std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const override; std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const override;

View File

@ -43,9 +43,11 @@ namespace Catch {
{} {}
bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {
return std::find(begin(testCase.lcaseTags), return std::find_if(begin(testCase.tags),
end(testCase.lcaseTags), end(testCase.tags),
m_tag) != end(testCase.lcaseTags); [&](Tag const& tag) {
return tag.lowerCased == m_tag;
}) != end(testCase.tags);
} }
bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {

View File

@ -48,12 +48,17 @@ namespace Catch {
return std::string(timeStamp); return std::string(timeStamp);
} }
std::string fileNameTag(const std::vector<std::string> &tags) { std::string fileNameTag(std::vector<Tag> const& tags) {
auto it = std::find_if(begin(tags), auto it = std::find_if(begin(tags),
end(tags), end(tags),
[] (std::string const& tag) {return tag.front() == '#'; }); [] (Tag const& tag) {
if (it != tags.end()) return tag.original.size() > 0
return it->substr(1); && tag.original[0] == '#'; });
if (it != tags.end()) {
return static_cast<std::string>(
it->original.substr(1, it->original.size() - 1)
);
}
return std::string(); return std::string();
} }
} // anonymous namespace } // anonymous namespace

View File

@ -312,7 +312,7 @@ namespace Catch {
auto aliasTag = m_xml.scopedElement("Aliases"); auto aliasTag = m_xml.scopedElement("Aliases");
for (auto const& alias : tag.spellings) { for (auto const& alias : tag.spellings) {
m_xml.startElement("Alias", XmlFormatting::Indent) m_xml.startElement("Alias", XmlFormatting::Indent)
.writeText(alias, XmlFormatting::None) .writeText(static_cast<std::string>(alias), XmlFormatting::None)
.endElement(XmlFormatting::Newline); .endElement(XmlFormatting::Newline);
} }
} }

View File

@ -1662,7 +1662,7 @@ StringManip.tests.cpp:<line number>: passed: Catch::replaceInPlace(s, "'", "|'")
StringManip.tests.cpp:<line number>: passed: s == "didn|'t" for: "didn|'t" == "didn|'t" StringManip.tests.cpp:<line number>: passed: s == "didn|'t" for: "didn|'t" == "didn|'t"
Misc.tests.cpp:<line number>: failed: false with 1 message: '3' Misc.tests.cpp:<line number>: failed: false with 1 message: '3'
Message.tests.cpp:<line number>: failed: false with 2 messages: 'hi' and 'i := 7' Message.tests.cpp:<line number>: failed: false with 2 messages: 'hi' and 'i := 7'
Tag.tests.cpp:<line number>: passed: testcase->tags, Catch::VectorContains(std::string("magic-tag")) && Catch::VectorContains(std::string(".")) for: { ".", "magic-tag" } ( Contains: "magic-tag" and Contains: "." ) Tag.tests.cpp:<line number>: passed: tags, Catch::VectorContains("magic-tag"_catch_sr) && Catch::VectorContains("."_catch_sr) for: { ., magic-tag } ( Contains: magic-tag and Contains: . )
StringManip.tests.cpp:<line number>: passed: splitStringRef("", ','), Equals(std::vector<StringRef>()) for: { } Equals: { } StringManip.tests.cpp:<line number>: passed: splitStringRef("", ','), Equals(std::vector<StringRef>()) for: { } Equals: { }
StringManip.tests.cpp:<line number>: passed: splitStringRef("abc", ','), Equals(std::vector<StringRef>{"abc"}) for: { abc } Equals: { abc } StringManip.tests.cpp:<line number>: passed: splitStringRef("abc", ','), Equals(std::vector<StringRef>{"abc"}) for: { abc } Equals: { abc }
StringManip.tests.cpp:<line number>: passed: splitStringRef("abc,def", ','), Equals(std::vector<StringRef>{"abc", "def"}) for: { abc, def } Equals: { abc, def } StringManip.tests.cpp:<line number>: passed: splitStringRef("abc,def", ','), Equals(std::vector<StringRef>{"abc", "def"}) for: { abc, def } Equals: { abc, def }

View File

@ -12341,9 +12341,9 @@ Tag.tests.cpp:<line number>
............................................................................... ...............................................................................
Tag.tests.cpp:<line number>: PASSED: Tag.tests.cpp:<line number>: PASSED:
REQUIRE_THAT( testcase->tags, Catch::VectorContains(std::string("magic-tag")) && Catch::VectorContains(std::string(".")) ) REQUIRE_THAT( tags, Catch::VectorContains("magic-tag"_catch_sr) && Catch::VectorContains("."_catch_sr) )
with expansion: with expansion:
{ ".", "magic-tag" } ( Contains: "magic-tag" and Contains: "." ) { ., magic-tag } ( Contains: magic-tag and Contains: . )
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
splitString splitString

View File

@ -1730,7 +1730,7 @@ Nor would this
</Failure> </Failure>
<OverallResult success="false"/> <OverallResult success="false"/>
</TestCase> </TestCase>
<TestCase name="A failing expression with a non streamable type is still captured" tags="[.][Tricky][failing]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="A failing expression with a non streamable type is still captured" tags="[.][failing][Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Expression success="false" type="CHECK" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <Expression success="false" type="CHECK" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Original> <Original>
&amp;o1 == &amp;o2 &amp;o1 == &amp;o2
@ -2510,7 +2510,7 @@ Nor would this
</Expression> </Expression>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="Comparing function pointers" tags="[Tricky][function pointer]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="Comparing function pointers" tags="[function pointer][Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Original> <Original>
a a
@ -10985,7 +10985,7 @@ Message from section two
</Section> </Section>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="StringRef at compilation time" tags="[StringRef][Strings][constexpr]" filename="projects/<exe-name>/IntrospectiveTests/String.tests.cpp" > <TestCase name="StringRef at compilation time" tags="[constexpr][StringRef][Strings]" filename="projects/<exe-name>/IntrospectiveTests/String.tests.cpp" >
<Section name="Simple constructors" filename="projects/<exe-name>/IntrospectiveTests/String.tests.cpp" > <Section name="Simple constructors" filename="projects/<exe-name>/IntrospectiveTests/String.tests.cpp" >
<OverallResults successes="5" failures="0" expectedFailures="0"/> <OverallResults successes="5" failures="0" expectedFailures="0"/>
</Section> </Section>
@ -13709,13 +13709,13 @@ There is no extra whitespace here
<TestCase name="When unchecked exceptions are thrown, but caught, they do not affect the test" tags="[!throws]" filename="projects/<exe-name>/UsageTests/Exception.tests.cpp" > <TestCase name="When unchecked exceptions are thrown, but caught, they do not affect the test" tags="[!throws]" filename="projects/<exe-name>/UsageTests/Exception.tests.cpp" >
<OverallResult success="false"/> <OverallResult success="false"/>
</TestCase> </TestCase>
<TestCase name="Where the LHS is not a simple value" tags="[.][Tricky][failing]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="Where the LHS is not a simple value" tags="[.][failing][Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Warning> <Warning>
Uncomment the code in this test to check that it gives a sensible compiler error Uncomment the code in this test to check that it gives a sensible compiler error
</Warning> </Warning>
<OverallResult success="false"/> <OverallResult success="false"/>
</TestCase> </TestCase>
<TestCase name="Where there is more to the expression after the RHS" tags="[.][Tricky][failing]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="Where there is more to the expression after the RHS" tags="[.][failing][Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Warning> <Warning>
Uncomment the code in this test to check that it gives a sensible compiler error Uncomment the code in this test to check that it gives a sensible compiler error
</Warning> </Warning>
@ -13724,7 +13724,7 @@ There is no extra whitespace here
<TestCase name="X/level/0/a" tags="[Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="X/level/0/a" tags="[Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="X/level/0/b" tags="[Tricky][fizz]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="X/level/0/b" tags="[fizz][Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="X/level/1/a" tags="[Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="X/level/1/a" tags="[Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
@ -14504,7 +14504,7 @@ loose text artifact
</Expression> </Expression>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="parseEnums" tags="[Strings][enums]" filename="projects/<exe-name>/IntrospectiveTests/ToString.tests.cpp" > <TestCase name="parseEnums" tags="[enums][Strings]" filename="projects/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Section name="No enums" filename="projects/<exe-name>/IntrospectiveTests/ToString.tests.cpp" > <Section name="No enums" filename="projects/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Expression success="true" type="CHECK_THAT" filename="projects/<exe-name>/IntrospectiveTests/ToString.tests.cpp" > <Expression success="true" type="CHECK_THAT" filename="projects/<exe-name>/IntrospectiveTests/ToString.tests.cpp" >
<Original> <Original>
@ -14861,10 +14861,10 @@ loose text artifact
<TestCase name="shortened hide tags are split apart" filename="projects/<exe-name>/IntrospectiveTests/Tag.tests.cpp" > <TestCase name="shortened hide tags are split apart" filename="projects/<exe-name>/IntrospectiveTests/Tag.tests.cpp" >
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/IntrospectiveTests/Tag.tests.cpp" > <Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/IntrospectiveTests/Tag.tests.cpp" >
<Original> <Original>
testcase->tags, Catch::VectorContains(std::string("magic-tag")) &amp;&amp; Catch::VectorContains(std::string(".")) tags, Catch::VectorContains("magic-tag"_catch_sr) &amp;&amp; Catch::VectorContains("."_catch_sr)
</Original> </Original>
<Expanded> <Expanded>
{ ".", "magic-tag" } ( Contains: "magic-tag" and Contains: "." ) { ., magic-tag } ( Contains: magic-tag and Contains: . )
</Expanded> </Expanded>
</Expression> </Expression>
<OverallResult success="true"/> <OverallResult success="true"/>
@ -15050,7 +15050,7 @@ loose text artifact
</Expression> </Expression>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="string literals of different sizes can be compared" tags="[.][Tricky][failing]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <TestCase name="string literals of different sizes can be compared" tags="[.][failing][Tricky]" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Expression success="false" type="REQUIRE" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" > <Expression success="false" type="REQUIRE" filename="projects/<exe-name>/UsageTests/Tricky.tests.cpp" >
<Original> <Original>
std::string( "first" ) == "second" std::string( "first" ) == "second"

View File

@ -42,6 +42,12 @@ TEST_CASE( "Tag alias can be registered against tag patterns" ) {
} }
TEST_CASE("shortened hide tags are split apart") { TEST_CASE("shortened hide tags are split apart") {
using Catch::StringRef;
auto testcase = Catch::makeTestCaseInfo("", {"fake test name", "[.magic-tag]"}, CATCH_INTERNAL_LINEINFO); auto testcase = Catch::makeTestCaseInfo("", {"fake test name", "[.magic-tag]"}, CATCH_INTERNAL_LINEINFO);
REQUIRE_THAT(testcase->tags, Catch::VectorContains(std::string("magic-tag")) && Catch::VectorContains(std::string("."))); // Transform ...
std::vector<StringRef> tags;
for (auto const& tag : testcase->tags) {
tags.push_back(tag.original);
}
REQUIRE_THAT(tags, Catch::VectorContains("magic-tag"_catch_sr) && Catch::VectorContains("."_catch_sr));
} }