From 986ee2c793a5b9a00ab2be5ddc0ce93a7a27086a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Fri, 13 Sep 2024 19:17:39 +0200 Subject: [PATCH] WIP: rebase pipe redirect onto current redirect interface --- src/catch2/internal/catch_output_redirect.cpp | 261 +++++++++++++++++- src/catch2/internal/catch_output_redirect.hpp | 4 +- 2 files changed, 259 insertions(+), 6 deletions(-) diff --git a/src/catch2/internal/catch_output_redirect.cpp b/src/catch2/internal/catch_output_redirect.cpp index 245e1376..d8f029c3 100644 --- a/src/catch2/internal/catch_output_redirect.cpp +++ b/src/catch2/internal/catch_output_redirect.cpp @@ -17,12 +17,18 @@ #include #include -#if defined( CATCH_CONFIG_NEW_CAPTURE ) +#if defined(CATCH_CONFIG_USE_ASYNC) +#include +#endif + +#if defined( CATCH_CONFIG_NEW_CAPTURE ) || defined(CATCH_CONFIG_USE_ASYNC) # if defined( _MSC_VER ) # include //_dup and _dup2 +# include // _O_BINARY # define dup _dup # define dup2 _dup2 # define fileno _fileno +# define close _close # else # include // dup and dup2 # endif @@ -48,13 +54,13 @@ namespace Catch { * that the underlying stream's rdbuf aren't changed by other * users. */ - class RedirectedStreamNew { + class RedirectedStream { std::ostream& m_originalStream; std::ostream& m_redirectionStream; std::streambuf* m_prevBuf; public: - RedirectedStreamNew( std::ostream& originalStream, + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ): m_originalStream( originalStream ), m_redirectionStream( redirectionStream ), @@ -72,7 +78,7 @@ namespace Catch { */ class StreamRedirect : public OutputRedirect { ReusableStringStream m_redirectedOut, m_redirectedErr; - RedirectedStreamNew m_cout, m_cerr, m_clog; + RedirectedStream m_cout, m_cerr, m_clog; public: StreamRedirect(): @@ -244,6 +250,246 @@ namespace Catch { #endif // CATCH_CONFIG_NEW_CAPTURE + +#if defined( CATCH_CONFIG_USE_ASYNC ) + + struct UniqueFileDescriptor final { + constexpr UniqueFileDescriptor() noexcept; + explicit UniqueFileDescriptor( int value ) noexcept; + + UniqueFileDescriptor( UniqueFileDescriptor const& ) = delete; + constexpr UniqueFileDescriptor( + UniqueFileDescriptor&& other ) noexcept; + + ~UniqueFileDescriptor() noexcept; + + UniqueFileDescriptor& + operator=( UniqueFileDescriptor const& ) = delete; + UniqueFileDescriptor& + operator=( UniqueFileDescriptor&& other ) noexcept; + + constexpr int get(); + + private: + int m_value; + }; + + struct OutputFileRedirector final { + explicit OutputFileRedirector( std::FILE* file, + std::string& result ); + + OutputFileRedirector( OutputFileRedirector const& ) = delete; + OutputFileRedirector( OutputFileRedirector&& ) = delete; + + ~OutputFileRedirector() noexcept; + + OutputFileRedirector& + operator=( OutputFileRedirector const& ) = delete; + OutputFileRedirector& operator=( OutputFileRedirector&& ) = delete; + + private: + std::FILE* m_file; + int m_fd; + UniqueFileDescriptor m_previous; + std::thread m_readThread; + }; + + struct PipeRedirect final { + PipeRedirect( std::string& output, std::string& error ): + m_output{ stdout, output }, m_error{ stderr, error } {} + + private: + OutputFileRedirector m_output; + OutputFileRedirector m_error; + }; + + class PipeRedirectWrapper : public OutputRedirect { + void activateImpl() override { m_redirect = Detail::make_unique( m_stdout, m_stderr ); } + void deactivateImpl() override { m_redirect.reset(); } + std::string getStdout() override { return m_stdout; } + std::string getStderr() override { return m_stderr; } + void clearBuffers() override { + m_stdout.clear(); + m_stderr.clear(); + } + Detail::unique_ptr m_redirect; + std::string m_stdout, m_stderr; + }; + + static inline void close_or_throw( int descriptor ) { + if ( close( descriptor ) ) { + CATCH_INTERNAL_ERROR( "close-or-throw" ); + //CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + } + + static inline int dup_or_throw( int descriptor ) { + int result{ dup( descriptor ) }; + + if ( result == -1 ) { + CATCH_INTERNAL_ERROR( "dup-or-throw" ); + //CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + + return result; + } + + static inline int dup2_or_throw( int sourceDescriptor, + int destinationDescriptor ) { + int result{ dup2( sourceDescriptor, destinationDescriptor ) }; + + if ( result == -1 ) { + CATCH_INTERNAL_ERROR( "dup2-or-throw" ); + //CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + + return result; + } + + static inline int fileno_or_throw( std::FILE* file ) { + int result{ fileno( file ) }; + + if ( result == -1 ) { + CATCH_INTERNAL_ERROR( "fileno-or-throw" ); + //CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + + return result; + } + + static inline void pipe_or_throw( int descriptors[2] ) { +# if defined( _MSC_VER ) + constexpr int defaultPipeSize{ 0 }; + + int result{ _pipe( descriptors, defaultPipeSize, _O_BINARY ) }; +# else + int result{ pipe( descriptors ) }; +# endif + + if ( result ) { + CATCH_INTERNAL_ERROR( "pipe-or-throw" ); + // CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + } + + static inline size_t + read_or_throw( int descriptor, void* buffer, size_t size ) { +# if defined( _MSC_VER ) + int result{ + _read( descriptor, buffer, static_cast( size ) ) }; +# else + ssize_t result{ read( descriptor, buffer, size ) }; +# endif + + if ( result == -1 ) { + CATCH_INTERNAL_ERROR( "read-or-throw" ); + //CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + + return static_cast( result ); + } + + static inline void fflush_or_throw( std::FILE* file ) { + if ( std::fflush( file ) ) { + CATCH_INTERNAL_ERROR( "fflush-or-throw" ); + // CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + } + + constexpr UniqueFileDescriptor::UniqueFileDescriptor() noexcept: + m_value{} {} + + UniqueFileDescriptor::UniqueFileDescriptor( int value ) noexcept: + m_value{ value } {} + + constexpr UniqueFileDescriptor::UniqueFileDescriptor( + UniqueFileDescriptor&& other ) noexcept: + m_value{ other.m_value } { + other.m_value = 0; + } + + UniqueFileDescriptor::~UniqueFileDescriptor() noexcept { + if ( m_value == 0 ) { return; } + + close_or_throw( + m_value ); // std::terminate on failure (due to noexcept) + } + + UniqueFileDescriptor& UniqueFileDescriptor::operator=( + UniqueFileDescriptor&& other ) noexcept { + std::swap( m_value, other.m_value ); + return *this; + } + + constexpr int UniqueFileDescriptor::get() { return m_value; } + + static inline void + create_pipe( UniqueFileDescriptor& readDescriptor, + UniqueFileDescriptor& writeDescriptor ) { + readDescriptor = {}; + writeDescriptor = {}; + + int descriptors[2]; + pipe_or_throw( descriptors ); + + readDescriptor = UniqueFileDescriptor{ descriptors[0] }; + writeDescriptor = UniqueFileDescriptor{ descriptors[1] }; + } + + static inline void read_thread( UniqueFileDescriptor&& file, + std::string& result ) { + std::string buffer{}; + constexpr size_t bufferSize{ 4096 }; + buffer.resize( bufferSize ); + size_t sizeRead{}; + + while ( ( sizeRead = read_or_throw( + file.get(), &buffer[0], bufferSize ) ) != 0 ) { + result.append( buffer.data(), sizeRead ); + } + } + + OutputFileRedirector::OutputFileRedirector( FILE* file, + std::string& result ): + m_file{ file }, + m_fd{ fileno_or_throw( m_file ) }, + m_previous{ dup_or_throw( m_fd ) } { + fflush_or_throw( m_file ); + + UniqueFileDescriptor readDescriptor{}; + UniqueFileDescriptor writeDescriptor{}; + create_pipe( readDescriptor, writeDescriptor ); + + // Anonymous pipes have a limited buffer and require an active + // reader to ensure the writer does not become blocked. Use a + // separate thread to ensure the buffer does not get stuck full. + m_readThread = + std::thread{ [readDescriptor{ CATCH_MOVE( readDescriptor ) }, + &result]() mutable { + read_thread( CATCH_MOVE( readDescriptor ), result ); + } }; + + // Replace the stdout or stderr file descriptor with the write end + // of the pipe. + dup2_or_throw( writeDescriptor.get(), m_fd ); + } + + OutputFileRedirector::~OutputFileRedirector() noexcept { + fflush_or_throw( + m_file ); // std::terminate on failure (due to noexcept) + + // Restore the original stdout or stderr file descriptor. + dup2_or_throw( + m_previous.get(), + m_fd ); // std::terminate on failure (due to noexcept) + + if ( m_readThread.joinable() ) { m_readThread.join(); } + } + + +#endif // CATCH_CONFIG_USE_ASYNC + + } // end namespace bool isRedirectAvailable( OutputRedirect::Kind kind ) { @@ -255,6 +501,10 @@ namespace Catch { #if defined( CATCH_CONFIG_NEW_CAPTURE ) case OutputRedirect::FileDescriptors: return true; +#endif +#if defined(CATCH_CONFIG_USE_ASYNC) + case OutputRedirect::Pipes: + return true; #endif default: return false; @@ -267,7 +517,8 @@ namespace Catch { #if defined( CATCH_CONFIG_NEW_CAPTURE ) return Detail::make_unique(); #else - return Detail::make_unique(); + //return Detail::make_unique(); + return Detail::make_unique(); #endif } else { return Detail::make_unique(); diff --git a/src/catch2/internal/catch_output_redirect.hpp b/src/catch2/internal/catch_output_redirect.hpp index 51b796ba..619f1733 100644 --- a/src/catch2/internal/catch_output_redirect.hpp +++ b/src/catch2/internal/catch_output_redirect.hpp @@ -25,8 +25,10 @@ namespace Catch { None, //! Redirect std::cout/std::cerr/std::clog streams internally Streams, - //! Redirect the stdout/stderr file descriptors into files + //! Redirect the stdout/stderr file descriptors into temp files FileDescriptors, + //! Redirect the stdout/stderr file descriptors into anonymous pipes + Pipes, }; virtual ~OutputRedirect(); // = default;