From 1a81536f3ca051a11b0c9b799a7711a877eecc34 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 9 Feb 2022 15:33:03 +0100 Subject: [PATCH 1/6] Improve tests - Check callability of function.expect.with(...) - Check serialization of MOCK_CONSTRAINT - Actually test some variations of MOCK_CONSTRAINT usages - Add more test for unique_ptr (move-only class) - Remove redundant stuff from test_log and change a few values to catch mistakes - Add test for *-matcher serialization --- test/detail/test_function.cpp | 37 ++++++++++++++++++ test/test_constraint.cpp | 31 +++++++++++++-- test/test_integration.cpp | 27 ++++++++++--- test/test_log.cpp | 72 +++++++++++++++-------------------- test/test_matcher.cpp | 51 +++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 50 deletions(-) diff --git a/test/detail/test_function.cpp b/test/detail/test_function.cpp index f26176f..01aaa85 100644 --- a/test/detail/test_function.cpp +++ b/test/detail/test_function.cpp @@ -10,6 +10,7 @@ #include "../undefined.hpp" #include #include +#include #include #include #include @@ -24,6 +25,42 @@ static_assert(std::is_same{}())>:: static_assert(std::is_same{}())>::value, "!"); static_assert(std::is_same{}(std::declval()))>::value, "!"); static_assert(std::is_same{}(std::declval()))>::value, "!"); + +template +using with_call_t = decltype(std::declval().expect().with(std::declval()...)); +template +struct with_is_callable : std::false_type +{}; +template +struct with_is_callable>, TFunction, TParams...> : + std::true_type +{}; +// Detect if we can call function.expect().with(params) +template +using with_is_callable_t = with_is_callable; + +// For function with no parameters `with` can never be called +using function_0_args = mock::detail::function; +static_assert(!with_is_callable_t::value, "Can't call with()"); +static_assert(!with_is_callable_t::value, "Can't call with(int)"); +static_assert(!with_is_callable_t::value, "Can't call with custom constraint"); + +// For functions with parameters `with` can be called with a custom constraint (taking all params) +// or exactly as many parameters as the function has (here 1 and 4 respectively) +using function_1_args = mock::detail::function; +static_assert(!with_is_callable_t::value, "Can't call with()"); +static_assert(with_is_callable_t::value, "Can call with(int)"); +static_assert(with_is_callable_t::value, "Can call with custom constraint"); +static_assert(!with_is_callable_t::value, "Can't call with(int, int)"); + +using function_4_args = mock::detail::function; +static_assert(!with_is_callable_t::value, "Can't call with()"); +static_assert(with_is_callable_t::value, "Can call with custom constraint"); +static_assert(!with_is_callable_t::value, "Can't call with(int, int)"); +static_assert(!with_is_callable_t::value, "Can't call with(int, int, int)"); +static_assert(with_is_callable_t::value, "Can call with(int, int, int, int)"); +static_assert(!with_is_callable_t::value, + "Can't call with(int, int, int, int, int)"); } // namespace // functor diff --git a/test/test_constraint.cpp b/test/test_constraint.cpp index 0eb525c..8e98ed9 100644 --- a/test/test_constraint.cpp +++ b/test/test_constraint.cpp @@ -8,16 +8,39 @@ #include #include +#include namespace { MOCK_CONSTRAINT(constraint_0, actual == 0) MOCK_CONSTRAINT(constraint_1, expected, actual == expected) MOCK_CONSTRAINT(constraint_2, expected_0, expected_1, actual == expected_0 || actual == expected_1) + +template +std::string to_string(const mock::constraint& t) +{ + std::ostringstream s; + s << t.c_; + return s.str(); +} } // namespace -BOOST_AUTO_TEST_CASE(mock_constraint_is_supported_by_compilers_with_variadic_macros) +BOOST_AUTO_TEST_CASE(mock_constraint_works_for_0_to_2_args) { - BOOST_CHECK(constraint_0.c_(0)); - BOOST_CHECK(constraint_1(0).c_(0)); - BOOST_CHECK(constraint_2(0, 0).c_(0)); + BOOST_TEST(constraint_0.c_(0)); + BOOST_TEST(!constraint_0.c_(42)); + + BOOST_TEST(constraint_1(0).c_(0)); + BOOST_TEST(!constraint_1(1).c_(0)); + BOOST_TEST(!constraint_1(42).c_(1337)); + + BOOST_TEST(constraint_2(42, 1337).c_(42)); + BOOST_TEST(constraint_2(42, 1337).c_(1337)); + BOOST_TEST(!constraint_2(42, 1337).c_(99)); +} + +BOOST_AUTO_TEST_CASE(mock_constraint_outputs_human_readable_representation) +{ + BOOST_TEST(to_string(constraint_0) == "constraint_0"); + BOOST_TEST(to_string(constraint_1(42)) == "constraint_1( 42 )"); + BOOST_TEST(to_string(constraint_2(42, 1337)) == "constraint_2( 42, 1337 )"); } diff --git a/test/test_integration.cpp b/test/test_integration.cpp index 2442b8f..ef0ca9b 100644 --- a/test/test_integration.cpp +++ b/test/test_integration.cpp @@ -726,12 +726,12 @@ BOOST_FIXTURE_TEST_CASE(std_unique_ptr_argument_is_supported_in_retrieve_constra } { std::unique_ptr i; - MOCK_FUNCTOR(f, void(std::unique_ptr)); - MOCK_EXPECT(f).once().with(nullptr); - MOCK_EXPECT(f).once().with(mock::retrieve(i)); - f(0); + MOCK_FUNCTOR(f, void(std::unique_ptr, std::unique_ptr, std::unique_ptr)); + MOCK_EXPECT(f).once().with(nullptr, nullptr, nullptr); + MOCK_EXPECT(f).once().with(nullptr, mock::retrieve(i), nullptr); + f(nullptr, nullptr, nullptr); std::unique_ptr j(new int(7)); - f(std::move(j)); + f(nullptr, std::move(j), nullptr); BOOST_CHECK(!j); BOOST_REQUIRE(i); BOOST_CHECK_EQUAL(7, *i); @@ -745,3 +745,20 @@ struct my_unique_ptr_class MOCK_METHOD(m, 1, void(std::unique_ptr), m) MOCK_STATIC_METHOD(ms, 1, void(std::unique_ptr), ms) }; + +BOOST_FIXTURE_TEST_CASE(unique_ptr_works_in_class, mock_error_fixture) +{ + MOCK_EXPECT(my_unique_ptr_class::constructor).once().with(nullptr); + MOCK_EXPECT(my_unique_ptr_class::ms).once().with(7); + my_unique_ptr_class instance(nullptr); + auto j = std::make_unique(7); + my_unique_ptr_class::ms(std::move(j)); + BOOST_TEST(!j); // Otherwise it wasn't a value parameter and we leak memory + MOCK_EXPECT(instance.ms).once().with(42); + j = std::make_unique(42); + instance.ms(std::move(j)); + BOOST_TEST(!j); // Same here + + BOOST_TEST(mock::verify()); + CHECK_CALLS(3); +} diff --git a/test/test_log.cpp b/test/test_log.cpp index 623b6b1..c26d3bb 100644 --- a/test/test_log.cpp +++ b/test/test_log.cpp @@ -32,18 +32,23 @@ #include #include +// Convention: +// "Serializable: Implements operator<<(std::ostream&, ...) +// "Streamable": Implements operator<<(mock::stream&, ...) +// "Mock Streamable": Implements operator<<(stream&, ...) in namespace mock + namespace { template std::string to_string(const T& t) { - std::stringstream s; + std::ostringstream s; s << mock::format(t); return s.str(); } template std::string to_string(T* t) { - std::stringstream s; + std::ostringstream s; s << mock::format(t); return s.str(); } @@ -56,7 +61,6 @@ BOOST_AUTO_TEST_CASE(pointer_yields_its_value_when_serialized) std::ostringstream s; s << &i; const std::string pointerValue = s.str(); - BOOST_CHECK_NE("?", to_string(&i)); BOOST_CHECK_EQUAL(pointerValue, to_string(&i)); } { @@ -64,7 +68,6 @@ BOOST_AUTO_TEST_CASE(pointer_yields_its_value_when_serialized) std::ostringstream s; s << &i; const std::string pointerValue = s.str(); - BOOST_CHECK_NE("?", to_string(&i)); BOOST_CHECK_EQUAL(pointerValue, to_string(&i)); } } @@ -178,6 +181,19 @@ BOOST_AUTO_TEST_CASE(type_derived_from_streamable_yields_a_question_mark_when_se #endif } +namespace { +// Class which can be converted to many other types making implicit conversions ambiguous +struct ambiguous_convertible +{ + operator float() const; + operator int() const; + operator serializable() const; + operator streamable() const; + template + operator T() const; +}; +} // namespace + #ifndef MOCK_USE_CONVERSIONS // all this does not compile with conversions activated, which is precisely the purpose of // having this compilation flag @@ -217,33 +233,14 @@ BOOST_AUTO_TEST_CASE(type_convertible_to_streamable_yields_a_question_mark_when_ BOOST_CHECK_EQUAL("?", to_string(convertible_to_streamable())); } -namespace { -struct ambiguous_convertible -{ - operator float() const; - operator int() const; - operator serializable() const; - operator streamable() const; - template - operator T() const; -}; -} // namespace - BOOST_AUTO_TEST_CASE(type_ambiguous_convertible_yields_a_question_mark_when_serialized) { BOOST_CHECK_EQUAL("?", to_string(ambiguous_convertible())); } namespace { -struct ambiguous_convertible_serializable -{ - operator float() const; - operator int() const; - operator serializable() const; - operator streamable() const; - template - operator T() const; -}; +struct ambiguous_convertible_serializable : public ambiguous_convertible +{}; std::ostream& operator<<(std::ostream& s, const ambiguous_convertible_serializable&) { return s << "ambiguous_convertible_serializable"; @@ -258,15 +255,8 @@ BOOST_AUTO_TEST_CASE(type_convertible_serializable_yields_its_value_when_seriali #endif // MOCK_USE_CONVERSIONS namespace { -struct ambiguous_convertible_streamable -{ - operator float() const; - operator int() const; - operator serializable() const; - operator streamable() const; - template - operator T() const; -}; +struct ambiguous_convertible_streamable : public ambiguous_convertible +{}; BOOST_ATTRIBUTE_UNUSED std::ostream& operator<<(std::ostream& s, const ambiguous_convertible_streamable&) { BOOST_FAIL("should not have been called"); @@ -435,17 +425,17 @@ BOOST_AUTO_TEST_CASE(std_vectors_are_serialized) BOOST_AUTO_TEST_CASE(std_maps_are_serialized) { std::map m; - m[12] = "12"; - m[42] = "42"; - BOOST_CHECK_EQUAL("((12,\"12\"),(42,\"42\"))", to_string(m)); + m[12] = "15"; + m[42] = "46"; + BOOST_CHECK_EQUAL("((12,\"15\"),(42,\"46\"))", to_string(m)); } BOOST_AUTO_TEST_CASE(std_multimaps_are_serialized) { std::multimap m; - m.insert(std::make_pair(12, "12")); - m.insert(std::make_pair(42, "42")); - BOOST_CHECK_EQUAL("((12,\"12\"),(42,\"42\"))", to_string(m)); + m.insert(std::make_pair(12, "15")); + m.insert(std::make_pair(42, "46")); + BOOST_CHECK_EQUAL("((12,\"15\"),(42,\"46\"))", to_string(m)); } BOOST_AUTO_TEST_CASE(std_sets_are_serialized) @@ -495,7 +485,7 @@ BOOST_AUTO_TEST_CASE(boost_assign_list_of_are_serialized) BOOST_AUTO_TEST_CASE(boost_assign_map_list_of_are_serialized) { - BOOST_CHECK_EQUAL("((12,\"12\"),(42,\"42\"))", to_string(boost::assign::map_list_of(12, "12")(42, "42"))); + BOOST_CHECK_EQUAL("((12,\"16\"),(42,\"43\"))", to_string(boost::assign::map_list_of(12, "16")(42, "43"))); } BOOST_AUTO_TEST_CASE(std_reference_wrappers_are_serialized) diff --git a/test/test_matcher.cpp b/test/test_matcher.cpp index b352a20..e966d2d 100644 --- a/test/test_matcher.cpp +++ b/test/test_matcher.cpp @@ -7,7 +7,9 @@ // http://www.boost.org/LICENSE_1_0.txt) #include +#include #include +#include namespace { template @@ -72,3 +74,52 @@ BOOST_FIXTURE_TEST_CASE(const_char_pointer_and_std_string_can_be_compared, fixtu BOOST_CHECK(match(std::string("same text"), actual)); BOOST_CHECK(!match(std::string("different text"), actual)); } + +namespace { +template +std::string serialize(const T& t) +{ + std::ostringstream s; + s << t; + return s.str(); +} +} // namespace + +BOOST_AUTO_TEST_CASE(default_matcher_is_serialized_to_any) +{ + using mock::detail::default_matcher; + BOOST_TEST(serialize(default_matcher{}) == ""); + BOOST_TEST(serialize(default_matcher{}) == "any"); + BOOST_TEST(serialize(default_matcher{}) == "any, any"); + BOOST_TEST(serialize(default_matcher{}) == "any, any, any, any, any"); +} + +namespace { +struct custom_constraint +{ + int expected_ = 42; + custom_constraint(int expected = 42) : expected_(expected) {} + friend std::ostream& operator<<(std::ostream& s, const custom_constraint& c) + { + return s << "custom" << c.expected_; + } + bool operator()(int actual) { return actual == expected_; } +}; +} // namespace + +BOOST_AUTO_TEST_CASE(single_matcher_serializes) +{ + using mock::detail::single_matcher; + BOOST_TEST(serialize(single_matcher(1)) == "1"); + BOOST_TEST(serialize(single_matcher(1, 2)) == "1, 2"); + BOOST_TEST( + serialize( + single_matcher, int, int), void(int, int, int, int, int)>( + 1, 2, custom_constraint(), 4, 5)) == "1, 2, custom42, 4, 5"); +} + +BOOST_AUTO_TEST_CASE(multi_matcher_serializes) +{ + using mock::detail::multi_matcher; + BOOST_TEST(serialize(multi_matcher(custom_constraint(1337))) == "custom1337"); +} From f19caf428c17d6fcc01fb74d133e4e6e87d266b0 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 9 Feb 2022 14:33:10 +0100 Subject: [PATCH 2/6] Fixup coverage of type_name.hpp to 100% Reassign the demangled name to avoid the (always untaken) branch and reduce code duplication --- include/turtle/detail/type_name.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/turtle/detail/type_name.hpp b/include/turtle/detail/type_name.hpp index bc97522..1aaa79d 100644 --- a/include/turtle/detail/type_name.hpp +++ b/include/turtle/detail/type_name.hpp @@ -47,10 +47,9 @@ namespace mock { namespace detail { }; std::unique_ptr demangled(abi::__cxa_demangle(name, 0, 0, &status)); if(!status && demangled) - serialize(s, demangled.get()); - else + name = demangled.get(); #endif - serialize(s, name); + serialize(s, name); } typedef std::string::size_type size_type; From e2687dea1a0e5569d28b66bd450c56e1322d053b Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 9 Feb 2022 14:59:46 +0100 Subject: [PATCH 3/6] Add missing test cases for the invocation class - error case where between was called with a min>max - Corner case for `between(x, x)` - Human readable output via stream operator of the base class --- test/detail/test_invocation.cpp | 118 ++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 37 deletions(-) diff --git a/test/detail/test_invocation.cpp b/test/detail/test_invocation.cpp index be9957e..ad7421e 100644 --- a/test/detail/test_invocation.cpp +++ b/test/detail/test_invocation.cpp @@ -8,69 +8,113 @@ #include #include +#include + +/// Serialize using the base class as done by e.g. the expectation class +std::string to_string(const mock::detail::invocation& invocation) +{ + std::ostringstream s; + s << invocation; + return s.str(); +} BOOST_AUTO_TEST_CASE(unlimited) { mock::detail::unlimited invocation; - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(to_string(invocation) == "unlimited()"); } BOOST_AUTO_TEST_CASE(once) { mock::detail::once invocation; - BOOST_CHECK(!invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(invocation.exhausted()); - BOOST_CHECK(!invocation.invoke()); + BOOST_TEST(!invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(invocation.exhausted()); + BOOST_TEST(!invocation.invoke()); + BOOST_TEST(to_string(invocation) == "once()"); } BOOST_AUTO_TEST_CASE(never) { mock::detail::never invocation; - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(invocation.exhausted()); - BOOST_CHECK(!invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(invocation.exhausted()); + BOOST_TEST(!invocation.invoke()); + BOOST_TEST(to_string(invocation) == "never()"); } BOOST_AUTO_TEST_CASE(at_most) { mock::detail::at_most invocation(1); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(invocation.exhausted()); - BOOST_CHECK(!invocation.invoke()); + BOOST_TEST(to_string(invocation) == "at_most( 0/1 )"); + BOOST_TEST(invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(to_string(invocation) == "at_most( 1/1 )"); + BOOST_TEST(invocation.verify()); + BOOST_TEST(invocation.exhausted()); + BOOST_TEST(!invocation.invoke()); + BOOST_TEST(to_string(invocation) == "at_most( 1/1 )"); } BOOST_AUTO_TEST_CASE(at_least) { mock::detail::at_least invocation(1); - BOOST_CHECK(!invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); + BOOST_TEST(to_string(invocation) == "at_least( 0/1 )"); + BOOST_TEST(!invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(to_string(invocation) == "at_least( 1/1 )"); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(to_string(invocation) == "at_least( 2/1 )"); } BOOST_AUTO_TEST_CASE(between) { - mock::detail::between invocation(1, 2); - BOOST_CHECK(!invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(!invocation.exhausted()); - BOOST_CHECK(invocation.invoke()); - BOOST_CHECK(invocation.verify()); - BOOST_CHECK(invocation.exhausted()); - BOOST_CHECK(!invocation.invoke()); + { + mock::detail::between invocation(1, 2); + BOOST_TEST(to_string(invocation) == "between( 0/[1,2] )"); + BOOST_TEST(!invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(to_string(invocation) == "between( 1/[1,2] )"); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(to_string(invocation) == "between( 2/[1,2] )"); + BOOST_TEST(invocation.exhausted()); + BOOST_TEST(!invocation.invoke()); + BOOST_TEST(to_string(invocation) == "between( 2/[1,2] )"); + } + { + mock::detail::between invocation(2, 2); + BOOST_TEST(to_string(invocation) == "between( 0/[2,2] )"); + BOOST_TEST(!invocation.verify()); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(!invocation.verify()); + BOOST_TEST(to_string(invocation) == "between( 1/[2,2] )"); + BOOST_TEST(!invocation.exhausted()); + BOOST_TEST(invocation.invoke()); + BOOST_TEST(invocation.verify()); + BOOST_TEST(to_string(invocation) == "between( 2/[2,2] )"); + BOOST_TEST(invocation.exhausted()); + BOOST_TEST(!invocation.invoke()); + BOOST_TEST(to_string(invocation) == "between( 2/[2,2] )"); + } + + // First must be equal or less than 2nd + BOOST_CHECK_THROW(mock::detail::between invalid(2, 1), std::invalid_argument); } From 23ac665c2211e47b34afa3cc3f380fc5b306ff1b Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Wed, 9 Feb 2022 15:23:14 +0100 Subject: [PATCH 4/6] Add some more cases for the serialization test of expectations Make sure the various constraint names/values are kept (they are stored type-erased!) --- test/detail/test_function.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/detail/test_function.cpp b/test/detail/test_function.cpp index 01aaa85..bd640a2 100644 --- a/test/detail/test_function.cpp +++ b/test/detail/test_function.cpp @@ -751,11 +751,18 @@ BOOST_FIXTURE_TEST_CASE(expectation_can_be_serialized_to_be_human_readable, mock { mock::detail::function f; f.expect().once().with(1); - f.expect().once().with(2); + f.expect().once().with(mock::close(3, 1)); + int target = 0; + f.expect().once().with(mock::retrieve(target)); + f.expect().once().with(mock::same(target)); BOOST_CHECK_NO_THROW(f(2)); - const std::string expected = "?\n" - ". once().with( 1 )\n" - "v once().with( 2 )"; + std::string expected = "?\n"; // Not in a current call context + expected += ". once().with( 1 )\n"; // Unmet/Unverified expectation with value + expected += "v once().with( close( 3, 1 ) )\n"; // Verified expectation with constraint + target = 42; + // (Unverified) expectation with retrieve/same constraint print current value + expected += ". once().with( retrieve( 42 ) )\n"; + expected += ". once().with( same( 42 ) )"; BOOST_CHECK_EQUAL(expected, to_string(f)); CHECK_CALLS(1); f.reset(); From 9e2223d4bec5cff8aff7fbd7548d9712f9acbc7a Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 10 Feb 2022 11:37:52 +0100 Subject: [PATCH 5/6] Remove BOOST_THREAD_USES_MOVE define from tests No longer needed as we require C++11/14 already and hence rvalue references can be used by Boost.Thread --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 39bb8ff..c43f646 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,7 +54,7 @@ foreach(testFile IN LISTS testFiles) target_compile_definitions(${name}_use_conversions PRIVATE MOCK_USE_CONVERSIONS) target_link_libraries(${name}_thread_safe PRIVATE Boost::thread) - target_compile_definitions(${name}_thread_safe PRIVATE MOCK_THREAD_SAFE BOOST_THREAD_USES_MOVE) + target_compile_definitions(${name}_thread_safe PRIVATE MOCK_THREAD_SAFE) endforeach() # Link test only to check for a regression From 68700d4c3ad4d5716585765a04c51f4d1f4506a0 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Thu, 10 Feb 2022 11:42:33 +0100 Subject: [PATCH 6/6] Document mechanism used in the functor constructor --- include/turtle/detail/functor.hpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/include/turtle/detail/functor.hpp b/include/turtle/detail/functor.hpp index e4ce1e8..dfb793d 100644 --- a/include/turtle/detail/functor.hpp +++ b/include/turtle/detail/functor.hpp @@ -27,16 +27,25 @@ namespace mock { namespace detail { functor() { scoped_lock _(functor_mutex); - static functor* f = 0; + // MOCK_FUNCTOR creates 2 functor objects: + // The user-usable one with the passed name and a 2nd used by MOCK_EXPECT with a suffixed name + // We need the 2nd to be a copy of the first and use a static variable for storing a pointer to the first + static functor* f = nullptr; if(f) { - *this = *f; - f = 0; + // Release the lock from the first call (see below) so other threads can create functors again + // after the function exits (the scoped_lock still holds the mutex) functor_mutex.unlock(); + // Copy the first functor to the current (2nd) one + *this = *f; + f = nullptr; } else { - functor_mutex.lock(); + // This is the first object, store its pointer f = this; + // Lock the mutex again so only this thread can create new instances of a functor + // making sure that we copy the right instance above and not one from a concurrent thread + functor_mutex.lock(); } } };