diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
| commit | bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (patch) | |
| tree | 1e629e7b46b1d9972a973bc93fd100bcebd395be /src/catch2/matchers | |
| download | nihil-bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1.tar.gz nihil-bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1.tar.bz2 | |
import catch2 3.8.1vendor/catch2/3.8.1vendor/catch2
Diffstat (limited to 'src/catch2/matchers')
22 files changed, 2221 insertions, 0 deletions
diff --git a/src/catch2/matchers/catch_matchers.cpp b/src/catch2/matchers/catch_matchers.cpp new file mode 100644 index 0000000..123b304 --- /dev/null +++ b/src/catch2/matchers/catch_matchers.cpp @@ -0,0 +1,25 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + + +#include <catch2/matchers/catch_matchers.hpp> + +namespace Catch { +namespace Matchers { + + std::string MatcherUntypedBase::toString() const { + if (m_cachedToString.empty()) { + m_cachedToString = describe(); + } + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + +} // namespace Matchers +} // namespace Catch diff --git a/src/catch2/matchers/catch_matchers.hpp b/src/catch2/matchers/catch_matchers.hpp new file mode 100644 index 0000000..3d996c3 --- /dev/null +++ b/src/catch2/matchers/catch_matchers.hpp @@ -0,0 +1,237 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_HPP_INCLUDED +#define CATCH_MATCHERS_HPP_INCLUDED + +#include <catch2/matchers/internal/catch_matchers_impl.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +#include <string> +#include <vector> + +namespace Catch { +namespace Matchers { + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + + MatcherUntypedBase(MatcherUntypedBase const&) = default; + MatcherUntypedBase(MatcherUntypedBase&&) = default; + + MatcherUntypedBase& operator = (MatcherUntypedBase const&) = delete; + MatcherUntypedBase& operator = (MatcherUntypedBase&&) = delete; + + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); // = default; + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + + + template<typename T> + class MatcherBase : public MatcherUntypedBase { + public: + virtual bool match( T const& arg ) const = 0; + }; + + namespace Detail { + + template<typename ArgT> + class MatchAllOf final : public MatcherBase<ArgT> { + std::vector<MatcherBase<ArgT> const*> m_matchers; + + public: + MatchAllOf() = default; + MatchAllOf(MatchAllOf const&) = delete; + MatchAllOf& operator=(MatchAllOf const&) = delete; + MatchAllOf(MatchAllOf&&) = default; + MatchAllOf& operator=(MatchAllOf&&) = default; + + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase<ArgT> const& rhs) { + lhs.m_matchers.push_back(&rhs); + return CATCH_MOVE(lhs); + } + friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) { + rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); + return CATCH_MOVE(rhs); + } + }; + + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAllOf<ArgT> operator&& (MatchAllOf<ArgT> const& lhs, MatcherBase<ArgT> const& rhs) = delete; + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAllOf<ArgT> operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf<ArgT> const& rhs) = delete; + + template<typename ArgT> + class MatchAnyOf final : public MatcherBase<ArgT> { + std::vector<MatcherBase<ArgT> const*> m_matchers; + public: + MatchAnyOf() = default; + MatchAnyOf(MatchAnyOf const&) = delete; + MatchAnyOf& operator=(MatchAnyOf const&) = delete; + MatchAnyOf(MatchAnyOf&&) = default; + MatchAnyOf& operator=(MatchAnyOf&&) = default; + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase<ArgT> const& rhs) { + lhs.m_matchers.push_back(&rhs); + return CATCH_MOVE(lhs); + } + friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) { + rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); + return CATCH_MOVE(rhs); + } + }; + + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAnyOf<ArgT> operator|| (MatchAnyOf<ArgT> const& lhs, MatcherBase<ArgT> const& rhs) = delete; + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAnyOf<ArgT> operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf<ArgT> const& rhs) = delete; + + template<typename ArgT> + class MatchNotOf final : public MatcherBase<ArgT> { + MatcherBase<ArgT> const& m_underlyingMatcher; + + public: + explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ): + m_underlyingMatcher( underlyingMatcher ) + {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + }; + + } // namespace Detail + + template <typename T> + Detail::MatchAllOf<T> operator&& (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) { + return Detail::MatchAllOf<T>{} && lhs && rhs; + } + template <typename T> + Detail::MatchAnyOf<T> operator|| (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) { + return Detail::MatchAnyOf<T>{} || lhs || rhs; + } + + template <typename T> + Detail::MatchNotOf<T> operator! (MatcherBase<T> const& matcher) { + return Detail::MatchNotOf<T>{ matcher }; + } + + +} // namespace Matchers +} // namespace Catch + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) + #define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) + + #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + #define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + + #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) + #define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) + #define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + #define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) + #define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) + + #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + #define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + + #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) + #define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CHECK_THROWS_WITH( expr, matcher ) (void)(0) + #define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CHECK_THAT( arg, matcher ) (void)(0) + #define REQUIRE_THAT( arg, matcher ) (void)(0) + +#endif // end of user facing macro declarations + +#endif // CATCH_MATCHERS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_all.hpp b/src/catch2/matchers/catch_matchers_all.hpp new file mode 100644 index 0000000..83fe538 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_all.hpp @@ -0,0 +1,36 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +/** \file + * This is a convenience header for Catch2's Matcher support. It includes + * **all** of Catch2 headers related to matchers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of increased compilation times. + * + * When a new header is added to either the `matchers` folder, or to + * the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_MATCHERS_ALL_HPP_INCLUDED +#define CATCH_MATCHERS_ALL_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers.hpp> +#include <catch2/matchers/catch_matchers_container_properties.hpp> +#include <catch2/matchers/catch_matchers_contains.hpp> +#include <catch2/matchers/catch_matchers_exception.hpp> +#include <catch2/matchers/catch_matchers_floating_point.hpp> +#include <catch2/matchers/catch_matchers_predicate.hpp> +#include <catch2/matchers/catch_matchers_quantifiers.hpp> +#include <catch2/matchers/catch_matchers_range_equals.hpp> +#include <catch2/matchers/catch_matchers_string.hpp> +#include <catch2/matchers/catch_matchers_templated.hpp> +#include <catch2/matchers/catch_matchers_vector.hpp> +#include <catch2/matchers/internal/catch_matchers_impl.hpp> + +#endif // CATCH_MATCHERS_ALL_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_container_properties.cpp b/src/catch2/matchers/catch_matchers_container_properties.cpp new file mode 100644 index 0000000..f0c535b --- /dev/null +++ b/src/catch2/matchers/catch_matchers_container_properties.cpp @@ -0,0 +1,34 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/matchers/catch_matchers_container_properties.hpp> +#include <catch2/internal/catch_reusable_string_stream.hpp> + +namespace Catch { +namespace Matchers { + + std::string IsEmptyMatcher::describe() const { + return "is empty"; + } + + std::string HasSizeMatcher::describe() const { + ReusableStringStream sstr; + sstr << "has size == " << m_target_size; + return sstr.str(); + } + + IsEmptyMatcher IsEmpty() { + return {}; + } + + HasSizeMatcher SizeIs(std::size_t sz) { + return HasSizeMatcher{ sz }; + } + +} // end namespace Matchers +} // end namespace Catch diff --git a/src/catch2/matchers/catch_matchers_container_properties.hpp b/src/catch2/matchers/catch_matchers_container_properties.hpp new file mode 100644 index 0000000..5f3fc7f --- /dev/null +++ b/src/catch2/matchers/catch_matchers_container_properties.hpp @@ -0,0 +1,90 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED +#define CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers_templated.hpp> +#include <catch2/internal/catch_container_nonmembers.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +namespace Catch { + namespace Matchers { + + class IsEmptyMatcher final : public MatcherGenericBase { + public: + template <typename RangeLike> + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::empty; +#else + using std::empty; +#endif + return empty(rng); + } + + std::string describe() const override; + }; + + class HasSizeMatcher final : public MatcherGenericBase { + std::size_t m_target_size; + public: + explicit HasSizeMatcher(std::size_t target_size): + m_target_size(target_size) + {} + + template <typename RangeLike> + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return size(rng) == m_target_size; + } + + std::string describe() const override; + }; + + template <typename Matcher> + class SizeMatchesMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + explicit SizeMatchesMatcher(Matcher m): + m_matcher(CATCH_MOVE(m)) + {} + + template <typename RangeLike> + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return m_matcher.match(size(rng)); + } + + std::string describe() const override { + return "size matches " + m_matcher.describe(); + } + }; + + + //! Creates a matcher that accepts empty ranges/containers + IsEmptyMatcher IsEmpty(); + //! Creates a matcher that accepts ranges/containers with specific size + HasSizeMatcher SizeIs(std::size_t sz); + template <typename Matcher> + std::enable_if_t<Detail::is_matcher<Matcher>::value, + SizeMatchesMatcher<Matcher>> SizeIs(Matcher&& m) { + return SizeMatchesMatcher<Matcher>{CATCH_FORWARD(m)}; + } + + } // end namespace Matchers +} // end namespace Catch + +#endif // CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_contains.hpp b/src/catch2/matchers/catch_matchers_contains.hpp new file mode 100644 index 0000000..877d692 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_contains.hpp @@ -0,0 +1,102 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_CONTAINS_HPP_INCLUDED +#define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers_templated.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +#include <algorithm> +#include <functional> + +namespace Catch { + namespace Matchers { + //! Matcher for checking that an element in range is equal to specific element + template <typename T, typename Equality> + class ContainsElementMatcher final : public MatcherGenericBase { + T m_desired; + Equality m_eq; + public: + template <typename T2, typename Equality2> + ContainsElementMatcher(T2&& target, Equality2&& predicate): + m_desired(CATCH_FORWARD(target)), + m_eq(CATCH_FORWARD(predicate)) + {} + + std::string describe() const override { + return "contains element " + Catch::Detail::stringify(m_desired); + } + + template <typename RangeLike> + bool match( RangeLike&& rng ) const { + for ( auto&& elem : rng ) { + if ( m_eq( elem, m_desired ) ) { return true; } + } + return false; + } + }; + + //! Meta-matcher for checking that an element in a range matches a specific matcher + template <typename Matcher> + class ContainsMatcherMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + // Note that we do a copy+move to avoid having to SFINAE this + // constructor (and also avoid some perfect forwarding failure + // cases) + ContainsMatcherMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "contains element matching " + m_matcher.describe(); + } + }; + + /** + * Creates a matcher that checks whether a range contains a specific element. + * + * Uses `std::equal_to` to do the comparison + */ + template <typename T> + std::enable_if_t<!Detail::is_matcher<T>::value, + ContainsElementMatcher<T, std::equal_to<>>> Contains(T&& elem) { + return { CATCH_FORWARD(elem), std::equal_to<>{} }; + } + + //! Creates a matcher that checks whether a range contains element matching a matcher + template <typename Matcher> + std::enable_if_t<Detail::is_matcher<Matcher>::value, + ContainsMatcherMatcher<Matcher>> Contains(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + /** + * Creates a matcher that checks whether a range contains a specific element. + * + * Uses `eq` to do the comparisons, the element is provided on the rhs + */ + template <typename T, typename Equality> + ContainsElementMatcher<T, Equality> Contains(T&& elem, Equality&& eq) { + return { CATCH_FORWARD(elem), CATCH_FORWARD(eq) }; + } + + } +} + +#endif // CATCH_MATCHERS_CONTAINS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_exception.cpp b/src/catch2/matchers/catch_matchers_exception.cpp new file mode 100644 index 0000000..8147390 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_exception.cpp @@ -0,0 +1,26 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#include <catch2/matchers/catch_matchers_exception.hpp> + +namespace Catch { +namespace Matchers { + +bool ExceptionMessageMatcher::match(std::exception const& ex) const { + return ex.what() == m_message; +} + +std::string ExceptionMessageMatcher::describe() const { + return "exception message matches \"" + m_message + '"'; +} + +ExceptionMessageMatcher Message(std::string const& message) { + return ExceptionMessageMatcher(message); +} + +} // namespace Matchers +} // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_exception.hpp b/src/catch2/matchers/catch_matchers_exception.hpp new file mode 100644 index 0000000..e7c3a63 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_exception.hpp @@ -0,0 +1,61 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED +#define CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers.hpp> + +namespace Catch { +namespace Matchers { + +class ExceptionMessageMatcher final : public MatcherBase<std::exception> { + std::string m_message; +public: + + ExceptionMessageMatcher(std::string const& message): + m_message(message) + {} + + bool match(std::exception const& ex) const override; + + std::string describe() const override; +}; + +//! Creates a matcher that checks whether a std derived exception has the provided message +ExceptionMessageMatcher Message(std::string const& message); + +template <typename StringMatcherType> +class ExceptionMessageMatchesMatcher final + : public MatcherBase<std::exception> { + StringMatcherType m_matcher; + +public: + ExceptionMessageMatchesMatcher( StringMatcherType matcher ): + m_matcher( CATCH_MOVE( matcher ) ) {} + + bool match( std::exception const& ex ) const override { + return m_matcher.match( ex.what() ); + } + + std::string describe() const override { + return " matches \"" + m_matcher.describe() + '"'; + } +}; + +//! Creates a matcher that checks whether a message from an std derived +//! exception matches a provided matcher +template <typename StringMatcherType> +ExceptionMessageMatchesMatcher<StringMatcherType> +MessageMatches( StringMatcherType&& matcher ) { + return { CATCH_FORWARD( matcher ) }; +} + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_floating_point.cpp b/src/catch2/matchers/catch_matchers_floating_point.cpp new file mode 100644 index 0000000..fc7b444 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_floating_point.cpp @@ -0,0 +1,226 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#include <catch2/matchers/catch_matchers_floating_point.hpp> +#include <catch2/internal/catch_enforce.hpp> +#include <catch2/internal/catch_polyfills.hpp> +#include <catch2/internal/catch_to_string.hpp> +#include <catch2/catch_tostring.hpp> +#include <catch2/internal/catch_floating_point_helpers.hpp> + +#include <algorithm> +#include <cmath> +#include <cstdlib> +#include <cstdint> +#include <sstream> +#include <iomanip> +#include <limits> + + +namespace Catch { +namespace { + + template <typename FP> + bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + // This should also handle positive and negative zeros, infinities + const auto ulpDist = ulpDistance(lhs, rhs); + + return ulpDist <= maxUlpDiff; + } + + +template <typename FP> +FP step(FP start, FP direction, uint64_t steps) { + for (uint64_t i = 0; i < steps; ++i) { + start = Catch::nextafter(start, direction); + } + return start; +} + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +template <typename FloatingPoint> +void write(std::ostream& out, FloatingPoint num) { + out << std::scientific + << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1) + << num; +} + +} // end anonymous namespace + +namespace Matchers { +namespace Detail { + + enum class FloatingPointKind : uint8_t { + Float, + Double + }; + +} // end namespace Detail + + + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // 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 >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + + WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double + || m_ulps < (std::numeric_limits<uint32_t>::max)(), + "Provided ULP is impossibly large for a float comparison."); + CATCH_ENFORCE( std::numeric_limits<double>::is_iec559, + "WithinUlp matcher only supports platforms with " + "IEEE-754 compatible floating point representation" ); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case Detail::FloatingPointKind::Float: + return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps); + case Detail::FloatingPointKind::Double: + return almostEqualUlps<double>(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown Detail::FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + std::stringstream ret; + + ret << "is within " << m_ulps << " ULPs of "; + + if (m_type == Detail::FloatingPointKind::Float) { + write(ret, static_cast<float>(m_target)); + ret << 'f'; + } else { + write(ret, m_target); + } + + ret << " (["; + if (m_type == Detail::FloatingPointKind::Double) { + write( ret, + step( m_target, + -std::numeric_limits<double>::infinity(), + m_ulps ) ); + ret << ", "; + write( ret, + step( m_target, + std::numeric_limits<double>::infinity(), + m_ulps ) ); + } else { + // We have to cast INFINITY to float because of MinGW, see #1782 + write( ret, + step( static_cast<float>( m_target ), + -std::numeric_limits<float>::infinity(), + m_ulps ) ); + ret << ", "; + write( ret, + step( static_cast<float>( m_target ), + std::numeric_limits<float>::infinity(), + m_ulps ) ); + } + ret << "])"; + + return ret.str(); + } + + WithinRelMatcher::WithinRelMatcher(double target, double epsilon): + m_target(target), + m_epsilon(epsilon){ + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); + } + + bool WithinRelMatcher::match(double const& matchee) const { + const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin)? 0 : relMargin); + } + + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << ::Catch::Detail::stringify(m_target) << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); + } + + +WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Double); +} + +WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Float); +} + +WithinAbsMatcher WithinAbs(double target, double margin) { + return WithinAbsMatcher(target, margin); +} + +WithinRelMatcher WithinRel(double target, double eps) { + return WithinRelMatcher(target, eps); +} + +WithinRelMatcher WithinRel(double target) { + return WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100); +} + +WithinRelMatcher WithinRel(float target, float eps) { + return WithinRelMatcher(target, eps); +} + +WithinRelMatcher WithinRel(float target) { + return WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100); +} + + + +bool IsNaNMatcher::match( double const& matchee ) const { + return std::isnan( matchee ); +} + +std::string IsNaNMatcher::describe() const { + using namespace std::string_literals; + return "is NaN"s; +} + +IsNaNMatcher IsNaN() { return IsNaNMatcher(); } + + } // namespace Matchers +} // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_floating_point.hpp b/src/catch2/matchers/catch_matchers_floating_point.hpp new file mode 100644 index 0000000..7681663 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_floating_point.hpp @@ -0,0 +1,94 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED +#define CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers.hpp> + +namespace Catch { +namespace Matchers { + + namespace Detail { + enum class FloatingPointKind : uint8_t; + } + + class WithinAbsMatcher final : public MatcherBase<double> { + public: + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + //! Creates a matcher that accepts numbers within certain range of target + WithinAbsMatcher WithinAbs( double target, double margin ); + + + + class WithinUlpsMatcher final : public MatcherBase<double> { + public: + WithinUlpsMatcher( double target, + uint64_t ulps, + Detail::FloatingPointKind baseType ); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + uint64_t m_ulps; + Detail::FloatingPointKind m_type; + }; + + //! Creates a matcher that accepts doubles within certain ULP range of target + WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); + //! Creates a matcher that accepts floats within certain ULP range of target + WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); + + + + // Given IEEE-754 format for floats and doubles, we can assume + // that float -> double promotion is lossless. Given this, we can + // assume that if we do the standard relative comparison of + // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get + // the same result if we do this for floats, as if we do this for + // doubles that were promoted from floats. + class WithinRelMatcher final : public MatcherBase<double> { + public: + WithinRelMatcher( double target, double epsilon ); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_epsilon; + }; + + //! Creates a matcher that accepts doubles within certain relative range of target + WithinRelMatcher WithinRel(double target, double eps); + //! Creates a matcher that accepts doubles within 100*DBL_EPS relative range of target + WithinRelMatcher WithinRel(double target); + //! Creates a matcher that accepts doubles within certain relative range of target + WithinRelMatcher WithinRel(float target, float eps); + //! Creates a matcher that accepts floats within 100*FLT_EPS relative range of target + WithinRelMatcher WithinRel(float target); + + + + class IsNaNMatcher final : public MatcherBase<double> { + public: + IsNaNMatcher() = default; + bool match( double const& matchee ) const override; + std::string describe() const override; + }; + + IsNaNMatcher IsNaN(); + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_predicate.cpp b/src/catch2/matchers/catch_matchers_predicate.cpp new file mode 100644 index 0000000..f544537 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_predicate.cpp @@ -0,0 +1,17 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/matchers/catch_matchers_predicate.hpp> + +std::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} diff --git a/src/catch2/matchers/catch_matchers_predicate.hpp b/src/catch2/matchers/catch_matchers_predicate.hpp new file mode 100644 index 0000000..2d1cc33 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_predicate.hpp @@ -0,0 +1,59 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_PREDICATE_HPP_INCLUDED +#define CATCH_MATCHERS_PREDICATE_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers.hpp> +#include <catch2/internal/catch_meta.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +#include <string> + +namespace Catch { +namespace Matchers { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} // namespace Detail + +template <typename T, typename Predicate> +class PredicateMatcher final : public MatcherBase<T> { + Predicate m_predicate; + std::string m_description; +public: + + PredicateMatcher(Predicate&& elem, std::string const& descr) + :m_predicate(CATCH_FORWARD(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; + } +}; + + /** + * Creates a matcher that calls delegates `match` to the provided predicate. + * + * The user has to explicitly specify the argument type to the matcher + */ + template<typename T, typename Pred> + PredicateMatcher<T, Pred> Predicate(Pred&& predicate, std::string const& description = "") { + static_assert(is_callable<Pred(T)>::value, "Predicate not callable with argument T"); + static_assert(std::is_same<bool, FunctionReturnType<Pred, T>>::value, "Predicate does not return bool"); + return PredicateMatcher<T, Pred>(CATCH_FORWARD(predicate), description); + } + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_PREDICATE_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_quantifiers.cpp b/src/catch2/matchers/catch_matchers_quantifiers.cpp new file mode 100644 index 0000000..5a75246 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_quantifiers.cpp @@ -0,0 +1,24 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#include <catch2/matchers/catch_matchers_quantifiers.hpp> + +namespace Catch { + namespace Matchers { + std::string AllTrueMatcher::describe() const { return "contains only true"; } + + AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } + + std::string NoneTrueMatcher::describe() const { return "contains no true"; } + + NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } + + std::string AnyTrueMatcher::describe() const { return "contains at least one true"; } + + AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } + } // namespace Matchers +} // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_quantifiers.hpp b/src/catch2/matchers/catch_matchers_quantifiers.hpp new file mode 100644 index 0000000..977b0c7 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_quantifiers.hpp @@ -0,0 +1,165 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED +#define CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers_templated.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +namespace Catch { + namespace Matchers { + // Matcher for checking that all elements in range matches a given matcher. + template <typename Matcher> + class AllMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + AllMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "all match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (!m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range matches a given matcher. + template <typename Matcher> + class NoneMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + NoneMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "none match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that at least one element in range matches a given matcher. + template <typename Matcher> + class AnyMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + AnyMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "any match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + }; + + // Matcher for checking that all elements in range are true. + class AllTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (!elem) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range is true. + class NoneTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (elem) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that any element in range is true. + class AnyTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (elem) { + return true; + } + } + return false; + } + }; + + // Creates a matcher that checks whether all elements in a range match a matcher + template <typename Matcher> + AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether no element in a range matches a matcher. + template <typename Matcher> + NoneMatchMatcher<Matcher> NoneMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether any element in a range matches a matcher. + template <typename Matcher> + AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether all elements in a range are true + AllTrueMatcher AllTrue(); + + // Creates a matcher that checks whether no element in a range is true + NoneTrueMatcher NoneTrue(); + + // Creates a matcher that checks whether any element in a range is true + AnyTrueMatcher AnyTrue(); + } +} + +#endif // CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_range_equals.hpp b/src/catch2/matchers/catch_matchers_range_equals.hpp new file mode 100644 index 0000000..8130f60 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_range_equals.hpp @@ -0,0 +1,161 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED +#define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED + +#include <catch2/internal/catch_is_permutation.hpp> +#include <catch2/matchers/catch_matchers_templated.hpp> + +#include <algorithm> +#include <utility> + +namespace Catch { + namespace Matchers { + + /** + * Matcher for checking that an element contains the same + * elements in the same order + */ + template <typename TargetRangeLike, typename Equality> + class RangeEqualsMatcher final : public MatcherGenericBase { + TargetRangeLike m_desired; + Equality m_predicate; + + public: + template <typename TargetRangeLike2, typename Equality2> + constexpr + RangeEqualsMatcher( TargetRangeLike2&& range, + Equality2&& predicate ): + m_desired( CATCH_FORWARD( range ) ), + m_predicate( CATCH_FORWARD( predicate ) ) {} + + template <typename RangeLike> + constexpr + bool match( RangeLike&& rng ) const { + auto rng_start = begin( rng ); + const auto rng_end = end( rng ); + auto target_start = begin( m_desired ); + const auto target_end = end( m_desired ); + + while (rng_start != rng_end && target_start != target_end) { + if (!m_predicate(*rng_start, *target_start)) { + return false; + } + ++rng_start; + ++target_start; + } + return rng_start == rng_end && target_start == target_end; + } + + std::string describe() const override { + return "elements are " + Catch::Detail::stringify( m_desired ); + } + }; + + /** + * Matcher for checking that an element contains the same + * elements (but not necessarily in the same order) + */ + template <typename TargetRangeLike, typename Equality> + class UnorderedRangeEqualsMatcher final : public MatcherGenericBase { + TargetRangeLike m_desired; + Equality m_predicate; + + public: + template <typename TargetRangeLike2, typename Equality2> + constexpr + UnorderedRangeEqualsMatcher( TargetRangeLike2&& range, + Equality2&& predicate ): + m_desired( CATCH_FORWARD( range ) ), + m_predicate( CATCH_FORWARD( predicate ) ) {} + + template <typename RangeLike> + constexpr + bool match( RangeLike&& rng ) const { + using std::begin; + using std::end; + return Catch::Detail::is_permutation( begin( m_desired ), + end( m_desired ), + begin( rng ), + end( rng ), + m_predicate ); + } + + std::string describe() const override { + return "unordered elements are " + + ::Catch::Detail::stringify( m_desired ); + } + }; + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in another range. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename RangeLike, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + RangeEqualsMatcher<RangeLike, Equality> + RangeEquals( RangeLike&& range, + Equality&& predicate = std::equal_to<>{} ) { + return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in an initializer list. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename T, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + RangeEqualsMatcher<std::initializer_list<T>, Equality> + RangeEquals( std::initializer_list<T> range, + Equality&& predicate = std::equal_to<>{} ) { + return { range, CATCH_FORWARD( predicate ) }; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in another range, in some permutation. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename RangeLike, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + UnorderedRangeEqualsMatcher<RangeLike, Equality> + UnorderedRangeEquals( RangeLike&& range, + Equality&& predicate = std::equal_to<>{} ) { + return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in an initializer list, in some permutation. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename T, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + UnorderedRangeEqualsMatcher<std::initializer_list<T>, Equality> + UnorderedRangeEquals( std::initializer_list<T> range, + Equality&& predicate = std::equal_to<>{} ) { + return { range, CATCH_FORWARD( predicate ) }; + } + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_string.cpp b/src/catch2/matchers/catch_matchers_string.cpp new file mode 100644 index 0000000..5500284 --- /dev/null +++ b/src/catch2/matchers/catch_matchers_string.cpp @@ -0,0 +1,114 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#include <catch2/matchers/catch_matchers_string.hpp> +#include <catch2/internal/catch_string_manip.hpp> +#include <catch2/catch_tostring.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +#include <regex> + +namespace Catch { +namespace Matchers { + + CasedString::CasedString( std::string const& str, CaseSensitive caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + StringRef CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::Yes + ? StringRef() + : " (case insensitive)"_sr; + } + + + StringMatcherBase::StringMatcherBase( StringRef operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += '"'; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals"_sr, comparator ) {} + + bool StringEqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + + StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains"_sr, comparator ) {} + + bool StringContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with"_sr, comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with"_sr, comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Yes)? " case sensitively" : " case insensitively"); + } + + + StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) { + return StringEqualsMatcher( CasedString( str, caseSensitivity) ); + } + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) { + return StringContainsMatcher( CasedString( str, caseSensitivity) ); + } + EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) { + return EndsWithMatcher( CasedString( str, caseSensitivity) ); + } + StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity ) { + return StartsWithMatcher( CasedString( str, caseSensitivity) ); + } + + RegexMatcher Matches(std::string const& regex, CaseSensitive caseSensitivity) { + return RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_string.hpp b/src/catch2/matchers/catch_matchers_string.hpp new file mode 100644 index 0000000..61a385d --- /dev/null +++ b/src/catch2/matchers/catch_matchers_string.hpp @@ -0,0 +1,85 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_STRING_HPP_INCLUDED +#define CATCH_MATCHERS_STRING_HPP_INCLUDED + +#include <catch2/internal/catch_stringref.hpp> +#include <catch2/matchers/catch_matchers.hpp> +#include <catch2/catch_case_sensitive.hpp> + +#include <string> + +namespace Catch { +namespace Matchers { + + struct CasedString { + CasedString( std::string const& str, CaseSensitive caseSensitivity ); + std::string adjustString( std::string const& str ) const; + StringRef caseSensitivitySuffix() const; + + CaseSensitive m_caseSensitivity; + std::string m_str; + }; + + class StringMatcherBase : public MatcherBase<std::string> { + protected: + CasedString m_comparator; + StringRef m_operation; + + public: + StringMatcherBase( StringRef operation, + CasedString const& comparator ); + std::string describe() const override; + }; + + class StringEqualsMatcher final : public StringMatcherBase { + public: + StringEqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + class StringContainsMatcher final : public StringMatcherBase { + public: + StringContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + class StartsWithMatcher final : public StringMatcherBase { + public: + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + class EndsWithMatcher final : public StringMatcherBase { + public: + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + class RegexMatcher final : public MatcherBase<std::string> { + std::string m_regex; + CaseSensitive m_caseSensitivity; + + public: + RegexMatcher( std::string regex, CaseSensitive caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + }; + + //! Creates matcher that accepts strings that are exactly equal to `str` + StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings that contain `str` + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings that _end_ with `str` + EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings that _start_ with `str` + StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings matching `regex` + RegexMatcher Matches( std::string const& regex, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_STRING_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_templated.cpp b/src/catch2/matchers/catch_matchers_templated.cpp new file mode 100644 index 0000000..2fc529d --- /dev/null +++ b/src/catch2/matchers/catch_matchers_templated.cpp @@ -0,0 +1,41 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#include <catch2/matchers/catch_matchers_templated.hpp> + +namespace Catch { +namespace Matchers { + MatcherGenericBase::~MatcherGenericBase() = default; + + namespace Detail { + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end) { + std::string description; + std::size_t combined_size = 4; + for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + combined_size += desc->size(); + } + combined_size += static_cast<size_t>(descriptions_end - descriptions_begin - 1) * combine.size(); + + description.reserve(combined_size); + + description += "( "; + bool first = true; + for( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + if( first ) + first = false; + else + description += combine; + description += *desc; + } + description += " )"; + return description; + } + + } // namespace Detail +} // namespace Matchers +} // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_templated.hpp b/src/catch2/matchers/catch_matchers_templated.hpp new file mode 100644 index 0000000..ba0661a --- /dev/null +++ b/src/catch2/matchers/catch_matchers_templated.hpp @@ -0,0 +1,296 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED +#define CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers.hpp> +#include <catch2/internal/catch_stringref.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> +#include <catch2/internal/catch_logical_traits.hpp> + +#include <array> +#include <algorithm> +#include <string> +#include <type_traits> + +namespace Catch { +namespace Matchers { + class MatcherGenericBase : public MatcherUntypedBase { + public: + MatcherGenericBase() = default; + ~MatcherGenericBase() override; // = default; + + MatcherGenericBase(MatcherGenericBase const&) = default; + MatcherGenericBase(MatcherGenericBase&&) = default; + + MatcherGenericBase& operator=(MatcherGenericBase const&) = delete; + MatcherGenericBase& operator=(MatcherGenericBase&&) = delete; + }; + + + namespace Detail { + template<std::size_t N, std::size_t M> + std::array<void const*, N + M> array_cat(std::array<void const*, N> && lhs, std::array<void const*, M> && rhs) { + std::array<void const*, N + M> arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + std::copy_n(rhs.begin(), M, arr.begin() + N); + return arr; + } + + template<std::size_t N> + std::array<void const*, N+1> array_cat(std::array<void const*, N> && lhs, void const* rhs) { + std::array<void const*, N+1> arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + arr[N] = rhs; + return arr; + } + + template<std::size_t N> + std::array<void const*, N+1> array_cat(void const* lhs, std::array<void const*, N> && rhs) { + std::array<void const*, N + 1> arr{ {lhs} }; + std::copy_n(rhs.begin(), N, arr.begin() + 1); + return arr; + } + + template<typename T> + using is_generic_matcher = std::is_base_of< + Catch::Matchers::MatcherGenericBase, + std::remove_cv_t<std::remove_reference_t<T>> + >; + + template<typename... Ts> + using are_generic_matchers = Catch::Detail::conjunction<is_generic_matcher<Ts>...>; + + template<typename T> + using is_matcher = std::is_base_of< + Catch::Matchers::MatcherUntypedBase, + std::remove_cv_t<std::remove_reference_t<T>> + >; + + + template<std::size_t N, typename Arg> + bool match_all_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) { + return true; + } + + template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices> + bool match_all_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) { + return static_cast<T const*>(matchers[Idx])->match(arg) && match_all_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{}); + } + + + template<std::size_t N, typename Arg> + bool match_any_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) { + return false; + } + + template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices> + bool match_any_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) { + return static_cast<T const*>(matchers[Idx])->match(arg) || match_any_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{}); + } + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end); + + template<typename... MatcherTs, std::size_t... Idx> + std::string describe_multi_matcher(StringRef combine, std::array<void const*, sizeof...(MatcherTs)> const& matchers, std::index_sequence<Idx...>) { + std::array<std::string, sizeof...(MatcherTs)> descriptions {{ + static_cast<MatcherTs const*>(matchers[Idx])->toString()... + }}; + + return describe_multi_matcher(combine, descriptions.data(), descriptions.data() + descriptions.size()); + } + + + template<typename... MatcherTs> + class MatchAllOfGeneric final : public MatcherGenericBase { + public: + MatchAllOfGeneric(MatchAllOfGeneric const&) = delete; + MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete; + MatchAllOfGeneric(MatchAllOfGeneric&&) = default; + MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default; + + MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} + explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {} + + template<typename Arg> + bool match(Arg&& arg) const { + return match_all_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + std::string describe() const override { + return describe_multi_matcher<MatcherTs...>(" and "_sr, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array<void const*, sizeof...( MatcherTs )> m_matchers; + + + //! Avoids type nesting for `GenericAllOf && GenericAllOf` case + template<typename... MatchersRHS> + friend + MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && ( + MatchAllOfGeneric<MatcherTs...>&& lhs, + MatchAllOfGeneric<MatchersRHS...>&& rhs) { + return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; + } + + //! Avoids type nesting for `GenericAllOf && some matcher` case + template<typename MatcherRHS> + friend std::enable_if_t<is_matcher<MatcherRHS>::value, + MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && ( + MatchAllOfGeneric<MatcherTs...>&& lhs, + MatcherRHS const& rhs) { + return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))}; + } + + //! Avoids type nesting for `some matcher && GenericAllOf` case + template<typename MatcherLHS> + friend std::enable_if_t<is_matcher<MatcherLHS>::value, + MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && ( + MatcherLHS const& lhs, + MatchAllOfGeneric<MatcherTs...>&& rhs) { + return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; + } + }; + + + template<typename... MatcherTs> + class MatchAnyOfGeneric final : public MatcherGenericBase { + public: + MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete; + MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete; + MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default; + MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default; + + MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} + explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {} + + template<typename Arg> + bool match(Arg&& arg) const { + return match_any_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + std::string describe() const override { + return describe_multi_matcher<MatcherTs...>(" or "_sr, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array<void const*, sizeof...( MatcherTs )> m_matchers; + + //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case + template<typename... MatchersRHS> + friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || ( + MatchAnyOfGeneric<MatcherTs...>&& lhs, + MatchAnyOfGeneric<MatchersRHS...>&& rhs) { + return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; + } + + //! Avoids type nesting for `GenericAnyOf || some matcher` case + template<typename MatcherRHS> + friend std::enable_if_t<is_matcher<MatcherRHS>::value, + MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || ( + MatchAnyOfGeneric<MatcherTs...>&& lhs, + MatcherRHS const& rhs) { + return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))}; + } + + //! Avoids type nesting for `some matcher || GenericAnyOf` case + template<typename MatcherLHS> + friend std::enable_if_t<is_matcher<MatcherLHS>::value, + MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || ( + MatcherLHS const& lhs, + MatchAnyOfGeneric<MatcherTs...>&& rhs) { + return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; + } + }; + + + template<typename MatcherT> + class MatchNotOfGeneric final : public MatcherGenericBase { + MatcherT const& m_matcher; + + public: + MatchNotOfGeneric(MatchNotOfGeneric const&) = delete; + MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete; + MatchNotOfGeneric(MatchNotOfGeneric&&) = default; + MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default; + + explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {} + + template<typename Arg> + bool match(Arg&& arg) const { + return !m_matcher.match(arg); + } + + std::string describe() const override { + return "not " + m_matcher.toString(); + } + + //! Negating negation can just unwrap and return underlying matcher + friend MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) { + return matcher.m_matcher; + } + }; + } // namespace Detail + + + // compose only generic matchers + template<typename MatcherLHS, typename MatcherRHS> + std::enable_if_t<Detail::are_generic_matchers<MatcherLHS, MatcherRHS>::value, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>> + operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + + template<typename MatcherLHS, typename MatcherRHS> + std::enable_if_t<Detail::are_generic_matchers<MatcherLHS, MatcherRHS>::value, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>> + operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + + //! Wrap provided generic matcher in generic negator + template<typename MatcherT> + std::enable_if_t<Detail::is_generic_matcher<MatcherT>::value, Detail::MatchNotOfGeneric<MatcherT>> + operator ! (MatcherT const& matcher) { + return Detail::MatchNotOfGeneric<MatcherT>{matcher}; + } + + + // compose mixed generic and non-generic matchers + template<typename MatcherLHS, typename ArgRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherLHS>::value, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>> + operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) { + return { lhs, rhs }; + } + + template<typename ArgLHS, typename MatcherRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherRHS>::value, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>> + operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + + template<typename MatcherLHS, typename ArgRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherLHS>::value, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>> + operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) { + return { lhs, rhs }; + } + + template<typename ArgLHS, typename MatcherRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherRHS>::value, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>> + operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_vector.hpp b/src/catch2/matchers/catch_matchers_vector.hpp new file mode 100644 index 0000000..fffbfdf --- /dev/null +++ b/src/catch2/matchers/catch_matchers_vector.hpp @@ -0,0 +1,194 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_VECTOR_HPP_INCLUDED +#define CATCH_MATCHERS_VECTOR_HPP_INCLUDED + +#include <catch2/matchers/catch_matchers.hpp> +#include <catch2/catch_approx.hpp> + +#include <algorithm> + +namespace Catch { +namespace Matchers { + + template<typename T, typename Alloc> + class VectorContainsElementMatcher final : public MatcherBase<std::vector<T, Alloc>> { + T const& m_comparator; + + public: + VectorContainsElementMatcher(T const& comparator): + m_comparator(comparator) + {} + + bool match(std::vector<T, Alloc> const& v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class ContainsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + + public: + ContainsMatcher(std::vector<T, AllocComp> const& comparator): + m_comparator( comparator ) + {} + + bool match(std::vector<T, AllocMatch> const& v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class EqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + + public: + EqualsMatcher(std::vector<T, AllocComp> const& comparator): + m_comparator( comparator ) + {} + + bool match(std::vector<T, AllocMatch> const& v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector<T> etc + // - then just call that directly + if ( m_comparator.size() != v.size() ) { return false; } + for ( std::size_t i = 0; i < v.size(); ++i ) { + if ( !( m_comparator[i] == v[i] ) ) { return false; } + } + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class ApproxMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + mutable Catch::Approx approx = Catch::Approx::custom(); + + public: + ApproxMatcher(std::vector<T, AllocComp> const& comparator): + m_comparator( comparator ) + {} + + bool match(std::vector<T, AllocMatch> const& v) const override { + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != approx(v[i])) + return false; + return true; + } + std::string describe() const override { + return "is approx: " + ::Catch::Detail::stringify( m_comparator ); + } + template <typename = std::enable_if_t<std::is_constructible<double, T>::value>> + ApproxMatcher& epsilon( T const& newEpsilon ) { + approx.epsilon(static_cast<double>(newEpsilon)); + return *this; + } + template <typename = std::enable_if_t<std::is_constructible<double, T>::value>> + ApproxMatcher& margin( T const& newMargin ) { + approx.margin(static_cast<double>(newMargin)); + return *this; + } + template <typename = std::enable_if_t<std::is_constructible<double, T>::value>> + ApproxMatcher& scale( T const& newScale ) { + approx.scale(static_cast<double>(newScale)); + return *this; + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class UnorderedEqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_target; + + public: + UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target): + m_target(target) + {} + bool match(std::vector<T, AllocMatch> const& vec) const override { + if (m_target.size() != vec.size()) { + return false; + } + return std::is_permutation(m_target.begin(), m_target.end(), vec.begin()); + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + }; + + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + //! Creates a matcher that matches vectors that contain all elements in `comparator` + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) { + return ContainsMatcher<T, AllocComp, AllocMatch>(comparator); + } + + //! Creates a matcher that matches vectors that contain `comparator` as an element + template<typename T, typename Alloc = std::allocator<T>> + VectorContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) { + return VectorContainsElementMatcher<T, Alloc>(comparator); + } + + //! Creates a matcher that matches vectors that are exactly equal to `comparator` + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) { + return EqualsMatcher<T, AllocComp, AllocMatch>(comparator); + } + + //! Creates a matcher that matches vectors that `comparator` as an element + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) { + return ApproxMatcher<T, AllocComp, AllocMatch>(comparator); + } + + //! Creates a matcher that matches vectors that is equal to `target` modulo permutation + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) { + return UnorderedEqualsMatcher<T, AllocComp, AllocMatch>(target); + } + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_VECTOR_HPP_INCLUDED diff --git a/src/catch2/matchers/internal/catch_matchers_impl.cpp b/src/catch2/matchers/internal/catch_matchers_impl.cpp new file mode 100644 index 0000000..41b462e --- /dev/null +++ b/src/catch2/matchers/internal/catch_matchers_impl.cpp @@ -0,0 +1,25 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include <catch2/matchers/internal/catch_matchers_impl.hpp> +#include <catch2/matchers/catch_matchers.hpp> +#include <catch2/interfaces/catch_interfaces_registry_hub.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +namespace Catch { + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr<std::string, StringMatcher const&> expr( CATCH_MOVE(exceptionMessage), matcher ); + handler.handleExpr( expr ); + } + +} // namespace Catch diff --git a/src/catch2/matchers/internal/catch_matchers_impl.hpp b/src/catch2/matchers/internal/catch_matchers_impl.hpp new file mode 100644 index 0000000..24a3f8b --- /dev/null +++ b/src/catch2/matchers/internal/catch_matchers_impl.hpp @@ -0,0 +1,109 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 +#ifndef CATCH_MATCHERS_IMPL_HPP_INCLUDED +#define CATCH_MATCHERS_IMPL_HPP_INCLUDED + +#include <catch2/internal/catch_assertion_handler.hpp> +#include <catch2/internal/catch_source_line_info.hpp> +#include <catch2/internal/catch_decomposer.hpp> +#include <catch2/internal/catch_preprocessor_internal_stringify.hpp> +#include <catch2/internal/catch_move_and_forward.hpp> + +#include <string> + +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; + MatcherT const& m_matcher; + public: + constexpr MatchExpr( ArgT && arg, MatcherT const& matcher ) + : ITransientExpression{ true, matcher.match( arg ) }, // not forwarding arg here on purpose + m_arg( CATCH_FORWARD(arg) ), + m_matcher( matcher ) + {} + + void streamReconstructedExpression( std::ostream& os ) const override { + os << Catch::Detail::stringify( m_arg ) + << ' ' + << m_matcher.toString(); + } + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + + + namespace Matchers { + template <typename ArgT> + class MatcherBase; + } + + using StringMatcher = Matchers::MatcherBase<std::string>; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher ); + + template<typename ArgT, typename MatcherT> + constexpr MatchExpr<ArgT, MatcherT> + makeMatchExpr( ArgT&& arg, MatcherT const& matcher ) { + return MatchExpr<ArgT, MatcherT>( CATCH_FORWARD(arg), matcher ); + } + +} // namespace Catch + + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + catchAssertionHandler.complete(); \ + } while( false ) + + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast<void>(__VA_ARGS__ ); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while( false ) + + +#endif // CATCH_MATCHERS_IMPL_HPP_INCLUDED |
