mirror of
https://github.com/catchorg/Catch2.git
synced 2024-12-22 19:33:29 +01:00
WIP: rebase pipe redirect onto current redirect interface
This commit is contained in:
parent
18df97df00
commit
986ee2c793
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user