Skip to content

Commit

Permalink
WIP: rebase pipe redirect onto current redirect interface
Browse files Browse the repository at this point in the history
  • Loading branch information
horenmar committed Sep 13, 2024
1 parent 18df97d commit 986ee2c
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 6 deletions.
261 changes: 256 additions & 5 deletions src/catch2/internal/catch_output_redirect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ),
Expand All @@ -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():
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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;
Expand All @@ -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>();
Expand Down
4 changes: 3 additions & 1 deletion src/catch2/internal/catch_output_redirect.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 986ee2c

Please sign in to comment.