From 36bd22a58521242c07b8e669942e49a2773877b6 Mon Sep 17 00:00:00 2001 From: Mara Sophie Grosch Date: Sun, 29 Oct 2023 04:42:37 +0100 Subject: [PATCH 1/2] Add public interface for XmlUnitTestResultPrinter Allowing custom gtest_main implementations to instantiate the XmlUnitTestResultPrinter with either a given file path or any other kind of std::ostream to write to. This is useful for e.g. embedded cases where an XML report is still wanted, but not file system is available, by instantiating with a std::stringstream and delivering the data via any custom mean. Related to #1930 --- googletest/include/gtest/gtest.h | 17 ++++++ googletest/src/gtest.cc | 97 +++++++++++++++++++------------- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/googletest/include/gtest/gtest.h b/googletest/include/gtest/gtest.h index a932e686..836dbc3a 100644 --- a/googletest/include/gtest/gtest.h +++ b/googletest/include/gtest/gtest.h @@ -1018,6 +1018,23 @@ class EmptyTestEventListener : public TestEventListener { void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} }; +class XmlUnitTestResultPrinter : public EmptyTestEventListener { + public: + XmlUnitTestResultPrinter(const char* output_file); + + XmlUnitTestResultPrinter(std::ostream* output_stream); + + XmlUnitTestResultPrinter(const XmlUnitTestResultPrinter&) = delete; + XmlUnitTestResultPrinter& operator=(const XmlUnitTestResultPrinter&) = delete; + + void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; + void ListTestsMatchingFilter(const std::vector& test_suites); + + private: + const std::string output_file_; + std::ostream* output_stream_; +}; + // TestEventListeners lets users add listeners to track events in Google Test. class GTEST_API_ TestEventListeners { public: diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 99b22ed3..649e109f 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -3897,10 +3897,7 @@ void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, // This class generates an XML output file. class XmlUnitTestResultPrinter : public EmptyTestEventListener { public: - explicit XmlUnitTestResultPrinter(const char* output_file); - - void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; - void ListTestsMatchingFilter(const std::vector& test_suites); + XmlUnitTestResultPrinter() = delete; // Prints an XML summary of all unit tests. static void PrintXmlTestsList(std::ostream* stream, @@ -3982,40 +3979,9 @@ class XmlUnitTestResultPrinter : public EmptyTestEventListener { static void OutputXmlTestProperties(std::ostream* stream, const TestResult& result); - // The output file. - const std::string output_file_; - - XmlUnitTestResultPrinter(const XmlUnitTestResultPrinter&) = delete; - XmlUnitTestResultPrinter& operator=(const XmlUnitTestResultPrinter&) = delete; + friend ::testing::XmlUnitTestResultPrinter; }; -// Creates a new XmlUnitTestResultPrinter. -XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) - : output_file_(output_file) { - if (output_file_.empty()) { - GTEST_LOG_(FATAL) << "XML output file may not be null"; - } -} - -// Called after the unit test ends. -void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, - int /*iteration*/) { - FILE* xmlout = OpenFileForWriting(output_file_); - std::stringstream stream; - PrintXmlUnitTest(&stream, unit_test); - fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); - fclose(xmlout); -} - -void XmlUnitTestResultPrinter::ListTestsMatchingFilter( - const std::vector& test_suites) { - FILE* xmlout = OpenFileForWriting(output_file_); - std::stringstream stream; - PrintXmlTestsList(&stream, test_suites); - fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); - fclose(xmlout); -} - // Returns an XML-escaped copy of the input string str. If is_attribute // is true, the text is meant to appear as an attribute value, and // normalizable whitespace is preserved by replacing it with character @@ -5145,6 +5111,59 @@ void TestEventListeners::SuppressEventForwarding(bool suppress) { repeater_->set_forwarding_enabled(!suppress); } +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.empty()) { + GTEST_LOG_(FATAL) << "XML output file may not be null"; + } +} + +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(std::ostream* output_stream) + : output_stream_(output_stream) { + if (!output_stream->good()) { + GTEST_LOG_(FATAL) << "XML output is not good"; + } +} + +// Called after the unit test ends. +void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* xmlout = 0; + if(!output_stream_) { + xmlout = internal::OpenFileForWriting(output_file_); + output_stream_ = new std::stringstream; + } + + internal::XmlUnitTestResultPrinter::PrintXmlUnitTest(output_stream_, unit_test); + + if(xmlout) { + fprintf(xmlout, "%s", internal::StringStreamToString(static_cast(output_stream_)).c_str()); + fclose(xmlout); + delete output_stream_; + output_stream_ = 0; + } +} + +void XmlUnitTestResultPrinter::ListTestsMatchingFilter( + const std::vector& test_suites) { + FILE* xmlout = 0; + if(!output_stream_) { + xmlout = internal::OpenFileForWriting(output_file_); + output_stream_ = new std::stringstream; + } + + internal::XmlUnitTestResultPrinter::PrintXmlTestsList(output_stream_, test_suites); + + if(xmlout) { + fprintf(xmlout, "%s", internal::StringStreamToString(static_cast(output_stream_)).c_str()); + fclose(xmlout); + delete output_stream_; + output_stream_ = 0; + } +} + // class UnitTest // Gets the singleton UnitTest object. The first time this method is @@ -5630,7 +5649,7 @@ void UnitTestImpl::ConfigureXmlOutput() { const std::string& output_format = UnitTestOptions::GetOutputFormat(); #if GTEST_HAS_FILE_SYSTEM if (output_format == "xml") { - listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( + listeners()->SetDefaultXmlGenerator(new ::testing::XmlUnitTestResultPrinter( UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); } else if (output_format == "json") { listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter( @@ -6206,9 +6225,7 @@ void UnitTestImpl::ListTestsMatchingFilter() { UnitTestOptions::GetAbsolutePathToOutputFile().c_str()); std::stringstream stream; if (output_format == "xml") { - XmlUnitTestResultPrinter( - UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) - .PrintXmlTestsList(&stream, test_suites_); + XmlUnitTestResultPrinter::PrintXmlTestsList(&stream, test_suites_); } else if (output_format == "json") { JsonUnitTestResultPrinter( UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) From 4ac8c39200d36e332ae3a26373bb7f54059619a4 Mon Sep 17 00:00:00 2001 From: Mara Sophie Grosch Date: Sun, 29 Oct 2023 05:52:10 +0100 Subject: [PATCH 2/2] make XmlUnitTestResultPrinter usable without filesystem Make XmlUnitTestResultPrinter usable when GTEST_HAS_FILE_SYSTEM=0, though this should probably be another class extending XmlUnitTestResultPrinter instead - but feels like the preprocessor feature flag logic matches the existing code base. --- googletest/include/gtest/gtest.h | 4 ++++ googletest/src/gtest.cc | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/googletest/include/gtest/gtest.h b/googletest/include/gtest/gtest.h index 836dbc3a..50fef296 100644 --- a/googletest/include/gtest/gtest.h +++ b/googletest/include/gtest/gtest.h @@ -1020,7 +1020,9 @@ class EmptyTestEventListener : public TestEventListener { class XmlUnitTestResultPrinter : public EmptyTestEventListener { public: +#if GTEST_HAS_FILE_SYSTEM XmlUnitTestResultPrinter(const char* output_file); +#endif // GTEST_HAS_FILE_SYSTEM XmlUnitTestResultPrinter(std::ostream* output_stream); @@ -1031,7 +1033,9 @@ class XmlUnitTestResultPrinter : public EmptyTestEventListener { void ListTestsMatchingFilter(const std::vector& test_suites); private: +#if GTEST_HAS_FILE_SYSTEM const std::string output_file_; +#endif // GTEST_HAS_FILE_SYSTEM std::ostream* output_stream_; }; diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 649e109f..600b8d1a 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -2342,7 +2342,6 @@ static std::vector GetReservedAttributesForElement( return std::vector(); } -#if GTEST_HAS_FILE_SYSTEM // TODO(jdesprez): Merge the two getReserved attributes once skip is improved // This function is only used when file systems are enabled. static std::vector GetReservedOutputAttributesForElement( @@ -2359,7 +2358,6 @@ static std::vector GetReservedOutputAttributesForElement( // This code is unreachable but some compilers may not realizes that. return std::vector(); } -#endif static std::string FormatWordList(const std::vector& words) { Message word_list; @@ -3893,7 +3891,6 @@ void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, // End TestEventRepeater -#if GTEST_HAS_FILE_SYSTEM // This class generates an XML output file. class XmlUnitTestResultPrinter : public EmptyTestEventListener { public: @@ -4414,7 +4411,6 @@ void XmlUnitTestResultPrinter::OutputXmlTestProperties( } // End XmlUnitTestResultPrinter -#endif // GTEST_HAS_FILE_SYSTEM #if GTEST_HAS_FILE_SYSTEM // This class generates an JSON output file. @@ -5111,6 +5107,7 @@ void TestEventListeners::SuppressEventForwarding(bool suppress) { repeater_->set_forwarding_enabled(!suppress); } +#if GTEST_HAS_FILE_SYSTEM // Creates a new XmlUnitTestResultPrinter. XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) : output_file_(output_file) { @@ -5118,6 +5115,7 @@ XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) GTEST_LOG_(FATAL) << "XML output file may not be null"; } } +#endif // GTEST_HAS_FILE_SYSTEM // Creates a new XmlUnitTestResultPrinter. XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(std::ostream* output_stream) @@ -5130,38 +5128,46 @@ XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(std::ostream* output_stream) // Called after the unit test ends. void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, int /*iteration*/) { +#if GTEST_HAS_FILE_SYSTEM FILE* xmlout = 0; if(!output_stream_) { xmlout = internal::OpenFileForWriting(output_file_); output_stream_ = new std::stringstream; } +#endif // GTEST_HAS_FILE_SYSTEM internal::XmlUnitTestResultPrinter::PrintXmlUnitTest(output_stream_, unit_test); +#if GTEST_HAS_FILE_SYSTEM if(xmlout) { fprintf(xmlout, "%s", internal::StringStreamToString(static_cast(output_stream_)).c_str()); fclose(xmlout); delete output_stream_; output_stream_ = 0; } +#endif // GTEST_HAS_FILE_SYSTEM } void XmlUnitTestResultPrinter::ListTestsMatchingFilter( const std::vector& test_suites) { +#if GTEST_HAS_FILE_SYSTEM FILE* xmlout = 0; if(!output_stream_) { xmlout = internal::OpenFileForWriting(output_file_); output_stream_ = new std::stringstream; } +#endif // GTEST_HAS_FILE_SYSTEM internal::XmlUnitTestResultPrinter::PrintXmlTestsList(output_stream_, test_suites); +#if GTEST_HAS_FILE_SYSTEM if(xmlout) { fprintf(xmlout, "%s", internal::StringStreamToString(static_cast(output_stream_)).c_str()); fclose(xmlout); delete output_stream_; output_stream_ = 0; } +#endif // GTEST_HAS_FILE_SYSTEM } // class UnitTest