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

Verify Using QString gives bad_alloc #324

Closed cancech closed 6 months ago

cancech commented 7 months ago

Greetings,

I've trying to use fakeit for unit testing of a Qt based application, and I'm running into an issue when verifying that a mocked method is called with a specific QString as a parameter.

The class I am trying to test is as follows:

class CORELIB_EXPORT PathManager {
    virtual QString runtimePath() const;
    virtual QString runtimeConfigPath() const;

    virtual QString templateConfigPath() const;
}

class CORELIB_EXPORT FileHelper {
public:
    virtual bool createDirIfNotExist(const QString &path) const;
    virtual bool copyDir(const QString &from, const QString &to, bool recursively = true) const;
};

class CORELIB_EXPORT ConfigManager {
public:
    ConfigManager(const PathManager *pathManager, const FileHelper *fileHelper) {
        fileHelper->createDirIfNotExist(pathManager->runtimePath());
        const QString &cfgPath = pathManager->runtimeConfigPath();
        if (fileHelper->createDirIfNotExist(cfgPath)) {
            fileHelper->copyDir(pathManager->templateConfigPath(), cfgPath);
        }
    }
};

The code itself works as I would expect it to (I can compile/execute it and it does exactly what I would expect it to at runtime). My unit test is as follows:

TEST(ConfigManagerTest, TestInitializationNoRuntimeDir) {
    QString runtimePath = "runtimePath";
    QString runtimeConfigPath = "runtimeConfigPath";
    QString templateConfigPath = "templateConfigPath";

    fakeit::Mock<file::PathManager> mockPathManager;
    fakeit::Mock<file::FileHelper> mockFileHelper;

    fakeit::When(Method(mockPathManager, runtimePath)).AlwaysReturn(runtimePath);
    fakeit::When(Method(mockPathManager, runtimeConfigPath)).AlwaysReturn(runtimeConfigPath);
    fakeit::When(Method(mockPathManager, templateConfigPath)).AlwaysReturn(templateConfigPath);
    fakeit::When(Method(mockFileHelper, createDirIfNotExist).Using(runtimePath)).AlwaysReturn(true);
    fakeit::When(Method(mockFileHelper, createDirIfNotExist).Using(runtimeConfigPath)).AlwaysReturn(true);
    fakeit::When(Method(mockFileHelper, copyDir).Using(templateConfigPath, runtimeConfigPath, true)).AlwaysReturn(true);

    file::ConfigManager mgr(&mockPathManager.get(), &mockFileHelper.get());

    fakeit::Verify(Method(mockPathManager, runtimePath)).Once();
    fakeit::Verify(Method(mockFileHelper, createDirIfNotExist).Using(runtimePath)).Once(); // std::bad_alloc
    fakeit::Verify(Method(mockPathManager, runtimeConfigPath)).Once();
    fakeit::Verify(Method(mockFileHelper, createDirIfNotExist).Using(runtimeConfigPath)).Once(); // std::bad_alloc
    fakeit::Verify(Method(mockPathManager, templateConfigPath)).Once();
    fakeit::Verify(Method(mockFileHelper, copyDir).Using(templateConfigPath, runtimeConfigPath, true)).Once(); // std::bad_alloc

    fakeit::VerifyNoOtherInvocations(mockPathManager, mockFileHelper);
}

The issue I'm running into is that I get a std::bad_alloc every time I run on any of the Verify checks that attempt to verify the appropriate QString argument via Using(QString) as indicated above. By this I mean

fakeit::Verify(Method(mockFileHelper, copyDir)).Once(); // Works
fakeit::Verify(Method(mockFileHelper, copyDir).Using(templateConfigPath, runtimeConfigPath, true)).Once(); // std::bad_alloc

The exact error as reported:

[----------] 1 test from ConfigManagerTest
[ RUN      ] ConfigManagerTest.TestInitializationNoRuntimeDir
unknown file: Failure
C++ exception with description "std::bad_alloc" thrown in the test body.
unknown file: Failure
C++ exception with description "std::bad_alloc" thrown in the test body.
[  FAILED  ] ConfigManagerTest.TestInitializationNoRuntimeDir (5 ms)
[----------] 1 test from ConfigManagerTest (5 ms total)

Stepping through fakeit::Verify(Method(mockFileHelper, copyDir).Using(templateConfigPath, runtimeConfigPath, true)).Once(); with the debugger I'm not sure where the std::bad_alloc is actually coming from. The first step into

        template<int id, typename R, typename T, typename ... arglist, class = typename std::enable_if<
                std::is_base_of<T, C>::value>::type>
        MockingContext<internal::WithCommonVoid_t<R>, arglist...> stub(R (T::*vMethod)(arglist...) const) {
            auto methodWithoutConstVolatile = reinterpret_cast<internal::WithCommonVoid_t<R> (T::*)(arglist...)>(vMethod);
            return impl.template stubMethod<id>(methodWithoutConstVolatile);
        }

seems to work fine (at least it gets to the end without any issues) and once the above returns the next step enters into

      basic_string(const _CharT* __s, const _Alloc& __a = _Alloc())
      : _M_dataplus(_M_local_data(), __a)
      {
    const _CharT* __end = __s ? __s + traits_type::length(__s)
      // We just need a non-null pointer here to get an exception:
      : reinterpret_cast<const _CharT*>(__alignof__(_CharT));
    _M_construct(__s, __end, random_access_iterator_tag());
      }

where __s = "copyDir". As soon as the above block ends the std::bad_alloc is thrown and the test terminates.

Note: I'm using GTest for the test framework and the unified fakeit.hpp for GTest.

OS: Windows 11 FakeIt v2.4.0 GTest v1.13.0 Qt v6.5.1

Any thoughts on what could be going on, or what more to do to troubleshoot would be greatly appreciated!

Thanks.

FranckRJ commented 6 months ago

To me it looks like it's the same issue as the one explained here: https://github.com/eranpeer/FakeIt/issues/274

TL;DR: this occurs when Verifying references, it's a limitation of the current design of the library, but there are some workarounds (shown in the issue above).

cancech commented 6 months ago

Thanks for confirming, That was my impression, but didn't really know how to verify.