mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	WIP: rebase pipe redirect onto current redirect interface
This commit is contained in:
		| @@ -17,12 +17,18 @@ | ||||
| #include <iosfwd> | ||||
| #include <sstream> | ||||
|  | ||||
| #if defined( CATCH_CONFIG_NEW_CAPTURE ) | ||||
| #if defined(CATCH_CONFIG_USE_ASYNC) | ||||
| #include <thread> | ||||
| #endif | ||||
|  | ||||
| #if defined( CATCH_CONFIG_NEW_CAPTURE ) || defined(CATCH_CONFIG_USE_ASYNC) | ||||
| #    if defined( _MSC_VER ) | ||||
| #        include <io.h> //_dup and _dup2 | ||||
| #        include <fcntl.h> // _O_BINARY | ||||
| #        define dup _dup | ||||
| #        define dup2 _dup2 | ||||
| #        define fileno _fileno | ||||
| #        define close _close | ||||
| #    else | ||||
| #        include <unistd.h> // 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<PipeRedirect>( 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<PipeRedirect> 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<unsigned>( 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<size_t>( 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<FileRedirect>(); | ||||
| #else | ||||
|             return Detail::make_unique<StreamRedirect>(); | ||||
|             //return Detail::make_unique<StreamRedirect>(); | ||||
|             return Detail::make_unique<PipeRedirectWrapper>(); | ||||
| #endif | ||||
|         } else { | ||||
|             return Detail::make_unique<NoopRedirect>(); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský