Skip to content
22 changes: 13 additions & 9 deletions include/turtle/detail/mock_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <boost/preprocessor/repetition/enum.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <type_traits>

namespace mock { namespace detail {
Expand Down Expand Up @@ -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<signature>::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) \
Expand Down
66 changes: 66 additions & 0 deletions include/turtle/detail/pp_foreach.hpp
Original file line number Diff line number Diff line change
@@ -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 <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/empty.hpp>
#include <boost/preprocessor/facilities/overload.hpp>
#include <boost/preprocessor/tuple/rem.hpp>

/// 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
49 changes: 29 additions & 20 deletions include/turtle/detail/signature.hpp
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,36 +11,44 @@
#define MOCK_SIGNATURE_HPP_INCLUDED

#include "../config.hpp"
#include <boost/preprocessor/empty.hpp>
#include <type_traits>

namespace mock { namespace detail {
#define MOCK_NOARG
#define MOCK_STRIP_FUNCTION_QUALIFIERS(cv, ref) \
template<typename R, typename... Args> \
struct strip_function_qualifiers<R(Args...) cv ref> \
{ \
using type = R(Args...); \
}; \
template<typename R, typename... Args> \
struct strip_function_qualifiers<R(Args..., ...) cv ref> \
{ \
using type = R(Args..., ...); \
#define MOCK_STRIP_FUNCTION_QUALIFIERS(ne, cv, ref) \
template<typename R, typename... Args> \
struct strip_function_qualifiers<R(Args...) cv ref ne> \
{ \
using type = R(Args...); \
}; \
template<typename R, typename... Args> \
struct strip_function_qualifiers<R(Args..., ...) cv ref ne> \
{ \
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<typename>
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<typename M>
struct signature;
Expand Down
46 changes: 29 additions & 17 deletions include/turtle/mock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
3 changes: 3 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions test/test_mock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<const variadic>().m14(1)), "noexcept should be kept");
}

template<typename T>
MOCK_BASE_CLASS(variadic_tpl, base)
Expand Down