From 1bb945df9f6a3678c087d559c7d42e14fcdcb71f Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:31:38 -0400 Subject: [PATCH] fix(testing): capture stderr and stdout --- tests/cpp/test_testing.cpp | 19 +++++++ .../include/lizardbyte/common/testing.h | 19 ++++++- tests/support/testing.cpp | 54 +++++++++++++++++-- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/tests/cpp/test_testing.cpp b/tests/cpp/test_testing.cpp index f25cde6..078df70 100644 --- a/tests/cpp/test_testing.cpp +++ b/tests/cpp/test_testing.cpp @@ -4,6 +4,7 @@ */ // standard includes +#include #include #include #include @@ -21,6 +22,8 @@ namespace { using BaseTest::isOutputSuppressed; using BaseTest::isSystemTest; using BaseTest::skipTest; + using BaseTest::stderrBuffer; + using BaseTest::stdoutBuffer; }; class EnvGuard { @@ -67,6 +70,15 @@ namespace { } }; + class StdioCaptureFixture: public TestableBaseTest { + protected: + void TearDown() override { + BaseTest::TearDown(); + EXPECT_EQ(stdoutBuffer().str(), "captured stdout"); + EXPECT_EQ(stderrBuffer().str(), "captured stderr"); + } + }; + class TestableBufferedTestEventListener: public BufferedTestEventListener { public: using BufferedTestEventListener::clearBufferedTestOutput; @@ -86,6 +98,13 @@ TEST(TestingSupportTest, DefaultTestMacroUsesBaseFixture) { EXPECT_EQ(cerrBuffer().str(), "captured cerr"); } +TEST_F(StdioCaptureFixture, CapturesStdoutAndStderr) { + std::printf("captured stdout"); + std::fprintf(stderr, "captured stderr"); + std::fflush(stdout); + std::fflush(stderr); +} + TEST(TestingSupportTest, ArgumentMatchingCanReturnFullArgument) { EXPECT_TRUE(getArgWithMatchingPattern("--gtest_", false).has_value()); } diff --git a/tests/support/include/lizardbyte/common/testing.h b/tests/support/include/lizardbyte/common/testing.h index fedde55..41553e9 100644 --- a/tests/support/include/lizardbyte/common/testing.h +++ b/tests/support/include/lizardbyte/common/testing.h @@ -17,7 +17,7 @@ namespace lizardbyte::common::testing { /** * @brief Base class used by default for shared tests. * - * ``cout`` and ``cerr`` are redirected during tests and printed when a test fails. + * ``cout``, ``cerr``, ``stdout``, and ``stderr`` are redirected during tests and printed when a test fails. */ class BaseTest: public ::testing::Test { protected: @@ -85,12 +85,29 @@ namespace lizardbyte::common::testing { */ [[nodiscard]] std::stringstream &cerrBuffer(); + /** + * @brief Get captured stdout. + * @return Captured stdout stream. + */ + [[nodiscard]] std::stringstream &stdoutBuffer(); + + /** + * @brief Get captured stderr. + * @return Captured stderr stream. + */ + [[nodiscard]] std::stringstream &stderrBuffer(); + private: std::stringstream cout_buffer_; /**< Stores cout while output is suppressed. */ std::stringstream cerr_buffer_; /**< Stores cerr while output is suppressed. */ + std::stringstream stdout_buffer_; /**< Stores stdout while output is suppressed. */ + std::stringstream stderr_buffer_; /**< Stores stderr while output is suppressed. */ std::streambuf *cout_streambuf_ {nullptr}; /**< Original cout stream buffer. */ std::streambuf *cerr_streambuf_ {nullptr}; /**< Original cerr stream buffer. */ bool test_skipped_at_setup_ {false}; /**< True when SetUp skipped before redirection. */ + bool output_suppressed_ {false}; /**< True when output capture was enabled in SetUp. */ + bool stdout_capture_active_ {false}; /**< True when GoogleTest stdout capture is active. */ + bool stderr_capture_active_ {false}; /**< True when GoogleTest stderr capture is active. */ }; /** diff --git a/tests/support/testing.cpp b/tests/support/testing.cpp index 93e5062..d7e7be2 100644 --- a/tests/support/testing.cpp +++ b/tests/support/testing.cpp @@ -4,10 +4,14 @@ */ // standard includes +#include #include #include #include +// lib includes +#include + // platform includes #if defined(__linux__) #include @@ -19,17 +23,32 @@ namespace lizardbyte::common::testing { void BaseTest::SetUp() { + test_skipped_at_setup_ = false; + output_suppressed_ = false; + stdout_capture_active_ = false; + stderr_capture_active_ = false; + cout_buffer_.str({}); cout_buffer_.clear(); cerr_buffer_.str({}); cerr_buffer_.clear(); + stdout_buffer_.str({}); + stdout_buffer_.clear(); + stderr_buffer_.str({}); + stderr_buffer_.clear(); if (const auto skip_reason {skipTest()}; !skip_reason.empty()) { test_skipped_at_setup_ = true; GTEST_SKIP() << skip_reason; } - if (isOutputSuppressed()) { + output_suppressed_ = isOutputSuppressed(); + if (output_suppressed_) { + ::testing::internal::CaptureStdout(); + stdout_capture_active_ = true; + ::testing::internal::CaptureStderr(); + stderr_capture_active_ = true; + cout_streambuf_ = std::cout.rdbuf(); cerr_streambuf_ = std::cerr.rdbuf(); std::cout.rdbuf(cout_buffer_.rdbuf()); @@ -42,7 +61,12 @@ namespace lizardbyte::common::testing { return; } - if (isOutputSuppressed()) { + if (output_suppressed_) { + std::cout.flush(); + std::cerr.flush(); + std::fflush(stdout); + std::fflush(stderr); + if (cout_streambuf_ != nullptr) { std::cout.rdbuf(cout_streambuf_); cout_streambuf_ = nullptr; @@ -53,6 +77,16 @@ namespace lizardbyte::common::testing { cerr_streambuf_ = nullptr; } + if (stdout_capture_active_) { + stdout_buffer_ << ::testing::internal::GetCapturedStdout(); + stdout_capture_active_ = false; + } + + if (stderr_capture_active_) { + stderr_buffer_ << ::testing::internal::GetCapturedStderr(); + stderr_capture_active_ = false; + } + const auto *test_info = ::testing::UnitTest::GetInstance()->current_test_info(); if (test_info != nullptr && test_info->result()->Failed()) { std::cout << std::endl @@ -61,8 +95,14 @@ namespace lizardbyte::common::testing { << "Captured cout:" << std::endl << cout_buffer_.str() << std::endl << "Captured cerr:" << std::endl - << cerr_buffer_.str() << std::endl; + << cerr_buffer_.str() << std::endl + << "Captured stdout:" << std::endl + << stdout_buffer_.str() << std::endl + << "Captured stderr:" << std::endl + << stderr_buffer_.str() << std::endl; } + + output_suppressed_ = false; } } @@ -112,6 +152,14 @@ namespace lizardbyte::common::testing { return cerr_buffer_; } + std::stringstream &BaseTest::stdoutBuffer() { + return stdout_buffer_; + } + + std::stringstream &BaseTest::stderrBuffer() { + return stderr_buffer_; + } + void LinuxTest::SetUp() { #if !defined(__linux__) GTEST_SKIP() << "Skipping, this test is for Linux only.";