mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-16 10:42:25 +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 <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>();
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user