From 9428cd7c82cebe8c34d391b42015800c33d49dc0 Mon Sep 17 00:00:00 2001 From: FranckRJ Date: Thu, 9 May 2024 23:01:17 +0200 Subject: [PATCH] Allow move of mocks. --- include/fakeit/Mock.hpp | 2 - include/fakeit/MockImpl.hpp | 16 +++- include/mockutils/DynamicProxy.hpp | 57 +++++++------ include/mockutils/FakeObject.hpp | 9 +- include/mockutils/gcc/VirtualTable.hpp | 62 ++++++-------- include/mockutils/mscpp/VirtualTable.hpp | 54 ++++++------ tests/CMakeLists.txt | 1 + tests/moving_mocks_around.cpp | 101 +++++++++++++++++++++++ 8 files changed, 200 insertions(+), 102 deletions(-) create mode 100644 tests/moving_mocks_around.cpp diff --git a/include/fakeit/Mock.hpp b/include/fakeit/Mock.hpp index 8d234c39..fffde197 100644 --- a/include/fakeit/Mock.hpp +++ b/include/fakeit/Mock.hpp @@ -36,8 +36,6 @@ namespace fakeit { class Mock : public ActualInvocationsSource { MockImpl impl; public: - ~Mock() override = default; - static_assert(std::is_polymorphic::value, "Can only mock a polymorphic type"); Mock() : impl(Fakeit) { diff --git a/include/fakeit/MockImpl.hpp b/include/fakeit/MockImpl.hpp index d3bac9bd..96eaa12f 100644 --- a/include/fakeit/MockImpl.hpp +++ b/include/fakeit/MockImpl.hpp @@ -33,10 +33,22 @@ namespace fakeit { MockImpl(FakeitContext &fakeit) : MockImpl(fakeit, *(createFakeInstance()), false){ - FakeObject *fake = asFakeObject(_instanceOwner.get()); - fake->getVirtualTable().setCookie(1, this); + _instanceOwner.get()->getVirtualTable().setCookie(1, this); } + MockImpl(const MockImpl&) = delete; + MockImpl(MockImpl&& other) FAKEIT_NO_THROWS + : _instanceOwner(std::move(other._instanceOwner)) + , _proxy(std::move(other._proxy)) + , _fakeit(other._fakeit) { + if (isOwner()) { + _instanceOwner.get()->getVirtualTable().setCookie(1, this); + } + } + + MockImpl& operator=(const MockImpl&) = delete; + MockImpl& operator=(MockImpl&&) = delete; + ~MockImpl() FAKEIT_NO_THROWS override { _proxy.detach(); } diff --git a/include/mockutils/DynamicProxy.hpp b/include/mockutils/DynamicProxy.hpp index 0d06a715..2d562f23 100644 --- a/include/mockutils/DynamicProxy.hpp +++ b/include/mockutils/DynamicProxy.hpp @@ -42,14 +42,8 @@ namespace fakeit { } public: - InvocationHandlers( - std::vector> &methodMocks, - std::vector &offsets) : - _methodMocks(methodMocks), _offsets(offsets) { - for (std::vector::iterator it = _offsets.begin(); it != _offsets.end(); ++it) - { - *it = std::numeric_limits::max(); - } + InvocationHandlers(std::vector> &methodMocks, std::vector &offsets) + : _methodMocks(methodMocks), _offsets(offsets) { } Destructible *getInvocatoinHandlerPtrById(unsigned int id) override { @@ -66,26 +60,39 @@ namespace fakeit { static_assert(std::is_polymorphic::value, "DynamicProxy requires a polymorphic type"); DynamicProxy(C &inst) : - instance(inst), - originalVtHandle(VirtualTable::getVTable(instance).createHandle()), + _instancePtr(&inst), _methodMocks(VTUtils::getVTSize()), - _offsets(VTUtils::getVTSize()), + _offsets(VTUtils::getVTSize(), std::numeric_limits::max()), _invocationHandlers(_methodMocks, _offsets) { - _cloneVt.copyFrom(originalVtHandle.restore()); - _cloneVt.setCookie(InvocationHandlerCollection::VtCookieIndex, &_invocationHandlers); - getFake().setVirtualTable(_cloneVt); + _originalVt.copyFrom(VirtualTable::getVTable(*_instancePtr)); + _originalVt.setCookie(InvocationHandlerCollection::VtCookieIndex, &_invocationHandlers); + getFake().swapVirtualTable(_originalVt); } - void detach() { - getFake().setVirtualTable(originalVtHandle.restore()); + DynamicProxy(const DynamicProxy&) = delete; + DynamicProxy(DynamicProxy&& other) FAKEIT_NO_THROWS + : _originalVt(std::move(other._originalVt)) + , _methodMocks(std::move(other._methodMocks)) + , _members(std::move(other._members)) + , _offsets(std::move(other._offsets)) + , _invocationHandlers(_methodMocks, _offsets) { + std::swap(_instancePtr, other._instancePtr); + VirtualTable::getVTable(*_instancePtr).setCookie(InvocationHandlerCollection::VtCookieIndex, &_invocationHandlers); } - ~DynamicProxy() { - _cloneVt.dispose(); + DynamicProxy& operator=(const DynamicProxy&) = delete; + DynamicProxy& operator=(DynamicProxy&&) = delete; + + ~DynamicProxy() = default; + + void detach() { + if (_instancePtr != nullptr) { + getFake().swapVirtualTable(_originalVt); + } } C &get() { - return instance; + return *_instancePtr; } void Reset() { @@ -94,7 +101,7 @@ namespace fakeit { _members = {}; _offsets = {}; _offsets.resize(VTUtils::getVTSize()); - _cloneVt.copyFrom(originalVtHandle.restore()); + VirtualTable::getVTable(*_instancePtr).copyFrom(_originalVt); } void Clear() @@ -170,8 +177,7 @@ namespace fakeit { } VirtualTable &getOriginalVT() { - VirtualTable &vt = originalVtHandle.restore(); - return vt; + return _originalVt; } template @@ -206,9 +212,8 @@ namespace fakeit { static_assert(sizeof(C) == sizeof(FakeObject), "This is a problem"); - C &instance; - typename VirtualTable::Handle originalVtHandle; // avoid delete!! this is the original! - VirtualTable _cloneVt; + C* _instancePtr = nullptr; + VirtualTable _originalVt; // avoid delete!! this is the original! // std::vector> _methodMocks; std::vector> _members; @@ -216,7 +221,7 @@ namespace fakeit { InvocationHandlers _invocationHandlers; FakeObject &getFake() { - return reinterpret_cast &>(instance); + return reinterpret_cast &>(*_instancePtr); } void bind(const MethodProxy &methodProxy, Destructible *invocationHandler) { diff --git a/include/mockutils/FakeObject.hpp b/include/mockutils/FakeObject.hpp index bf4260d7..0caa7414 100644 --- a/include/mockutils/FakeObject.hpp +++ b/include/mockutils/FakeObject.hpp @@ -58,11 +58,6 @@ namespace fakeit this->initializeDataMembersArea(); } - ~FakeObject() - { - this->vtable.dispose(); - } - void setMethod(unsigned int index, void* method) { this->vtable.setMethod(index, method); @@ -73,9 +68,9 @@ namespace fakeit return this->vtable; } - void setVirtualTable(VirtualTable& t) + void swapVirtualTable(VirtualTable& t) { - this->vtable = t; + std::swap(this->vtable, t); } void setDtor(void* dtor) diff --git a/include/mockutils/gcc/VirtualTable.hpp b/include/mockutils/gcc/VirtualTable.hpp index 79d0e106..03891818 100644 --- a/include/mockutils/gcc/VirtualTable.hpp +++ b/include/mockutils/gcc/VirtualTable.hpp @@ -7,12 +7,16 @@ */ #pragma once +#include + #ifndef __clang__ #include "mockutils/gcc/is_simple_inheritance_layout.hpp" #endif +#include "mockutils/Macros.hpp" + namespace fakeit { struct VirtualTableBase { @@ -24,6 +28,26 @@ namespace fakeit { VirtualTableBase(void **firstMethod) : _firstMethod(firstMethod) { } + VirtualTableBase(const VirtualTableBase&) = delete; + VirtualTableBase(VirtualTableBase&& other) FAKEIT_NO_THROWS { + std::swap(_firstMethod, other._firstMethod); + } + + VirtualTableBase& operator=(const VirtualTableBase&) = delete; + VirtualTableBase& operator=(VirtualTableBase&& other) FAKEIT_NO_THROWS { + std::swap(_firstMethod, other._firstMethod); + return *this; + } + + ~VirtualTableBase() { + if (_firstMethod != nullptr) { + _firstMethod--; // type_info + _firstMethod--; // top_offset + _firstMethod -= numOfCookies; // skip cookies + delete[] _firstMethod; + } + } + void *getCookie(int index) { return _firstMethod[-3 - index]; } @@ -41,7 +65,8 @@ namespace fakeit { } protected: - void **_firstMethod; + static const unsigned int numOfCookies = 2; + void **_firstMethod = nullptr; }; template @@ -51,23 +76,6 @@ namespace fakeit { static_assert(is_simple_inheritance_layout::value, "Can't mock a type with multiple inheritance"); #endif - class Handle { - - friend struct VirtualTable; - void **firstMethod; - - Handle(void **method) : - firstMethod(method) { - } - - public: - - VirtualTable &restore() { - VirtualTable *vt = (VirtualTable *) this; - return *vt; - } - }; - static VirtualTable &getVTable(C &instance) { fakeit::VirtualTable *vt = (fakeit::VirtualTable *) (&instance); return *vt; @@ -85,17 +93,10 @@ namespace fakeit { VirtualTable(buildVTArray()) { } - void dispose() { - _firstMethod--; // type_info - _firstMethod--; // top_offset - _firstMethod -= numOfCookies; // skip cookies - delete[] _firstMethod; - } - unsigned int dtor(int) { C *c = (C *) this; C &cRef = *c; - auto vt = VirtualTable::getVTable(cRef); + auto& vt = VirtualTable::getVTable(cRef); unsigned int index = VTUtils::getDestructorOffset(); void *dtorPtr = vt.getMethod(index); void(*method)(C *) = union_cast(dtorPtr); @@ -103,7 +104,6 @@ namespace fakeit { return 0; } - void setDtor(void *method) { unsigned int index = VTUtils::getDestructorOffset(); void *dtorPtr = union_cast(&VirtualTable::dtor); @@ -114,7 +114,6 @@ namespace fakeit { _firstMethod[index + 1] = dtorPtr; } - unsigned int getSize() { return VTUtils::getVTSize(); } @@ -130,14 +129,7 @@ namespace fakeit { return (const std::type_info *) (_firstMethod[-1]); } - Handle createHandle() { - Handle h(_firstMethod); - return h; - } - private: - static const unsigned int numOfCookies = 2; - static void **buildVTArray() { int size = VTUtils::getVTSize(); auto array = new void *[size + 2 + numOfCookies]{}; diff --git a/include/mockutils/mscpp/VirtualTable.hpp b/include/mockutils/mscpp/VirtualTable.hpp index 0b3c7ac5..0d57d241 100644 --- a/include/mockutils/mscpp/VirtualTable.hpp +++ b/include/mockutils/mscpp/VirtualTable.hpp @@ -7,6 +7,11 @@ */ #pragma once +#include +#include + +#include "mockutils/Macros.hpp" + namespace fakeit { typedef unsigned long dword_; @@ -167,22 +172,6 @@ namespace fakeit { template struct VirtualTable : public VirtualTableBase { - class Handle { - - friend struct VirtualTable; - - void **firstMethod; - - Handle(void **method) : firstMethod(method) { } - - public: - - VirtualTable &restore() { - VirtualTable *vt = (VirtualTable *) this; - return *vt; - } - }; - static VirtualTable &getVTable(C &instance) { fakeit::VirtualTable *vt = (fakeit::VirtualTable *) (&instance); return *vt; @@ -200,23 +189,33 @@ namespace fakeit { VirtualTable() : VirtualTable(buildVTArray()) { } - ~VirtualTable() { + VirtualTable(const VirtualTable&) = delete; + VirtualTable(VirtualTable&& other) FAKEIT_NO_THROWS + : VirtualTableBase(nullptr) { + std::swap(_firstMethod, other._firstMethod); + } + VirtualTable& operator=(const VirtualTable&) = delete; + VirtualTable& operator=(VirtualTable&& other) FAKEIT_NO_THROWS { + std::swap(_firstMethod, other._firstMethod); + return *this; } - void dispose() { - _firstMethod--; // skip objectLocator - RTTICompleteObjectLocator *locator = (RTTICompleteObjectLocator *) _firstMethod[0]; - delete locator; - _firstMethod -= numOfCookies; // skip cookies - delete[] _firstMethod; + ~VirtualTable() { + if (_firstMethod != nullptr) { + _firstMethod--; // skip objectLocator + RTTICompleteObjectLocator *locator = (RTTICompleteObjectLocator *) _firstMethod[0]; + delete locator; + _firstMethod -= numOfCookies; // skip cookies + delete[] _firstMethod; + } } // the dtor VC++ must of the format: int dtor(int) unsigned int dtor(int) { C *c = (C *) this; C &cRef = *c; - auto vt = VirtualTable::getVTable(cRef); + auto& vt = VirtualTable::getVTable(cRef); void *dtorPtr = vt.getCookie(dtorCookieIndex); void(*method)(C *) = reinterpret_cast(dtorPtr); method(c); @@ -246,18 +245,13 @@ namespace fakeit { } } - Handle createHandle() { - Handle h(_firstMethod); - return h; - } - private: - class SimpleType { }; static_assert(sizeof(unsigned int (SimpleType::*)()) == sizeof(unsigned int (C::*)()), "Can't mock a type with multiple inheritance or with non-polymorphic base class"); + static const unsigned int numOfCookies = 3; static const unsigned int dtorCookieIndex = numOfCookies - 1; // use the last cookie diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 05676a02..02af1c4b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(FakeIt_tests gcc_type_info_tests.cpp miscellaneous_tests.cpp move_only_return_tests.cpp + moving_mocks_around.cpp msc_stubbing_multiple_values_tests.cpp msc_type_info_tests.cpp overloadded_methods_tests.cpp diff --git a/tests/moving_mocks_around.cpp b/tests/moving_mocks_around.cpp new file mode 100644 index 00000000..4a0a778c --- /dev/null +++ b/tests/moving_mocks_around.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2014 Eran Pe'er. + * + * This program is made available under the terms of the MIT License. + * + * Created on Mar 10, 2014 + */ + +#include "tpunit++.hpp" +#include "fakeit.hpp" + +using namespace fakeit; + +struct MovingMocksAround : tpunit::TestFixture +{ + + MovingMocksAround() : + TestFixture( + TEST(MovingMocksAround::move_mock), + TEST(MovingMocksAround::move_mock_then_delete), + TEST(MovingMocksAround::create_mock_from_function), + TEST(MovingMocksAround::create_multiple_mocks_from_function) + ) + { + } + + class Interface + { + public: + virtual std::string function(std::string) = 0; + virtual ~Interface() = default; + }; + + Mock createMock(const std::string& str) + { + Mock mock; + When(Method(mock, function)).AlwaysReturn(str); + return mock; + } + + void move_mock() + { + const std::string paramString = "long param string to not be in SSO ------------------------------------------"; + const std::string returnedString = "long returned string to not be in SSO ------------------------------------"; + + Mock mock; + When(Method(mock, function)).AlwaysReturn(returnedString); + + Mock mockMove = std::move(mock); + + EXPECT_EQUAL(mockMove.get().function(paramString), returnedString); + + Verify(Method(mockMove, function).Using(paramString)).Exactly(1); + } + + void move_mock_then_delete() + { + const std::string paramString = "long param string to not be in SSO ------------------------------------------"; + const std::string returnedString = "long returned string to not be in SSO ------------------------------------"; + + Mock *mock = new Mock; + When(Method(*mock, function)).AlwaysReturn(returnedString); + + Mock mockMove = std::move(*mock); + delete mock; + + EXPECT_EQUAL(mockMove.get().function(paramString), returnedString); + + Verify(Method(mockMove, function).Using(paramString)).Exactly(1); + } + + void create_mock_from_function() + { + const std::string paramString = "long param string to not be in SSO ------------------------------------------"; + const std::string returnedString = "long returned string to not be in SSO ------------------------------------"; + + Mock mock = createMock(returnedString); + + EXPECT_EQUAL(mock.get().function(paramString), returnedString); + + Verify(Method(mock, function).Using(paramString)).Exactly(1); + } + + void create_multiple_mocks_from_function() + { + const std::string paramString1 = "long param 1 string to not be in SSO ---------------------------------------"; + const std::string returnedString1 = "long returned 1 string to not be in SSO ---------------------------------"; + const std::string paramString2 = "long param 2 string to not be in SSO ---------------------------------------"; + const std::string returnedString2 = "long returned 2 string to not be in SSO ---------------------------------"; + + Mock mock1 = createMock(returnedString1); + Mock mock2 = createMock(returnedString2); + + EXPECT_EQUAL(mock1.get().function(paramString1), returnedString1); + EXPECT_EQUAL(mock2.get().function(paramString2), returnedString2); + + Verify(Method(mock1, function).Using(paramString1)).Exactly(1); + Verify(Method(mock2, function).Using(paramString2)).Exactly(1); + } + +} __MovingMocksAround;