Add a DistanceFrom() matcher for general distance comparison.
We have a bunch of matchers for asserting that a value is near the target value, e.g. `DoubleNear()` and `FloatNear()`. These matchers only work for specific types (`double` and `float`). They are not flexible enough to support other types that have the notion of a "distance" (e.g. N-dimensional points and vectors, which are commonly used in ML). In this diff, we generalize the idea to a `DistanceFrom(target, get_distance, m)` matcher that works on arbitrary types that have the "distance" concept (the `get_distance` argument is optional and can be omitted for types that support `-`, and `std::abs()`). What it does: 1. compute the distance between the value and the target using `get_distance(value, target)`; if `get_distance` is omitted, compute the distance as `std::abs(value - target)`. 2. match the distance against matcher `m`; if the match succeeds, the `DistanceFrom()` match succeeds. Examples: ``` // 0.5's distance from 0.6 should be <= 0.2. EXPECT_THAT(0.5, DistanceFrom(0.6, Le(0.2))); Vector2D v1(3.0, 4.0), v2(3.2, 6.0); // v1's distance from v2, as computed by EuclideanDistance(v1, v2), // should be >= 1.0. EXPECT_THAT(v1, DistanceFrom(v2, EuclideanDistance, Ge(1.0))); ``` PiperOrigin-RevId: 734593292 Change-Id: Id6bb7074dc4aa4d8abd78b57ad2426637e590de5
This commit is contained in:
committed by
Copybara-Service
parent
e88cb95b92
commit
0bdccf4aa2
@@ -2855,6 +2855,54 @@ class ContainsMatcherImpl : public QuantifierMatcherImpl<Container> {
|
||||
}
|
||||
};
|
||||
|
||||
// Implements DistanceFrom(target, get_distance, distance_matcher) for the given
|
||||
// argument types:
|
||||
// * V is the type of the value to be matched.
|
||||
// * T is the type of the target value.
|
||||
// * Distance is the type of the distance between V and T.
|
||||
// * GetDistance is the type of the functor for computing the distance between
|
||||
// V and T.
|
||||
template <typename V, typename T, typename Distance, typename GetDistance>
|
||||
class DistanceFromMatcherImpl : public MatcherInterface<V> {
|
||||
public:
|
||||
// Arguments:
|
||||
// * target: the target value.
|
||||
// * get_distance: the functor for computing the distance between the value
|
||||
// being matched and target.
|
||||
// * distance_matcher: the matcher for checking the distance.
|
||||
DistanceFromMatcherImpl(T target, GetDistance get_distance,
|
||||
Matcher<const Distance&> distance_matcher)
|
||||
: target_(std::move(target)),
|
||||
get_distance_(std::move(get_distance)),
|
||||
distance_matcher_(std::move(distance_matcher)) {}
|
||||
|
||||
// Describes what this matcher does.
|
||||
void DescribeTo(::std::ostream* os) const override {
|
||||
distance_matcher_.DescribeTo(os);
|
||||
*os << " away from " << PrintToString(target_);
|
||||
}
|
||||
|
||||
void DescribeNegationTo(::std::ostream* os) const override {
|
||||
distance_matcher_.DescribeNegationTo(os);
|
||||
*os << " away from " << PrintToString(target_);
|
||||
}
|
||||
|
||||
bool MatchAndExplain(V value, MatchResultListener* listener) const override {
|
||||
const auto distance = get_distance_(value, target_);
|
||||
const bool match = distance_matcher_.Matches(distance);
|
||||
if (!match && listener->IsInterested()) {
|
||||
*listener << "which is " << PrintToString(distance) << " away from "
|
||||
<< PrintToString(target_);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
private:
|
||||
const T target_;
|
||||
const GetDistance get_distance_;
|
||||
const Matcher<const Distance&> distance_matcher_;
|
||||
};
|
||||
|
||||
// Implements Each(element_matcher) for the given argument type Container.
|
||||
// Symmetric to ContainsMatcherImpl.
|
||||
template <typename Container>
|
||||
@@ -2990,6 +3038,50 @@ auto Second(T& x, Rank1) -> decltype((x.second)) { // NOLINT
|
||||
}
|
||||
} // namespace pair_getters
|
||||
|
||||
// Default functor for computing the distance between two values.
|
||||
struct DefaultGetDistance {
|
||||
template <typename T, typename U>
|
||||
auto operator()(const T& lhs, const U& rhs) const {
|
||||
return std::abs(lhs - rhs);
|
||||
}
|
||||
};
|
||||
|
||||
// Implements polymorphic DistanceFrom(target, get_distance, distance_matcher)
|
||||
// matcher. Template arguments:
|
||||
// * T is the type of the target value.
|
||||
// * GetDistance is the type of the functor for computing the distance between
|
||||
// the value being matched and the target.
|
||||
// * DistanceMatcher is the type of the matcher for checking the distance.
|
||||
template <typename T, typename GetDistance, typename DistanceMatcher>
|
||||
class DistanceFromMatcher {
|
||||
public:
|
||||
// Arguments:
|
||||
// * target: the target value.
|
||||
// * get_distance: the functor for computing the distance between the value
|
||||
// being matched and target.
|
||||
// * distance_matcher: the matcher for checking the distance.
|
||||
DistanceFromMatcher(T target, GetDistance get_distance,
|
||||
DistanceMatcher distance_matcher)
|
||||
: target_(std::move(target)),
|
||||
get_distance_(std::move(get_distance)),
|
||||
distance_matcher_(std::move(distance_matcher)) {}
|
||||
|
||||
DistanceFromMatcher(const DistanceFromMatcher& other) = default;
|
||||
|
||||
// Implicitly converts to a monomorphic matcher of the given type.
|
||||
template <typename V>
|
||||
operator Matcher<V>() const { // NOLINT
|
||||
using Distance = decltype(get_distance_(std::declval<V>(), target_));
|
||||
return Matcher<V>(new DistanceFromMatcherImpl<V, T, Distance, GetDistance>(
|
||||
target_, get_distance_, distance_matcher_));
|
||||
}
|
||||
|
||||
private:
|
||||
const T target_;
|
||||
const GetDistance get_distance_;
|
||||
const DistanceMatcher distance_matcher_;
|
||||
};
|
||||
|
||||
// Implements Key(inner_matcher) for the given argument pair type.
|
||||
// Key(inner_matcher) matches an std::pair whose 'first' field matches
|
||||
// inner_matcher. For example, Contains(Key(Ge(5))) can be used to match an
|
||||
@@ -4372,6 +4464,42 @@ inline internal::FloatingEqMatcher<double> DoubleNear(double rhs,
|
||||
return internal::FloatingEqMatcher<double>(rhs, false, max_abs_error);
|
||||
}
|
||||
|
||||
// The DistanceFrom(target, get_distance, m) and DistanceFrom(target, m)
|
||||
// matchers work on arbitrary types that have the "distance" concept. What they
|
||||
// do:
|
||||
//
|
||||
// 1. compute the distance between the value and the target using
|
||||
// get_distance(value, target) if get_distance is provided; otherwise compute
|
||||
// the distance as std::abs(value - target).
|
||||
// 2. match the distance against the user-provided matcher m; if the match
|
||||
// succeeds, the DistanceFrom() match succeeds.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// // 0.5's distance from 0.6 should be <= 0.2.
|
||||
// EXPECT_THAT(0.5, DistanceFrom(0.6, Le(0.2)));
|
||||
//
|
||||
// Vector2D v1(3.0, 4.0), v2(3.2, 6.0);
|
||||
// // v1's distance from v2, as computed by EuclideanDistance(v1, v2),
|
||||
// // should be >= 1.0.
|
||||
// EXPECT_THAT(v1, DistanceFrom(v2, EuclideanDistance, Ge(1.0)));
|
||||
|
||||
template <typename T, typename GetDistance, typename DistanceMatcher>
|
||||
inline internal::DistanceFromMatcher<T, GetDistance, DistanceMatcher>
|
||||
DistanceFrom(T target, GetDistance get_distance,
|
||||
DistanceMatcher distance_matcher) {
|
||||
return internal::DistanceFromMatcher<T, GetDistance, DistanceMatcher>(
|
||||
std::move(target), std::move(get_distance), std::move(distance_matcher));
|
||||
}
|
||||
|
||||
template <typename T, typename DistanceMatcher>
|
||||
inline internal::DistanceFromMatcher<T, internal::DefaultGetDistance,
|
||||
DistanceMatcher>
|
||||
DistanceFrom(T target, DistanceMatcher distance_matcher) {
|
||||
return DistanceFrom(std::move(target), internal::DefaultGetDistance(),
|
||||
std::move(distance_matcher));
|
||||
}
|
||||
|
||||
// Creates a matcher that matches any double argument approximately equal to
|
||||
// rhs, up to the specified max absolute error bound, including NaN values when
|
||||
// rhs is NaN. The max absolute error bound must be non-negative.
|
||||
|
||||
Reference in New Issue
Block a user