mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +01:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			v3.8.0
			...
			devel-pipe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 432695291a | ||
|   | e63f3cc817 | ||
|   | 986ee2c793 | 
| @@ -17,12 +17,19 @@ | ||||
| #include <iosfwd> | ||||
| #include <sstream> | ||||
|  | ||||
| #if defined( CATCH_CONFIG_NEW_CAPTURE ) | ||||
| #if defined(CATCH_CONFIG_USE_ASYNC) | ||||
| #include <thread> | ||||
| #include <mutex> | ||||
| #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 +55,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 +79,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 +251,182 @@ namespace Catch { | ||||
|  | ||||
| #endif // CATCH_CONFIG_NEW_CAPTURE | ||||
|  | ||||
|  | ||||
| #if defined( CATCH_CONFIG_USE_ASYNC ) | ||||
|  | ||||
|         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 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 fflush_or_throw( std::FILE* file ) { | ||||
|             if ( std::fflush( file ) ) { | ||||
|                 CATCH_INTERNAL_ERROR( "fflush-or-throw" ); | ||||
|                 // CATCH_SYSTEM_ERROR( errno, std::generic_category() ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         class StreamPipeHandler { | ||||
|             int m_originalFd = -1; | ||||
|             int m_pipeReadEnd = -1; | ||||
|             int m_pipeWriteEnd = -1; | ||||
|             FILE* m_targetStream; | ||||
|             std::mutex m_mutex; | ||||
|             std::string m_captured; | ||||
|             std::thread m_readThread; | ||||
|  | ||||
|         public: | ||||
|             StreamPipeHandler( FILE* original ): | ||||
|                 m_originalFd( dup_or_throw( fileno( original ) ) ), | ||||
|                 m_targetStream(original) | ||||
|             { | ||||
|                 CATCH_ENFORCE( m_originalFd >= 0, "Could not dup stream" ); | ||||
|                 int pipe_fds[2]; | ||||
|                 pipe_or_throw( pipe_fds ); | ||||
|                 m_pipeReadEnd = pipe_fds[0]; | ||||
|                 m_pipeWriteEnd = pipe_fds[1]; | ||||
|  | ||||
|                 m_readThread = std::thread([this]() { | ||||
|                     constexpr size_t bufferSize = 4096; | ||||
|                     char buffer[bufferSize]; | ||||
|                     size_t sizeRead; | ||||
|                     while ( ( sizeRead = read_or_throw( | ||||
|                                   m_pipeReadEnd, buffer, bufferSize ) ) != 0 ) { | ||||
|                         std::unique_lock<std::mutex> _( m_mutex ); | ||||
|                         m_captured.append( buffer, sizeRead ); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             ~StreamPipeHandler() { | ||||
|                 close_or_throw( m_pipeWriteEnd ); | ||||
|                 m_readThread.join(); | ||||
|             } | ||||
|  | ||||
|             std::string getCapturedOutput() { | ||||
|                 std::unique_lock<std::mutex> _( m_mutex ); | ||||
|                 return m_captured; | ||||
|             } | ||||
|  | ||||
|             void clearCapturedOutput() { | ||||
|                 std::unique_lock<std::mutex> _( m_mutex ); | ||||
|                 m_captured.clear(); | ||||
|             } | ||||
|  | ||||
|             void startCapture() { | ||||
|                 fflush_or_throw( m_targetStream ); | ||||
|                 int ret = dup2_or_throw( m_pipeWriteEnd, fileno( m_targetStream ) ); | ||||
|                 CATCH_ENFORCE( ret >= 0, | ||||
|                                "dup2 pipe-write -> original stream failed " << errno ); | ||||
|             } | ||||
|             void stopCapture() { | ||||
|                 fflush_or_throw( m_targetStream ); | ||||
|                 int ret = dup2_or_throw( m_originalFd, fileno( m_targetStream ) ); | ||||
|                 CATCH_ENFORCE( ret >= 0, | ||||
|                                "dup2 of original fd -> original stream failed " << errno ); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         class PipeRedirect : public OutputRedirect { | ||||
|         private: | ||||
|             StreamPipeHandler m_stdout; | ||||
|             StreamPipeHandler m_stderr; | ||||
|         public: | ||||
|             PipeRedirect(): | ||||
|                 m_stdout(stdout), | ||||
|                 m_stderr(stderr){} | ||||
|  | ||||
|             void activateImpl() override { | ||||
|                 m_stdout.startCapture(); | ||||
|                 m_stderr.startCapture(); | ||||
|             } | ||||
|             void deactivateImpl() override { | ||||
|                 m_stdout.stopCapture(); | ||||
|                 m_stderr.stopCapture(); | ||||
|             } | ||||
|             std::string getStdout() override { | ||||
|                 return m_stdout.getCapturedOutput(); | ||||
|             } | ||||
|             std::string getStderr() override { | ||||
|                 return m_stderr.getCapturedOutput(); | ||||
|             } | ||||
|             void clearBuffers() override { | ||||
|                 m_stdout.clearCapturedOutput(); | ||||
|                 m_stderr.clearCapturedOutput(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| #endif // CATCH_CONFIG_USE_ASYNC | ||||
|  | ||||
|  | ||||
|     } // end namespace | ||||
|  | ||||
|     bool isRedirectAvailable( OutputRedirect::Kind kind ) { | ||||
| @@ -255,6 +438,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 +454,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<PipeRedirect>(); | ||||
| #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; | ||||
|   | ||||
| @@ -308,10 +308,15 @@ TEST_CASE( "X/level/0/b", "[Tricky][fizz]" ){ SUCCEED(""); } | ||||
| TEST_CASE( "X/level/1/a", "[Tricky]" )      { SUCCEED(""); } | ||||
| TEST_CASE( "X/level/1/b", "[Tricky]" )      { SUCCEED(""); } | ||||
|  | ||||
| #include <chrono> | ||||
| #include <thread> | ||||
|  | ||||
| TEST_CASE( "has printf" ) { | ||||
|  | ||||
|     // This can cause problems as, currently, stdout itself is not redirected - only the cout (and cerr) buffer | ||||
|     printf( "loose text artifact\n" ); | ||||
|     fflush( stdout ); | ||||
|     std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); | ||||
| } | ||||
|  | ||||
| namespace { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user