Catch2: REQUIRE_THROWS_MATCHES compile error

Created on 26 Jul 2019  Â·  8Comments  Â·  Source: catchorg/Catch2

Describe the bug
A clear and concise description of what the bug is.
The assertion REQUIRE_THROWS_MATCHES doesn't seem to accept string matcher.

Expected behavior
I'd expect it to compile and give an succeed in this case, or fail if an exception other than std::invalid_argument is thrown or the description doesn't match.

Reproduction steps
Steps to reproduce the bug.

Compile this example code. For the compiler features I only selected C++17. Note that if I comment the REQUIRE_THROWS_MATCHES line, it compiles successfully and executes the tests as expected.

target_compile_features(${PROJECT_NAME}
    PRIVATE
      cxx_std_17
  )
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"

#include <exception>

void SomeFunction(int i) {
    if (i > 10) {
      throw std::invalid_argument("I must be smaller than 10");
    }
}

TEST_CASE( "My test", "[group]" ) {
    REQUIRE_THROWS_AS(SomeFunction(22), std::invalid_argument); // Compiles OK
    REQUIRE_THROWS_WITH(SomeFunction(22), "I must be smaller than 10" ); // // Compiles OK
    REQUIRE_THROWS_MATCHES(SomeFunction(22), std::invalid_argument, "I must be smaller than 10"); // Compile error
}

Platform information:

  • OS: macOS
  • Compiler+version: Apple LLVM version 10.0.1 (clang-1001.0.46.4)
  • Catch version: v2.9.1

And I'm using catch as CMake module with the following options changed.

  option(CATCH_BUILD_TESTING "Build SelfTest project" OFF)
  option(CATCH_INSTALL_DOCS "Install documentation alongside library" OFF)
  option(CATCH_INSTALL_HELPERS "Install contrib alongside library" OFF)
Not a bug

All 8 comments

The third argument to *_THROWS_MATCHES must be a real matcher, not just a string literal.

I'll check the documentation to see if it can be made clearer.

@horenmar Thanks for your reply. If I understood correctly (also from the examples) the matchers are e.g. Catch::StartsWith. Therefore, I changed the code to the following, but am still getting the compile error (see below).

Code

REQUIRE_THROWS_MATCHES(SomeFunction(22), std::invalid_argument, Catch::StartsWith("I must be smaller than")); // Compile error

Compile error

