Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matchers interface #1307

Closed
visstro opened this issue Jun 5, 2018 · 6 comments
Closed

Matchers interface #1307

visstro opened this issue Jun 5, 2018 · 6 comments

Comments

@visstro
Copy link

visstro commented Jun 5, 2018

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

@pr0g
Copy link

pr0g commented Jul 30, 2019

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.

@BillyDonahue
Copy link

BillyDonahue commented Sep 20, 2019 via email

@pr0g
Copy link

pr0g commented Sep 22, 2019

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

horenmar added a commit that referenced this issue Feb 16, 2020
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>
@horenmar
Copy link
Member

This will be possible for Matchers in v3.

@pr0g
Copy link

pr0g commented Feb 16, 2020

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

@horenmar
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants