eranpeer / FakeIt

C++ mocking made easy. A simple yet very expressive, headers only library for c++ mocking.
MIT License
1.22k stars 170 forks source link

exception with unique_ptr #288

Closed zhlongfj closed 1 year ago

zhlongfj commented 1 year ago

//Constructing real class is fine unique_ptr scheduler = make_unique((unique_ptr)make_unique());

//Moc class caused exception Mock schedulerCoreMock; unique_ptr scheduler = make_unique(unique_ptr(&schedulerCoreMock.get(), {}));

//TimeLineScheduler constructor and destructor TimeLineScheduler::TimeLineScheduler(std::unique_ptr schedulerCore) :_playingMediaEntities(std::make_shared()) , _schedulerCore(move(schedulerCore)) { _schedulerCore->setPlayingMediaEntities(_playingMediaEntities); }

TimeLineScheduler::~TimeLineScheduler() = default;

//Exception info: 0x0000000000000000 Exception raised at TimelineUnitTest.exe: 0xC0000005: Access conflict occurred at execution location 0x0000000000000000.

//Call stack:

TimeLineUnitTest.exe!fakeit::VirtualTable::dtor(int __formal) 行 5541 C++ TimeLineScheduler::~TimeLineScheduler() = default;

malcolmdavey commented 1 year ago

Hi. Can you please give a more complete example,, which includes the classes you are using?

zhlongfj commented 1 year ago

//ITimeLineSchedulerCore1.h

pragma once

class ITimeLineSchedulerCore1 { public:

virtual ~ITimeLineSchedulerCore1() {}

virtual void func() = 0;

};

//TimeLineSchedulerCore1.h

pragma once

include

include "itimelineschedulercore1.h"

class TimeLineSchedulerCore1 : public ITimeLineSchedulerCore1 { public:

virtual ~TimeLineSchedulerCore1() {}

void func() {}

};

//TimeLineScheduler1.h

pragma once

include

include "itimelineschedulercore1.h"

class ITimeLineSchedulerCore1; class TimeLineScheduler1 { public: TimeLineScheduler1(std::unique_ptr schedulerCore); ~TimeLineScheduler1(); private: std::unique_ptr _schedulerCore; };

//TimeLineScheduler1.cpp

pragma once

include

include "timelinescheduler1.h"

include

include "itimelineschedulercore1.h"

TimeLineScheduler1::TimeLineScheduler1(std::unique_ptr schedulerCore) : _schedulerCore(std::move(schedulerCore)) {

}

TimeLineScheduler1::~TimeLineScheduler1() = default;

//timelinescheduler1_tests.cpp

include "fakeit.hpp"

include

include "timelinescheduler1.h"

include "timelineschedulercore1.h"

using namespace std; using namespace fakeit;

SCENARIO("TimeLineScheduler") { GIVEN("") { Mock schedulerCoreMock; auto schedulerCore = unique_ptr(&schedulerCoreMock.get(), {}); //auto schedulerCore = (unique_ptr)make_unique(); auto scheduler = std::make_unique(move(schedulerCore));

    WHEN("") {
        THEN("") {
        }
    }
}

}

zhlongfj commented 1 year ago

The code above will crash in VS2019, but the commented code is ok

malcolmdavey commented 1 year ago

