diff --git a/include/trompeloeil.hpp b/include/trompeloeil.hpp index 990636a..45ec5d7 100644 --- a/include/trompeloeil.hpp +++ b/include/trompeloeil.hpp @@ -32,6 +32,9 @@ #include "trompeloeil/matcher/any.hpp" #include "trompeloeil/matcher/compare.hpp" #include "trompeloeil/matcher/deref.hpp" +#if TROMPELOEIL_CPLUSPLUS >= 201402L +#include "trompeloeil/matcher/member_is.hpp" +#endif #include "trompeloeil/matcher/not.hpp" #if TROMPELOEIL_CPLUSPLUS >= 201402L #include "trompeloeil/matcher/range.hpp" diff --git a/include/trompeloeil/matcher/member_is.hpp b/include/trompeloeil/matcher/member_is.hpp new file mode 100644 index 0000000..1a21781 --- /dev/null +++ b/include/trompeloeil/matcher/member_is.hpp @@ -0,0 +1,61 @@ +/* +* Trompeloeil C++ mocking framework +* +* Copyright (C) Björn Fahller +* Copyright (C) Andrew Paxie +* +* Use, modification and distribution is subject to the +* Boost Software License, Version 1.0. (See accompanying +* file LICENSE_1_0.txt or copy atl +* http://www.boost.org/LICENSE_1_0.txt) +* +* Project home: https://github.com/rollbear/trompeloeil + */ + +#ifndef TROMPELOEIL_MEMBER_IS_HPP +#define TROMPELOEIL_MEMBER_IS_HPP + +#ifndef TROMPELOEIL_MATCHER_HPP +#include "../matcher.hpp" +#endif + +namespace trompeloeil +{ + +namespace impl { +template +struct member_is_matcher +{ + template + bool operator()(const V& v, const C& c) const + { + return trompeloeil::param_matches(c, std::ref(m(v))); + } + M m; +}; +} + +template +auto match_member_is(M m, const char* name, C&& c) +{ + return trompeloeil::make_matcher( + impl::member_is_matcher{m}, + [name](std::ostream& os, const C& compare) { + os << ' ' << name << compare; + }, + std::forward(c) + ); +} +} + +#define TROMPELOEIL_MEMBER_IS(member_name, compare) \ + trompeloeil::match_member_is( \ + std::mem_fn(member_name), \ + #member_name, \ + compare) + +#ifndef TROMPELOEIL_LONG_MACROS +#define MEMBER_IS TROMPELOEIL_MEMBER_IS +#endif + +#endif // TROMPELOEIL_MEMBER_IS_HPP diff --git a/test/compiling_tests_14.cpp b/test/compiling_tests_14.cpp index 1842193..35c23d8 100644 --- a/test/compiling_tests_14.cpp +++ b/test/compiling_tests_14.cpp @@ -4331,6 +4331,109 @@ TEST_CASE_METHOD( REQUIRE(reports.empty()); } +struct xy_coord { + int x; + int y; + friend std::ostream& operator<<(std::ostream& os, xy_coord c) + { + return os << "{ .x=" << c.x << ", .y=" << c.y << " }"; + } +}; +struct xy_mock { + MAKE_MOCK1(func, void(const xy_coord&)); + MAKE_MOCK1(vfunc, void(const std::vector&)); +}; + + +TEST_CASE_METHOD( + Fixture, + "C++14: MEMBER_IS can match a public member of a struct", + "[C++14][matching][matchers][MEMBER_IS]" + ) +{ + { + xy_mock m; + using trompeloeil::eq; + REQUIRE_CALL(m, func(MEMBER_IS(&xy_coord::x, eq(3)))); + m.func({3, 2}); + } + REQUIRE(reports.empty()); +} + +TEST_CASE_METHOD( + Fixture, + "C++14: MEMBER_IS mismatching is reported", + "[C++14][matching][matchers][MEMBER_IS]" + ) +{ + try { + xy_mock m; + using trompeloeil::eq; + REQUIRE_CALL(m, func(MEMBER_IS(&xy_coord::x, eq(3)))); + m.func({1, 2}); + FAIL("didn't throw"); + } + catch (reported) { + REQUIRE(reports.size() == 1U); + INFO("report=" << reports.front().msg); + auto re = R":(No match for call of func with signature void\(const xy_coord&\) with\. + param _1 == \{ \.x=1, \.y=2 \} + +Tried m\.func\(MEMBER_IS\(&xy_coord::x, eq\(3\)\)\) at [A-Za-z0-9_ ./:\]*:[0-9]*.* + Expected _1 &xy_coord::x == 3):"; + REQUIRE(std::regex_search(reports.front().msg, std::regex(re))); + } +} + +TEST_CASE_METHOD( + Fixture, + "C++14: MEMBER_IS, range_is_all and any_of can be composed", + "[C++14][matching][matchers][MEMBER_IS][range_is_all][any_of]" + ) +{ + { + xy_mock m; + using trompeloeil::lt; + using trompeloeil::gt; + using trompeloeil::any_of; + using trompeloeil::range_is_all; + REQUIRE_CALL(m, vfunc(range_is_all(any_of(MEMBER_IS(&xy_coord::x, gt(0)), MEMBER_IS(&xy_coord::y, lt(0)))))); + m.vfunc({{1,1},{0,-1},{-1,-1}}); + } + REQUIRE(reports.empty()); +} + +TEST_CASE_METHOD( + Fixture, + "C++14: A failed composition of MEMBER_IS, range_is_all and any_of is reported", + "[C++14][matching][matchers][MEMBER_IS][range_is_all][any_of]" +) +{ + try { + xy_mock m; + using trompeloeil::lt; + using trompeloeil::gt; + using trompeloeil::any_of; + using trompeloeil::range_is_all; + REQUIRE_CALL(m, vfunc(range_is_all(any_of(MEMBER_IS(&xy_coord::x, gt(0)), MEMBER_IS(&xy_coord::y, lt(0)))))); + m.vfunc({{1,1},{0,-1},{-1,1}}); + FAIL("didn't throw"); + } + catch (reported) + { + REQUIRE(reports.size() == 1U); + auto re = R":(No match for call of vfunc with signature void\(const std::vector&\) with\. + param _1 == \{ .* \} + +Tried m\.vfunc\(range_is_all\(any_of\(MEMBER_IS\(&xy_coord::x, gt\(0\)\), MEMBER_IS\(&xy_coord::y, lt\(0\)\)\)\)\) at [A-Za-z0-9_ ./:\]*:[0-9]*.* + Expected _1 range is all to be any of \{ &xy_coord::x > 0, &xy_coord::y < 0 \}):"; + auto& msg = reports.front().msg; + INFO("msg=" << msg); + REQUIRE(std::regex_search(msg, std::regex(re))); + + } +} + #if TROMPELOEIL_TEST_REGEX_FAILURES TEST_CASE_METHOD( @@ -4358,6 +4461,8 @@ Tried obj\.foo\(not_empty\{\}\) at [A-Za-z0-9_ ./:\]*:[0-9]*.* } } + + #endif /* TROMPELOEIL_TEST_REGEX_FAILURES */ #if !(TROMPELOEIL_CLANG && \