1
0
mirror of https://github.com/catchorg/Catch2.git synced 2025-05-03 05:23:52 +00:00
Catch2/src/catch2/catch_matchers_templates.hpp
melak47 17c4b2d093
Feature: generic matchers (#1843)
This commit extends the Matchers feature with the ability to have type-independent (e.g. templated) matchers. This is done by adding a new base type that Matchers can extend, `MatcherGenericBase`, and overloads of operators `!`, `&&` and `||` that handle matchers extending `MatcherGenericBase` in a special manner.

These new matchers can also take their arguments as values and non-const references.

Closes #1307 
Closes #1553 
Closes #1554 

Co-authored-by: Martin Hořeňovský <martin.horenovsky@gmail.com>
2020-02-16 11:19:10 +01:00

263 lines
11 KiB
C++

#ifndef TWOBLUECUBES_CATCH_MATCHERS_TEMPLATES_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_MATCHERS_TEMPLATES_HPP_INCLUDED
#include <catch2/catch_common.h>
#include <catch2/catch_matchers.h>
#include <catch2/catch_stringref.h>
#include <array>
#include <algorithm>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
namespace Catch {
namespace Matchers {
namespace Impl {
template<typename... MatcherTs> struct MatchAllOfGeneric;
template<typename... MatcherTs> struct MatchAnyOfGeneric;
template<typename MatcherT> struct MatchNotOfGeneric;
struct MatcherGenericBase : MatcherUntypedBase {
virtual ~MatcherGenericBase();
};
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;
}
#ifdef CATCH_CPP17_OR_GREATER
using std::conjunction;
#else // CATCH_CPP17_OR_GREATER
template<typename... Cond>
struct conjunction : std::true_type {};
template<typename Cond, typename... Rest>
struct conjunction<Cond, Rest...> : std::integral_constant<bool, Cond::value && conjunction<Rest...>::value> {};
#endif // CATCH_CPP17_OR_GREATER
template<typename T>
using is_generic_matcher = std::is_base_of<
Catch::Matchers::Impl::MatcherGenericBase,
typename std::remove_cv<typename std::remove_reference<T>::type>::type
>;
template<typename... Ts>
using are_generic_matchers = conjunction<is_generic_matcher<Ts>...>;
template<typename T>
using is_matcher = std::is_base_of<
Catch::Matchers::Impl::MatcherUntypedBase,
typename std::remove_cv<typename std::remove_reference<T>::type>::type
>;
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>
struct MatchAllOfGeneric : MatcherGenericBase {
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...>{});
}
std::array<void const*, sizeof...(MatcherTs)> m_matchers;
};
template<typename... MatcherTs>
struct MatchAnyOfGeneric : MatcherGenericBase {
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...>{});
}
std::array<void const*, sizeof...(MatcherTs)> m_matchers;
};
template<typename MatcherT>
struct MatchNotOfGeneric : MatcherGenericBase {
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();
}
MatcherT const& m_matcher;
};
// compose only generic matchers
template<typename MatcherLHS, typename MatcherRHS>
typename std::enable_if<are_generic_matchers<MatcherLHS, MatcherRHS>::value, MatchAllOfGeneric<MatcherLHS, MatcherRHS>>::type
operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) {
return {lhs, rhs};
}
template<typename MatcherLHS, typename MatcherRHS>
typename std::enable_if<are_generic_matchers<MatcherLHS, MatcherRHS>::value, MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>::type
operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) {
return {lhs, rhs};
}
template<typename MatcherT>
typename std::enable_if<is_generic_matcher<MatcherT>::value, MatchNotOfGeneric<MatcherT>>::type
operator ! (MatcherT const& matcher) {
return MatchNotOfGeneric<MatcherT>{matcher};
}
// compose mixed generic and non-generic matchers
template<typename MatcherLHS, typename ArgRHS>
typename std::enable_if<is_generic_matcher<MatcherLHS>::value, MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>::type
operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
return {lhs, rhs};
}
template<typename ArgLHS, typename MatcherRHS>
typename std::enable_if<is_generic_matcher<MatcherRHS>::value, MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>::type
operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
return {lhs, rhs};
}
template<typename MatcherLHS, typename ArgRHS>
typename std::enable_if<is_generic_matcher<MatcherLHS>::value, MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>>::type
operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) {
return {lhs, rhs};
}
template<typename ArgLHS, typename MatcherRHS>
typename std::enable_if<is_generic_matcher<MatcherRHS>::value, MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>>::type
operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) {
return {lhs, rhs};
}
// avoid deep nesting of matcher templates
template<typename... MatchersLHS, typename... MatchersRHS>
MatchAllOfGeneric<MatchersLHS..., MatchersRHS...>
operator && (MatchAllOfGeneric<MatchersLHS...> && lhs, MatchAllOfGeneric<MatchersRHS...> && rhs) {
return MatchAllOfGeneric<MatchersLHS..., MatchersRHS...>{array_cat(std::move(lhs.m_matchers), std::move(rhs.m_matchers))};
}
template<typename... MatchersLHS, typename MatcherRHS>
typename std::enable_if<is_matcher<MatcherRHS>::value, MatchAllOfGeneric<MatchersLHS..., MatcherRHS>>::type
operator && (MatchAllOfGeneric<MatchersLHS...> && lhs, MatcherRHS const& rhs) {
return MatchAllOfGeneric<MatchersLHS..., MatcherRHS>{array_cat(std::move(lhs.m_matchers), static_cast<void const*>(&rhs))};
}
template<typename MatcherLHS, typename... MatchersRHS>
typename std::enable_if<is_matcher<MatcherLHS>::value, MatchAllOfGeneric<MatcherLHS, MatchersRHS...>>::type
operator && (MatcherLHS const& lhs, MatchAllOfGeneric<MatchersRHS...> && rhs) {
return MatchAllOfGeneric<MatcherLHS, MatchersRHS...>{array_cat(static_cast<void const*>(std::addressof(lhs)), std::move(rhs.m_matchers))};
}
template<typename... MatchersLHS, typename... MatchersRHS>
MatchAnyOfGeneric<MatchersLHS..., MatchersRHS...>
operator || (MatchAnyOfGeneric<MatchersLHS...> && lhs, MatchAnyOfGeneric<MatchersRHS...> && rhs) {
return MatchAnyOfGeneric<MatchersLHS..., MatchersRHS...>{array_cat(std::move(lhs.m_matchers), std::move(rhs.m_matchers))};
}
template<typename... MatchersLHS, typename MatcherRHS>
typename std::enable_if<is_matcher<MatcherRHS>::value, MatchAnyOfGeneric<MatchersLHS..., MatcherRHS>>::type
operator || (MatchAnyOfGeneric<MatchersLHS...> && lhs, MatcherRHS const& rhs) {
return MatchAnyOfGeneric<MatchersLHS..., MatcherRHS>{array_cat(std::move(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))};
}
template<typename MatcherLHS, typename... MatchersRHS>
typename std::enable_if<is_matcher<MatcherLHS>::value, MatchAnyOfGeneric<MatcherLHS, MatchersRHS...>>::type
operator || (MatcherLHS const& lhs, MatchAnyOfGeneric<MatchersRHS...> && rhs) {
return MatchAnyOfGeneric<MatcherLHS, MatchersRHS...>{array_cat(static_cast<void const*>(std::addressof(lhs)), std::move(rhs.m_matchers))};
}
template<typename MatcherT>
MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) {
return matcher.m_matcher;
}
} // namespace Impl
} // namespace Matchers
using namespace Matchers;
using Matchers::Impl::MatcherGenericBase;
} // namespace Catch
#endif //TWOBLUECUBES_CATCH_MATCHERS_TEMPLATES_HPP_INCLUDED