diff --git a/src/catch2/internal/catch_enforce.cpp b/src/catch2/internal/catch_enforce.cpp index 4bc47ce3d3..67f2e41d92 100644 --- a/src/catch2/internal/catch_enforce.cpp +++ b/src/catch2/internal/catch_enforce.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace Catch { @@ -36,6 +37,11 @@ namespace Catch { throw_exception(std::runtime_error(msg)); } + [[noreturn]] + void throw_system_error(int ev, const std::error_category& ecat) { + throw_exception(std::system_error(ev, ecat)); + } + } // namespace Catch; diff --git a/src/catch2/internal/catch_enforce.hpp b/src/catch2/internal/catch_enforce.hpp index db52a0e2d2..6ec810fc44 100644 --- a/src/catch2/internal/catch_enforce.hpp +++ b/src/catch2/internal/catch_enforce.hpp @@ -32,6 +32,8 @@ namespace Catch { void throw_domain_error(std::string const& msg); [[noreturn]] void throw_runtime_error(std::string const& msg); + [[noreturn]] + void throw_system_error(int ev, const std::error_category& ecat); } // namespace Catch; @@ -47,6 +49,9 @@ namespace Catch { #define CATCH_RUNTIME_ERROR(...) \ Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ )) +#define CATCH_SYSTEM_ERROR(ev, ecat) \ + Catch::throw_system_error((ev), (ecat)) + #define CATCH_ENFORCE( condition, ... ) \ do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false) diff --git a/src/catch2/internal/catch_output_redirect.cpp b/src/catch2/internal/catch_output_redirect.cpp index ae06014127..3686636b77 100644 --- a/src/catch2/internal/catch_output_redirect.cpp +++ b/src/catch2/internal/catch_output_redirect.cpp @@ -5,33 +5,46 @@ // https://www.boost.org/LICENSE_1_0.txt) // SPDX-License-Identifier: BSL-1.0 -#include #include +#include +#include #include - #include #include #include -#if defined(CATCH_CONFIG_NEW_CAPTURE) - #if defined(_MSC_VER) - #include //_dup and _dup2 - #define dup _dup - #define dup2 _dup2 - #define fileno _fileno - #else - #include // dup and dup2 - #endif +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC ) +# include +# if defined( _MSC_VER ) +# include // _O_TEXT +# include // _close, _dup, _dup2, _fileno, _pipe and _read +# define close _close +# define dup _dup +# define dup2 _dup2 +# define fileno _fileno +# else +# include // close, dup, dup2, fileno, pipe and read +# endif +# else +# if defined( _MSC_VER ) +# include //_dup and _dup2 +# define dup _dup +# define dup2 _dup2 +# define fileno _fileno +# else +# include // dup and dup2 +# endif +# endif #endif - namespace Catch { - RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) - : m_originalStream( originalStream ), + RedirectedStream::RedirectedStream( std::ostream& originalStream, + std::ostream& redirectionStream ): + m_originalStream( originalStream ), m_redirectionStream( redirectionStream ), - m_prevBuf( m_originalStream.rdbuf() ) - { + m_prevBuf( m_originalStream.rdbuf() ) { m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); } @@ -39,108 +52,282 @@ namespace Catch { 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(); } - RedirectedStdErr::RedirectedStdErr() - : m_cerr( Catch::cerr(), m_rss.get() ), - m_clog( Catch::clog(), m_rss.get() ) - {} + RedirectedStdErr::RedirectedStdErr(): + m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) {} auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } - RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr) - : m_redirectedCout(redirectedCout), - m_redirectedCerr(redirectedCerr) - {} + RedirectedStreams::RedirectedStreams( std::string& redirectedCout, + std::string& redirectedCerr ): + m_redirectedCout( redirectedCout ), + m_redirectedCerr( redirectedCerr ) {} RedirectedStreams::~RedirectedStreams() { m_redirectedCout += m_redirectedStdOut.str(); m_redirectedCerr += m_redirectedStdErr.str(); } -#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + +# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC ) + + static inline void close_or_throw( int descriptor ) { + if ( close( descriptor ) ) { + CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + } + + static inline int dup_or_throw( int descriptor ) { + int result{ dup( descriptor ) }; + + if ( result == -1 ) { + 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_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( size ) ) }; +# else + ssize_t result{ read( descriptor, buffer, size ) }; +# endif + + if ( result == -1 ) { + CATCH_SYSTEM_ERROR( errno, std::generic_category() ); + } + + return static_cast( 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; + } + + 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(); + } + } + + OutputRedirect::OutputRedirect( std::string& output, std::string& error ): + m_output{ stdout, output }, m_error{ stderr, error } {} -#if defined(_MSC_VER) +# 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 ( tmpnam_s( m_buffer ) ) { + CATCH_RUNTIME_ERROR( "Could not get a temp filename" ); } - if (fopen_s(&m_file, m_buffer, "w+")) { + 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"); + 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); + CATCH_RUNTIME_ERROR( "Could not open the temp file: '" + << m_buffer << "' because: " << buffer ); } } -#else +# else TempFile::TempFile() { m_file = std::tmpfile(); - if (!m_file) { - CATCH_RUNTIME_ERROR("Could not create a temp file."); + if ( !m_file ) { + CATCH_RUNTIME_ERROR( "Could not create a temp file." ); } } -#endif +# 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 + // 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; - } + 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)) { + 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( 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); + 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); + fflush( stderr ); - dup2(m_originalStdout, 1); - dup2(m_originalStderr, 2); + 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 } // namespace Catch -#if defined(CATCH_CONFIG_NEW_CAPTURE) - #if defined(_MSC_VER) - #undef dup - #undef dup2 - #undef fileno - #endif +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( _MSC_VER ) +# undef close +# undef dup +# undef dup2 +# undef fileno +# endif #endif diff --git a/src/catch2/internal/catch_output_redirect.hpp b/src/catch2/internal/catch_output_redirect.hpp index d3463d992c..599424f7a2 100644 --- a/src/catch2/internal/catch_output_redirect.hpp +++ b/src/catch2/internal/catch_output_redirect.hpp @@ -8,14 +8,19 @@ #ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED #define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED +#include #include #include -#include - #include #include #include +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC ) +# include +# endif +#endif + namespace Catch { class RedirectedStream { @@ -24,13 +29,15 @@ namespace Catch { std::streambuf* m_prevBuf; public: - RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + RedirectedStream( std::ostream& originalStream, + std::ostream& redirectionStream ); ~RedirectedStream(); }; class RedirectedStdOut { ReusableStringStream m_rss; RedirectedStream m_cout; + public: RedirectedStdOut(); auto str() const -> std::string; @@ -43,6 +50,7 @@ namespace Catch { ReusableStringStream m_rss; RedirectedStream m_cerr; RedirectedStream m_clog; + public: RedirectedStdErr(); auto str() const -> std::string; @@ -50,13 +58,15 @@ namespace Catch { class RedirectedStreams { public: - RedirectedStreams(RedirectedStreams const&) = delete; - RedirectedStreams& operator=(RedirectedStreams const&) = delete; - RedirectedStreams(RedirectedStreams&&) = delete; - RedirectedStreams& operator=(RedirectedStreams&&) = delete; + RedirectedStreams( RedirectedStreams const& ) = delete; + RedirectedStreams& operator=( RedirectedStreams const& ) = delete; + RedirectedStreams( RedirectedStreams&& ) = delete; + RedirectedStreams& operator=( RedirectedStreams&& ) = delete; - RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr); + RedirectedStreams( std::string& redirectedCout, + std::string& redirectedCerr ); ~RedirectedStreams(); + private: std::string& m_redirectedCout; std::string& m_redirectedCerr; @@ -64,7 +74,56 @@ namespace Catch { RedirectedStdErr m_redirectedStdErr; }; -#if defined(CATCH_CONFIG_NEW_CAPTURE) +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + +# if defined( CATCH_INTERNAL_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 OutputRedirect final { + OutputRedirect( std::string& output, std::string& error ); + + private: + OutputFileRedirector m_output; + OutputFileRedirector m_error; + }; + +# else // !CATCH_INTERNAL_CONFIG_USE_ASYNC // Windows's implementation of std::tmpfile is terrible (it tries // to create a file inside system folder, thus requiring elevated @@ -72,10 +131,10 @@ namespace Catch { // create the file ourselves there. class TempFile { public: - TempFile(TempFile const&) = delete; - TempFile& operator=(TempFile const&) = delete; - TempFile(TempFile&&) = delete; - TempFile& operator=(TempFile&&) = delete; + TempFile( TempFile const& ) = delete; + TempFile& operator=( TempFile const& ) = delete; + TempFile( TempFile&& ) = delete; + TempFile& operator=( TempFile&& ) = delete; TempFile(); ~TempFile(); @@ -85,21 +144,19 @@ namespace Catch { private: std::FILE* m_file = nullptr; - #if defined(_MSC_VER) +# if defined( _MSC_VER ) char m_buffer[L_tmpnam] = { 0 }; - #endif +# endif }; - class OutputRedirect { public: - OutputRedirect(OutputRedirect const&) = delete; - OutputRedirect& operator=(OutputRedirect const&) = delete; - OutputRedirect(OutputRedirect&&) = delete; - OutputRedirect& operator=(OutputRedirect&&) = delete; + OutputRedirect( OutputRedirect const& ) = delete; + OutputRedirect& operator=( OutputRedirect const& ) = delete; + OutputRedirect( OutputRedirect&& ) = delete; + OutputRedirect& operator=( OutputRedirect&& ) = delete; - - OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + OutputRedirect( std::string& stdout_dest, std::string& stderr_dest ); ~OutputRedirect(); private: @@ -111,7 +168,8 @@ namespace Catch { std::string& m_stderrDest; }; -#endif +# endif // CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // CATCH_CONFIG_NEW_CAPTURE } // end namespace Catch diff --git a/tests/SelfTest/IntrospectiveTests/Details.tests.cpp b/tests/SelfTest/IntrospectiveTests/Details.tests.cpp index 987910a948..34b6ecf358 100644 --- a/tests/SelfTest/IntrospectiveTests/Details.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Details.tests.cpp @@ -20,6 +20,7 @@ TEST_CASE("Check that our error handling macros throw the right exceptions", "[! REQUIRE_THROWS_AS(CATCH_INTERNAL_ERROR(""), std::logic_error); REQUIRE_THROWS_AS(CATCH_ERROR(""), std::domain_error); REQUIRE_THROWS_AS(CATCH_RUNTIME_ERROR(""), std::runtime_error); + REQUIRE_THROWS_AS(CATCH_SYSTEM_ERROR(0, std::generic_category()), std::system_error); REQUIRE_THROWS_AS([](){CATCH_ENFORCE(false, "");}(), std::domain_error); REQUIRE_NOTHROW([](){CATCH_ENFORCE(true, "");}()); }