-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Showing
15 changed files
with
1,192 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#include <catch2/catch_matchers_templates.hpp> | ||
|
||
namespace Catch { | ||
namespace Matchers { | ||
namespace Impl { | ||
MatcherGenericBase::~MatcherGenericBase() {} | ||
|
||
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 += (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 Matchers | ||
} // namespace Catch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.