[build] Starting build
[proc] Executing command: /home/linuxbrew/.linuxbrew/bin/cmake --build /home/jdebruijn/withthegrid/projects/emb-system/build --config Debug --target SystemTest -- -j 6
[build] [1/3  33% :: 6.954] Building CXX object CMakeFiles/SystemTest.dir/test/runner.cpp.o
[build] FAILED: CMakeFiles/SystemTest.dir/test/runner.cpp.o 
[build] /usr/bin/c++   -I../source -I../modules/Catch2/single_include -g   -std=gnu++17 -MD -MT CMakeFiles/SystemTest.dir/test/runner.cpp.o -MF CMakeFiles/SystemTest.dir/test/runner.cpp.o.d -o CMakeFiles/SystemTest.dir/test/runner.cpp.o -c ../test/runner.cpp
[build] In file included from ../test/runner.cpp:2:
[build] ../modules/Catch2/single_include/catch2/catch.hpp: In instantiation of ‘Catch::MatchExpr<ArgT, MatcherT>::MatchExpr(const ArgT&, const MatcherT&, const Catch::StringRef&) [with ArgT = std::invalid_argument; MatcherT = Catch::Matchers::StdString::StartsWithMatcher]’:
[build] ../modules/Catch2/single_include/catch2/catch.hpp:3661:16:   required from ‘Catch::MatchExpr<ArgT, MatcherT> Catch::makeMatchExpr(const ArgT&, const MatcherT&, const Catch::StringRef&) [with ArgT = std::invalid_argument; MatcherT = Catch::Matchers::StdString::StartsWithMatcher]’
[build] ../test/runner.cpp:13:5:   required from here
[build] ../modules/Catch2/single_include/catch2/catch.hpp:3642:44: error: cannot convert ‘const std::invalid_argument’ to ‘const string&’ {aka ‘const std::__cxx11::basic_string<char>&’}
[build]  3642 |             m_matcherString( matcherString )
[build]       |                                            ^
[build] In file included from ../test/runner.cpp:2:
[build] ../modules/Catch2/single_include/catch2/catch.hpp:11174:59: note:   initializing argument 1 of ‘virtual bool Catch::Matchers::StdString::StartsWithMatcher::match(const string&) const’
[build] 11174 |         bool StartsWithMatcher::match( std::string const& source ) const {
[build]       |                                        ~~~~~~~~~~~~~~~~~~~^~~~~~
[build] In file included from ../test/runner.cpp:2:
[build] ../modules/Catch2/single_include/catch2/catch.hpp:3642:44: error: no matching function for call to ‘Catch::ITransientExpression::ITransientExpression(<brace-enclosed initializer list>)’
[build]  3642 |             m_matcherString( matcherString )
[build]       |                                            ^
[build] In file included from ../test/runner.cpp:2:
[build] ../modules/Catch2/single_include/catch2/catch.hpp:2147:9: note: candidate: ‘Catch::ITransientExpression::ITransientExpression(bool, bool)’
[build]  2147 |         ITransientExpression( bool isBinaryExpression, bool result )
[build]       |         ^~~~~~~~~~~~~~~~~~~~
[build] ../modules/Catch2/single_include/catch2/catch.hpp:2147:9: note:   conversion of argument 2 would be ill-formed:
[build] ../modules/Catch2/single_include/catch2/catch.hpp:2142:12: note: candidate: ‘constexpr Catch::ITransientExpression::ITransientExpression(const Catch::ITransientExpression&)’
[build]  2142 |     struct ITransientExpression {
[build]       |            ^~~~~~~~~~~~~~~~~~~~
[build] ../modules/Catch2/single_include/catch2/catch.hpp:2142:12: note:   candidate expects 1 argument, 2 provided
[build] ninja: build stopped: subcommand failed.
[build] Build finished with exit code 1

No, the full macro requires type specific matches, as they are aimed towards validating custom exception types and logic, e.g. checking that the parse exception properly reports line and column information:

struct ParseException : std::exception {
    size_t line, col;
    ParseException(size_t l, size_t c):
        line(l), col(c) {}
};

void foo() {
    throw ParseException(2, 3);
}

class ParseMatcher : public Catch::MatcherBase<ParseException> {    
    size_t target_line, target_col;
public:
    ParseMatcher(size_t line, size_t col):
        target_line(line), target_col(col)
    {}

    bool match(ParseException const& pex) const override {
        return pex.line == target_line && pex.col == target_col;
    }

    std::string describe() const override { return ""; }
};

TEST_CASE("showcase") {
    REQUIRE_THROWS_MATCHES(foo(), ParseException, ParseMatcher(2, 3));
}

Thanks for elaborating. I guess that _ matcher for given exception type_ of REQUIRE_THROWS_MATCHES in the docs then means the full matcher as you explained above.
I think I've confused that with the _string matcher_ of e.g. REQUIRE_THROWS_WITH.

Basically the thing I want to check is whether the correct standard exception (std::invalid_argument) is thrown and that is has a proper description. I'm currently doing so with REQUIRE_THROWS_AS and REQUIRE_THROWS_WITH, respectively. IMHO the downside to that is that I have to call the same function multipel times for the two checks.
@horenmar Do you think that is the way to go or could I perhaps adopt the matcher you've shown above for std::invalid_argument?

If all you want is a simple message match and type match for an exception, the easiest thing to do is something like this

#define MY_REQUIRE_THROWS_WITH(expr, type, message_match) \
    REQUIRE_THROWS_AS(expr, type); \
    REQUIRE_THROWS_MATCHES(expr, message_match)

and then use the MY_REQUIRE_THROWS_WITH macro inside the tests. This has the drawback of calling the expression twice, but it is the simplest way to get the result you want.

The other possibility is to define your own matcher like I've shown... You can even use the generic predicate matcher Catch already provides, like this

TEST_CASE("showcase2") {
    REQUIRE_THROWS_MATCHES(foo(), std::exception,
        Catch::Matchers::Predicate<std::exception>([](std::exception const& e) { return e.what() == "some-message"; })
    );
}

I couldn't get the Predicate work. It compiles but gives me the assertion error below, which I believe is from a missing description somewhere. Maybe I'm doing something wrong here?

../test/models/test_app_instruction.cpp:196: FAILED:
  REQUIRE_THROWS_MATCHES( wtg::models::AppInstruction("key", 0ms), std::invalid_argument, Catch::Matchers::Predicate<std::invalid_argument>([](std::invalid_argument const& e) { return e.what() == "timeout MUST be positive"; }) )
with expansion:
  timeout MUST be positive matches undescribed predicate

I also fiddled a bit around with the matcher you described in a previous commend and got that working nicely. I now use the following matcher and test. That does indeed fail when either the exception type or the description doesn't match. @horenmar Thanks for your help!

template <typename T>
class ExceptionMatcher : public Catch::MatcherBase<T> {
public:
    ExceptionMatcher(std::string expected_description) : _expected_description(expected_description) {}

    bool match(T const &e) const override {
        return e.what() == _expected_description;
    }

    std::string describe() const override {
        return "equals: \"" + _expected_description + '"';

private:
    std::string _expected_description;
};


// Test
REQUIRE_THROWS_MATCHES(wtg::models::AppInstruction("key", 0ms), std::invalid_argument,
            ExceptionMatcher<std::invalid_argument>{"timeout MUST be positivww"}
        );

One thing that that I noticed is that the message for mismatch in description is missing the " around the left-hand side. See the following two FAILED messages below. Is that a bug or is that intentional?

  REQUIRE_THROWS_WITH( wtg::models::AppInstruction("key", 0ms), "timeout MUST be positiv" )
with expansion:
  "timeout MUST be positive" equals: "timeout MUST be positiv"

  REQUIRE_THROWS_MATCHES( wtg::models::AppInstruction("key", 0ms), std::invalid_argument, ExceptionMatcher<std::invalid_argument>{"timeout MUST be positivww"} )
with expansion:
  timeout MUST be positive equals: "timeout MUST be positivww"

For your predicate example, you are comparing two char* and not strings. This ends up comparing the pointer values, instead of the underlying strings. This would work:

REQUIRE_THROWS_MATCHES( wtg::models::AppInstruction("key", 0ms), std::invalid_argument, Catch::Matchers::Predicate<std::invalid_argument>([](std::invalid_argument const& e) { return e.what() == std::string("timeout MUST be positive"); }) );

As to the description, Predicate takes a second argument that is the description string. If you do not provide any, it will self-describe as "undescribed predicate".


The difference in output is interesting, I'll take a look at it separately.

Awesome, the predicate also works now. Thanks 😄

The difference in output is interesting, I'll take a look at it separately.

Perfect, thank you. I see that the output difference is also there with the predicate.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

johnthagen picture johnthagen  Â·  3Comments

qneverless picture qneverless  Â·  6Comments

pwinston picture pwinston  Â·  5Comments

emil-e picture emil-e  Â·  8Comments

Viatorus picture Viatorus  Â·  3Comments