From 7bf136b501fef3e189b45091e8a4757a5eaef31f Mon Sep 17 00:00:00 2001 From: Beartama <8091245+uyha@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:52:15 +0200 Subject: [PATCH] Add JSON reporter (#2706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martin Hořeňovský --- src/CMakeLists.txt | 4 + src/catch2/catch_all.hpp | 1 + src/catch2/internal/catch_jsonwriter.cpp | 115 +++++ src/catch2/internal/catch_jsonwriter.hpp | 123 ++++++ .../internal/catch_reporter_registry.cpp | 5 +- src/catch2/meson.build | 4 + src/catch2/reporters/catch_reporter_json.cpp | 395 ++++++++++++++++++ src/catch2/reporters/catch_reporter_json.hpp | 95 +++++ src/catch2/reporters/catch_reporters_all.hpp | 1 + tests/CMakeLists.txt | 6 +- .../Baselines/automake.sw.approved.txt | 1 + .../Baselines/automake.sw.multi.approved.txt | 1 + .../Baselines/compact.sw.approved.txt | 135 +++++- .../Baselines/compact.sw.multi.approved.txt | 135 +++++- .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 316 +++++++++++++- .../Baselines/console.sw.multi.approved.txt | 316 +++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 15 +- .../Baselines/junit.sw.multi.approved.txt | 15 +- .../Baselines/sonarqube.sw.approved.txt | 15 + .../Baselines/sonarqube.sw.multi.approved.txt | 15 + tests/SelfTest/Baselines/tap.sw.approved.txt | 34 +- .../Baselines/tap.sw.multi.approved.txt | 34 +- .../Baselines/teamcity.sw.approved.txt | 2 + .../Baselines/teamcity.sw.multi.approved.txt | 2 + tests/SelfTest/Baselines/xml.sw.approved.txt | 298 ++++++++++++- .../Baselines/xml.sw.multi.approved.txt | 298 ++++++++++++- .../IntrospectiveTests/Json.tests.cpp | 116 +++++ .../IntrospectiveTests/Reporters.tests.cpp | 4 +- 29 files changed, 2484 insertions(+), 21 deletions(-) create mode 100644 src/catch2/internal/catch_jsonwriter.cpp create mode 100644 src/catch2/internal/catch_jsonwriter.hpp create mode 100644 src/catch2/reporters/catch_reporter_json.cpp create mode 100644 src/catch2/reporters/catch_reporter_json.hpp create mode 100644 tests/SelfTest/IntrospectiveTests/Json.tests.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e3b3566..a70eef03 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -93,6 +93,7 @@ set(IMPL_HEADERS ${SOURCES_DIR}/internal/catch_getenv.hpp ${SOURCES_DIR}/internal/catch_istream.hpp ${SOURCES_DIR}/internal/catch_is_permutation.hpp + ${SOURCES_DIR}/internal/catch_jsonwriter.hpp ${SOURCES_DIR}/internal/catch_lazy_expr.hpp ${SOURCES_DIR}/internal/catch_leak_detector.hpp ${SOURCES_DIR}/internal/catch_list.hpp @@ -177,6 +178,7 @@ set(IMPL_SOURCES ${SOURCES_DIR}/internal/catch_floating_point_helpers.cpp ${SOURCES_DIR}/internal/catch_getenv.cpp ${SOURCES_DIR}/internal/catch_istream.cpp + ${SOURCES_DIR}/internal/catch_jsonwriter.cpp ${SOURCES_DIR}/internal/catch_lazy_expr.cpp ${SOURCES_DIR}/internal/catch_leak_detector.cpp ${SOURCES_DIR}/internal/catch_list.cpp @@ -289,6 +291,7 @@ set(REPORTER_HEADERS ${SOURCES_DIR}/reporters/catch_reporter_cumulative_base.hpp ${SOURCES_DIR}/reporters/catch_reporter_event_listener.hpp ${SOURCES_DIR}/reporters/catch_reporter_helpers.hpp + ${SOURCES_DIR}/reporters/catch_reporter_json.hpp ${SOURCES_DIR}/reporters/catch_reporter_junit.hpp ${SOURCES_DIR}/reporters/catch_reporter_multi.hpp ${SOURCES_DIR}/reporters/catch_reporter_registrars.hpp @@ -307,6 +310,7 @@ set(REPORTER_SOURCES ${SOURCES_DIR}/reporters/catch_reporter_cumulative_base.cpp ${SOURCES_DIR}/reporters/catch_reporter_event_listener.cpp ${SOURCES_DIR}/reporters/catch_reporter_helpers.cpp + ${SOURCES_DIR}/reporters/catch_reporter_json.cpp ${SOURCES_DIR}/reporters/catch_reporter_junit.cpp ${SOURCES_DIR}/reporters/catch_reporter_multi.cpp ${SOURCES_DIR}/reporters/catch_reporter_registrars.cpp diff --git a/src/catch2/catch_all.hpp b/src/catch2/catch_all.hpp index cdef3946..a7969fef 100644 --- a/src/catch2/catch_all.hpp +++ b/src/catch2/catch_all.hpp @@ -74,6 +74,7 @@ #include #include #include +#include #include #include #include diff --git a/src/catch2/internal/catch_jsonwriter.cpp b/src/catch2/internal/catch_jsonwriter.cpp new file mode 100644 index 00000000..f6c279d8 --- /dev/null +++ b/src/catch2/internal/catch_jsonwriter.cpp @@ -0,0 +1,115 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#include +#include + +namespace Catch { + void JsonUtils::indent( std::ostream& os, std::uint64_t level ) { + for ( std::uint64_t i = 0; i < level; ++i ) { + os << " "; + } + } + void JsonUtils::appendCommaNewline( std::ostream& os, + bool& should_comma, + std::uint64_t level ) { + if ( should_comma ) { os << ','; } + should_comma = true; + os << '\n'; + indent( os, level ); + } + + JsonObjectWriter::JsonObjectWriter( std::ostream& os ): + JsonObjectWriter{ os, 0 } {} + + JsonObjectWriter::JsonObjectWriter( std::ostream& os, + std::uint64_t indent_level ): + m_os{ os }, m_indent_level{ indent_level } { + m_os << "{"; + } + JsonObjectWriter::JsonObjectWriter( JsonObjectWriter&& source ): + m_os{ source.m_os }, + m_indent_level{ source.m_indent_level }, + m_should_comma{ source.m_should_comma }, + m_active{ source.m_active } { + source.m_active = false; + } + + JsonObjectWriter::~JsonObjectWriter() { + if ( !m_active ) { return; } + + m_os << '\n'; + JsonUtils::indent( m_os, m_indent_level ); + m_os << '}'; + } + + JsonValueWriter JsonObjectWriter::write( std::string const& key ) { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + + m_os << '"' << key << '"' << ": "; + return JsonValueWriter{ m_os, m_indent_level + 1 }; + } + + JsonArrayWriter::JsonArrayWriter( std::ostream& os ): + JsonArrayWriter{ os, 0 } {} + JsonArrayWriter::JsonArrayWriter( std::ostream& os, + std::uint64_t indent_level ): + m_os{ os }, m_indent_level{ indent_level } { + m_os << "["; + } + JsonArrayWriter::JsonArrayWriter( JsonArrayWriter&& source ): + m_os{ source.m_os }, + m_indent_level{ source.m_indent_level }, + m_should_comma{ source.m_should_comma }, + m_active{ source.m_active } { + source.m_active = false; + } + JsonArrayWriter::~JsonArrayWriter() { + if ( !m_active ) { return; } + + m_os << '\n'; + JsonUtils::indent( m_os, m_indent_level ); + m_os << ']'; + } + + JsonObjectWriter JsonArrayWriter::writeObject() { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + return JsonObjectWriter{ m_os, m_indent_level + 1 }; + } + + JsonArrayWriter JsonArrayWriter::writeArray() { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + return JsonArrayWriter{ m_os, m_indent_level + 1 }; + } + + JsonArrayWriter& JsonArrayWriter::write( bool value ) { + return writeImpl( value ); + } + + JsonValueWriter::JsonValueWriter( std::ostream& os ): + JsonValueWriter{ os, 0 } {} + + JsonValueWriter::JsonValueWriter( std::ostream& os, + std::uint64_t indent_level ): + m_os{ os }, m_indent_level{ indent_level } {} + + JsonObjectWriter JsonValueWriter::writeObject() && { + return JsonObjectWriter{ m_os, m_indent_level }; + } + + JsonArrayWriter JsonValueWriter::writeArray() && { + return JsonArrayWriter{ m_os, m_indent_level }; + } + + void JsonValueWriter::write( bool value ) && { + writeImpl( value ? "true" : "false", false ); + } + +} // namespace Catch diff --git a/src/catch2/internal/catch_jsonwriter.hpp b/src/catch2/internal/catch_jsonwriter.hpp new file mode 100644 index 00000000..9cac3de6 --- /dev/null +++ b/src/catch2/internal/catch_jsonwriter.hpp @@ -0,0 +1,123 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_JSONWRITER_HPP_INCLUDED +#define CATCH_JSONWRITER_HPP_INCLUDED + +#include +#include + +#include +#include + +namespace Catch { + class JsonObjectWriter; + class JsonArrayWriter; + + struct JsonUtils { + static void indent( std::ostream& os, std::uint64_t level ); + static void appendCommaNewline( std::ostream& os, + bool& should_comma, + std::uint64_t level ); + }; + + class JsonValueWriter { + public: + JsonValueWriter( std::ostream& os ); + JsonValueWriter( std::ostream& os, std::uint64_t indent_level ); + + JsonObjectWriter writeObject() &&; + JsonArrayWriter writeArray() &&; + + template + void write( T const& value ) && { + writeImpl( value, !std::is_arithmetic::value ); + } + + void write( bool value ) &&; + + private: + template + 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; } + if ( c == '"' ) { + m_os << '\\' << '"'; + } else { + m_os << c; + } + } + if ( quote_value ) { m_os << '"'; } + } + + std::ostream& m_os; + std::stringstream m_sstream{}; + std::uint64_t m_indent_level; + }; + + class JsonObjectWriter { + public: + JsonObjectWriter( std::ostream& os ); + JsonObjectWriter( std::ostream& os, std::uint64_t indent_level ); + + JsonObjectWriter( JsonObjectWriter&& source ); + JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete; + + ~JsonObjectWriter(); + + JsonValueWriter write( std::string const& key ); + + private: + std::ostream& m_os; + std::uint64_t m_indent_level; + bool m_should_comma = false; + bool m_active = true; + }; + + class JsonArrayWriter { + public: + JsonArrayWriter( std::ostream& os ); + JsonArrayWriter( std::ostream& os, std::uint64_t indent_level ); + + JsonArrayWriter( JsonArrayWriter&& source ); + JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete; + + ~JsonArrayWriter(); + + JsonObjectWriter writeObject(); + JsonArrayWriter writeArray(); + + template + JsonArrayWriter& write( T const& value ) { + return writeImpl( value ); + } + + JsonArrayWriter& write( bool value ); + + private: + template + JsonArrayWriter& writeImpl( T const& value ) { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + JsonValueWriter{ m_os }.write( value ); + + return *this; + } + + std::ostream& m_os; + std::uint64_t m_indent_level; + bool m_should_comma = false; + bool m_active = true; + }; + +} // namespace Catch + +#endif // CATCH_JSONWRITER_HPP_INCLUDED diff --git a/src/catch2/internal/catch_reporter_registry.cpp b/src/catch2/internal/catch_reporter_registry.cpp index b2b0cd07..cea8c4dc 100644 --- a/src/catch2/internal/catch_reporter_registry.cpp +++ b/src/catch2/internal/catch_reporter_registry.cpp @@ -6,13 +6,14 @@ // SPDX-License-Identifier: BSL-1.0 -#include #include #include #include +#include #include #include #include +#include #include #include #include @@ -47,6 +48,8 @@ namespace Catch { Detail::make_unique>(); m_impl->factories["XML"] = Detail::make_unique>(); + m_impl->factories["JSON"] = + Detail::make_unique>(); } ReporterRegistry::~ReporterRegistry() = default; diff --git a/src/catch2/meson.build b/src/catch2/meson.build index 1f9bf45a..09a3db1b 100644 --- a/src/catch2/meson.build +++ b/src/catch2/meson.build @@ -98,6 +98,7 @@ internal_headers = [ 'internal/catch_getenv.hpp', 'internal/catch_istream.hpp', 'internal/catch_is_permutation.hpp', + 'internal/catch_jsonwriter.hpp', 'internal/catch_lazy_expr.hpp', 'internal/catch_leak_detector.hpp', 'internal/catch_list.hpp', @@ -214,6 +215,7 @@ internal_sources = files( 'internal/catch_floating_point_helpers.cpp', 'internal/catch_getenv.cpp', 'internal/catch_istream.cpp', + 'internal/catch_jsonwriter.cpp', 'internal/catch_lazy_expr.cpp', 'internal/catch_leak_detector.cpp', 'internal/catch_list.cpp', @@ -280,6 +282,7 @@ reporter_headers = [ 'reporters/catch_reporter_cumulative_base.hpp', 'reporters/catch_reporter_event_listener.hpp', 'reporters/catch_reporter_helpers.hpp', + 'reporters/catch_reporter_json.hpp', 'reporters/catch_reporter_junit.hpp', 'reporters/catch_reporter_multi.hpp', 'reporters/catch_reporter_registrars.hpp', @@ -299,6 +302,7 @@ reporter_sources = files( 'reporters/catch_reporter_cumulative_base.cpp', 'reporters/catch_reporter_event_listener.cpp', 'reporters/catch_reporter_helpers.cpp', + 'reporters/catch_reporter_json.cpp', 'reporters/catch_reporter_junit.cpp', 'reporters/catch_reporter_multi.cpp', 'reporters/catch_reporter_registrars.cpp', diff --git a/src/catch2/reporters/catch_reporter_json.cpp b/src/catch2/reporters/catch_reporter_json.cpp new file mode 100644 index 00000000..7ddc7036 --- /dev/null +++ b/src/catch2/reporters/catch_reporter_json.cpp @@ -0,0 +1,395 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +// +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + namespace { + 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 ); + } + + void writeTags( JsonArrayWriter writer, std::vector const& tags ) { + for ( auto const& tag : tags ) { + writer.write( tag.original ); + } + } + + 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" ); } + } + + //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 ): + StreamingReporterBase{ CATCH_MOVE( config ) } { + + m_preferences.shouldRedirectStdOut = true; + // TBD: Do we want to report all assertions? XML reporter does + // not, but for machine-parseable reporters I think the answer + // should be yes. + m_preferences.shouldReportAllAssertions = true; + + m_objectWriters.emplace( m_stream ); + m_writers.emplace( Writer::Object ); + auto& writer = m_objectWriters.top(); + + writer.write( "version" ).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() ); + if ( m_config->testSpec().hasFilters() ) { + metadata_writer.write( "filters" ) + .write( m_config->testSpec() ); + } + } + } + + JsonReporter::~JsonReporter() { + endListing(); + // TODO: Ensure this closes the top level object, add asserts + assert( m_writers.size() == 1 && "Only the top level object should be open" ); + assert( m_writers.top() == Writer::Object ); + endObject(); + m_stream << '\n' << std::flush; + assert( m_writers.empty() ); + } + + JsonArrayWriter& JsonReporter::startArray() { + m_arrayWriters.emplace( m_arrayWriters.top().writeArray() ); + m_writers.emplace( Writer::Array ); + return m_arrayWriters.top(); + } + JsonArrayWriter& JsonReporter::startArray( std::string const& key ) { + m_arrayWriters.emplace( + m_objectWriters.top().write( key ).writeArray() ); + m_writers.emplace( Writer::Array ); + return m_arrayWriters.top(); + } + + JsonObjectWriter& JsonReporter::startObject() { + m_objectWriters.emplace( m_arrayWriters.top().writeObject() ); + m_writers.emplace( Writer::Object ); + return m_objectWriters.top(); + } + JsonObjectWriter& JsonReporter::startObject( std::string const& key ) { + m_objectWriters.emplace( + m_objectWriters.top().write( key ).writeObject() ); + m_writers.emplace( Writer::Object ); + return m_objectWriters.top(); + } + + void JsonReporter::endObject() { + assert( isInside( Writer::Object ) ); + m_objectWriters.pop(); + m_writers.pop(); + } + void JsonReporter::endArray() { + assert( isInside( Writer::Array ) ); + m_arrayWriters.pop(); + m_writers.pop(); + } + + bool JsonReporter::isInside( Writer writer ) { + return !m_writers.empty() && m_writers.top() == writer; + } + + void JsonReporter::startListing() { + if ( !m_startedListing ) { startObject( "listings" ); } + m_startedListing = true; + } + void JsonReporter::endListing() { + if ( m_startedListing ) { endObject(); } + m_startedListing = false; + } + + std::string JsonReporter::getDescription() { + return "WIP! Reports test results as a JSON document. WIP!"; + } + + void JsonReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + endListing(); + + assert( isInside( Writer::Object ) ); + startObject( "test-run" ); + startArray( "test-cases" ); + } + + 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 ); + } + + void JsonReporter::testRunEnded(TestRunStats const& runStats) { + assert( isInside( Writer::Array ) ); + // End "test-cases" + endArray(); + + { + auto totals = m_objectWriters.top().write( "totals" ).writeObject(); + writeCounts( totals.write( "assertions" ).writeObject(), + runStats.totals.assertions ); + writeCounts( totals.write( "test-cases" ).writeObject(), + runStats.totals.testCases ); + } + + // End the "test-run" object + endObject(); + } + + void JsonReporter::testCaseStarting( TestCaseInfo const& tcInfo ) { + StreamingReporterBase::testCaseStarting( tcInfo ); + + assert( isInside( Writer::Array ) && + "We should be in the 'test-cases' array" ); + startObject(); + // "test-info" prelude + { + auto testInfo = + m_objectWriters.top().write( "test-info" ).writeObject(); + // TODO: handle testName vs className!! + testInfo.write( "name" ).write( tcInfo.name ); + writeSourceInfo(testInfo, tcInfo.lineInfo); + writeTags( testInfo.write( "tags" ).writeArray(), tcInfo.tags ); + writeProperties( testInfo.write( "properties" ).writeArray(), + tcInfo ); + } + + + // Start the array for individual test runs (testCasePartial pairs) + startArray( "runs" ); + } + + void JsonReporter::testCaseEnded( TestCaseStats const& tcStats ) { + StreamingReporterBase::testCaseEnded( tcStats ); + + // We need to close the 'runs' array before finishing the test case + assert( isInside( Writer::Array ) ); + endArray(); + + { + auto totals = m_objectWriters.top().write( "totals" ).writeObject(); + writeCounts( totals.write( "assertions" ).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? + } + // We do not write out stderr/stdout, because we instead wrote those out in partial runs + + // TODO: aborting? + + // And we also close this test case's object + assert( isInside( Writer::Object ) ); + endObject(); + } + + void JsonReporter::testCasePartialStarting( TestCaseInfo const& /*tcInfo*/, + uint64_t index ) { + startObject(); + m_objectWriters.top().write( "run-idx" ).write( index ); + startArray( "path" ); + //startObject( "path" ); + // TODO: we want to delay most of the printing to the 'root' section + // TODO: childSection key name? + } + + void JsonReporter::testCasePartialEnded( TestCaseStats const& tcStats, + uint64_t /*index*/ ) { + // Fixme: the top level section handles this. + //// path object + endArray(); + if ( !tcStats.stdOut.empty() ) { + m_objectWriters.top() + .write( "captured-stdout" ) + .write( tcStats.stdOut ); + } + if ( !tcStats.stdErr.empty() ) { + m_objectWriters.top() + .write( "captured-stderr" ) + .write( tcStats.stdErr ); + } + { + auto totals = m_objectWriters.top().write( "totals" ).writeObject(); + writeCounts( totals.write( "assertions" ).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? + } + // TODO: aborting? + // run object + endObject(); + } + + void JsonReporter::sectionStarting( SectionInfo const& sectionInfo ) { + assert( isInside( Writer::Array ) && + "Section should always start inside an object" ); + // 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 ); + writeSourceInfo( m_objectWriters.top(), sectionInfo.lineInfo ); + + + // TBD: Do we want to create this event lazily? It would become + // rather complex, but we could do it, and it would look + // better for empty sections. OTOH, empty sections should + // be rare. + startArray( "path" ); + } + void JsonReporter::sectionEnded( SectionStats const& /*sectionStats */) { + // End the subpath array + endArray(); + // TODO: metadata + // TODO: what info do we have here? + + // End the section object + endObject(); + } + + void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} + void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { + // TODO: There is lot of different things to handle here, but + // we can fill it in later, after we show that the basic + // outline and streaming reporter impl works well enough. + //if ( !m_config->includeSuccessfulResults() + // && assertionStats.assertionResult.isOk() ) { + // return; + //} + assert( isInside( Writer::Array ) ); + auto assertionObject = m_arrayWriters.top().writeObject(); + + assertionObject.write( "kind" ).write( "assertion" ); + writeSourceInfo( assertionObject, + assertionStats.assertionResult.getSourceInfo() ); + assertionObject.write( "status" ) + .write( assertionStats.assertionResult.isOk() ); + // TODO: handling of result. + // TODO: messages + // TODO: totals? + } + + + void JsonReporter::benchmarkPreparing( StringRef name ) { (void)name; } + void JsonReporter::benchmarkStarting( BenchmarkInfo const& ) {} + void JsonReporter::benchmarkEnded( BenchmarkStats<> const& ) {} + void JsonReporter::benchmarkFailed( StringRef error ) { (void)error; } + + void JsonReporter::listReporters( + std::vector const& descriptions ) { + startListing(); + + auto writer = m_objectWriters.top().write( "reporters" ).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 ); + } + } + void JsonReporter::listListeners( + std::vector const& descriptions ) { + startListing(); + + auto writer = m_objectWriters.top().write( "listeners" ).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 ); + } + } + void JsonReporter::listTests( std::vector const& tests ) { + startListing(); + + auto writer = m_objectWriters.top().write( "tests" ).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 ); + { + auto tag_writer = desc_writer.write( "tags" ).writeArray(); + for ( auto const& tag : info.tags ) { + tag_writer.write( tag.original ); + } + } + writeSourceInfo( desc_writer, info.lineInfo ); + } + } + void JsonReporter::listTags( std::vector const& tags ) { + startListing(); + + auto writer = m_objectWriters.top().write( "tags" ).writeArray(); + for ( auto const& tag : tags ) { + auto tag_writer = writer.writeObject(); + { + auto aliases_writer = + tag_writer.write( "aliases" ).writeArray(); + for ( auto alias : tag.spellings ) { + aliases_writer.write( alias ); + } + } + tag_writer.write( "count" ).write( tag.count ); + } + } +} // namespace Catch diff --git a/src/catch2/reporters/catch_reporter_json.hpp b/src/catch2/reporters/catch_reporter_json.hpp new file mode 100644 index 00000000..33a43613 --- /dev/null +++ b/src/catch2/reporters/catch_reporter_json.hpp @@ -0,0 +1,95 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#ifndef CATCH_REPORTER_JSON_HPP_INCLUDED +#define CATCH_REPORTER_JSON_HPP_INCLUDED + +#include +#include +#include + +#include + +namespace Catch { + class JsonReporter : public StreamingReporterBase { + public: + JsonReporter( ReporterConfig&& config ); + + ~JsonReporter() override; + + static std::string getDescription(); + + public: // StreamingReporterBase + void testRunStarting( TestRunInfo const& runInfo ) override; + void testRunEnded( TestRunStats const& runStats ) override; + + void testCaseStarting( TestCaseInfo const& tcInfo ) override; + void testCaseEnded( TestCaseStats const& tcStats ) override; + + void testCasePartialStarting( TestCaseInfo const& tcInfo, + uint64_t index ) override; + void testCasePartialEnded( TestCaseStats const& tcStats, + uint64_t index ) override; + + void sectionStarting( SectionInfo const& sectionInfo ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + + void assertionStarting( AssertionInfo const& assertionInfo ) override; + void assertionEnded( AssertionStats const& assertionStats ) override; + + //void testRunEndedCumulative() override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting( BenchmarkInfo const& ) override; + void benchmarkEnded( BenchmarkStats<> const& ) override; + void benchmarkFailed( StringRef error ) override; + + void listReporters( + std::vector const& descriptions ) override; + void listListeners( + std::vector const& descriptions ) override; + void listTests( std::vector const& tests ) override; + void listTags( std::vector const& tags ) override; + + private: + Timer m_testCaseTimer; + enum class Writer { + Object, + Array + }; + + JsonArrayWriter& startArray(); + JsonArrayWriter& startArray( std::string const& key ); + + JsonObjectWriter& startObject(); + JsonObjectWriter& startObject( std::string const& key ); + + void endObject(); + void endArray(); + + bool isInside( Writer writer ); + + void startListing(); + void endListing(); + + // Invariant: + // When m_writers is not empty and its top element is + // - Writer::Object, then m_objectWriters is not be empty + // - Writer::Array, then m_arrayWriters shall not be empty + std::stack m_objectWriters{}; + std::stack m_arrayWriters{}; + std::stack m_writers{}; + + bool m_startedListing = false; + + // std::size_t m_sectionDepth = 0; + // std::size_t m_sectionStarted = 0; + }; +} // namespace Catch + +#endif // CATCH_REPORTER_JSON_HPP_INCLUDED diff --git a/src/catch2/reporters/catch_reporters_all.hpp b/src/catch2/reporters/catch_reporters_all.hpp index 16f7bd70..5c713fe1 100644 --- a/src/catch2/reporters/catch_reporters_all.hpp +++ b/src/catch2/reporters/catch_reporters_all.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 74017c38..4c3bad84 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,6 +87,7 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/IntrospectiveTests/FloatingPoint.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/InternalBenchmark.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/Json.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/Parse.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/RandomNumberGeneration.tests.cpp @@ -642,7 +643,8 @@ foreach (reporterName # "Automake" - the simple .trs format does not support any "SonarQube" "TAP" # "TeamCity" - does not seem to support test suite-level metadata/comments - "XML") + "XML" + "JSON") add_test(NAME "Reporters:Filters:${reporterName}" COMMAND @@ -652,6 +654,8 @@ foreach (reporterName # "Automake" - the simple .trs format does not support any # Different regex for these two reporters, because the commas end up xml-escaped if (reporterName MATCHES "JUnit|XML") set(testCaseNameFormat ""CaseInsensitiveLess is case insensitive"") + elseif(reporterName MATCHES "JSON") + set(testCaseNameFormat "\\\\\"CaseInsensitiveLess is case insensitive\\\\\"") else() set(testCaseNameFormat "\"CaseInsensitiveLess is case insensitive\"") endif() diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 6748c0ec..0f029e6d 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -170,6 +170,7 @@ Nor would this :test-result: XFAIL Incomplete AssertionHandler :test-result: XFAIL Inequality checks that should fail :test-result: PASS Inequality checks that should succeed +:test-result: PASS JsonWriter :test-result: PASS Lambdas in assertions :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered diff --git a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt index a8bdcfa1..a3af0658 100644 --- a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt @@ -168,6 +168,7 @@ :test-result: XFAIL Incomplete AssertionHandler :test-result: XFAIL Inequality checks that should fail :test-result: PASS Inequality checks that should succeed +:test-result: PASS JsonWriter :test-result: PASS Lambdas in assertions :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index dafa08db..e78c989f 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -979,6 +979,83 @@ Condition.tests.cpp:: passed: data.str_hello != "goodbye" for: "hel Condition.tests.cpp:: passed: data.str_hello != "hell" for: "hello" != "hell" Condition.tests.cpp:: passed: data.str_hello != "hello1" for: "hello" != "hello1" Condition.tests.cpp:: passed: data.str_hello.size() != 6 for: 5 != 6 +Json.tests.cpp:: passed: stream.str() == "" for: "" == "" +Json.tests.cpp:: passed: stream.str() == "{\n}" for: "{ +}" +== +"{ +}" +Json.tests.cpp:: passed: stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) for: "{ + "int": 1, + "double": 1.5, + "true": true, + "false": false, + "string": "this is a string", + "array": [ + 1, + 2 + ] +}" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: ""true": true," and contains: ""false": false," and contains: ""string": "this is a string"," and contains: ""array": [ + 1, + 2 + ] +}" ) +Json.tests.cpp:: passed: stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) for: "{ + "empty_object": { + }, + "fully_object": { + "key": 1 + } +}" ( contains: ""empty_object": { + }," and contains: ""fully_object": { + "key": 1 + }" ) +Json.tests.cpp:: passed: stream.str() == "[\n]" for: "[ +]" +== +"[ +]" +Json.tests.cpp:: passed: stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" for: "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" +== +"[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" +Json.tests.cpp:: passed: stream.str() == "{\n}" for: "{ +}" +== +"{ +}" +Json.tests.cpp:: passed: stream.str() == "[\n]" for: "[ +]" +== +"[ +]" +Json.tests.cpp:: passed: stream.str() == "\"custom\"" for: ""custom"" == ""custom"" +Json.tests.cpp:: passed: stream.str() == "\"\\\"\"" for: ""\""" == ""\""" Compilation.tests.cpp:: passed: []() { return true; }() for: true Approx.tests.cpp:: passed: d <= Approx( 1.24 ) for: 1.23 <= Approx( 1.24 ) Approx.tests.cpp:: passed: d <= Approx( 1.23 ) for: 1.23 <= Approx( 1.23 ) @@ -1343,6 +1420,60 @@ Reporters.tests.cpp:: passed: listingString, ContainsSubstring( "fa " ( contains: "fake test name" and contains: "fakeTestTag" ) with 1 message: 'Tested reporter: console' Reporters.tests.cpp:: passed: !(factories.empty()) for: !false +Reporters.tests.cpp:: passed: listingString, ContainsSubstring("fakeTag"s) for: "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tags": [ + { + "aliases": [ + "fakeTag" + ], + "count": 1 + } + ]" contains: "fakeTag" with 1 message: 'Tested reporter: JSON' +Reporters.tests.cpp:: passed: !(factories.empty()) for: !false +Reporters.tests.cpp:: passed: listingString, ContainsSubstring("fake reporter"s) for: "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "reporters": [ + { + "name": "fake reporter", + "description": "fake description" + } + ]" contains: "fake reporter" with 1 message: 'Tested reporter: JSON' +Reporters.tests.cpp:: passed: !(factories.empty()) for: !false +Reporters.tests.cpp:: passed: listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) for: "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tests": [ + { + "name": "fake test name", + "class-name": "", + "tags": [ + "fakeTestTag" + ], + "source-location": { + "filename": "fake-file.cpp", + "line": 123456789 + } + } + ]" ( contains: "fake test name" and contains: "fakeTestTag" ) with 1 message: 'Tested reporter: JSON' +Reporters.tests.cpp:: passed: !(factories.empty()) for: !false Reporters.tests.cpp:: passed: listingString, ContainsSubstring("fakeTag"s) for: " All available tags: 1 [fakeTag] @@ -2544,7 +2675,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 413 | 308 passed | 85 failed | 6 skipped | 14 failed as expected -assertions: 2230 | 2049 passed | 146 failed | 35 failed as expected +test cases: 414 | 309 passed | 85 failed | 6 skipped | 14 failed as expected +assertions: 2246 | 2065 passed | 146 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index 36debdb7..8f177e46 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -977,6 +977,83 @@ Condition.tests.cpp:: passed: data.str_hello != "goodbye" for: "hel Condition.tests.cpp:: passed: data.str_hello != "hell" for: "hello" != "hell" Condition.tests.cpp:: passed: data.str_hello != "hello1" for: "hello" != "hello1" Condition.tests.cpp:: passed: data.str_hello.size() != 6 for: 5 != 6 +Json.tests.cpp:: passed: stream.str() == "" for: "" == "" +Json.tests.cpp:: passed: stream.str() == "{\n}" for: "{ +}" +== +"{ +}" +Json.tests.cpp:: passed: stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) for: "{ + "int": 1, + "double": 1.5, + "true": true, + "false": false, + "string": "this is a string", + "array": [ + 1, + 2 + ] +}" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: ""true": true," and contains: ""false": false," and contains: ""string": "this is a string"," and contains: ""array": [ + 1, + 2 + ] +}" ) +Json.tests.cpp:: passed: stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) for: "{ + "empty_object": { + }, + "fully_object": { + "key": 1 + } +}" ( contains: ""empty_object": { + }," and contains: ""fully_object": { + "key": 1 + }" ) +Json.tests.cpp:: passed: stream.str() == "[\n]" for: "[ +]" +== +"[ +]" +Json.tests.cpp:: passed: stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" for: "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" +== +"[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" +Json.tests.cpp:: passed: stream.str() == "{\n}" for: "{ +}" +== +"{ +}" +Json.tests.cpp:: passed: stream.str() == "[\n]" for: "[ +]" +== +"[ +]" +Json.tests.cpp:: passed: stream.str() == "\"custom\"" for: ""custom"" == ""custom"" +Json.tests.cpp:: passed: stream.str() == "\"\\\"\"" for: ""\""" == ""\""" Compilation.tests.cpp:: passed: []() { return true; }() for: true Approx.tests.cpp:: passed: d <= Approx( 1.24 ) for: 1.23 <= Approx( 1.24 ) Approx.tests.cpp:: passed: d <= Approx( 1.23 ) for: 1.23 <= Approx( 1.23 ) @@ -1341,6 +1418,60 @@ Reporters.tests.cpp:: passed: listingString, ContainsSubstring( "fa " ( contains: "fake test name" and contains: "fakeTestTag" ) with 1 message: 'Tested reporter: console' Reporters.tests.cpp:: passed: !(factories.empty()) for: !false +Reporters.tests.cpp:: passed: listingString, ContainsSubstring("fakeTag"s) for: "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tags": [ + { + "aliases": [ + "fakeTag" + ], + "count": 1 + } + ]" contains: "fakeTag" with 1 message: 'Tested reporter: JSON' +Reporters.tests.cpp:: passed: !(factories.empty()) for: !false +Reporters.tests.cpp:: passed: listingString, ContainsSubstring("fake reporter"s) for: "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "reporters": [ + { + "name": "fake reporter", + "description": "fake description" + } + ]" contains: "fake reporter" with 1 message: 'Tested reporter: JSON' +Reporters.tests.cpp:: passed: !(factories.empty()) for: !false +Reporters.tests.cpp:: passed: listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) for: "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tests": [ + { + "name": "fake test name", + "class-name": "", + "tags": [ + "fakeTestTag" + ], + "source-location": { + "filename": "fake-file.cpp", + "line": 123456789 + } + } + ]" ( contains: "fake test name" and contains: "fakeTestTag" ) with 1 message: 'Tested reporter: JSON' +Reporters.tests.cpp:: passed: !(factories.empty()) for: !false Reporters.tests.cpp:: passed: listingString, ContainsSubstring("fakeTag"s) for: " All available tags: 1 [fakeTag] @@ -2533,7 +2664,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 413 | 308 passed | 85 failed | 6 skipped | 14 failed as expected -assertions: 2230 | 2049 passed | 146 failed | 35 failed as expected +test cases: 414 | 309 passed | 85 failed | 6 skipped | 14 failed as expected +assertions: 2246 | 2065 passed | 146 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 4ad25ed8..9aa0d038 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1588,6 +1588,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 413 | 322 passed | 70 failed | 7 skipped | 14 failed as expected -assertions: 2213 | 2049 passed | 129 failed | 35 failed as expected +test cases: 414 | 323 passed | 70 failed | 7 skipped | 14 failed as expected +assertions: 2229 | 2065 passed | 129 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index d56e8c41..71cbbd27 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -7258,6 +7258,195 @@ Condition.tests.cpp:: PASSED: with expansion: 5 != 6 +------------------------------------------------------------------------------- +JsonWriter + Newly constructed JsonWriter does nothing +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "" ) +with expansion: + "" == "" + +------------------------------------------------------------------------------- +JsonWriter + Calling writeObject will create an empty pair of braces +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "{\n}" ) +with expansion: + "{ + }" + == + "{ + }" + +------------------------------------------------------------------------------- +JsonWriter + Calling writeObject with key will create an object to write the value +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE_THAT( stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) ) +with expansion: + "{ + "int": 1, + "double": 1.5, + "true": true, + "false": false, + "string": "this is a string", + "array": [ + 1, + 2 + ] + }" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: + ""true": true," and contains: ""false": false," and contains: ""string": + "this is a string"," and contains: ""array": [ + 1, + 2 + ] + }" ) + +------------------------------------------------------------------------------- +JsonWriter + nesting objects +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE_THAT( stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) ) +with expansion: + "{ + "empty_object": { + }, + "fully_object": { + "key": 1 + } + }" ( contains: ""empty_object": { + }," and contains: ""fully_object": { + "key": 1 + }" ) + +------------------------------------------------------------------------------- +JsonWriter + Calling writeArray will create an empty pair of braces +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "[\n]" ) +with expansion: + "[ + ]" + == + "[ + ]" + +------------------------------------------------------------------------------- +JsonWriter + Calling writeArray creates array to write the values to +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" ) +with expansion: + "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] + ]" + == + "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] + ]" + +------------------------------------------------------------------------------- +JsonWriter + Moved from JsonObjectWriter shall not insert superfluous brace +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "{\n}" ) +with expansion: + "{ + }" + == + "{ + }" + +------------------------------------------------------------------------------- +JsonWriter + Moved from JsonArrayWriter shall not insert superfluous bracket +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "[\n]" ) +with expansion: + "[ + ]" + == + "[ + ]" + +------------------------------------------------------------------------------- +JsonWriter + Custom class shall be quoted +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "\"custom\"" ) +with expansion: + ""custom"" == ""custom"" + +------------------------------------------------------------------------------- +JsonWriter + String with a quote shall be espaced +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "\"\\\"\"" ) +with expansion: + ""\""" == ""\""" + ------------------------------------------------------------------------------- Lambdas in assertions ------------------------------------------------------------------------------- @@ -9756,6 +9945,129 @@ Reporter's write listings to provided stream Reporters.tests.cpp: ............................................................................... +Reporters.tests.cpp:: PASSED: + REQUIRE_FALSE( factories.empty() ) +with expansion: + !false + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream + JSON reporter lists tags +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_THAT( listingString, ContainsSubstring("fakeTag"s) ) +with expansion: + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tags": [ + { + "aliases": [ + "fakeTag" + ], + "count": 1 + } + ]" contains: "fakeTag" +with message: + Tested reporter: JSON + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_FALSE( factories.empty() ) +with expansion: + !false + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream + JSON reporter lists reporters +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_THAT( listingString, ContainsSubstring("fake reporter"s) ) +with expansion: + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "reporters": [ + { + "name": "fake reporter", + "description": "fake description" + } + ]" contains: "fake reporter" +with message: + Tested reporter: JSON + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_FALSE( factories.empty() ) +with expansion: + !false + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream + JSON reporter lists tests +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_THAT( listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) ) +with expansion: + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tests": [ + { + "name": "fake test name", + "class-name": "", + "tags": [ + "fakeTestTag" + ], + "source-location": { + "filename": "fake-file.cpp", + "line": 123456789 + } + } + ]" ( contains: "fake test name" and contains: "fakeTestTag" ) +with message: + Tested reporter: JSON + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + Reporters.tests.cpp:: PASSED: REQUIRE_FALSE( factories.empty() ) with expansion: @@ -18283,6 +18595,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 413 | 308 passed | 85 failed | 6 skipped | 14 failed as expected -assertions: 2230 | 2049 passed | 146 failed | 35 failed as expected +test cases: 414 | 309 passed | 85 failed | 6 skipped | 14 failed as expected +assertions: 2246 | 2065 passed | 146 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index be965932..ccb27333 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -7256,6 +7256,195 @@ Condition.tests.cpp:: PASSED: with expansion: 5 != 6 +------------------------------------------------------------------------------- +JsonWriter + Newly constructed JsonWriter does nothing +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "" ) +with expansion: + "" == "" + +------------------------------------------------------------------------------- +JsonWriter + Calling writeObject will create an empty pair of braces +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "{\n}" ) +with expansion: + "{ + }" + == + "{ + }" + +------------------------------------------------------------------------------- +JsonWriter + Calling writeObject with key will create an object to write the value +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE_THAT( stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) ) +with expansion: + "{ + "int": 1, + "double": 1.5, + "true": true, + "false": false, + "string": "this is a string", + "array": [ + 1, + 2 + ] + }" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: + ""true": true," and contains: ""false": false," and contains: ""string": + "this is a string"," and contains: ""array": [ + 1, + 2 + ] + }" ) + +------------------------------------------------------------------------------- +JsonWriter + nesting objects +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE_THAT( stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) ) +with expansion: + "{ + "empty_object": { + }, + "fully_object": { + "key": 1 + } + }" ( contains: ""empty_object": { + }," and contains: ""fully_object": { + "key": 1 + }" ) + +------------------------------------------------------------------------------- +JsonWriter + Calling writeArray will create an empty pair of braces +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "[\n]" ) +with expansion: + "[ + ]" + == + "[ + ]" + +------------------------------------------------------------------------------- +JsonWriter + Calling writeArray creates array to write the values to +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" ) +with expansion: + "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] + ]" + == + "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] + ]" + +------------------------------------------------------------------------------- +JsonWriter + Moved from JsonObjectWriter shall not insert superfluous brace +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "{\n}" ) +with expansion: + "{ + }" + == + "{ + }" + +------------------------------------------------------------------------------- +JsonWriter + Moved from JsonArrayWriter shall not insert superfluous bracket +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "[\n]" ) +with expansion: + "[ + ]" + == + "[ + ]" + +------------------------------------------------------------------------------- +JsonWriter + Custom class shall be quoted +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "\"custom\"" ) +with expansion: + ""custom"" == ""custom"" + +------------------------------------------------------------------------------- +JsonWriter + String with a quote shall be espaced +------------------------------------------------------------------------------- +Json.tests.cpp: +............................................................................... + +Json.tests.cpp:: PASSED: + REQUIRE( stream.str() == "\"\\\"\"" ) +with expansion: + ""\""" == ""\""" + ------------------------------------------------------------------------------- Lambdas in assertions ------------------------------------------------------------------------------- @@ -9754,6 +9943,129 @@ Reporter's write listings to provided stream Reporters.tests.cpp: ............................................................................... +Reporters.tests.cpp:: PASSED: + REQUIRE_FALSE( factories.empty() ) +with expansion: + !false + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream + JSON reporter lists tags +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_THAT( listingString, ContainsSubstring("fakeTag"s) ) +with expansion: + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tags": [ + { + "aliases": [ + "fakeTag" + ], + "count": 1 + } + ]" contains: "fakeTag" +with message: + Tested reporter: JSON + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_FALSE( factories.empty() ) +with expansion: + !false + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream + JSON reporter lists reporters +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_THAT( listingString, ContainsSubstring("fake reporter"s) ) +with expansion: + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "reporters": [ + { + "name": "fake reporter", + "description": "fake description" + } + ]" contains: "fake reporter" +with message: + Tested reporter: JSON + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_FALSE( factories.empty() ) +with expansion: + !false + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream + JSON reporter lists tests +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + +Reporters.tests.cpp:: PASSED: + REQUIRE_THAT( listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) ) +with expansion: + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tests": [ + { + "name": "fake test name", + "class-name": "", + "tags": [ + "fakeTestTag" + ], + "source-location": { + "filename": "fake-file.cpp", + "line": 123456789 + } + } + ]" ( contains: "fake test name" and contains: "fakeTestTag" ) +with message: + Tested reporter: JSON + +------------------------------------------------------------------------------- +Reporter's write listings to provided stream +------------------------------------------------------------------------------- +Reporters.tests.cpp: +............................................................................... + Reporters.tests.cpp:: PASSED: REQUIRE_FALSE( factories.empty() ) with expansion: @@ -18272,6 +18584,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 413 | 308 passed | 85 failed | 6 skipped | 14 failed as expected -assertions: 2230 | 2049 passed | 146 failed | 35 failed as expected +test cases: 414 | 309 passed | 85 failed | 6 skipped | 14 failed as expected +assertions: 2246 | 2065 passed | 146 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 3b6c8fa0..beb235b5 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -853,6 +853,16 @@ at Condition.tests.cpp: + + + + + + + + + + @@ -1190,6 +1200,9 @@ at Matchers.tests.cpp: + + + diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt index 48b8acc8..a5dd3063 100644 --- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt @@ -1,6 +1,6 @@ - + @@ -852,6 +852,16 @@ at Condition.tests.cpp: + + + + + + + + + + @@ -1189,6 +1199,9 @@ at Matchers.tests.cpp: + + + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index b634d27e..eaef6357 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -130,6 +130,18 @@ at AssertionHandler.tests.cpp: + + + + + + + + + + + + @@ -178,6 +190,9 @@ at AssertionHandler.tests.cpp: + + + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt index 33c9c208..bb23a995 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt @@ -129,6 +129,18 @@ at AssertionHandler.tests.cpp: + + + + + + + + + + + + @@ -177,6 +189,9 @@ at AssertionHandler.tests.cpp: + + + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 26a7fa94..e32e6589 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -1866,6 +1866,26 @@ ok {test-number} - data.str_hello != "hell" for: "hello" != "hell" ok {test-number} - data.str_hello != "hello1" for: "hello" != "hello1" # Inequality checks that should succeed ok {test-number} - data.str_hello.size() != 6 for: 5 != 6 +# JsonWriter +ok {test-number} - stream.str() == "" for: "" == "" +# JsonWriter +ok {test-number} - stream.str() == "{\n}" for: "{ }" == "{ }" +# JsonWriter +ok {test-number} - stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) for: "{ "int": 1, "double": 1.5, "true": true, "false": false, "string": "this is a string", "array": [ 1, 2 ] }" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: ""true": true," and contains: ""false": false," and contains: ""string": "this is a string"," and contains: ""array": [ 1, 2 ] }" ) +# JsonWriter +ok {test-number} - stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) for: "{ "empty_object": { }, "fully_object": { "key": 1 } }" ( contains: ""empty_object": { }," and contains: ""fully_object": { "key": 1 }" ) +# JsonWriter +ok {test-number} - stream.str() == "[\n]" for: "[ ]" == "[ ]" +# JsonWriter +ok {test-number} - stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" for: "[ 1, 1.5, true, false, "this is a string", { "object": 42 }, [ "array", 42.5 ] ]" == "[ 1, 1.5, true, false, "this is a string", { "object": 42 }, [ "array", 42.5 ] ]" +# JsonWriter +ok {test-number} - stream.str() == "{\n}" for: "{ }" == "{ }" +# JsonWriter +ok {test-number} - stream.str() == "[\n]" for: "[ ]" == "[ ]" +# JsonWriter +ok {test-number} - stream.str() == "\"custom\"" for: ""custom"" == ""custom"" +# JsonWriter +ok {test-number} - stream.str() == "\"\\\"\"" for: ""\""" == ""\""" # Lambdas in assertions ok {test-number} - []() { return true; }() for: true # Less-than inequalities with different epsilons @@ -2459,6 +2479,18 @@ ok {test-number} - listingString, ContainsSubstring( "fake test name"s ) && Cont # Reporter's write listings to provided stream ok {test-number} - !(factories.empty()) for: !false # Reporter's write listings to provided stream +ok {test-number} - listingString, ContainsSubstring("fakeTag"s) for: "{ "version": 1, "metadata": { "name": "", "rng-seed": 1234, "catch2-version": "" }, "listings": { "tags": [ { "aliases": [ "fakeTag" ], "count": 1 } ]" contains: "fakeTag" with 1 message: 'Tested reporter: JSON' +# Reporter's write listings to provided stream +ok {test-number} - !(factories.empty()) for: !false +# Reporter's write listings to provided stream +ok {test-number} - listingString, ContainsSubstring("fake reporter"s) for: "{ "version": 1, "metadata": { "name": "", "rng-seed": 1234, "catch2-version": "" }, "listings": { "reporters": [ { "name": "fake reporter", "description": "fake description" } ]" contains: "fake reporter" with 1 message: 'Tested reporter: JSON' +# Reporter's write listings to provided stream +ok {test-number} - !(factories.empty()) for: !false +# Reporter's write listings to provided stream +ok {test-number} - listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) for: "{ "version": 1, "metadata": { "name": "", "rng-seed": 1234, "catch2-version": "" }, "listings": { "tests": [ { "name": "fake test name", "class-name": "", "tags": [ "fakeTestTag" ], "source-location": { "filename": "fake-file.cpp", "line": 123456789 } } ]" ( contains: "fake test name" and contains: "fakeTestTag" ) with 1 message: 'Tested reporter: JSON' +# Reporter's write listings to provided stream +ok {test-number} - !(factories.empty()) for: !false +# Reporter's write listings to provided stream ok {test-number} - listingString, ContainsSubstring("fakeTag"s) for: " All available tags: 1 [fakeTag] 1 tag " contains: "fakeTag" with 1 message: 'Tested reporter: JUnit' # Reporter's write listings to provided stream ok {test-number} - !(factories.empty()) for: !false @@ -4489,5 +4521,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2242 +1..2258 diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index b9a075c1..5d3f0560 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -1864,6 +1864,26 @@ ok {test-number} - data.str_hello != "hell" for: "hello" != "hell" ok {test-number} - data.str_hello != "hello1" for: "hello" != "hello1" # Inequality checks that should succeed ok {test-number} - data.str_hello.size() != 6 for: 5 != 6 +# JsonWriter +ok {test-number} - stream.str() == "" for: "" == "" +# JsonWriter +ok {test-number} - stream.str() == "{\n}" for: "{ }" == "{ }" +# JsonWriter +ok {test-number} - stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) for: "{ "int": 1, "double": 1.5, "true": true, "false": false, "string": "this is a string", "array": [ 1, 2 ] }" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: ""true": true," and contains: ""false": false," and contains: ""string": "this is a string"," and contains: ""array": [ 1, 2 ] }" ) +# JsonWriter +ok {test-number} - stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) for: "{ "empty_object": { }, "fully_object": { "key": 1 } }" ( contains: ""empty_object": { }," and contains: ""fully_object": { "key": 1 }" ) +# JsonWriter +ok {test-number} - stream.str() == "[\n]" for: "[ ]" == "[ ]" +# JsonWriter +ok {test-number} - stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" for: "[ 1, 1.5, true, false, "this is a string", { "object": 42 }, [ "array", 42.5 ] ]" == "[ 1, 1.5, true, false, "this is a string", { "object": 42 }, [ "array", 42.5 ] ]" +# JsonWriter +ok {test-number} - stream.str() == "{\n}" for: "{ }" == "{ }" +# JsonWriter +ok {test-number} - stream.str() == "[\n]" for: "[ ]" == "[ ]" +# JsonWriter +ok {test-number} - stream.str() == "\"custom\"" for: ""custom"" == ""custom"" +# JsonWriter +ok {test-number} - stream.str() == "\"\\\"\"" for: ""\""" == ""\""" # Lambdas in assertions ok {test-number} - []() { return true; }() for: true # Less-than inequalities with different epsilons @@ -2457,6 +2477,18 @@ ok {test-number} - listingString, ContainsSubstring( "fake test name"s ) && Cont # Reporter's write listings to provided stream ok {test-number} - !(factories.empty()) for: !false # Reporter's write listings to provided stream +ok {test-number} - listingString, ContainsSubstring("fakeTag"s) for: "{ "version": 1, "metadata": { "name": "", "rng-seed": 1234, "catch2-version": "" }, "listings": { "tags": [ { "aliases": [ "fakeTag" ], "count": 1 } ]" contains: "fakeTag" with 1 message: 'Tested reporter: JSON' +# Reporter's write listings to provided stream +ok {test-number} - !(factories.empty()) for: !false +# Reporter's write listings to provided stream +ok {test-number} - listingString, ContainsSubstring("fake reporter"s) for: "{ "version": 1, "metadata": { "name": "", "rng-seed": 1234, "catch2-version": "" }, "listings": { "reporters": [ { "name": "fake reporter", "description": "fake description" } ]" contains: "fake reporter" with 1 message: 'Tested reporter: JSON' +# Reporter's write listings to provided stream +ok {test-number} - !(factories.empty()) for: !false +# Reporter's write listings to provided stream +ok {test-number} - listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) for: "{ "version": 1, "metadata": { "name": "", "rng-seed": 1234, "catch2-version": "" }, "listings": { "tests": [ { "name": "fake test name", "class-name": "", "tags": [ "fakeTestTag" ], "source-location": { "filename": "fake-file.cpp", "line": 123456789 } } ]" ( contains: "fake test name" and contains: "fakeTestTag" ) with 1 message: 'Tested reporter: JSON' +# Reporter's write listings to provided stream +ok {test-number} - !(factories.empty()) for: !false +# Reporter's write listings to provided stream ok {test-number} - listingString, ContainsSubstring("fakeTag"s) for: " All available tags: 1 [fakeTag] 1 tag " contains: "fakeTag" with 1 message: 'Tested reporter: JUnit' # Reporter's write listings to provided stream ok {test-number} - !(factories.empty()) for: !false @@ -4478,5 +4510,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2242 +1..2258 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index 4293464d..35ff74ac 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -420,6 +420,8 @@ ##teamcity[testFinished name='Inequality checks that should fail' duration="{duration}"] ##teamcity[testStarted name='Inequality checks that should succeed'] ##teamcity[testFinished name='Inequality checks that should succeed' duration="{duration}"] +##teamcity[testStarted name='JsonWriter'] +##teamcity[testFinished name='JsonWriter' duration="{duration}"] ##teamcity[testStarted name='Lambdas in assertions'] ##teamcity[testFinished name='Lambdas in assertions' duration="{duration}"] ##teamcity[testStarted name='Less-than inequalities with different epsilons'] diff --git a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt index b68d8bdb..d559c1fe 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt @@ -420,6 +420,8 @@ ##teamcity[testFinished name='Inequality checks that should fail' duration="{duration}"] ##teamcity[testStarted name='Inequality checks that should succeed'] ##teamcity[testFinished name='Inequality checks that should succeed' duration="{duration}"] +##teamcity[testStarted name='JsonWriter'] +##teamcity[testFinished name='JsonWriter' duration="{duration}"] ##teamcity[testStarted name='Lambdas in assertions'] ##teamcity[testFinished name='Lambdas in assertions' duration="{duration}"] ##teamcity[testStarted name='Less-than inequalities with different epsilons'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 28ed5c86..c6059ea8 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -8779,6 +8779,186 @@ C + +
+ + + stream.str() == "" + + + "" == "" + + + +
+
+ + + stream.str() == "{\n}" + + + "{ +}" +== +"{ +}" + + + +
+
+ + + stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) + + + "{ + "int": 1, + "double": 1.5, + "true": true, + "false": false, + "string": "this is a string", + "array": [ + 1, + 2 + ] +}" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: ""true": true," and contains: ""false": false," and contains: ""string": "this is a string"," and contains: ""array": [ + 1, + 2 + ] +}" ) + + + +
+
+ + + stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) + + + "{ + "empty_object": { + }, + "fully_object": { + "key": 1 + } +}" ( contains: ""empty_object": { + }," and contains: ""fully_object": { + "key": 1 + }" ) + + + +
+
+ + + stream.str() == "[\n]" + + + "[ +]" +== +"[ +]" + + + +
+
+ + + stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" + + + "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" +== +"[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" + + + +
+
+ + + stream.str() == "{\n}" + + + "{ +}" +== +"{ +}" + + + +
+
+ + + stream.str() == "[\n]" + + + "[ +]" +== +"[ +]" + + + +
+
+ + + stream.str() == "\"custom\"" + + + ""custom"" == ""custom"" + + + +
+
+ + + stream.str() == "\"\\\"\"" + + + ""\""" == ""\""" + + + +
+ +
@@ -11695,6 +11875,120 @@ C !false +
+ + Tested reporter: JSON + + + + listingString, ContainsSubstring("fakeTag"s) + + + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tags": [ + { + "aliases": [ + "fakeTag" + ], + "count": 1 + } + ]" contains: "fakeTag" + + + +
+ + + !(factories.empty()) + + + !false + + +
+ + Tested reporter: JSON + + + + listingString, ContainsSubstring("fake reporter"s) + + + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "reporters": [ + { + "name": "fake reporter", + "description": "fake description" + } + ]" contains: "fake reporter" + + + +
+ + + !(factories.empty()) + + + !false + + +
+ + Tested reporter: JSON + + + + listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) + + + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tests": [ + { + "name": "fake test name", + "class-name": "", + "tags": [ + "fakeTestTag" + ], + "source-location": { + "filename": "fake-file.cpp", + "line": 123456789 + } + } + ]" ( contains: "fake test name" and contains: "fakeTestTag" ) + + + +
+ + + !(factories.empty()) + + + !false + +
Tested reporter: JUnit @@ -21268,6 +21562,6 @@ b1!
- - + + diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index 0f3210db..23df72f3 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -8779,6 +8779,186 @@ C
+ +
+ + + stream.str() == "" + + + "" == "" + + + +
+
+ + + stream.str() == "{\n}" + + + "{ +}" +== +"{ +}" + + + +
+
+ + + stream.str(), ContainsSubstring( "\"int\": 1," ) && ContainsSubstring( "\"double\": 1.5," ) && ContainsSubstring( "\"true\": true," ) && ContainsSubstring( "\"false\": false," ) && ContainsSubstring( "\"string\": \"this is a string\"," ) && ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) + + + "{ + "int": 1, + "double": 1.5, + "true": true, + "false": false, + "string": "this is a string", + "array": [ + 1, + 2 + ] +}" ( contains: ""int": 1," and contains: ""double": 1.5," and contains: ""true": true," and contains: ""false": false," and contains: ""string": "this is a string"," and contains: ""array": [ + 1, + 2 + ] +}" ) + + + +
+
+ + + stream.str(), ContainsSubstring( "\"empty_object\": {\n }," ) && ContainsSubstring( "\"fully_object\": {\n \"key\": 1\n }" ) + + + "{ + "empty_object": { + }, + "fully_object": { + "key": 1 + } +}" ( contains: ""empty_object": { + }," and contains: ""fully_object": { + "key": 1 + }" ) + + + +
+
+ + + stream.str() == "[\n]" + + + "[ +]" +== +"[ +]" + + + +
+
+ + + stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" + + + "[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" +== +"[ + 1, + 1.5, + true, + false, + "this is a string", + { + "object": 42 + }, + [ + "array", + 42.5 + ] +]" + + + +
+
+ + + stream.str() == "{\n}" + + + "{ +}" +== +"{ +}" + + + +
+
+ + + stream.str() == "[\n]" + + + "[ +]" +== +"[ +]" + + + +
+
+ + + stream.str() == "\"custom\"" + + + ""custom"" == ""custom"" + + + +
+
+ + + stream.str() == "\"\\\"\"" + + + ""\""" == ""\""" + + + +
+ +
@@ -11695,6 +11875,120 @@ C !false +
+ + Tested reporter: JSON + + + + listingString, ContainsSubstring("fakeTag"s) + + + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tags": [ + { + "aliases": [ + "fakeTag" + ], + "count": 1 + } + ]" contains: "fakeTag" + + + +
+ + + !(factories.empty()) + + + !false + + +
+ + Tested reporter: JSON + + + + listingString, ContainsSubstring("fake reporter"s) + + + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "reporters": [ + { + "name": "fake reporter", + "description": "fake description" + } + ]" contains: "fake reporter" + + + +
+ + + !(factories.empty()) + + + !false + + +
+ + Tested reporter: JSON + + + + listingString, ContainsSubstring( "fake test name"s ) && ContainsSubstring( "fakeTestTag"s ) + + + "{ + "version": 1, + "metadata": { + "name": "", + "rng-seed": 1234, + "catch2-version": "" + }, + "listings": { + "tests": [ + { + "name": "fake test name", + "class-name": "", + "tags": [ + "fakeTestTag" + ], + "source-location": { + "filename": "fake-file.cpp", + "line": 123456789 + } + } + ]" ( contains: "fake test name" and contains: "fakeTestTag" ) + + + +
+ + + !(factories.empty()) + + + !false + +
Tested reporter: JUnit @@ -21267,6 +21561,6 @@ b1!
- - + + diff --git a/tests/SelfTest/IntrospectiveTests/Json.tests.cpp b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp new file mode 100644 index 00000000..9ade94ee --- /dev/null +++ b/tests/SelfTest/IntrospectiveTests/Json.tests.cpp @@ -0,0 +1,116 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include +#include +#include + +#include + +namespace { + struct Custom {}; + std::ostream& operator<<( std::ostream& os, Custom const& ) { + return os << "custom"; + } +} // namespace + +TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) { + + std::stringstream stream{}; + SECTION( "Newly constructed JsonWriter does nothing" ) { + Catch::JsonValueWriter writer{ stream }; + REQUIRE( stream.str() == "" ); + } + + SECTION( "Calling writeObject will create an empty pair of braces" ) { + { auto writer = Catch::JsonValueWriter{ stream }.writeObject(); } + REQUIRE( stream.str() == "{\n}" ); + } + + SECTION( "Calling writeObject with key will create an object to write the " + "value" ) { + using Catch::Matchers::ContainsSubstring; + { + auto writer = Catch::JsonValueWriter{ stream }.writeObject(); + writer.write( "int" ).write( 1 ); + writer.write( "double" ).write( 1.5 ); + writer.write( "true" ).write( true ); + writer.write( "false" ).write( false ); + writer.write( "string" ).write( "this is a string" ); + writer.write( "array" ).writeArray().write( 1 ).write( 2 ); + } + REQUIRE_THAT( + stream.str(), + ContainsSubstring( "\"int\": 1," ) && + ContainsSubstring( "\"double\": 1.5," ) && + ContainsSubstring( "\"true\": true," ) && + ContainsSubstring( "\"false\": false," ) && + ContainsSubstring( "\"string\": \"this is a string\"," ) && + ContainsSubstring( "\"array\": [\n 1,\n 2\n ]\n}" ) ); + } + + SECTION( "nesting objects" ) { + using Catch::Matchers::ContainsSubstring; + { + auto writer = Catch::JsonValueWriter{ stream }.writeObject(); + writer.write( "empty_object" ).writeObject(); + writer.write( "fully_object" ) + .writeObject() + .write( "key" ) + .write( 1 ); + } + REQUIRE_THAT( stream.str(), + ContainsSubstring( "\"empty_object\": {\n }," ) && + ContainsSubstring( + "\"fully_object\": {\n \"key\": 1\n }" ) ); + } + + SECTION( "Calling writeArray will create an empty pair of braces" ) { + { auto writer = Catch::JsonValueWriter{ stream }.writeArray(); } + REQUIRE( stream.str() == "[\n]" ); + } + + SECTION( "Calling writeArray creates array to write the values to" ) { + { + auto writer = Catch::JsonValueWriter{ stream }.writeArray(); + writer.write( 1 ); + writer.write( 1.5 ); + writer.write( true ); + writer.write( false ); + writer.write( "this is a string" ); + writer.writeObject().write( "object" ).write( 42 ); + writer.writeArray().write( "array" ).write( 42.5 ); + } + REQUIRE( stream.str() == "[\n 1,\n 1.5,\n true,\n false,\n \"this is a string\",\n {\n \"object\": 42\n },\n [\n \"array\",\n 42.5\n ]\n]" ); + } + + SECTION( + "Moved from JsonObjectWriter shall not insert superfluous brace" ) { + { + auto writer = Catch::JsonObjectWriter{ stream }; + auto another_writer = std::move( writer ); + } + REQUIRE( stream.str() == "{\n}" ); + } + SECTION( + "Moved from JsonArrayWriter shall not insert superfluous bracket" ) { + { + auto writer = Catch::JsonArrayWriter{ stream }; + auto another_writer = std::move( writer ); + } + REQUIRE( stream.str() == "[\n]" ); + } + SECTION( "Custom class shall be quoted" ) { + Catch::JsonValueWriter{ stream }.write( Custom{} ); + REQUIRE( stream.str() == "\"custom\"" ); + } + SECTION( "String with a quote shall be espaced" ) { + Catch::JsonValueWriter{ stream }.write( "\"" ); + REQUIRE( stream.str() == "\"\\\"\"" ); + } +} diff --git a/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp b/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp index 1568c951..e5a65bda 100644 --- a/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Reporters.tests.cpp @@ -109,7 +109,9 @@ TEST_CASE( "Reporter's write listings to provided stream", "[reporters]" ) { auto sstream = Catch::Detail::make_unique(); auto& sstreamRef = *sstream.get(); - Catch::Config config( Catch::ConfigData{} ); + Catch::ConfigData cfg_data; + cfg_data.rngSeed = 1234; + Catch::Config config( cfg_data ); auto reporter = factory.second->create( Catch::ReporterConfig{ &config, CATCH_MOVE( sstream ), Catch::ColourMode::None, {} } );