mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-22 21:36:11 +01:00
Incorporate PR feedback.
Keep previous implementation when threads are not available.
This commit is contained in:
parent
713e31cd53
commit
10459a76f7
@ -37,10 +37,6 @@
|
|||||||
# define CATCH_CPP17_OR_GREATER
|
# define CATCH_CPP17_OR_GREATER
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
|
|
||||||
# define CATCH_CPP20_OR_GREATER
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Only GCC compiler should be used in this block, so other compilers trying to
|
// Only GCC compiler should be used in this block, so other compilers trying to
|
||||||
|
@ -5,37 +5,46 @@
|
|||||||
// https://www.boost.org/LICENSE_1_0.txt)
|
// https://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
|
||||||
// SPDX-License-Identifier: BSL-1.0
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
#include <catch2/internal/catch_output_redirect.hpp>
|
|
||||||
#include <catch2/internal/catch_enforce.hpp>
|
#include <catch2/internal/catch_enforce.hpp>
|
||||||
|
#include <catch2/internal/catch_move_and_forward.hpp>
|
||||||
|
#include <catch2/internal/catch_output_redirect.hpp>
|
||||||
#include <catch2/internal/catch_stdstreams.hpp>
|
#include <catch2/internal/catch_stdstreams.hpp>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#if defined(CATCH_CONFIG_NEW_CAPTURE)
|
#if defined( CATCH_CONFIG_NEW_CAPTURE )
|
||||||
#include <catch2/internal/catch_move_and_forward.hpp>
|
# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC )
|
||||||
#include <system_error>
|
# include <system_error>
|
||||||
#if defined(_MSC_VER)
|
# if defined( _MSC_VER )
|
||||||
#include <fcntl.h> // _O_TEXT
|
# include <fcntl.h> // _O_TEXT
|
||||||
#include <io.h> // _close, _dup, _dup2, _fileno, _pipe and _read
|
# include <io.h> // _close, _dup, _dup2, _fileno, _pipe and _read
|
||||||
#define close _close
|
# define close _close
|
||||||
#define dup _dup
|
# define dup _dup
|
||||||
#define dup2 _dup2
|
# define dup2 _dup2
|
||||||
#define fileno _fileno
|
# define fileno _fileno
|
||||||
#else
|
# else
|
||||||
#include <unistd.h> // close, dup, dup2, fileno, pipe and read
|
# include <unistd.h> // close, dup, dup2, fileno, pipe and read
|
||||||
#endif
|
# endif
|
||||||
|
# else
|
||||||
|
# if defined( _MSC_VER )
|
||||||
|
# include <io.h> //_dup and _dup2
|
||||||
|
# define dup _dup
|
||||||
|
# define dup2 _dup2
|
||||||
|
# define fileno _fileno
|
||||||
|
# else
|
||||||
|
# include <unistd.h> // dup and dup2
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace Catch {
|
namespace Catch {
|
||||||
|
|
||||||
RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )
|
RedirectedStream::RedirectedStream( std::ostream& originalStream,
|
||||||
: m_originalStream( originalStream ),
|
std::ostream& redirectionStream ):
|
||||||
|
m_originalStream( originalStream ),
|
||||||
m_redirectionStream( redirectionStream ),
|
m_redirectionStream( redirectionStream ),
|
||||||
m_prevBuf( m_originalStream.rdbuf() )
|
m_prevBuf( m_originalStream.rdbuf() ) {
|
||||||
{
|
|
||||||
m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
|
m_originalStream.rdbuf( m_redirectionStream.rdbuf() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,225 +52,282 @@ namespace Catch {
|
|||||||
m_originalStream.rdbuf( m_prevBuf );
|
m_originalStream.rdbuf( m_prevBuf );
|
||||||
}
|
}
|
||||||
|
|
||||||
RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}
|
RedirectedStdOut::RedirectedStdOut():
|
||||||
|
m_cout( Catch::cout(), m_rss.get() ) {}
|
||||||
auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }
|
auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }
|
||||||
|
|
||||||
RedirectedStdErr::RedirectedStdErr()
|
RedirectedStdErr::RedirectedStdErr():
|
||||||
: m_cerr( Catch::cerr(), m_rss.get() ),
|
m_cerr( Catch::cerr(), m_rss.get() ),
|
||||||
m_clog( Catch::clog(), m_rss.get() )
|
m_clog( Catch::clog(), m_rss.get() ) {}
|
||||||
{}
|
|
||||||
auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }
|
auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }
|
||||||
|
|
||||||
RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr)
|
RedirectedStreams::RedirectedStreams( std::string& redirectedCout,
|
||||||
: m_redirectedCout(redirectedCout),
|
std::string& redirectedCerr ):
|
||||||
m_redirectedCerr(redirectedCerr)
|
m_redirectedCout( redirectedCout ),
|
||||||
{}
|
m_redirectedCerr( redirectedCerr ) {}
|
||||||
|
|
||||||
RedirectedStreams::~RedirectedStreams() {
|
RedirectedStreams::~RedirectedStreams() {
|
||||||
m_redirectedCout += m_redirectedStdOut.str();
|
m_redirectedCout += m_redirectedStdOut.str();
|
||||||
m_redirectedCerr += m_redirectedStdErr.str();
|
m_redirectedCerr += m_redirectedStdErr.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CATCH_CONFIG_NEW_CAPTURE)
|
#if defined( CATCH_CONFIG_NEW_CAPTURE )
|
||||||
|
|
||||||
inline void close_or_throw(int descriptor)
|
# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC )
|
||||||
{
|
|
||||||
if (close(descriptor))
|
|
||||||
{
|
|
||||||
CATCH_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int dup_or_throw(int descriptor)
|
static inline void close_or_throw( int descriptor ) {
|
||||||
{
|
if ( close( descriptor ) ) {
|
||||||
int result{ dup(descriptor) };
|
CATCH_SYSTEM_ERROR( errno, std::generic_category() );
|
||||||
|
}
|
||||||
if (result == -1)
|
|
||||||
{
|
|
||||||
CATCH_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
static inline int dup_or_throw( int descriptor ) {
|
||||||
}
|
int result{ dup( descriptor ) };
|
||||||
|
|
||||||
inline int dup2_or_throw(int sourceDescriptor, int destinationDescriptor)
|
if ( result == -1 ) {
|
||||||
{
|
CATCH_SYSTEM_ERROR( errno, std::generic_category() );
|
||||||
int result{ dup2(sourceDescriptor, destinationDescriptor) };
|
|
||||||
|
|
||||||
if (result == -1)
|
|
||||||
{
|
|
||||||
CATCH_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int fileno_or_throw(std::FILE* file)
|
|
||||||
{
|
|
||||||
int result{ fileno(file) };
|
|
||||||
|
|
||||||
if (result == -1)
|
|
||||||
{
|
|
||||||
CATCH_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void pipe_or_throw(int descriptors[2])
|
|
||||||
{
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
constexpr int defaultPipeSize{ 0 };
|
|
||||||
|
|
||||||
int result{ _pipe(descriptors, defaultPipeSize, _O_TEXT) };
|
|
||||||
#else
|
|
||||||
int result{ pipe(descriptors) };
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
CATCH_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<size_t>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void fflush_or_throw(std::FILE* file)
|
|
||||||
{
|
|
||||||
if (std::fflush(file))
|
|
||||||
{
|
|
||||||
CATCH_SYSTEM_ERROR(errno, std::generic_category());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jthread::jthread() noexcept : m_thread{} {}
|
|
||||||
|
|
||||||
template <typename F, typename... Args>
|
|
||||||
jthread::jthread(F&& f, Args&&... args) : m_thread{ CATCH_FORWARD(f), CATCH_FORWARD(args)... } {}
|
|
||||||
|
|
||||||
// Not exactly like std::jthread, but close enough for the code below.
|
|
||||||
jthread::~jthread() noexcept
|
|
||||||
{
|
|
||||||
if (m_thread.joinable())
|
|
||||||
{
|
|
||||||
m_thread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
if (this != &other)
|
|
||||||
{
|
|
||||||
if (m_value != 0)
|
|
||||||
{
|
|
||||||
close_or_throw(m_value); // std::terminate on failure (due to noexcept)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_value = other.m_value;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int dup2_or_throw( int sourceDescriptor,
|
||||||
|
int destinationDescriptor ) {
|
||||||
|
int result{ dup2( sourceDescriptor, destinationDescriptor ) };
|
||||||
|
|
||||||
|
if ( result == -1 ) {
|
||||||
|
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_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_TEXT ) };
|
||||||
|
# else
|
||||||
|
int result{ pipe( descriptors ) };
|
||||||
|
# endif
|
||||||
|
|
||||||
|
if ( result ) {
|
||||||
|
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_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_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;
|
other.m_value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
UniqueFileDescriptor::~UniqueFileDescriptor() noexcept {
|
||||||
}
|
if ( m_value == 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr int UniqueFileDescriptor::get() { return m_value; }
|
close_or_throw(
|
||||||
|
m_value ); // std::terminate on failure (due to noexcept)
|
||||||
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] };
|
|
||||||
}
|
|
||||||
|
|
||||||
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) :
|
UniqueFileDescriptor&
|
||||||
m_file{ file },
|
UniqueFileDescriptor::operator=( UniqueFileDescriptor&& other ) noexcept {
|
||||||
m_fd{ fileno_or_throw(m_file) },
|
std::swap( m_value, other.m_value );
|
||||||
m_previous{ dup_or_throw(m_fd) }
|
return *this;
|
||||||
{
|
}
|
||||||
fflush_or_throw(m_file);
|
|
||||||
|
|
||||||
UniqueFileDescriptor readDescriptor{};
|
constexpr int UniqueFileDescriptor::get() { return m_value; }
|
||||||
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.
|
static inline void create_pipe( UniqueFileDescriptor& readDescriptor,
|
||||||
// Use a separate thread to ensure the buffer does not get stuck full.
|
UniqueFileDescriptor& writeDescriptor ) {
|
||||||
m_readThread = jthread{ [readDescriptor{ CATCH_MOVE(readDescriptor) }, &result] () mutable {
|
readDescriptor = {};
|
||||||
read_thread(CATCH_MOVE(readDescriptor), result); } };
|
writeDescriptor = {};
|
||||||
|
|
||||||
dup2_or_throw(writeDescriptor.get(), m_fd);
|
int descriptors[2];
|
||||||
}
|
pipe_or_throw( descriptors );
|
||||||
|
|
||||||
OutputFileRedirector::~OutputFileRedirector() noexcept
|
readDescriptor = UniqueFileDescriptor{ descriptors[0] };
|
||||||
{
|
writeDescriptor = UniqueFileDescriptor{ descriptors[1] };
|
||||||
fflush_or_throw(m_file); // std::terminate on failure (due to noexcept)
|
}
|
||||||
dup2_or_throw(m_previous.get(), m_fd); // std::terminate on failure (due to noexcept)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputRedirect::OutputRedirect(std::string& output, std::string& error) :
|
static inline void read_thread( UniqueFileDescriptor&& file,
|
||||||
m_output{ stdout, output }, m_error{ stderr, error } {}
|
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 {
|
||||||
|
if ( m_readThread.joinable() ) {
|
||||||
|
m_readThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputRedirect::OutputRedirect( std::string& output, std::string& error ):
|
||||||
|
m_output{ stdout, output }, m_error{ stderr, error } {}
|
||||||
|
|
||||||
|
# else // !CATCH_INTERNAL_CONFIG_USE_ASYNC
|
||||||
|
|
||||||
|
# if defined( _MSC_VER )
|
||||||
|
TempFile::TempFile() {
|
||||||
|
if ( tmpnam_s( m_buffer ) ) {
|
||||||
|
CATCH_RUNTIME_ERROR( "Could not get a temp filename" );
|
||||||
|
}
|
||||||
|
if ( fopen_s( &m_file, m_buffer, "w+" ) ) {
|
||||||
|
char buffer[100];
|
||||||
|
if ( strerror_s( buffer, errno ) ) {
|
||||||
|
CATCH_RUNTIME_ERROR( "Could not translate errno to a string" );
|
||||||
|
}
|
||||||
|
CATCH_RUNTIME_ERROR( "Could not open the temp file: '"
|
||||||
|
<< m_buffer << "' because: " << buffer );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# else
|
||||||
|
TempFile::TempFile() {
|
||||||
|
m_file = std::tmpfile();
|
||||||
|
if ( !m_file ) {
|
||||||
|
CATCH_RUNTIME_ERROR( "Could not create a temp file." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# endif
|
||||||
|
|
||||||
|
TempFile::~TempFile() {
|
||||||
|
// TBD: What to do about errors here?
|
||||||
|
std::fclose( m_file );
|
||||||
|
// We manually create the file on Windows only, on Linux
|
||||||
|
// it will be autodeleted
|
||||||
|
# if defined( _MSC_VER )
|
||||||
|
std::remove( m_buffer );
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* TempFile::getFile() { return m_file; }
|
||||||
|
|
||||||
|
std::string TempFile::getContents() {
|
||||||
|
std::stringstream sstr;
|
||||||
|
char buffer[100] = {};
|
||||||
|
std::rewind( m_file );
|
||||||
|
while ( std::fgets( buffer, sizeof( buffer ), m_file ) ) {
|
||||||
|
sstr << buffer;
|
||||||
|
}
|
||||||
|
return sstr.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputRedirect::OutputRedirect( std::string& stdout_dest,
|
||||||
|
std::string& stderr_dest ):
|
||||||
|
m_originalStdout( dup( 1 ) ),
|
||||||
|
m_originalStderr( dup( 2 ) ),
|
||||||
|
m_stdoutDest( stdout_dest ),
|
||||||
|
m_stderrDest( stderr_dest ) {
|
||||||
|
dup2( fileno( m_stdoutFile.getFile() ), 1 );
|
||||||
|
dup2( fileno( m_stderrFile.getFile() ), 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputRedirect::~OutputRedirect() {
|
||||||
|
Catch::cout() << std::flush;
|
||||||
|
fflush( stdout );
|
||||||
|
// Since we support overriding these streams, we flush cerr
|
||||||
|
// even though std::cerr is unbuffered
|
||||||
|
Catch::cerr() << std::flush;
|
||||||
|
Catch::clog() << std::flush;
|
||||||
|
fflush( stderr );
|
||||||
|
|
||||||
|
dup2( m_originalStdout, 1 );
|
||||||
|
dup2( m_originalStderr, 2 );
|
||||||
|
|
||||||
|
m_stdoutDest += m_stdoutFile.getContents();
|
||||||
|
m_stderrDest += m_stderrFile.getContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
# endif // CATCH_INTERNAL_CONFIG_USE_ASYNC
|
||||||
|
|
||||||
#endif // CATCH_CONFIG_NEW_CAPTURE
|
#endif // CATCH_CONFIG_NEW_CAPTURE
|
||||||
|
|
||||||
} // namespace Catch
|
} // namespace Catch
|
||||||
|
|
||||||
#if defined(CATCH_CONFIG_NEW_CAPTURE)
|
#if defined( CATCH_CONFIG_NEW_CAPTURE )
|
||||||
#if defined(_MSC_VER)
|
# if defined( _MSC_VER )
|
||||||
#undef close
|
# undef close
|
||||||
#undef dup
|
# undef dup
|
||||||
#undef dup2
|
# undef dup2
|
||||||
#undef fileno
|
# undef fileno
|
||||||
#endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
@ -8,16 +8,17 @@
|
|||||||
#ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED
|
#ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED
|
||||||
#define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED
|
#define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <catch2/internal/catch_compiler_capabilities.hpp>
|
||||||
#include <catch2/internal/catch_platform.hpp>
|
#include <catch2/internal/catch_platform.hpp>
|
||||||
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
#include <catch2/internal/catch_reusable_string_stream.hpp>
|
||||||
#include <catch2/internal/catch_compiler_capabilities.hpp>
|
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#if defined(CATCH_CONFIG_NEW_CAPTURE)
|
#if defined( CATCH_CONFIG_NEW_CAPTURE )
|
||||||
#include <thread>
|
# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC )
|
||||||
|
# include <thread>
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Catch {
|
namespace Catch {
|
||||||
@ -28,13 +29,15 @@ namespace Catch {
|
|||||||
std::streambuf* m_prevBuf;
|
std::streambuf* m_prevBuf;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );
|
RedirectedStream( std::ostream& originalStream,
|
||||||
|
std::ostream& redirectionStream );
|
||||||
~RedirectedStream();
|
~RedirectedStream();
|
||||||
};
|
};
|
||||||
|
|
||||||
class RedirectedStdOut {
|
class RedirectedStdOut {
|
||||||
ReusableStringStream m_rss;
|
ReusableStringStream m_rss;
|
||||||
RedirectedStream m_cout;
|
RedirectedStream m_cout;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RedirectedStdOut();
|
RedirectedStdOut();
|
||||||
auto str() const -> std::string;
|
auto str() const -> std::string;
|
||||||
@ -47,6 +50,7 @@ namespace Catch {
|
|||||||
ReusableStringStream m_rss;
|
ReusableStringStream m_rss;
|
||||||
RedirectedStream m_cerr;
|
RedirectedStream m_cerr;
|
||||||
RedirectedStream m_clog;
|
RedirectedStream m_clog;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RedirectedStdErr();
|
RedirectedStdErr();
|
||||||
auto str() const -> std::string;
|
auto str() const -> std::string;
|
||||||
@ -54,13 +58,15 @@ namespace Catch {
|
|||||||
|
|
||||||
class RedirectedStreams {
|
class RedirectedStreams {
|
||||||
public:
|
public:
|
||||||
RedirectedStreams(RedirectedStreams const&) = delete;
|
RedirectedStreams( RedirectedStreams const& ) = delete;
|
||||||
RedirectedStreams& operator=(RedirectedStreams const&) = delete;
|
RedirectedStreams& operator=( RedirectedStreams const& ) = delete;
|
||||||
RedirectedStreams(RedirectedStreams&&) = delete;
|
RedirectedStreams( RedirectedStreams&& ) = delete;
|
||||||
RedirectedStreams& operator=(RedirectedStreams&&) = delete;
|
RedirectedStreams& operator=( RedirectedStreams&& ) = delete;
|
||||||
|
|
||||||
RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr);
|
RedirectedStreams( std::string& redirectedCout,
|
||||||
|
std::string& redirectedCerr );
|
||||||
~RedirectedStreams();
|
~RedirectedStreams();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string& m_redirectedCout;
|
std::string& m_redirectedCout;
|
||||||
std::string& m_redirectedCerr;
|
std::string& m_redirectedCerr;
|
||||||
@ -68,83 +74,102 @@ namespace Catch {
|
|||||||
RedirectedStdErr m_redirectedStdErr;
|
RedirectedStdErr m_redirectedStdErr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(CATCH_CONFIG_NEW_CAPTURE)
|
#if defined( CATCH_CONFIG_NEW_CAPTURE )
|
||||||
|
|
||||||
#if defined(CATCH_CPP20_OR_GREATER)
|
# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC )
|
||||||
using jthread = std::jthread;
|
|
||||||
#else
|
|
||||||
// Just enough of std::jthread from C++20 for the code below.
|
|
||||||
struct jthread final
|
|
||||||
{
|
|
||||||
jthread() noexcept;
|
|
||||||
|
|
||||||
template <typename F, typename... Args>
|
struct UniqueFileDescriptor final {
|
||||||
jthread(F&& f, Args&&... args);
|
constexpr UniqueFileDescriptor() noexcept;
|
||||||
|
explicit UniqueFileDescriptor( int value ) noexcept;
|
||||||
|
|
||||||
jthread(jthread const&) = delete;
|
UniqueFileDescriptor( UniqueFileDescriptor const& ) = delete;
|
||||||
jthread(jthread&&) noexcept = default;
|
constexpr UniqueFileDescriptor( UniqueFileDescriptor&& other ) noexcept;
|
||||||
|
|
||||||
jthread& operator=(jthread const&) noexcept = delete;
|
~UniqueFileDescriptor() noexcept;
|
||||||
|
|
||||||
// Not exactly like std::jthread, but close enough for the code below.
|
UniqueFileDescriptor& operator=( UniqueFileDescriptor const& ) = delete;
|
||||||
jthread& operator=(jthread&&) noexcept = default;
|
UniqueFileDescriptor&
|
||||||
|
operator=( UniqueFileDescriptor&& other ) noexcept;
|
||||||
|
|
||||||
// Not exactly like std::jthread, but close enough for the code below.
|
constexpr int get();
|
||||||
~jthread() noexcept;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::thread m_thread;
|
int m_value;
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
struct UniqueFileDescriptor final
|
struct OutputFileRedirector final {
|
||||||
{
|
explicit OutputFileRedirector( std::FILE* file, std::string& result );
|
||||||
constexpr UniqueFileDescriptor() noexcept;
|
|
||||||
explicit UniqueFileDescriptor(int value) noexcept;
|
|
||||||
|
|
||||||
UniqueFileDescriptor(UniqueFileDescriptor const&) = delete;
|
OutputFileRedirector( OutputFileRedirector const& ) = delete;
|
||||||
constexpr UniqueFileDescriptor(UniqueFileDescriptor&& other) noexcept;
|
OutputFileRedirector( OutputFileRedirector&& ) = delete;
|
||||||
|
|
||||||
~UniqueFileDescriptor() noexcept;
|
~OutputFileRedirector() noexcept;
|
||||||
|
|
||||||
UniqueFileDescriptor& operator=(UniqueFileDescriptor const&) = delete;
|
OutputFileRedirector& operator=( OutputFileRedirector const& ) = delete;
|
||||||
UniqueFileDescriptor& operator=(UniqueFileDescriptor&& other) noexcept;
|
OutputFileRedirector& operator=( OutputFileRedirector&& ) = delete;
|
||||||
|
|
||||||
constexpr int get();
|
private:
|
||||||
|
std::FILE* m_file;
|
||||||
|
int m_fd;
|
||||||
|
UniqueFileDescriptor m_previous;
|
||||||
|
std::thread m_readThread;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
struct OutputRedirect final {
|
||||||
int m_value;
|
OutputRedirect( std::string& output, std::string& error );
|
||||||
};
|
|
||||||
|
|
||||||
struct OutputFileRedirector final
|
private:
|
||||||
{
|
OutputFileRedirector m_output;
|
||||||
explicit OutputFileRedirector(std::FILE* file, std::string& result);
|
OutputFileRedirector m_error;
|
||||||
|
};
|
||||||
|
|
||||||
OutputFileRedirector(OutputFileRedirector const&) = delete;
|
# else // !CATCH_INTERNAL_CONFIG_USE_ASYNC
|
||||||
OutputFileRedirector(OutputFileRedirector&&) = delete;
|
|
||||||
|
|
||||||
~OutputFileRedirector() noexcept;
|
// Windows's implementation of std::tmpfile is terrible (it tries
|
||||||
|
// to create a file inside system folder, thus requiring elevated
|
||||||
|
// privileges for the binary), so we have to use tmpnam(_s) and
|
||||||
|
// create the file ourselves there.
|
||||||
|
class TempFile {
|
||||||
|
public:
|
||||||
|
TempFile( TempFile const& ) = delete;
|
||||||
|
TempFile& operator=( TempFile const& ) = delete;
|
||||||
|
TempFile( TempFile&& ) = delete;
|
||||||
|
TempFile& operator=( TempFile&& ) = delete;
|
||||||
|
|
||||||
OutputFileRedirector& operator=(OutputFileRedirector const&) = delete;
|
TempFile();
|
||||||
OutputFileRedirector& operator=(OutputFileRedirector&&) = delete;
|
~TempFile();
|
||||||
|
|
||||||
private:
|
std::FILE* getFile();
|
||||||
std::FILE* m_file;
|
std::string getContents();
|
||||||
int m_fd;
|
|
||||||
UniqueFileDescriptor m_previous;
|
|
||||||
jthread m_readThread;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutputRedirect final
|
private:
|
||||||
{
|
std::FILE* m_file = nullptr;
|
||||||
OutputRedirect(std::string& output, std::string& error);
|
# if defined( _MSC_VER )
|
||||||
|
char m_buffer[L_tmpnam] = { 0 };
|
||||||
|
# endif
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
class OutputRedirect {
|
||||||
OutputFileRedirector m_output;
|
public:
|
||||||
OutputFileRedirector m_error;
|
OutputRedirect( OutputRedirect const& ) = delete;
|
||||||
};
|
OutputRedirect& operator=( OutputRedirect const& ) = delete;
|
||||||
|
OutputRedirect( OutputRedirect&& ) = delete;
|
||||||
|
OutputRedirect& operator=( OutputRedirect&& ) = delete;
|
||||||
|
|
||||||
#endif
|
OutputRedirect( std::string& stdout_dest, std::string& stderr_dest );
|
||||||
|
~OutputRedirect();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_originalStdout = -1;
|
||||||
|
int m_originalStderr = -1;
|
||||||
|
TempFile m_stdoutFile;
|
||||||
|
TempFile m_stderrFile;
|
||||||
|
std::string& m_stdoutDest;
|
||||||
|
std::string& m_stderrDest;
|
||||||
|
};
|
||||||
|
|
||||||
|
# endif // CATCH_INTERNAL_CONFIG_USE_ASYNC
|
||||||
|
#endif // CATCH_CONFIG_NEW_CAPTURE
|
||||||
|
|
||||||
} // end namespace Catch
|
} // end namespace Catch
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user