Compare commits

...

7 Commits

Author SHA1 Message Date
David Matson c0d5c431ba
Merge 3b474ce3cf into 4e8d92bf02 2024-05-05 20:09:00 -07:00
Martin Hořeňovský 4e8d92bf02
v3.6.0 2024-05-05 20:58:18 +02:00
davidmatson 3b474ce3cf Add coverage for CATCH_SYSTEM_ERROR. 2022-10-26 16:56:27 -07:00
davidmatson a560a9f827 Fix OutputFileRedirector destructor ordering. 2022-09-20 15:18:28 -07:00
davidmatson 10459a76f7 Incorporate PR feedback.
Keep previous implementation when threads are not available.
2022-09-13 17:11:58 -07:00
davidmatson 713e31cd53 Update per contributing docs. 2022-07-14 08:23:17 -07:00
davidmatson ac345f86af Use anonymous pipes for stdout/stderr redirection.
Closes #2444.

Avoid the overhead of creating and deleting temporary files.
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.
2022-07-03 16:29:34 -07:00
12 changed files with 711 additions and 180 deletions

View File

@ -33,7 +33,7 @@ if (CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
endif()
project(Catch2
VERSION 3.5.4 # CML version placeholder, don't delete
VERSION 3.6.0 # CML version placeholder, don't delete
LANGUAGES CXX
# HOMEPAGE_URL is not supported until CMake version 3.12, which
# we do not target yet.

View File

@ -2,6 +2,7 @@
# Release notes
**Contents**<br>
[3.6.0](#360)<br>
[3.5.4](#354)<br>
[3.5.3](#353)<br>
[3.5.2](#352)<br>
@ -62,6 +63,31 @@
[Even Older versions](#even-older-versions)<br>
## 3.6.0
### Fixes
* Fixed Windows ARM64 build by fixing the preprocessor condition guarding use `_umul128` intrinsic.
* Fixed Windows ARM64EC build by removing intrinsic pragma it does not understand. (#2858)
* Why doesn't the x64-emulation build mode understand x64 pragmas? Don't ask me, ask the MSVC guys.
* Fixed the JUnit reporter sometimes crashing when reporting a fatal error. (#1210, #2855)
* The binary will still exit, but through the original error, rather than secondary error inside the reporter.
* The underlying fix applies to all reporters, not just the JUnit one, but only JUnit was currently causing troubles.
### Improvements
* Disable `-Wnon-virtual-dtor` in Decomposer and Matchers (#2854)
* `precision` in floating point stringmakers defaults to `max_digits10`.
* This means that floating point values will be printed with enough precision to disambiguate any two floats.
* Column wrapping ignores ansi colour codes when calculating string width (#2833, #2849)
* This makes the output much more readable when the provided messages contain colour codes.
### Miscellaneous
* Conan support improvements
* `compatibility_cppstr` is set to False. (#2860)
* This means that Conan won't let you mix library and project with different C++ standard settings.
* The implementation library CMake target name through Conan is properly set to `Catch2::Catch2` (#2861)
* `SelfTest` target can be built through Bazel (#2857)
## 3.5.4
### Fixes

View File

@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
// Catch v3.5.4
// Generated: 2024-04-10 12:03:46.281848
// Catch v3.6.0
// Generated: 2024-05-05 20:53:27.562886
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@ -2156,13 +2156,13 @@ std::string StringMaker<unsigned char>::convert(unsigned char value) {
return ::Catch::Detail::stringify(static_cast<char>(value));
}
int StringMaker<float>::precision = 5;
int StringMaker<float>::precision = std::numeric_limits<float>::max_digits10;
std::string StringMaker<float>::convert(float value) {
return Detail::fpToString(value, precision) + 'f';
}
int StringMaker<double>::precision = 10;
int StringMaker<double>::precision = std::numeric_limits<double>::max_digits10;
std::string StringMaker<double>::convert(double value) {
return Detail::fpToString(value, precision);
@ -2273,7 +2273,7 @@ namespace Catch {
}
Version const& libraryVersion() {
static Version version( 3, 5, 4, "", 0 );
static Version version( 3, 6, 0, "", 0 );
return version;
}
@ -5853,6 +5853,13 @@ namespace Catch {
assertionEnded(CATCH_MOVE(result) );
resetAssertionInfo();
// Best effort cleanup for sections that have not been destructed yet
// Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly
while (!m_activeSections.empty()) {
auto nl = m_activeSections.back()->nameAndLocation();
SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 };
sectionEndedEarly(CATCH_MOVE(endInfo));
}
handleUnfinishedSections();
// Recreate section for test case (as we will lose the one that was in scope)
@ -7207,117 +7214,228 @@ namespace {
return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr;
}
bool isBoundary( std::string const& line, size_t at ) {
assert( at > 0 );
assert( at <= line.size() );
return at == line.size() ||
( isWhitespace( line[at] ) && !isWhitespace( line[at - 1] ) ) ||
isBreakableBefore( line[at] ) ||
isBreakableAfter( line[at - 1] );
}
} // namespace
namespace Catch {
namespace TextFlow {
void AnsiSkippingString::preprocessString() {
for ( auto it = m_string.begin(); it != m_string.end(); ) {
// try to read through an ansi sequence
while ( it != m_string.end() && *it == '\033' &&
it + 1 != m_string.end() && *( it + 1 ) == '[' ) {
auto cursor = it + 2;
while ( cursor != m_string.end() &&
( isdigit( *cursor ) || *cursor == ';' ) ) {
++cursor;
}
if ( cursor == m_string.end() || *cursor != 'm' ) {
break;
}
// 'm' -> 0xff
*cursor = AnsiSkippingString::sentinel;
// if we've read an ansi sequence, set the iterator and
// return to the top of the loop
it = cursor + 1;
}
if ( it != m_string.end() ) {
++m_size;
++it;
}
}
}
AnsiSkippingString::AnsiSkippingString( std::string const& text ):
m_string( text ) {
preprocessString();
}
AnsiSkippingString::AnsiSkippingString( std::string&& text ):
m_string( CATCH_MOVE( text ) ) {
preprocessString();
}
AnsiSkippingString::const_iterator AnsiSkippingString::begin() const {
return const_iterator( m_string );
}
AnsiSkippingString::const_iterator AnsiSkippingString::end() const {
return const_iterator( m_string, const_iterator::EndTag{} );
}
std::string AnsiSkippingString::substring( const_iterator begin,
const_iterator end ) const {
// There's one caveat here to an otherwise simple substring: when
// making a begin iterator we might have skipped ansi sequences at
// the start. If `begin` here is a begin iterator, skipped over
// initial ansi sequences, we'll use the true beginning of the
// string. Lastly: We need to transform any chars we replaced with
// 0xff back to 'm'
auto str = std::string( begin == this->begin() ? m_string.begin()
: begin.m_it,
end.m_it );
std::transform( str.begin(), str.end(), str.begin(), []( char c ) {
return c == AnsiSkippingString::sentinel ? 'm' : c;
} );
return str;
}
void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() {
// check if we've landed on an ansi sequence, and if so read through
// it
while ( m_it != m_string->end() && *m_it == '\033' &&
m_it + 1 != m_string->end() && *( m_it + 1 ) == '[' ) {
auto cursor = m_it + 2;
while ( cursor != m_string->end() &&
( isdigit( *cursor ) || *cursor == ';' ) ) {
++cursor;
}
if ( cursor == m_string->end() ||
*cursor != AnsiSkippingString::sentinel ) {
break;
}
// if we've read an ansi sequence, set the iterator and
// return to the top of the loop
m_it = cursor + 1;
}
}
void AnsiSkippingString::const_iterator::advance() {
assert( m_it != m_string->end() );
m_it++;
tryParseAnsiEscapes();
}
void AnsiSkippingString::const_iterator::unadvance() {
assert( m_it != m_string->begin() );
m_it--;
// if *m_it is 0xff, scan back to the \033 and then m_it-- once more
// (and repeat check)
while ( *m_it == AnsiSkippingString::sentinel ) {
while ( *m_it != '\033' ) {
assert( m_it != m_string->begin() );
m_it--;
}
// if this happens, we must have been a begin iterator that had
// skipped over ansi sequences at the start of a string
assert( m_it != m_string->begin() );
assert( *m_it == '\033' );
m_it--;
}
}
static bool isBoundary( AnsiSkippingString const& line,
AnsiSkippingString::const_iterator it ) {
return it == line.end() ||
( isWhitespace( *it ) &&
!isWhitespace( *it.oneBefore() ) ) ||
isBreakableBefore( *it ) ||
isBreakableAfter( *it.oneBefore() );
}
void Column::const_iterator::calcLength() {
m_addHyphen = false;
m_parsedTo = m_lineStart;
AnsiSkippingString const& current_line = m_column.m_string;
std::string const& current_line = m_column.m_string;
if ( current_line[m_lineStart] == '\n' ) {
++m_parsedTo;
if ( m_parsedTo == current_line.end() ) {
m_lineEnd = m_parsedTo;
return;
}
assert( m_lineStart != current_line.end() );
if ( *m_lineStart == '\n' ) { ++m_parsedTo; }
const auto maxLineLength = m_column.m_width - indentSize();
const auto maxParseTo = std::min(current_line.size(), m_lineStart + maxLineLength);
while ( m_parsedTo < maxParseTo &&
current_line[m_parsedTo] != '\n' ) {
std::size_t lineLength = 0;
while ( m_parsedTo != current_line.end() &&
lineLength < maxLineLength && *m_parsedTo != '\n' ) {
++m_parsedTo;
++lineLength;
}
// If we encountered a newline before the column is filled,
// then we linebreak at the newline and consider this line
// finished.
if ( m_parsedTo < m_lineStart + maxLineLength ) {
m_lineLength = m_parsedTo - m_lineStart;
if ( lineLength < maxLineLength ) {
m_lineEnd = m_parsedTo;
} else {
// Look for a natural linebreak boundary in the column
// (We look from the end, so that the first found boundary is
// the right one)
size_t newLineLength = maxLineLength;
while ( newLineLength > 0 && !isBoundary( current_line, m_lineStart + newLineLength ) ) {
--newLineLength;
m_lineEnd = m_parsedTo;
while ( lineLength > 0 &&
!isBoundary( current_line, m_lineEnd ) ) {
--lineLength;
--m_lineEnd;
}
while ( newLineLength > 0 &&
isWhitespace( current_line[m_lineStart + newLineLength - 1] ) ) {
--newLineLength;
while ( lineLength > 0 &&
isWhitespace( *m_lineEnd.oneBefore() ) ) {
--lineLength;
--m_lineEnd;
}
// If we found one, then that is where we linebreak
if ( newLineLength > 0 ) {
m_lineLength = newLineLength;
} else {
// Otherwise we have to split text with a hyphen
// If we found one, then that is where we linebreak, otherwise
// we have to split text with a hyphen
if ( lineLength == 0 ) {
m_addHyphen = true;
m_lineLength = maxLineLength - 1;
m_lineEnd = m_parsedTo.oneBefore();
}
}
}
size_t Column::const_iterator::indentSize() const {
auto initial =
m_lineStart == 0 ? m_column.m_initialIndent : std::string::npos;
auto initial = m_lineStart == m_column.m_string.begin()
? m_column.m_initialIndent
: std::string::npos;
return initial == std::string::npos ? m_column.m_indent : initial;
}
std::string
Column::const_iterator::addIndentAndSuffix( size_t position,
size_t length ) const {
std::string Column::const_iterator::addIndentAndSuffix(
AnsiSkippingString::const_iterator start,
AnsiSkippingString::const_iterator end ) const {
std::string ret;
const auto desired_indent = indentSize();
ret.reserve( desired_indent + length + m_addHyphen );
// ret.reserve( desired_indent + (end - start) + m_addHyphen );
ret.append( desired_indent, ' ' );
ret.append( m_column.m_string, position, length );
if ( m_addHyphen ) {
ret.push_back( '-' );
}
// ret.append( start, end );
ret += m_column.m_string.substring( start, end );
if ( m_addHyphen ) { ret.push_back( '-' ); }
return ret;
}
Column::const_iterator::const_iterator( Column const& column ): m_column( column ) {
Column::const_iterator::const_iterator( Column const& column ):
m_column( column ),
m_lineStart( column.m_string.begin() ),
m_lineEnd( column.m_string.begin() ),
m_parsedTo( column.m_string.begin() ) {
assert( m_column.m_width > m_column.m_indent );
assert( m_column.m_initialIndent == std::string::npos ||
m_column.m_width > m_column.m_initialIndent );
calcLength();
if ( m_lineLength == 0 ) {
m_lineStart = m_column.m_string.size();
if ( m_lineStart == m_lineEnd ) {
m_lineStart = m_column.m_string.end();
}
}
std::string Column::const_iterator::operator*() const {
assert( m_lineStart <= m_parsedTo );
return addIndentAndSuffix( m_lineStart, m_lineLength );
return addIndentAndSuffix( m_lineStart, m_lineEnd );
}
Column::const_iterator& Column::const_iterator::operator++() {
m_lineStart += m_lineLength;
std::string const& current_line = m_column.m_string;
if ( m_lineStart < current_line.size() && current_line[m_lineStart] == '\n' ) {
m_lineStart += 1;
m_lineStart = m_lineEnd;
AnsiSkippingString const& current_line = m_column.m_string;
if ( m_lineStart != current_line.end() && *m_lineStart == '\n' ) {
m_lineStart++;
} else {
while ( m_lineStart < current_line.size() &&
isWhitespace( current_line[m_lineStart] ) ) {
while ( m_lineStart != current_line.end() &&
isWhitespace( *m_lineStart ) ) {
++m_lineStart;
}
}
if ( m_lineStart != current_line.size() ) {
calcLength();
}
if ( m_lineStart != current_line.end() ) { calcLength(); }
return *this;
}
@ -7414,25 +7532,25 @@ namespace Catch {
return os;
}
Columns operator+(Column const& lhs, Column const& rhs) {
Columns operator+( Column const& lhs, Column const& rhs ) {
Columns cols;
cols += lhs;
cols += rhs;
return cols;
}
Columns operator+(Column&& lhs, Column&& rhs) {
Columns operator+( Column&& lhs, Column&& rhs ) {
Columns cols;
cols += CATCH_MOVE( lhs );
cols += CATCH_MOVE( rhs );
return cols;
}
Columns& operator+=(Columns& lhs, Column const& rhs) {
Columns& operator+=( Columns& lhs, Column const& rhs ) {
lhs.m_columns.push_back( rhs );
return lhs;
}
Columns& operator+=(Columns& lhs, Column&& rhs) {
lhs.m_columns.push_back( CATCH_MOVE(rhs) );
Columns& operator+=( Columns& lhs, Column&& rhs ) {
lhs.m_columns.push_back( CATCH_MOVE( rhs ) );
return lhs;
}
Columns operator+( Columns const& lhs, Column const& rhs ) {
@ -8077,7 +8195,7 @@ namespace Detail {
std::string WithinRelMatcher::describe() const {
Catch::ReusableStringStream sstr;
sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other";
sstr << "and " << ::Catch::Detail::stringify(m_target) << " are within " << m_epsilon * 100. << "% of each other";
return sstr.str();
}

View File

@ -6,8 +6,8 @@
// SPDX-License-Identifier: BSL-1.0
// Catch v3.5.4
// Generated: 2024-04-10 12:03:45.785902
// Catch v3.6.0
// Generated: 2024-05-05 20:53:27.071502
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
@ -5242,9 +5242,11 @@ namespace Detail {
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wsign-compare"
# pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
#if defined(CATCH_CPP20_OR_GREATER) && __has_include(<compare>)
@ -7269,8 +7271,8 @@ namespace Catch {
#define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3
#define CATCH_VERSION_MINOR 5
#define CATCH_VERSION_PATCH 4
#define CATCH_VERSION_MINOR 6
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED
@ -7949,7 +7951,10 @@ namespace Catch {
// it, and it provides an escape hatch to the users who need it.
#if defined( __SIZEOF_INT128__ )
# define CATCH_CONFIG_INTERNAL_UINT128
#elif defined( _MSC_VER ) && ( defined( _WIN64 ) || defined( _M_ARM64 ) )
// Unlike GCC, MSVC does not polyfill umul as mulh + mul pair on ARM machines.
// Currently we do not bother doing this ourselves, but we could if it became
// important for perf.
#elif defined( _MSC_VER ) && defined( _M_X64 )
# define CATCH_CONFIG_INTERNAL_MSVC_UMUL128
#endif
@ -7964,7 +7969,6 @@ namespace Catch {
!defined( CATCH_CONFIG_MSVC_UMUL128 )
# define CATCH_CONFIG_MSVC_UMUL128
# include <intrin.h>
# pragma intrinsic( _umul128 )
#endif
@ -10944,6 +10948,107 @@ namespace Catch {
class Columns;
/**
* Abstraction for a string with ansi escape sequences that
* automatically skips over escapes when iterating. Only graphical
* escape sequences are considered.
*
* Internal representation:
* An escape sequence looks like \033[39;49m
* We need bidirectional iteration and the unbound length of escape
* sequences poses a problem for operator-- To make this work we'll
* replace the last `m` with a 0xff (this is a codepoint that won't have
* any utf-8 meaning).
*/
class AnsiSkippingString {
std::string m_string;
std::size_t m_size = 0;
// perform 0xff replacement and calculate m_size
void preprocessString();
public:
class const_iterator;
using iterator = const_iterator;
// note: must be u-suffixed or this will cause a "truncation of
// constant value" warning on MSVC
static constexpr char sentinel = static_cast<char>( 0xffu );
explicit AnsiSkippingString( std::string const& text );
explicit AnsiSkippingString( std::string&& text );
const_iterator begin() const;
const_iterator end() const;
size_t size() const { return m_size; }
std::string substring( const_iterator begin,
const_iterator end ) const;
};
class AnsiSkippingString::const_iterator {
friend AnsiSkippingString;
struct EndTag {};
const std::string* m_string;
std::string::const_iterator m_it;
explicit const_iterator( const std::string& string, EndTag ):
m_string( &string ), m_it( string.end() ) {}
void tryParseAnsiEscapes();
void advance();
void unadvance();
public:
using difference_type = std::ptrdiff_t;
using value_type = char;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::bidirectional_iterator_tag;
explicit const_iterator( const std::string& string ):
m_string( &string ), m_it( string.begin() ) {
tryParseAnsiEscapes();
}
char operator*() const { return *m_it; }
const_iterator& operator++() {
advance();
return *this;
}
const_iterator operator++( int ) {
iterator prev( *this );
operator++();
return prev;
}
const_iterator& operator--() {
unadvance();
return *this;
}
const_iterator operator--( int ) {
iterator prev( *this );
operator--();
return prev;
}
bool operator==( const_iterator const& other ) const {
return m_it == other.m_it;
}
bool operator!=( const_iterator const& other ) const {
return !operator==( other );
}
bool operator<=( const_iterator const& other ) const {
return m_it <= other.m_it;
}
const_iterator oneBefore() const {
auto it = *this;
return --it;
}
};
/**
* Represents a column of text with specific width and indentation
*
@ -10953,10 +11058,11 @@ namespace Catch {
*/
class Column {
// String to be written out
std::string m_string;
AnsiSkippingString m_string;
// Width of the column for linebreaking
size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1;
// Indentation of other lines (including first if initial indent is unset)
// Indentation of other lines (including first if initial indent is
// unset)
size_t m_indent = 0;
// Indentation of the first line
size_t m_initialIndent = std::string::npos;
@ -10971,16 +11077,19 @@ namespace Catch {
Column const& m_column;
// Where does the current line start?
size_t m_lineStart = 0;
AnsiSkippingString::const_iterator m_lineStart;
// How long should the current line be?
size_t m_lineLength = 0;
AnsiSkippingString::const_iterator m_lineEnd;
// How far have we checked the string to iterate?
size_t m_parsedTo = 0;
AnsiSkippingString::const_iterator m_parsedTo;
// Should a '-' be appended to the line?
bool m_addHyphen = false;
const_iterator( Column const& column, EndTag ):
m_column( column ), m_lineStart( m_column.m_string.size() ) {}
m_column( column ),
m_lineStart( m_column.m_string.end() ),
m_lineEnd( column.m_string.end() ),
m_parsedTo( column.m_string.end() ) {}
// Calculates the length of the current line
void calcLength();
@ -10990,8 +11099,9 @@ namespace Catch {
// Creates an indented and (optionally) suffixed string from
// current iterator position, indentation and length.
std::string addIndentAndSuffix( size_t position,
size_t length ) const;
std::string addIndentAndSuffix(
AnsiSkippingString::const_iterator start,
AnsiSkippingString::const_iterator end ) const;
public:
using difference_type = std::ptrdiff_t;
@ -11008,7 +11118,8 @@ namespace Catch {
const_iterator operator++( int );
bool operator==( const_iterator const& other ) const {
return m_lineStart == other.m_lineStart && &m_column == &other.m_column;
return m_lineStart == other.m_lineStart &&
&m_column == &other.m_column;
}
bool operator!=( const_iterator const& other ) const {
return !operator==( other );
@ -11018,7 +11129,7 @@ namespace Catch {
explicit Column( std::string const& text ): m_string( text ) {}
explicit Column( std::string&& text ):
m_string( CATCH_MOVE(text)) {}
m_string( CATCH_MOVE( text ) ) {}
Column& width( size_t newWidth ) & {
assert( newWidth > 0 );
@ -11049,7 +11160,9 @@ namespace Catch {
size_t width() const { return m_width; }
const_iterator begin() const { return const_iterator( *this ); }
const_iterator end() const { return { *this, const_iterator::EndTag{} }; }
const_iterator end() const {
return { *this, const_iterator::EndTag{} };
}
friend std::ostream& operator<<( std::ostream& os,
Column const& col );
@ -11320,6 +11433,16 @@ namespace Catch {
namespace Catch {
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wsign-compare"
# pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-compare"
# pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
template<typename ArgT, typename MatcherT>
class MatchExpr : public ITransientExpression {
ArgT && m_arg;
@ -11338,6 +11461,13 @@ namespace Catch {
}
};
#ifdef __clang__
# pragma clang diagnostic pop
#elif defined __GNUC__
# pragma GCC diagnostic pop
#endif
namespace Matchers {
template <typename ArgT>
class MatcherBase;

View File

@ -8,7 +8,7 @@
project(
'catch2',
'cpp',
version: '3.5.4', # CML version placeholder, don't delete
version: '3.6.0', # CML version placeholder, don't delete
license: 'BSL-1.0',
meson_version: '>=0.54.1',
)

View File

@ -36,7 +36,7 @@ namespace Catch {
}
Version const& libraryVersion() {
static Version version( 3, 5, 4, "", 0 );
static Version version( 3, 6, 0, "", 0 );
return version;
}

View File

@ -9,7 +9,7 @@
#define CATCH_VERSION_MACROS_HPP_INCLUDED
#define CATCH_VERSION_MAJOR 3
#define CATCH_VERSION_MINOR 5
#define CATCH_VERSION_PATCH 4
#define CATCH_VERSION_MINOR 6
#define CATCH_VERSION_PATCH 0
#endif // CATCH_VERSION_MACROS_HPP_INCLUDED

View File

@ -9,6 +9,7 @@
#include <catch2/internal/catch_stdstreams.hpp>
#include <stdexcept>
#include <system_error>
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;

View File

@ -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)

View File

@ -5,33 +5,46 @@
// https://www.boost.org/LICENSE_1_0.txt)
// SPDX-License-Identifier: BSL-1.0
#include <catch2/internal/catch_output_redirect.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 <cstdio>
#include <cstring>
#include <sstream>
#if defined(CATCH_CONFIG_NEW_CAPTURE)
#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
#if defined( CATCH_CONFIG_NEW_CAPTURE )
# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC )
# include <system_error>
# if defined( _MSC_VER )
# include <fcntl.h> // _O_TEXT
# include <io.h> // _close, _dup, _dup2, _fileno, _pipe and _read
# define close _close
# define dup _dup
# define dup2 _dup2
# define fileno _fileno
# else
# include <unistd.h> // close, dup, dup2, fileno, pipe and read
# 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
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(_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);
# 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() );
}
}
#else
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<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;
}
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 } {}
# 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.");
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

View File

@ -8,14 +8,19 @@
#ifndef 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_reusable_string_stream.hpp>
#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <cstdio>
#include <iosfwd>
#include <string>
#if defined( CATCH_CONFIG_NEW_CAPTURE )
# if defined( CATCH_INTERNAL_CONFIG_USE_ASYNC )
# include <thread>
# 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

View File

@ -22,6 +22,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, "");}());
}