WIP: rebase pipe redirect onto current redirect interface

This commit is contained in:
Martin Hořeňovský 2024-09-13 19:17:39 +02:00
parent 18df97df00
commit 986ee2c793
2 changed files with 259 additions and 6 deletions

View File

@ -17,12 +17,18 @@
#include <iosfwd> #include <iosfwd>
#include <sstream> #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 ) # if defined( _MSC_VER )
# include <io.h> //_dup and _dup2 # include <io.h> //_dup and _dup2
# include <fcntl.h> // _O_BINARY
# define dup _dup # define dup _dup
# define dup2 _dup2 # define dup2 _dup2
# define fileno _fileno # define fileno _fileno
# define close _close
# else # else
# include <unistd.h> // dup and dup2 # include <unistd.h> // dup and dup2
# endif # endif
@ -48,13 +54,13 @@ namespace Catch {
* that the underlying stream's rdbuf aren't changed by other * that the underlying stream's rdbuf aren't changed by other
* users. * users.
*/ */
class RedirectedStreamNew { class RedirectedStream {
std::ostream& m_originalStream; std::ostream& m_originalStream;
std::ostream& m_redirectionStream; std::ostream& m_redirectionStream;
std::streambuf* m_prevBuf; std::streambuf* m_prevBuf;
public: public:
RedirectedStreamNew( std::ostream& originalStream, RedirectedStream( std::ostream& originalStream,
std::ostream& redirectionStream ): std::ostream& redirectionStream ):
m_originalStream( originalStream ), m_originalStream( originalStream ),
m_redirectionStream( redirectionStream ), m_redirectionStream( redirectionStream ),
@ -72,7 +78,7 @@ namespace Catch {
*/ */
class StreamRedirect : public OutputRedirect { class StreamRedirect : public OutputRedirect {
ReusableStringStream m_redirectedOut, m_redirectedErr; ReusableStringStream m_redirectedOut, m_redirectedErr;
RedirectedStreamNew m_cout, m_cerr, m_clog; RedirectedStream m_cout, m_cerr, m_clog;
public: public:
StreamRedirect(): StreamRedirect():
@ -244,6 +250,246 @@ namespace Catch {
#endif // CATCH_CONFIG_NEW_CAPTURE #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 } // end namespace
bool isRedirectAvailable( OutputRedirect::Kind kind ) { bool isRedirectAvailable( OutputRedirect::Kind kind ) {
@ -255,6 +501,10 @@ namespace Catch {
#if defined( CATCH_CONFIG_NEW_CAPTURE ) #if defined( CATCH_CONFIG_NEW_CAPTURE )
case OutputRedirect::FileDescriptors: case OutputRedirect::FileDescriptors:
return true; return true;
#endif
#if defined(CATCH_CONFIG_USE_ASYNC)
case OutputRedirect::Pipes:
return true;
#endif #endif
default: default:
return false; return false;
@ -267,7 +517,8 @@ namespace Catch {
#if defined( CATCH_CONFIG_NEW_CAPTURE ) #if defined( CATCH_CONFIG_NEW_CAPTURE )
return Detail::make_unique<FileRedirect>(); return Detail::make_unique<FileRedirect>();
#else #else
return Detail::make_unique<StreamRedirect>(); //return Detail::make_unique<StreamRedirect>();
return Detail::make_unique<PipeRedirectWrapper>();
#endif #endif
} else { } else {
return Detail::make_unique<NoopRedirect>(); return Detail::make_unique<NoopRedirect>();

View File

@ -25,8 +25,10 @@ namespace Catch {
None, None,
//! Redirect std::cout/std::cerr/std::clog streams internally //! Redirect std::cout/std::cerr/std::clog streams internally
Streams, Streams,
//! Redirect the stdout/stderr file descriptors into files //! Redirect the stdout/stderr file descriptors into temp files
FileDescriptors, FileDescriptors,
//! Redirect the stdout/stderr file descriptors into anonymous pipes
Pipes,
}; };
virtual ~OutputRedirect(); // = default; virtual ~OutputRedirect(); // = default;