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