Hi there. I think when you are copy and pasting into github must be removing the template parameters. I think you need to surround the code in triple `

``` some code more code ```

which gives this

some code
more code
zhlongfj commented 1 year ago
//itimelineschedulercore1.h
#pragma once

class ITimeLineSchedulerCore1
{
public:

    virtual ~ITimeLineSchedulerCore1() {}

    virtual void func() = 0;
};
//timelineschedulercore1.h
#pragma once
#include <memory>
#include "itimelineschedulercore1.h"

class TimeLineSchedulerCore1 : public ITimeLineSchedulerCore1
{
public:

    virtual ~TimeLineSchedulerCore1() {}

    void func() {}
};
//timelinescheduler1.h
#pragma once
#include <memory>
#include "itimelinescheduler.h"

class ITimeLineSchedulerCore1;
class TimeLineScheduler1
{
public:
    TimeLineScheduler1(std::unique_ptr<ITimeLineSchedulerCore1> schedulerCore);
    ~TimeLineScheduler1();
private:
    std::unique_ptr<ITimeLineSchedulerCore1> _schedulerCore;
};
//timelinescheduler1.cpp
#pragma once
#include <memory>
#include "timelinescheduler1.h"
#include <xutility>
#include "itimelineschedulercore1.h"

TimeLineScheduler1::TimeLineScheduler1(std::unique_ptr<ITimeLineSchedulerCore1> schedulerCore)
    : _schedulerCore(std::move(schedulerCore)) 
{

}

TimeLineScheduler1::~TimeLineScheduler1() = default;
//timelinescheduler1_tests.cpp
#include "fakeit.hpp"
#include <memory>
#include "timelinescheduler1.h"
#include "timelineschedulercore1.h"

using namespace std;
using namespace fakeit;

SCENARIO("TimeLineScheduler") {
    GIVEN("") {
         Mock<ITimeLineSchedulerCore1> schedulerCoreMock;
         auto schedulerCore = unique_ptr<ITimeLineSchedulerCore1>(&schedulerCoreMock.get(), {});
         //auto schedulerCore = (unique_ptr<ITimeLineSchedulerCore1>)make_unique<TimeLineSchedulerCore1>();
         auto scheduler = std::make_unique<TimeLineScheduler1>(move(schedulerCore));

        WHEN("") {
            THEN("") {
            }
        }
    }
}
malcolmdavey commented 1 year ago

For VC++ I get the same behaviour. (also note, I've configured my project for Edit and Continue, as per the instructions).

I've found I always need to do this.

    Mock<IMyInterface> myMock;
    Fake(Dtor(myMock));

    std::unique_ptr<IMyInterface> pObject(&myMock.get());

Also these two statements are the same as far as I understand

auto schedulerCore = unique_ptr<ITimeLineSchedulerCore1>(&schedulerCoreMock.get(), {});
auto schedulerCore = unique_ptr<ITimeLineSchedulerCore1>(&schedulerCoreMock.get());

unique_ptr is not the same as shared_ptr, and so this is just an instance of the destructor object whose type in this case is std::default_delete, and so {} will just result in the default constructor of std::default_delete. shared_ptr doesn't have the delete as a type, and you can just pass in a lambda as an arg.

See https://en.cppreference.com/w/cpp/memory/unique_ptr

So it seems we always need to mock the deconstructor, just like we need to mock any other called virtual function, though it would probably be better if it had a default implementation which threw as well (like other called but non-mocked virtual functions.

Not sure if there is the same behaviour on other platforms.

malcolmdavey commented 1 year ago

Looking close at the header, it seems it should work, but seems it doesn't, or no longer works for VC++ - at least for v142/2019 compiler (even with Edit and Continue)

        void unmockedDtor() {}

        void unmocked() {
            ActualInvocation<> invocation(Invocation::nextInvocationOrdinal(), UnknownMethod::instance());
            UnexpectedMethodCallEvent event(UnexpectedType::Unmocked, invocation);
            auto& fakeit = getMockImpl(this)->_fakeit;
            fakeit.handle(event);

            std::string format = fakeit.format(event);
            UnexpectedMethodCallException e(format);
            throw e;
        }
zhlongfj commented 1 year ago

It works, thanks

malcolmdavey commented 1 year ago

Anyway, I think it's not meant to crash by default. It should call unmockedDtor()

It seems it set's it up by default, but then makes a new VTable for the specific object but doesn't copy it across. Here is a fix: https://github.com/eranpeer/FakeIt/pull/289

malcolmdavey commented 1 year ago

@FranckRJ Did we want to mark this as closed now, given the fix, or are you waiting for the release or feedback?

FranckRJ commented 1 year ago

Usually I close the issues after testing on my setup that the fix did in fact fix them (when it's something that I can reproduce). I haven't yet been able to test it because I didn't have my hands on a VS setup, but I will be able soon.

malcolmdavey commented 1 year ago

I've added unit tests, but it seems unit tests they aren't yet running on CI for VS (or if they are, the results aren't shown)

FranckRJ commented 1 year ago

Fixed by #289 in FakeIt 2.3.1.