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

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMartin Hořeňovský2023-11-16 22:17:21 +0100
committerMartin Hořeňovský2023-11-17 09:54:03 +0100
commit32d9ae24bc4a0fed9a136a73e116b4ded0ac84b7 (patch)
tree03e04865114507d294be9b2e15225aad3ab8344b
parentde7ba4e8897bbe56c612b980d02f249a92d0eada (diff)
downloadcatch3-32d9ae24bc4a0fed9a136a73e116b4ded0ac84b7.tar.gz
JSONWriter deals in StringRefs instead of std::strings
Together with liberal use of `_sr` UDL to compile-time convert string literals into StringRefs, this will reduce the number of allocation and remove most of the strcpy calls inherent in converting string lits into `std::string`s.
-rw-r--r--src/catch2/internal/catch_jsonwriter.cpp43
-rw-r--r--src/catch2/internal/catch_jsonwriter.hpp45
-rw-r--r--src/catch2/reporters/catch_reporter_json.cpp153
-rw-r--r--src/catch2/reporters/catch_reporter_json.hpp4
4 files changed, 118 insertions, 127 deletions
diff --git a/src/catch2/internal/catch_jsonwriter.cpp b/src/catch2/internal/catch_jsonwriter.cpp
index f6c279d8..ff65a9d3 100644
--- a/src/catch2/internal/catch_jsonwriter.cpp
+++ b/src/catch2/internal/catch_jsonwriter.cpp
@@ -29,7 +29,7 @@ namespace Catch {
JsonObjectWriter::JsonObjectWriter( std::ostream& os,
std::uint64_t indent_level ):
m_os{ os }, m_indent_level{ indent_level } {
- m_os << "{";
+ m_os << '{';
}
JsonObjectWriter::JsonObjectWriter( JsonObjectWriter&& source ):
m_os{ source.m_os },
@@ -47,11 +47,11 @@ namespace Catch {
m_os << '}';
}
- JsonValueWriter JsonObjectWriter::write( std::string const& key ) {
+ JsonValueWriter JsonObjectWriter::write( StringRef key ) {
JsonUtils::appendCommaNewline(
m_os, m_should_comma, m_indent_level + 1 );
- m_os << '"' << key << '"' << ": ";
+ m_os << '"' << key << "\": ";
return JsonValueWriter{ m_os, m_indent_level + 1 };
}
@@ -60,7 +60,7 @@ namespace Catch {
JsonArrayWriter::JsonArrayWriter( std::ostream& os,
std::uint64_t indent_level ):
m_os{ os }, m_indent_level{ indent_level } {
- m_os << "[";
+ m_os << '[';
}
JsonArrayWriter::JsonArrayWriter( JsonArrayWriter&& source ):
m_os{ source.m_os },
@@ -108,8 +108,41 @@ namespace Catch {
return JsonArrayWriter{ m_os, m_indent_level };
}
+ void JsonValueWriter::write( Catch::StringRef value ) && {
+ writeImpl( value, true );
+ }
+
void JsonValueWriter::write( bool value ) && {
- writeImpl( value ? "true" : "false", false );
+ writeImpl( value ? "true"_sr : "false"_sr, false );
+ }
+
+ void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
+ if ( quote ) { m_os << '"'; }
+ for (char c : value) {
+ // Escape list taken from https://www.json.org/json-en.html,
+ // string definition.
+ // Note that while forward slash _can_ be escaped, it does
+ // not have to be, if JSON is not further embedded somewhere
+ // where forward slash is meaningful.
+ if ( c == '"' ) {
+ m_os << "\\\"";
+ } else if ( c == '\\' ) {
+ m_os << "\\\\";
+ } else if ( c == '\b' ) {
+ m_os << "\\b";
+ } else if ( c == '\f' ) {
+ m_os << "\\f";
+ } else if ( c == '\n' ) {
+ m_os << "\\n";
+ } else if ( c == '\r' ) {
+ m_os << "\\r";
+ } else if ( c == '\t' ) {
+ m_os << "\\t";
+ } else {
+ m_os << c;
+ }
+ }
+ if ( quote ) { m_os << '"'; }
}
} // namespace Catch
diff --git a/src/catch2/internal/catch_jsonwriter.hpp b/src/catch2/internal/catch_jsonwriter.hpp
index 1b6ac967..59c044e4 100644
--- a/src/catch2/internal/catch_jsonwriter.hpp
+++ b/src/catch2/internal/catch_jsonwriter.hpp
@@ -37,45 +37,26 @@ namespace Catch {
void write( T const& value ) && {
writeImpl( value, !std::is_arithmetic<T>::value );
}
-
+ void write( StringRef value ) &&;
void write( bool value ) &&;
private:
- template <typename T>
+ void writeImpl( StringRef value, bool quote );
+
+ // Without this SFINAE, this overload is a better match
+ // for `std::string`, `char const*`, `char const[N]` args.
+ // While it would still work, it would cause code bloat
+ // and multiple iteration over the strings
+ template <typename T,
+ typename = typename std::enable_if_t<
+ !std::is_convertible<T, StringRef>::value>>
void writeImpl( T const& value, bool quote_value ) {
- if ( quote_value ) { m_os << '"'; }
m_sstream << value;
- while ( true ) {
- char c = m_sstream.get();
- if ( m_sstream.eof() ) { break; }
-
- // see https://www.json.org/json-en.html, string definition for the escape list
- if ( c == '"' ) {
- m_os << "\\\"";
- } else if ( c == '\\' ) {
- m_os << "\\\\";
- // Note that while forward slash _can_ be escaped, it
- // does not have to be, if JSON is not further embedded
- // somewhere where forward slash is meaningful.
- } else if ( c == '\b' ) {
- m_os << "\\b";
- } else if ( c == '\f' ) {
- m_os << "\\f";
- } else if ( c == '\n' ) {
- m_os << "\\n";
- } else if ( c == '\r' ) {
- m_os << "\\r";
- } else if ( c == '\t') {
- m_os << "\\t";
- } else {
- m_os << c;
- }
- }
- if ( quote_value ) { m_os << '"'; }
+ writeImpl( m_sstream.str(), quote_value );
}
std::ostream& m_os;
- std::stringstream m_sstream{};
+ std::stringstream m_sstream;
std::uint64_t m_indent_level;
};
@@ -89,7 +70,7 @@ namespace Catch {
~JsonObjectWriter();
- JsonValueWriter write( std::string const& key );
+ JsonValueWriter write( StringRef key );
private:
std::ostream& m_os;
diff --git a/src/catch2/reporters/catch_reporter_json.cpp b/src/catch2/reporters/catch_reporter_json.cpp
index 7ddc7036..58ae388b 100644
--- a/src/catch2/reporters/catch_reporter_json.cpp
+++ b/src/catch2/reporters/catch_reporter_json.cpp
@@ -19,9 +19,10 @@ namespace Catch {
void writeSourceInfo( JsonObjectWriter& writer,
SourceLineInfo const& sourceInfo ) {
auto source_location_writer =
- writer.write( "source-location" ).writeObject();
- source_location_writer.write( "filename" ).write( sourceInfo.file );
- source_location_writer.write( "line" ).write( sourceInfo.line );
+ writer.write( "source-location"_sr ).writeObject();
+ source_location_writer.write( "filename"_sr )
+ .write( sourceInfo.file );
+ source_location_writer.write( "line"_sr ).write( sourceInfo.line );
}
void writeTags( JsonArrayWriter writer, std::vector<Tag> const& tags ) {
@@ -32,43 +33,14 @@ namespace Catch {
void writeProperties( JsonArrayWriter writer,
TestCaseInfo const& info ) {
- if ( info.isHidden() ) { writer.write( "is-hidden" ); }
- if ( info.okToFail() ) { writer.write( "ok-to-fail" ); }
- if ( info.expectedToFail() ) { writer.write( "expected-to-fail" ); }
- if ( info.throws() ) { writer.write( "throws" ); }
+ if ( info.isHidden() ) { writer.write( "is-hidden"_sr ); }
+ if ( info.okToFail() ) { writer.write( "ok-to-fail"_sr ); }
+ if ( info.expectedToFail() ) {
+ writer.write( "expected-to-fail"_sr );
+ }
+ if ( info.throws() ) { writer.write( "throws"_sr ); }
}
- //void writeCounts( JsonObjectWriter writer, Counts const& counts ) {
- // writer.write( "passed" ).write( counts.passed );
- // writer.write( "failed" ).write( counts.failed );
- // writer.write( "fail-but-ok" ).write( counts.failedButOk );
- // writer.write( "skipped" ).write( counts.skipped );
- //}
-
- //void writeTestInfo( JsonObjectWriter writer,
- // TestCaseInfo const& info ) {
- // writer.write( "name" ).write( info.name );
- // writeTags( writer.write( "tags" ).writeArray(), info.tags );
- // writeSourceInfo( writer, info.lineInfo );
- // writeProperties( writer.write( "properties" ).writeArray(), info );
- //}
-
- //void writeSection( JsonObjectWriter& writer,
- // CumulativeReporterBase::SectionNode const& section,
- // bool selfWrite ) {
- // if ( selfWrite ) {
- // writer.write( "name" ).write( section.stats.sectionInfo.name );
- // writeSourceInfo( writer, section.stats.sectionInfo.lineInfo );
- // writeCounts( writer.write( "assertions-stats" ).writeObject(),
- // section.stats.assertions );
- // }
- // if ( section.childSections.empty() ) { return; }
- // auto sectionsWriter = writer.write( "sections" ).writeArray();
- // for ( auto const& childPtr : section.childSections ) {
- // auto childSectionWriter = sectionsWriter.writeObject();
- // writeSection( childSectionWriter, *childPtr, true );
- // }
- //}
} // namespace
JsonReporter::JsonReporter( ReporterConfig&& config ):
@@ -84,15 +56,16 @@ namespace Catch {
m_writers.emplace( Writer::Object );
auto& writer = m_objectWriters.top();
- writer.write( "version" ).write( 1 );
+ writer.write( "version"_sr ).write( 1 );
{
- auto metadata_writer = writer.write( "metadata" ).writeObject();
- metadata_writer.write( "name" ).write( m_config->name() );
- metadata_writer.write( "rng-seed" ).write( m_config->rngSeed() );
- metadata_writer.write( "catch2-version" ).write( libraryVersion() );
+ auto metadata_writer = writer.write( "metadata"_sr ).writeObject();
+ metadata_writer.write( "name"_sr ).write( m_config->name() );
+ metadata_writer.write( "rng-seed"_sr ).write( m_config->rngSeed() );
+ metadata_writer.write( "catch2-version"_sr )
+ .write( libraryVersion() );
if ( m_config->testSpec().hasFilters() ) {
- metadata_writer.write( "filters" )
+ metadata_writer.write( "filters"_sr )
.write( m_config->testSpec() );
}
}
@@ -113,7 +86,7 @@ namespace Catch {
m_writers.emplace( Writer::Array );
return m_arrayWriters.top();
}
- JsonArrayWriter& JsonReporter::startArray( std::string const& key ) {
+ JsonArrayWriter& JsonReporter::startArray( StringRef key ) {
m_arrayWriters.emplace(
m_objectWriters.top().write( key ).writeArray() );
m_writers.emplace( Writer::Array );
@@ -125,7 +98,7 @@ namespace Catch {
m_writers.emplace( Writer::Object );
return m_objectWriters.top();
}
- JsonObjectWriter& JsonReporter::startObject( std::string const& key ) {
+ JsonObjectWriter& JsonReporter::startObject( StringRef key ) {
m_objectWriters.emplace(
m_objectWriters.top().write( key ).writeObject() );
m_writers.emplace( Writer::Object );
@@ -148,7 +121,7 @@ namespace Catch {
}
void JsonReporter::startListing() {
- if ( !m_startedListing ) { startObject( "listings" ); }
+ if ( !m_startedListing ) { startObject( "listings"_sr ); }
m_startedListing = true;
}
void JsonReporter::endListing() {
@@ -165,15 +138,15 @@ namespace Catch {
endListing();
assert( isInside( Writer::Object ) );
- startObject( "test-run" );
- startArray( "test-cases" );
+ startObject( "test-run"_sr );
+ startArray( "test-cases"_sr );
}
static void writeCounts( JsonObjectWriter&& writer, Counts const& counts ) {
- writer.write( "passed" ).write( counts.passed );
- writer.write( "failed" ).write( counts.failed );
- writer.write( "fail-but-ok" ).write( counts.failedButOk );
- writer.write( "skipped" ).write( counts.skipped );
+ writer.write( "passed"_sr ).write( counts.passed );
+ writer.write( "failed"_sr ).write( counts.failed );
+ writer.write( "fail-but-ok"_sr ).write( counts.failedButOk );
+ writer.write( "skipped"_sr ).write( counts.skipped );
}
void JsonReporter::testRunEnded(TestRunStats const& runStats) {
@@ -182,10 +155,11 @@ namespace Catch {
endArray();
{
- auto totals = m_objectWriters.top().write( "totals" ).writeObject();
- writeCounts( totals.write( "assertions" ).writeObject(),
+ auto totals =
+ m_objectWriters.top().write( "totals"_sr ).writeObject();
+ writeCounts( totals.write( "assertions"_sr ).writeObject(),
runStats.totals.assertions );
- writeCounts( totals.write( "test-cases" ).writeObject(),
+ writeCounts( totals.write( "test-cases"_sr ).writeObject(),
runStats.totals.testCases );
}
@@ -202,18 +176,18 @@ namespace Catch {
// "test-info" prelude
{
auto testInfo =
- m_objectWriters.top().write( "test-info" ).writeObject();
+ m_objectWriters.top().write( "test-info"_sr ).writeObject();
// TODO: handle testName vs className!!
- testInfo.write( "name" ).write( tcInfo.name );
+ testInfo.write( "name"_sr ).write( tcInfo.name );
writeSourceInfo(testInfo, tcInfo.lineInfo);
- writeTags( testInfo.write( "tags" ).writeArray(), tcInfo.tags );
- writeProperties( testInfo.write( "properties" ).writeArray(),
+ writeTags( testInfo.write( "tags"_sr ).writeArray(), tcInfo.tags );
+ writeProperties( testInfo.write( "properties"_sr ).writeArray(),
tcInfo );
}
// Start the array for individual test runs (testCasePartial pairs)
- startArray( "runs" );
+ startArray( "runs"_sr );
}
void JsonReporter::testCaseEnded( TestCaseStats const& tcStats ) {
@@ -224,8 +198,9 @@ namespace Catch {
endArray();
{
- auto totals = m_objectWriters.top().write( "totals" ).writeObject();
- writeCounts( totals.write( "assertions" ).writeObject(),
+ auto totals =
+ m_objectWriters.top().write( "totals"_sr ).writeObject();
+ writeCounts( totals.write( "assertions"_sr ).writeObject(),
tcStats.totals.assertions );
// We do not write the test case totals, because there will always be just one test case here.
// TODO: overall "result" -> success, skip, fail here? Or in partial result?
@@ -242,9 +217,8 @@ namespace Catch {
void JsonReporter::testCasePartialStarting( TestCaseInfo const& /*tcInfo*/,
uint64_t index ) {
startObject();
- m_objectWriters.top().write( "run-idx" ).write( index );
- startArray( "path" );
- //startObject( "path" );
+ m_objectWriters.top().write( "run-idx"_sr ).write( index );
+ startArray( "path"_sr );
// TODO: we want to delay most of the printing to the 'root' section
// TODO: childSection key name?
}
@@ -256,17 +230,18 @@ namespace Catch {
endArray();
if ( !tcStats.stdOut.empty() ) {
m_objectWriters.top()
- .write( "captured-stdout" )
+ .write( "captured-stdout"_sr )
.write( tcStats.stdOut );
}
if ( !tcStats.stdErr.empty() ) {
m_objectWriters.top()
- .write( "captured-stderr" )
+ .write( "captured-stderr"_sr )
.write( tcStats.stdErr );
}
{
- auto totals = m_objectWriters.top().write( "totals" ).writeObject();
- writeCounts( totals.write( "assertions" ).writeObject(),
+ auto totals =
+ m_objectWriters.top().write( "totals"_sr ).writeObject();
+ writeCounts( totals.write( "assertions"_sr ).writeObject(),
tcStats.totals.assertions );
// We do not write the test case totals, because there will
// always be just one test case here.
@@ -284,8 +259,8 @@ namespace Catch {
// We want to nest top level sections, even though it shares name
// and source loc with the TEST_CASE
auto& sectionObject = startObject();
- sectionObject.write( "kind" ).write( "section" );
- sectionObject.write( "name" ).write( sectionInfo.name );
+ sectionObject.write( "kind"_sr ).write( "section"_sr );
+ sectionObject.write( "name"_sr ).write( sectionInfo.name );
writeSourceInfo( m_objectWriters.top(), sectionInfo.lineInfo );
@@ -293,7 +268,7 @@ namespace Catch {
// rather complex, but we could do it, and it would look
// better for empty sections. OTOH, empty sections should
// be rare.
- startArray( "path" );
+ startArray( "path"_sr );
}
void JsonReporter::sectionEnded( SectionStats const& /*sectionStats */) {
// End the subpath array
@@ -317,10 +292,10 @@ namespace Catch {
assert( isInside( Writer::Array ) );
auto assertionObject = m_arrayWriters.top().writeObject();
- assertionObject.write( "kind" ).write( "assertion" );
+ assertionObject.write( "kind"_sr ).write( "assertion"_sr );
writeSourceInfo( assertionObject,
assertionStats.assertionResult.getSourceInfo() );
- assertionObject.write( "status" )
+ assertionObject.write( "status"_sr )
.write( assertionStats.assertionResult.isOk() );
// TODO: handling of result.
// TODO: messages
@@ -337,38 +312,40 @@ namespace Catch {
std::vector<ReporterDescription> const& descriptions ) {
startListing();
- auto writer = m_objectWriters.top().write( "reporters" ).writeArray();
+ auto writer =
+ m_objectWriters.top().write( "reporters"_sr ).writeArray();
for ( auto const& desc : descriptions ) {
auto desc_writer = writer.writeObject();
- desc_writer.write( "name" ).write( desc.name );
- desc_writer.write( "description" ).write( desc.description );
+ desc_writer.write( "name"_sr ).write( desc.name );
+ desc_writer.write( "description"_sr ).write( desc.description );
}
}
void JsonReporter::listListeners(
std::vector<ListenerDescription> const& descriptions ) {
startListing();
- auto writer = m_objectWriters.top().write( "listeners" ).writeArray();
+ auto writer =
+ m_objectWriters.top().write( "listeners"_sr ).writeArray();
for ( auto const& desc : descriptions ) {
auto desc_writer = writer.writeObject();
- desc_writer.write( "name" ).write( desc.name );
- desc_writer.write( "description" ).write( desc.description );
+ desc_writer.write( "name"_sr ).write( desc.name );
+ desc_writer.write( "description"_sr ).write( desc.description );
}
}
void JsonReporter::listTests( std::vector<TestCaseHandle> const& tests ) {
startListing();
- auto writer = m_objectWriters.top().write( "tests" ).writeArray();
+ auto writer = m_objectWriters.top().write( "tests"_sr ).writeArray();
for ( auto const& test : tests ) {
auto desc_writer = writer.writeObject();
auto const& info = test.getTestCaseInfo();
- desc_writer.write( "name" ).write( info.name );
- desc_writer.write( "class-name" ).write( info.className );
+ desc_writer.write( "name"_sr ).write( info.name );
+ desc_writer.write( "class-name"_sr ).write( info.className );
{
- auto tag_writer = desc_writer.write( "tags" ).writeArray();
+ auto tag_writer = desc_writer.write( "tags"_sr ).writeArray();
for ( auto const& tag : info.tags ) {
tag_writer.write( tag.original );
}
@@ -379,17 +356,17 @@ namespace Catch {
void JsonReporter::listTags( std::vector<TagInfo> const& tags ) {
startListing();
- auto writer = m_objectWriters.top().write( "tags" ).writeArray();
+ auto writer = m_objectWriters.top().write( "tags"_sr ).writeArray();
for ( auto const& tag : tags ) {
auto tag_writer = writer.writeObject();
{
auto aliases_writer =
- tag_writer.write( "aliases" ).writeArray();
+ tag_writer.write( "aliases"_sr ).writeArray();
for ( auto alias : tag.spellings ) {
aliases_writer.write( alias );
}
}
- tag_writer.write( "count" ).write( tag.count );
+ tag_writer.write( "count"_sr ).write( tag.count );
}
}
} // namespace Catch
diff --git a/src/catch2/reporters/catch_reporter_json.hpp b/src/catch2/reporters/catch_reporter_json.hpp
index 33a43613..c938ca39 100644
--- a/src/catch2/reporters/catch_reporter_json.hpp
+++ b/src/catch2/reporters/catch_reporter_json.hpp
@@ -64,10 +64,10 @@ namespace Catch {
};
JsonArrayWriter& startArray();
- JsonArrayWriter& startArray( std::string const& key );
+ JsonArrayWriter& startArray( StringRef key );
JsonObjectWriter& startObject();
- JsonObjectWriter& startObject( std::string const& key );
+ JsonObjectWriter& startObject( StringRef key );
void endObject();
void endArray();