diff --git a/tests/cpp/test_testing.cpp b/tests/cpp/test_testing.cpp index b65e791..f25cde6 100644 --- a/tests/cpp/test_testing.cpp +++ b/tests/cpp/test_testing.cpp @@ -66,6 +66,16 @@ namespace { return false; } }; + + class TestableBufferedTestEventListener: public BufferedTestEventListener { + public: + using BufferedTestEventListener::clearBufferedTestOutput; + using BufferedTestEventListener::logTestEvent; + + [[nodiscard]] std::string output() const { + return bufferedTestOutput(); + } + }; } // namespace TEST(TestingSupportTest, DefaultTestMacroUsesBaseFixture) { @@ -107,6 +117,17 @@ TEST_F(TestableBaseTest, HelpersAreAvailableOnDerivedFixtures) { EXPECT_FALSE(isSystemTest()); } +TEST(TestingSupportTest, BufferedEventListenerOutputCanBeCustomized) { + TestableBufferedTestEventListener listener; + + listener.logTestEvent("first line"); + listener.logTestEvent("second line"); + EXPECT_EQ(listener.output(), "first line\nsecond line\n"); + + listener.clearBufferedTestOutput(); + EXPECT_TRUE(listener.output().empty()); +} + TEST_F(LinuxTest, SkipsOrRunsLinuxFixture) { #if defined(__linux__) SUCCEED(); diff --git a/tests/support/include/lizardbyte/common/testing.h b/tests/support/include/lizardbyte/common/testing.h index 5719101..fedde55 100644 --- a/tests/support/include/lizardbyte/common/testing.h +++ b/tests/support/include/lizardbyte/common/testing.h @@ -132,6 +132,51 @@ namespace lizardbyte::common::testing { */ void SetUp() override; }; + + /** + * @brief GoogleTest event listener that buffers test log output until a test fails. + */ + class BufferedTestEventListener: public ::testing::EmptyTestEventListener { + public: + /** + * @brief Handle the start of a test. + * @param test_info GoogleTest test information. + */ + void OnTestStart(const ::testing::TestInfo &test_info) override; + + /** + * @brief Handle a test assertion result. + * @param test_part_result GoogleTest assertion result. + */ + void OnTestPartResult(const ::testing::TestPartResult &test_part_result) override; + + /** + * @brief Handle the end of a test. + * @param test_info GoogleTest test information. + */ + void OnTestEnd(const ::testing::TestInfo &test_info) override; + + protected: + /** + * @brief Add a test event line to the buffer. + * @param message Message to buffer. + */ + virtual void logTestEvent(const std::string &message); + + /** + * @brief Get buffered test output. + * @return Buffered output. + */ + [[nodiscard]] virtual std::string bufferedTestOutput() const; + + /** + * @brief Clear buffered test output. + */ + virtual void clearBufferedTestOutput(); + + private: + std::stringstream event_buffer_; /**< Stores event log output while tests run. */ + }; } // namespace lizardbyte::common::testing #if !defined(LIZARDBYTE_COMMON_TESTING_NO_GLOBAL_ALIASES) @@ -154,6 +199,11 @@ using MacOSTest = ::lizardbyte::common::testing::MacOSTest; * @brief Global compatibility alias for the shared Windows-only test fixture. */ using WindowsTest = ::lizardbyte::common::testing::WindowsTest; + +/** + * @brief Global compatibility alias for the shared buffered test event listener. + */ +using BufferedTestEventListener = ::lizardbyte::common::testing::BufferedTestEventListener; #endif #if !defined(LIZARDBYTE_COMMON_TESTING_KEEP_GTEST_TEST) diff --git a/tests/support/testing.cpp b/tests/support/testing.cpp index 9c29597..93e5062 100644 --- a/tests/support/testing.cpp +++ b/tests/support/testing.cpp @@ -148,4 +148,55 @@ namespace lizardbyte::common::testing { BaseTest::SetUp(); #endif } + + void BufferedTestEventListener::OnTestStart(const ::testing::TestInfo &test_info) { + const std::string file {test_info.file() == nullptr ? "" : test_info.file()}; + logTestEvent("From " + file + ":" + std::to_string(test_info.line())); + logTestEvent(" " + std::string {test_info.test_suite_name()} + "/" + test_info.name() + " started"); + } + + void BufferedTestEventListener::OnTestPartResult(const ::testing::TestPartResult &test_part_result) { + const std::string file {test_part_result.file_name() == nullptr ? "" : test_part_result.file_name()}; + logTestEvent("At " + file + ":" + std::to_string(test_part_result.line_number())); + + const std::string result_text { + test_part_result.passed() ? "Success" : + test_part_result.nonfatally_failed() ? "Non-fatal failure" : + test_part_result.fatally_failed() ? "Failure" : + "Skip" + }; + + const std::string summary {test_part_result.summary()}; + const std::string message {test_part_result.message()}; + logTestEvent(" " + result_text + ": " + summary); + if (message != summary) { + logTestEvent(" " + message); + } + } + + void BufferedTestEventListener::OnTestEnd(const ::testing::TestInfo &test_info) { + const auto &result {*test_info.result()}; + const std::string result_text {result.Passed() ? "passed" : result.Skipped() ? "skipped" : + "failed"}; + logTestEvent(std::string {test_info.test_suite_name()} + "/" + test_info.name() + " " + result_text); + + if (result.Failed()) { + std::cout << bufferedTestOutput(); + } + + clearBufferedTestOutput(); + } + + void BufferedTestEventListener::logTestEvent(const std::string &message) { + event_buffer_ << message << std::endl; + } + + std::string BufferedTestEventListener::bufferedTestOutput() const { + return event_buffer_.str(); + } + + void BufferedTestEventListener::clearBufferedTestOutput() { + event_buffer_.str({}); + event_buffer_.clear(); + } } // namespace lizardbyte::common::testing