diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5484c857..aa4b5f23 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,7 +6,7 @@ if(NOT DEFINED PROJECT_NAME)
set(NOT_SUBPROJECT ON)
endif()
-project(Catch2 LANGUAGES CXX VERSION 2.2.1)
+project(Catch2 LANGUAGES CXX VERSION 2.2.2)
include(GNUInstallDirs)
diff --git a/README.md b/README.md
index 8aae143c..5d7f4ff8 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,9 @@
[![Build Status](https://travis-ci.org/catchorg/Catch2.svg?branch=master)](https://travis-ci.org/catchorg/Catch2)
[![Build status](https://ci.appveyor.com/api/projects/status/github/catchorg/Catch2?svg=true)](https://ci.appveyor.com/project/catchorg/catch2)
[![codecov](https://codecov.io/gh/catchorg/Catch2/branch/master/graph/badge.svg)](https://codecov.io/gh/catchorg/Catch2)
-[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/zDKMK3eGMC9IP2jy)
+[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/fRDMfYjUnrbOFwLn)
-The latest version of the single header can be downloaded directly using this link
+The latest version of the single header can be downloaded directly using this link
## Catch2 is released!
diff --git a/conanfile.py b/conanfile.py
index 212da6a9..a3949588 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -4,7 +4,7 @@ from conans import ConanFile
class CatchConan(ConanFile):
name = "Catch"
- version = "2.2.1"
+ version = "2.2.2"
description = "A modern, C++-native, header-only, framework for unit-tests, TDD and BDD"
author = "philsquared"
generators = "cmake"
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 13ee3593..37849aff 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,5 +1,30 @@
+# 2.2.2
+
+## Fixes
+* Fixed bug in `WithinAbs::match()` failing spuriously (#1228)
+* Fixed clang-tidy diagnostic about virtual call in destructor (#1226)
+* Reduced the number of GCC warnings suppression leaking out of the header (#1090, #1091)
+ * Only `-Wparentheses` should be leaking now
+* Added upper bound on the time benchmark timer calibration is allowed to take (#1237)
+ * On platforms where `std::chrono::high_resolution_clock`'s resolution is low, the calibration would appear stuck
+* Fixed compilation error when stringifying static arrays of `unsigned char`s (#1238)
+
+## Improvements
+* XML encoder now hex-encodes invalid UTF-8 sequences (#1207)
+ * This affects xml and junit reporters
+ * Some invalid UTF-8 parts are left as is, e.g. surrogate pairs. This is because certain extensions of UTF-8 allow them, such as WTF-8.
+* CLR objects (`T^`) can now be stringified (#1216)
+ * This affects code compiled as C++/CLI
+* Added `PredicateMatcher`, a matcher that takes an arbitrary predicate function (#1236)
+ * See [documentation for details](https://github.com/catchorg/Catch2/blob/master/docs/matchers.md)
+
+## Others
+* Modified CMake-installed pkg-config to allow `#include `(#1239)
+ * The plans to standardize on `#include ` are still in effect
+
+
# 2.2.1
## Fixes
diff --git a/include/catch.hpp b/include/catch.hpp
index 9ea776de..10388cec 100644
--- a/include/catch.hpp
+++ b/include/catch.hpp
@@ -11,7 +11,7 @@
#define CATCH_VERSION_MAJOR 2
#define CATCH_VERSION_MINOR 2
-#define CATCH_VERSION_PATCH 1
+#define CATCH_VERSION_PATCH 2
#ifdef __clang__
# pragma clang system_header
diff --git a/include/internal/catch_version.cpp b/include/internal/catch_version.cpp
index 91a1e3bf..561117cf 100644
--- a/include/internal/catch_version.cpp
+++ b/include/internal/catch_version.cpp
@@ -37,7 +37,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 2, 2, 1, "", 0 );
+ static Version version( 2, 2, 2, "", 0 );
return version;
}
diff --git a/single_include/catch.hpp b/single_include/catch.hpp
index 081cb41d..ecd8907e 100644
--- a/single_include/catch.hpp
+++ b/single_include/catch.hpp
@@ -1,6 +1,6 @@
/*
- * Catch v2.2.1
- * Generated: 2018-03-11 12:01:31.654719
+ * Catch v2.2.2
+ * Generated: 2018-04-06 12:05:03.186665
* ----------------------------------------------------------
* This file has been merged from multiple headers. Please don't edit it directly
* Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved.
@@ -15,7 +15,7 @@
#define CATCH_VERSION_MAJOR 2
#define CATCH_VERSION_MINOR 2
-#define CATCH_VERSION_PATCH 1
+#define CATCH_VERSION_PATCH 2
#ifdef __clang__
# pragma clang system_header
@@ -37,9 +37,9 @@
# pragma clang diagnostic ignored "-Wcovered-switch-default"
# endif
#elif defined __GNUC__
-# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wparentheses"
# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wunused-variable"
# pragma GCC diagnostic ignored "-Wpadded"
#endif
// end catch_suppress_warnings.h
@@ -783,6 +783,18 @@ namespace Catch {
return convertUnknownEnumToString( value );
}
+#if defined(_MANAGED)
+ //! Convert a CLR string to a utf8 std::string
+ template
+ std::string clrReferenceToString( T^ ref ) {
+ if (ref == nullptr)
+ return std::string("null");
+ auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());
+ cli::pin_ptr p = &bytes[0];
+ return std::string(reinterpret_cast(p), bytes->Length);
+ }
+#endif
+
} // namespace Detail
// If we decide for C++14, change these to enable_if_ts
@@ -819,6 +831,13 @@ namespace Catch {
return ::Catch::Detail::stringify(static_cast::type>(e));
}
+#if defined(_MANAGED)
+ template
+ std::string stringify( T^ e ) {
+ return ::Catch::StringMaker::convert(e);
+ }
+#endif
+
} // namespace Detail
// Some predefined specializations
@@ -842,6 +861,7 @@ namespace Catch {
struct StringMaker {
static std::string convert(char * str);
};
+
#ifdef CATCH_CONFIG_WCHAR
template<>
struct StringMaker {
@@ -853,22 +873,24 @@ namespace Catch {
};
#endif
+ // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer,
+ // while keeping string semantics?
template
struct StringMaker {
- static std::string convert(const char* str) {
+ static std::string convert(char const* str) {
return ::Catch::Detail::stringify(std::string{ str });
}
};
template
struct StringMaker {
- static std::string convert(const char* str) {
- return ::Catch::Detail::stringify(std::string{ str });
+ static std::string convert(signed char const* str) {
+ return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) });
}
};
template
struct StringMaker {
- static std::string convert(const char* str) {
- return ::Catch::Detail::stringify(std::string{ str });
+ static std::string convert(unsigned char const* str) {
+ return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) });
}
};
@@ -952,6 +974,15 @@ namespace Catch {
}
};
+#if defined(_MANAGED)
+ template
+ struct StringMaker {
+ static std::string convert( T^ ref ) {
+ return ::Catch::Detail::clrReferenceToString(ref);
+ }
+ };
+#endif
+
namespace Detail {
template
std::string rangeToString(InputIterator first, InputIterator last) {
@@ -1080,6 +1111,13 @@ namespace Catch {
!std::is_same())), not_this_one>::value;
};
+#if defined(_MANAGED) // Managed types are never ranges
+ template
+ struct is_range {
+ static const bool value = false;
+ };
+#endif
+
template
std::string rangeToString( Range const& range ) {
return ::Catch::Detail::rangeToString( begin( range ), end( range ) );
@@ -2357,6 +2395,54 @@ namespace Matchers {
} // namespace Catch
// end catch_matchers_floating.h
+// start catch_matchers_generic.hpp
+
+#include
+#include
+
+namespace Catch {
+namespace Matchers {
+namespace Generic {
+
+namespace Detail {
+ std::string finalizeDescription(const std::string& desc);
+}
+
+template
+class PredicateMatcher : public MatcherBase {
+ std::function m_predicate;
+ std::string m_description;
+public:
+
+ PredicateMatcher(std::function const& elem, std::string const& descr)
+ :m_predicate(std::move(elem)),
+ m_description(Detail::finalizeDescription(descr))
+ {}
+
+ bool match( T const& item ) const override {
+ return m_predicate(item);
+ }
+
+ std::string describe() const override {
+ return m_description;
+ }
+};
+
+} // namespace Generic
+
+ // The following functions create the actual matcher objects.
+ // The user has to explicitly specify type to the function, because
+ // infering std::function is hard (but possible) and
+ // requires a lot of TMP.
+ template
+ Generic::PredicateMatcher Predicate(std::function const& predicate, std::string const& description = "") {
+ return Generic::PredicateMatcher(predicate, description);
+ }
+
+} // namespace Matchers
+} // namespace Catch
+
+// end catch_matchers_generic.hpp
// start catch_matchers_string.h
#include
@@ -4830,7 +4916,7 @@ namespace Catch {
public:
// !TBD We need to do this another way!
- bool aborting() const override;
+ bool aborting() const final;
private:
@@ -7880,7 +7966,7 @@ namespace Floating {
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool WithinAbsMatcher::match(double const& matchee) const {
- return (matchee + m_margin >= m_target) && (m_target + m_margin >= m_margin);
+ return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);
}
std::string WithinAbsMatcher::describe() const {
@@ -7927,6 +8013,16 @@ Floating::WithinAbsMatcher WithinAbs(double target, double margin) {
} // namespace Catch
// end catch_matchers_floating.cpp
+// start catch_matchers_generic.cpp
+
+std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) {
+ if (desc.empty()) {
+ return "matches undescribed predicate";
+ } else {
+ return "matches predicate: \"" + desc + '"';
+ }
+}
+// end catch_matchers_generic.cpp
// start catch_matchers_string.cpp
#include
@@ -10425,6 +10521,8 @@ namespace Catch {
#include
+static const uint64_t nanosecondsInSecond = 1000000000;
+
namespace Catch {
auto getCurrentNanosecondsSinceEpoch() -> uint64_t {
@@ -10435,17 +10533,25 @@ namespace Catch {
uint64_t sum = 0;
static const uint64_t iterations = 1000000;
+ auto startTime = getCurrentNanosecondsSinceEpoch();
+
for( std::size_t i = 0; i < iterations; ++i ) {
uint64_t ticks;
uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();
do {
ticks = getCurrentNanosecondsSinceEpoch();
- }
- while( ticks == baseTicks );
+ } while( ticks == baseTicks );
auto delta = ticks - baseTicks;
sum += delta;
+
+ // If we have been calibrating for over 3 seconds -- the clock
+ // is terrible and we should move on.
+ // TBD: How to signal that the measured resolution is probably wrong?
+ if (ticks > startTime + 3 * nanosecondsInSecond) {
+ return sum / i;
+ }
}
// We're just taking the mean, here. To do better we could take the std. dev and exclude outliers
@@ -10807,7 +10913,7 @@ namespace Catch {
}
Version const& libraryVersion() {
- static Version version( 2, 2, 1, "", 0 );
+ static Version version( 2, 2, 2, "", 0 );
return version;
}
@@ -10858,49 +10964,141 @@ namespace Catch {
#include
+using uchar = unsigned char;
+
namespace Catch {
+namespace {
+
+ size_t trailingBytes(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return 2;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return 3;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return 4;
+ }
+ CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ uint32_t headerValue(unsigned char c) {
+ if ((c & 0xE0) == 0xC0) {
+ return c & 0x1F;
+ }
+ if ((c & 0xF0) == 0xE0) {
+ return c & 0x0F;
+ }
+ if ((c & 0xF8) == 0xF0) {
+ return c & 0x07;
+ }
+ CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+ }
+
+ void hexEscapeChar(std::ostream& os, unsigned char c) {
+ os << "\\x"
+ << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+ << static_cast(c);
+ }
+
+} // anonymous namespace
+
XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
: m_str( str ),
m_forWhat( forWhat )
{}
void XmlEncode::encodeTo( std::ostream& os ) const {
-
// Apostrophe escaping not necessary if we always use " to write attributes
// (see: http://www.w3.org/TR/xml/#syntax)
- for( std::size_t i = 0; i < m_str.size(); ++ i ) {
- char c = m_str[i];
- switch( c ) {
- case '<': os << "<"; break;
- case '&': os << "&"; break;
+ for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+ uchar c = m_str[idx];
+ switch (c) {
+ case '<': os << "<"; break;
+ case '&': os << "&"; break;
- case '>':
- // See: http://www.w3.org/TR/xml/#syntax
- if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
- os << ">";
- else
- os << c;
+ case '>':
+ // See: http://www.w3.org/TR/xml/#syntax
+ if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+ os << ">";
+ else
+ os << c;
+ break;
+
+ case '\"':
+ if (m_forWhat == ForAttributes)
+ os << """;
+ else
+ os << c;
+ break;
+
+ default:
+ // Check for control characters and invalid utf-8
+
+ // Escape control characters in standard ascii
+ // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+ if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+ hexEscapeChar(os, c);
break;
+ }
- case '\"':
- if( m_forWhat == ForAttributes )
- os << """;
- else
- os << c;
+ // Plain ASCII: Write it to stream
+ if (c < 0x7F) {
+ os << c;
break;
+ }
- default:
- // Escape control chars - based on contribution by @espenalb in PR #465 and
- // by @mrpi PR #588
- if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) {
- // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
- os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
- << static_cast( c );
- }
- else
- os << c;
+ // UTF-8 territory
+ // Check if the encoding is valid and if it is not, hex escape bytes.
+ // Important: We do not check the exact decoded values for validity, only the encoding format
+ // First check that this bytes is a valid lead byte:
+ // This means that it is not encoded as 1111 1XXX
+ // Or as 10XX XXXX
+ if (c < 0xC0 ||
+ c >= 0xF8) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ auto encBytes = trailingBytes(c);
+ // Are there enough bytes left to avoid accessing out-of-bounds memory?
+ if (idx + encBytes - 1 >= m_str.size()) {
+ hexEscapeChar(os, c);
+ break;
+ }
+ // The header is valid, check data
+ // The next encBytes bytes must together be a valid utf-8
+ // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+ bool valid = true;
+ uint32_t value = headerValue(c);
+ for (std::size_t n = 1; n < encBytes; ++n) {
+ uchar nc = m_str[idx + n];
+ valid &= ((nc & 0xC0) == 0x80);
+ value = (value << 6) | (nc & 0x3F);
+ }
+
+ if (
+ // Wrong bit pattern of following bytes
+ (!valid) ||
+ // Overlong encodings
+ (value < 0x80) ||
+ (0x80 <= value && value < 0x800 && encBytes > 2) ||
+ (0x800 < value && value < 0x10000 && encBytes > 3) ||
+ // Encoded value out of range
+ (value >= 0x110000)
+ ) {
+ hexEscapeChar(os, c);
+ break;
+ }
+
+ // If we got here, this is in fact a valid(ish) utf-8 sequence
+ for (std::size_t n = 0; n < encBytes; ++n) {
+ os << m_str[idx + n];
+ }
+ idx += encBytes - 1;
+ break;
}
}
}
diff --git a/test_package/conanfile.py b/test_package/conanfile.py
index 3861db63..f95bfbdb 100644
--- a/test_package/conanfile.py
+++ b/test_package/conanfile.py
@@ -10,7 +10,7 @@ class CatchConanTest(ConanFile):
settings = "os", "compiler", "arch", "build_type"
username = getenv("CONAN_USERNAME", "philsquared")
channel = getenv("CONAN_CHANNEL", "testing")
- requires = "Catch/2.2.1@%s/%s" % (username, channel)
+ requires = "Catch/2.2.2@%s/%s" % (username, channel)
def build(self):
cmake = CMake(self)