diff --git a/include/turtle/detail/mock_impl.hpp b/include/turtle/detail/mock_impl.hpp index 59fe4e0..dda080f 100644 --- a/include/turtle/detail/mock_impl.hpp +++ b/include/turtle/detail/mock_impl.hpp @@ -12,11 +12,13 @@ #include "function.hpp" #include "functor.hpp" +#include "pp_foreach.hpp" #include "signature.hpp" #include "signature_traits.hpp" #include "type_name.hpp" #include #include +#include #include namespace mock { namespace detail { @@ -65,15 +67,17 @@ namespace mock { namespace detail { return MOCK_ANONYMOUS_HELPER(identifier)(MOCK_FORWARD_PARAMS(arity, signature)); \ } -#define MOCK_METHOD_EXT(name, arity, signature, identifier) \ - MOCK_METHOD_AUX(name, arity, signature, identifier, ) \ - MOCK_METHOD_AUX(name, arity, signature, identifier, const) \ - MOCK_METHOD_HELPER(signature, identifier) -#define MOCK_CONST_METHOD_EXT(name, arity, signature, identifier) \ - MOCK_METHOD_AUX(name, arity, signature, identifier, const) \ - MOCK_METHOD_HELPER(signature, identifier) -#define MOCK_NON_CONST_METHOD_EXT(name, arity, signature, identifier) \ - MOCK_METHOD_AUX(name, arity, signature, identifier, ) \ +/// MOCK_METHOD_ITER((name, arity, signature, identifier), qualifier) +#define MOCK_METHOD_ITER(M_n_S_t, qualifier) \ + MOCK_METHOD_AUX(BOOST_PP_TUPLE_ELEM(0, M_n_S_t), \ + BOOST_PP_TUPLE_ELEM(1, M_n_S_t), \ + BOOST_PP_TUPLE_ELEM(2, M_n_S_t), \ + BOOST_PP_TUPLE_ELEM(3, M_n_S_t), \ + qualifier) + +#define MOCK_METHOD_EXT_I(name, arity, signature, identifier, qualifiers) \ + static_assert(arity == mock::detail::function_arity_t::value, "Arity mismatch"); \ + MOCK_PP_TUPLE_FOR_EACH(MOCK_METHOD_ITER, (name, arity, signature, identifier), qualifiers) \ MOCK_METHOD_HELPER(signature, identifier) #define MOCK_FUNCTION_HELPER(signature, identifier, prefix) \ diff --git a/include/turtle/detail/pp_foreach.hpp b/include/turtle/detail/pp_foreach.hpp new file mode 100644 index 0000000..ee1529e --- /dev/null +++ b/include/turtle/detail/pp_foreach.hpp @@ -0,0 +1,66 @@ +// http://turtle.sourceforge.net +// +// Copyright 2025 Alexander Grund +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef MOCK_PP_FOREACH_HPP_INCLUDED +#define MOCK_PP_FOREACH_HPP_INCLUDED + +#include +#include +#include +#include + +/// Expand to `macro(data, elem)` for each element in the tuple. +/// Same as BOOST_PP_TUPLE_FOR_EACH but supports empty elements, +/// without causing a C4003 "not enough arguments for function-like macro invocation" warning on MSVC +#define MOCK_PP_TUPLE_FOR_EACH(macro, data, tuple) MOCK_PP_FOR_EACH(macro, data, BOOST_PP_REM tuple) + +/// Expand to `macro(data, elem)` for each variadic element passed +#if BOOST_PP_VARIADICS_MSVC +# define MOCK_PP_FOR_EACH(macro, data, ...) \ + BOOST_PP_CAT(BOOST_PP_OVERLOAD(MOCK_PP_INVOKE_, __VA_ARGS__)(macro, data, __VA_ARGS__), BOOST_PP_EMPTY()) +#else +# define MOCK_PP_FOR_EACH(macro, data, ...) BOOST_PP_OVERLOAD(MOCK_PP_INVOKE_, __VA_ARGS__)(macro, data, __VA_ARGS__) +#endif + +// When the compiler supports detection of empty variadic element (e.g. Clang & GCC) Boost.PP returns a size of zero. +// However an empty variadic is a valid case: A single invocation with an empty element. +#define MOCK_PP_INVOKE_0(macro, data, x) macro(data, x) +#define MOCK_PP_INVOKE_1(macro, data, x) macro(data, x) +#define MOCK_PP_INVOKE_2(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_1(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_3(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_2(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_4(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_3(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_5(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_4(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_6(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_5(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_7(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_6(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_8(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_7(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_9(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_8(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_10(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_9(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_11(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_10(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_12(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_11(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_13(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_12(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_14(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_13(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_15(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_14(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_16(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_15(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_17(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_16(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_18(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_17(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_19(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_18(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_20(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_19(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_21(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_20(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_22(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_21(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_23(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_22(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_24(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_23(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_25(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_24(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_26(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_25(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_27(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_26(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_28(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_27(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_29(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_28(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_30(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_29(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_31(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_30(macro, data, __VA_ARGS__) +#define MOCK_PP_INVOKE_32(macro, data, x, ...) macro(data, x) MOCK_PP_INVOKE_31(macro, data, __VA_ARGS__) + +#endif // MOCK_PP_FOREACH_HPP_INCLUDED diff --git a/include/turtle/detail/signature.hpp b/include/turtle/detail/signature.hpp index cb4b3eb..d931427 100644 --- a/include/turtle/detail/signature.hpp +++ b/include/turtle/detail/signature.hpp @@ -1,6 +1,7 @@ // http://turtle.sourceforge.net // // Copyright Mathieu Champlon 2012 +// Copyright 2020-2025 Alexander Grund // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at @@ -10,36 +11,44 @@ #define MOCK_SIGNATURE_HPP_INCLUDED #include "../config.hpp" +#include #include namespace mock { namespace detail { -#define MOCK_NOARG -#define MOCK_STRIP_FUNCTION_QUALIFIERS(cv, ref) \ - template \ - struct strip_function_qualifiers \ - { \ - using type = R(Args...); \ - }; \ - template \ - struct strip_function_qualifiers \ - { \ - using type = R(Args..., ...); \ +#define MOCK_STRIP_FUNCTION_QUALIFIERS(ne, cv, ref) \ + template \ + struct strip_function_qualifiers \ + { \ + using type = R(Args...); \ + }; \ + template \ + struct strip_function_qualifiers \ + { \ + using type = R(Args..., ...); \ }; -#define MOCK_STRIP_FUNCTION_QUALIFIERS_REF(cv) \ - MOCK_STRIP_FUNCTION_QUALIFIERS(cv, ) \ - MOCK_STRIP_FUNCTION_QUALIFIERS(cv, &) \ - MOCK_STRIP_FUNCTION_QUALIFIERS(cv, &&) +#define MOCK_STRIP_FUNCTION_QUALIFIERS_REF(ne, cv) \ + MOCK_STRIP_FUNCTION_QUALIFIERS(ne, cv, ) \ + MOCK_STRIP_FUNCTION_QUALIFIERS(ne, cv, &) \ + MOCK_STRIP_FUNCTION_QUALIFIERS(ne, cv, &&) +#define MOCK_STRIP_FUNCTION_QUALIFIERS_CV_REF(except_spec) \ + MOCK_STRIP_FUNCTION_QUALIFIERS_REF(except_spec, BOOST_PP_EMPTY()) \ + MOCK_STRIP_FUNCTION_QUALIFIERS_REF(except_spec, const) \ + MOCK_STRIP_FUNCTION_QUALIFIERS_REF(except_spec, volatile) \ + MOCK_STRIP_FUNCTION_QUALIFIERS_REF(except_spec, const volatile) template struct strip_function_qualifiers; - MOCK_STRIP_FUNCTION_QUALIFIERS_REF(MOCK_NOARG) - MOCK_STRIP_FUNCTION_QUALIFIERS_REF(const) - MOCK_STRIP_FUNCTION_QUALIFIERS_REF(volatile) - MOCK_STRIP_FUNCTION_QUALIFIERS_REF(const volatile) -#undef MOCK_NOARG + MOCK_STRIP_FUNCTION_QUALIFIERS_CV_REF(BOOST_PP_EMPTY()) + + // C++17 includes noexcept in the function type +#if MOCK_CXX_VERSION >= 201703L + MOCK_STRIP_FUNCTION_QUALIFIERS_CV_REF(noexcept) +#endif + #undef MOCK_STRIP_FUNCTION_QUALIFIERS #undef MOCK_STRIP_FUNCTION_QUALIFIERS_REF +#undef MOCK_STRIP_FUNCTION_QUALIFIERS_CV_REF template struct signature; diff --git a/include/turtle/mock.hpp b/include/turtle/mock.hpp index 31d5dc8..ecb0041 100644 --- a/include/turtle/mock.hpp +++ b/include/turtle/mock.hpp @@ -72,33 +72,45 @@ } \ MOCK_METHOD_HELPER(void(), identifier) -/// MOCK_METHOD( [calling convention] name, arity[, signature[, identifier]] ) -/// generates both const and non-const methods +/// MOCK_METHOD( [calling convention] name, arity[, signature[, identifier, [, specifiers]]] ) +/// generates both const and non-const methods by default, but can be changed by passing a specifier tuple. /// The 'signature' can be omitted if it can be uniquely identified from the base class /// if 'identifier' is omitted it will default to 'name' -#define MOCK_METHOD(M, ...) \ - MOCK_METHOD_EXT(M, \ - BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, ), \ - BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ - BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, )) +/// 'specifiers' is a potentially empty, tuple of method specifiers, e.g. (&, &&) or (const override) +#define MOCK_METHOD(M, ...) \ + MOCK_METHOD_EXT_I(M, \ + BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, ), \ + BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ + BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, ), \ + BOOST_PP_VARIADIC_ELEM(3, __VA_ARGS__, (const, ), (const, ), (const, ), )) /// MOCK_CONST_METHOD( [calling convention] name, arity[, signature[, identifier]] ) /// generates only the const version of the method /// The 'signature' can be omitted if it can be uniquely identified from the base class /// if 'identifier' is omitted it will default to 'name' -#define MOCK_CONST_METHOD(M, ...) \ - MOCK_CONST_METHOD_EXT(M, \ - BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, ), \ - BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ - BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, )) +#define MOCK_CONST_METHOD(M, ...) \ + MOCK_METHOD_EXT_I(M, \ + BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, ), \ + BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ + BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, ), \ + (const)) /// MOCK_NON_CONST_METHOD( [calling convention] name, arity[, signature[, identifier]] ) /// generates only the non-const version of the method /// The 'signature' can be omitted if it can be uniquely identified from the base class /// if 'identifier' is omitted it will default to 'name' -#define MOCK_NON_CONST_METHOD(M, ...) \ - MOCK_NON_CONST_METHOD_EXT(M, \ - BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, ), \ - BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ - BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, )) +#define MOCK_NON_CONST_METHOD(M, ...) \ + MOCK_METHOD_EXT_I(M, \ + BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, ), \ + BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ + BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, ), \ + ()) + +/// MOCK_METHOD_EXT( [calling convention] name, arity, qualifiers [, signature, [identifier]] ) +#define MOCK_METHOD_EXT(M, arity, ...) \ + MOCK_METHOD_EXT_I(M, \ + arity, \ + BOOST_PP_VARIADIC_ELEM(1, __VA_ARGS__, MOCK_SIGNATURE(M), ), \ + BOOST_PP_VARIADIC_ELEM(2, __VA_ARGS__, M, M, ), \ + BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__, )) /// MOCK_FUNCTION( [calling convention] name, arity, signature[, identifier] ) /// if 'identifier' is omitted it will default to 'name' diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 486fdb7..c6974ad 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,6 +20,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang" if(TURTLE_CXX_UNUSED_FUNCTION) target_compile_options(TurtleTestMain INTERFACE -Wno-unused-function) endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(TurtleTestMain INTERFACE -Wno-inconsistent-missing-override) + endif() if(TURTLE_WERROR) check_cxx_compiler_flag(-Wdeprecated-declarations TURTLE_CXX_DEPRECATED_DECLARATIONS) if(TURTLE_CXX_DEPRECATED_DECLARATIONS) diff --git a/test/test_mock.cpp b/test/test_mock.cpp index a61c305..6ca5c52 100644 --- a/test/test_mock.cpp +++ b/test/test_mock.cpp @@ -327,6 +327,15 @@ struct base virtual void m1() = 0; virtual void m10() const = 0; virtual void m11() = 0; + virtual void m12() noexcept = 0; + virtual void m13() && = 0; + virtual void m14() = 0; + virtual void m14(int, float) = 0; + virtual void m14(int, int) = 0; + virtual void m14(int) = 0; + virtual void m14(int) const noexcept = 0; + virtual void m15() & = 0; + virtual void m15() && = 0; }; MOCK_BASE_CLASS(variadic, base) @@ -340,9 +349,29 @@ MOCK_BASE_CLASS(variadic, base) MOCK_NON_CONST_METHOD(m11, 0) MOCK_NON_CONST_METHOD(m6, 0, void()) MOCK_NON_CONST_METHOD(m7, 0, void(), m7) + + MOCK_METHOD_EXT(m12, 0, (noexcept override)) + MOCK_METHOD_EXT(m13, 0, (&&override)) + // Overloaded method + MOCK_METHOD(m14, 0, void(), m14_empty) + MOCK_METHOD(m14, 2, void(int, float), m14_int_float, ()) + MOCK_METHOD(m14, 2, void(int, int), m14_int_int, (override)) + MOCK_METHOD(m14, 1, void(int), m14_int, (override, const noexcept override)) + MOCK_METHOD_EXT(m15, 0, (&override, &&override), void()) MOCK_STATIC_METHOD(m8, 0, void()) MOCK_STATIC_METHOD(m9, 0, void(), m9) }; +void instantiate_class() +{ + variadic inst; // If this compiles all pure virtual methods were mocked + const variadic& cinst = inst; + (void)cinst; // Avoid unused variable warning + static_assert(noexcept(inst.m12()), "noexcept should be kept"); + static_assert(!noexcept(inst.m14()), "noexcept should not be set"); + static_assert(noexcept(cinst.m14(1)), "noexcept should be kept"); + static_assert(!noexcept(inst.m14(1)), "noexcept should not be set"); + static_assert(noexcept(std::declval().m14(1)), "noexcept should be kept"); +} template MOCK_BASE_CLASS(variadic_tpl, base)