Catch2: Matchers interface

Created on 5 Jun 2018  Â·  6Comments  Â·  Source: catchorg/Catch2

Hello,

I wanted to extend matchers to cover functionalities known from gmock.
Starting from generic checks like "Contains" or "ElementsAre". Contains is defined for vector now, an cannot be easily extended to support other containers. Writing generic ElementsAre also fails for me.
In gmock they operate on any class that has container-like interface. You just write:
EXPECT_THAT(some_container, ElementsAre(e0, e1, ..., en));
In Catch we must directly specify container type under ElementsAre.
Gmock does this by trying to cast given matcher to expected one - at this point you can write generic conversion operator that will create matcher for specific container.

Maybe REQUIRE_THAT makro should be changed? Or current approach has some advantage that I fail to see?

BR

Feature Request

Most helpful comment

You can use it right now from the dev-v3 branch. Check tests for #1843 for examples.

All 6 comments

Hey there!

I recently started using Catch and stumbled across this question as I too wanted something to emulate the EXPECT_THAT call from Google Test.

I wound up writing something that relies on the gsl span type (will be added to the C++ standard in C++20 I believe)

The code looks like this:

#include "catch2/catch.hpp"

#include <sstream>

#include "gsl/span"

template<typename T>
class ElementsAreSpan
    : public Catch::MatcherBase<gsl::span<const T>>
{
    gsl::span<const T> m_span;
public:
    ElementsAreSpan(const T* const t, gsl::index size)
        : m_span(t, size) {}
    explicit ElementsAreSpan(gsl::span<const T> span)
        : m_span(span) {}

    bool match(const gsl::span<const T>& span) const override {
        return m_span == span;
    }

    std::string describe() const override {
        std::ostringstream ss;
        ss << "actual, expected: { ";
        for (const auto& element : m_span) {
            ss << element << ", ";
        }
        ss << "}";
        return ss.str();
    }
};

I'm currently compiling with C++17 which means you get class template argument deduction and can write code like this:

my_type_t result_end = calculate_something();
const float result_arr[] = {10.0f, 40.0f, 100.0f};

CHECK_THAT(span(result_end_arr), ElementsAreSpan(result_end.elems(), 3));

If you're not using C++17 you can wrap the calls in a function to avoid having to use this syntax:

CHECK_THAT(span<float>(result_end_arr), ElementsAreSpa<float>n(result_end.elems(), 3));

I hope this might be some use or might give you an idea about another approach.

Tom,

Your suggestion feels like a Catch::Matchers::Vector::Equals but for
span instead of vector.

I believe the Catch convention for something like this (following Vector's
precedent) would be Catch::Matchers::Span::Equals.

I wouldn't want to use the term "ElementsAre" here, because it's doing
something significantly different from Googletest's ElementsAre.
That Googletest function takes a sequence of element matchers and returns a
polymorphic matcher that can match any kind of sequence container for an
element-wise match against those element matchers.
https://github.com/google/googletest/blob/master/googlemock/docs/cheat_sheet.md#container-matchers

I feel it's wasteful for Catch2 to require a different variant of Equals
for each kind of sequence container, and that the Catch::Matchers::Vector
namespace is a useful first draft that should be generalized. Instead of
creating an Equals for each container type (vector, span, array, many
more), we would be better off making a Catch::Matcher::Sequence::Equals
template that can produce an element-wise Equals matcher for any sequence
type.

A sequence can be considered logically "equal" to an expected sequence and
still be of different type. This keeps the test author from having to
create a specific container type just to specify expectations. The test
author should be able to do that very concisely with a std::initializer
list, vector, or array, let's say.

On Tue, Jul 30, 2019 at 4:33 PM Tom Hulton-Harrop notifications@github.com
wrote:

Hey there!

I recently started using Catch and stumbled across this question as I too
wanted something to emulate the EXPECT_THAT call from Google Test.

I wound up writing something that relies on the gsl span type (will be
added to the C++ standard in C++20 I believe)

The code looks like this:

include "catch2/catch.hpp"

include

include "gsl/span"

template
class ElementsAreSpan
: public Catch::MatcherBase>
{
gsl::span m_span;
public:
ElementsAreSpan(const T* const t, gsl::index size)
: m_span(t, size) {}
explicit ElementsAreSpan(gsl::span span)
: m_span(span) {}

bool match(const gsl::span<const T>& span) const override {
    return m_span == span;
}

std::string describe() const override {
    std::ostringstream ss;
    ss << "actual, expected: { ";
    for (const auto& element : m_span) {
        ss << element << ", ";
    }
    ss << "}";
    return ss.str();
}

};

I'm currently compiling with C++17 which means you get class template
argument deduction and can write code like this:

my_type_t result_end = calculate_something();
const float result_arr[] = {10.0f, 40.0f, 100.0f};

CHECK_THAT(span(result_end_arr), ElementsAreSpan(result_end.elems(), 3));

If you're not using C++17 you can wrap the calls in a function to avoid
having to use this syntax:

CHECK_THAT(span(result_end_arr), ElementsAreSpan(result_end.elems(), 3));

I hope this might be some use or might give you an idea about another
approach.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/catchorg/Catch2/issues/1307?email_source=notifications&email_token=ACBWORG2AA5TBYFAPRWDUI3QCCQRXA5CNFSM4FDK2YZ2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3FHIXQ#issuecomment-516584542,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACBWORDLFMR7UDNSSJSSEC3QCCQRXANCNFSM4FDK2YZQ
.

Hey Billy,

Thanks for the feedback.

For some context, my matcher was just a quick experiment to try and help port some existing tests I had from Google Test to Catch2. I like the Hamcrest style naming of ElementsAre which is why I was following that convention.

I actually wound up moving away from the ElementsAreSpan type and wrote another version called ElementsAreSubscript which will work on any type so long as it provides the subscript [] operator.

I've combined this with gsl::span so in the matcher you have one templated type T with the subscript operator and the range to check against is a gsl::span (for example in my case I have a vector3 type and a number of array/span values to check against).

You could take this one step further and have two templated types that can both be subscripted but unfortunately, usage gets more complicated with template deductions where you have to wind up manually specifying both types which can be a bit of a pain...

So at the moment, I have code that looks like this

constexpr real_t x_axis[] = { 1.0f, 0.0f };
CHECK_THAT(make_span(x_axis), make_elements_sub(as::vec2::axis_x(), 2)); // function to avoid needing ctad - naming isn't great :P

whereas it could look like this...

constexpr real_t x_axis[] = { 1.0f, 1.0f };
auto matcher = make_elements_sub<vec2_t, decltype(x_axis)>(as::vec2::axis_x(), 2); // must be initialized outside CHECK_THAT otherwise you get compile errors about too many arguments provided to function-like macro invocation
CHECK_THAT(x_axis, matcher);

but it doesn't look that great either...

Do you have an example of what you had in mind? I'd be happy to port my code over to something more idiomatic.

Thanks!

Tom

This will be possible for Matchers in v3.

Oh awesome! 😄 Is this something we can do today or in the near future? Thanks!

You can use it right now from the dev-v3 branch. Check tests for #1843 for examples.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zwcloud picture zwcloud  Â·  5Comments

Eyenseo picture Eyenseo  Â·  8Comments

arnavb picture arnavb  Â·  7Comments

offa picture offa  Â·  7Comments

david8dixon picture david8dixon  Â·  7Comments