mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-11-03 21:49:32 +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