简体   繁体   中英

GoogleMock - Leaked mock objects due to cross-referencing mock classes

I have two classes, Server and Client , each with their own mock class and has a getClients and getServer method respectively (the actual classes are much bigger, but this is the minimal case). I want to add a variable in the MockServer that's returned by the getClients method, allowing me to modify this variable in my tests whenever appropriate (note the clients variable).

While this works, it will cause the two mock objects to keep each other alive, each keeping a shared_ptr reference to the other, causing the test to leak objects. Please, look at the example provided below.

Question: How can I modify the Mock Classes , without modifying the interfaces or the create functions, to prevent leaked mock objects while preserving the clients variable?

#include <gmock/gmock.h>
#include <gtest/gtest.h>

struct Client;
struct Server
{
  virtual const std::vector<std::shared_ptr<Client>>& getClients() = 0;
};
struct Client
{
  virtual std::shared_ptr<Server> getServer() = 0;
};

struct MockServer : virtual Server
{
  MOCK_METHOD0(getClients, const std::vector<std::shared_ptr<Client>>&());
  std::vector<std::shared_ptr<Client>> clients;  // I want to add this variable...

protected:
  MockServer()
  {
    ON_CALL(*this, getClients()).WillByDefault(testing::ReturnRef(this->clients));  // ...and return it whenever getClients is called.
  }
  ~MockServer()
  {
    clients.clear();  // This doesn't help...
    std::cout << __func__ << " - This is never printed." << std::endl;
  }

public:
  static std::shared_ptr<MockServer> create()
  {
    return std::make_shared<testing::NiceMock<MockServer>>();
  }
};
struct MockClient : virtual Client
{
  MOCK_METHOD0(getServer, std::shared_ptr<Server>());

protected:
  MockClient(std::shared_ptr<Server> server)
  {
    ON_CALL(*this, getServer()).WillByDefault(testing::Return(server));
  }
  ~MockClient()
  {
    std::cout << __func__ << " - This is never printed." << std::endl;
  }

public:
  static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server)
  {
    return std::make_shared<testing::NiceMock<MockClient>>(server);
  }
};

TEST(ExampleOfLeak, testLeakedMockObjects)
{
  auto server = MockServer::create();
  auto client1 = MockClient::create(server);
  auto client2 = MockClient::create(server);
  EXPECT_EQ(server, client1->getServer());
  EXPECT_EQ(server, client2->getServer());
  EXPECT_TRUE(server->getClients().empty());
  server->clients.push_back(client1);  // This causes leaked mock objects!
  server->clients.push_back(client2);  // And this too...
  EXPECT_EQ(client1, server->getClients().front());
  EXPECT_EQ(client2, server->getClients().back());
  // server->clients.clear();  // This prevents mock objects from leaking, but doing this manually everywhere is highly unfeasible. 
}

Result:

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ExampleOfLeak
[ RUN      ] ExampleOfLeak.testLeakedMockObjects
[       OK ] ExampleOfLeak.testLeakedMockObjects (0 ms)
[----------] 1 test from ExampleOfLeak (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

foo.cc:43: ERROR: this mock object (used in test ExampleOfLeak.testLeakedMockObjects) should be deleted but never is. Its address is @0x2fc3170.
foo.cc:43: ERROR: this mock object (used in test ExampleOfLeak.testLeakedMockObjects) should be deleted but never is. Its address is @0x2fcbcb0.
foo.cc:22: ERROR: this mock object (used in test ExampleOfLeak.testLeakedMockObjects) should be deleted but never is. Its address is @0x2fd6570.
ERROR: 3 leaked mock objects found at program exit.

It's possible to convert a weak_ptr into a shared_ptr using the lock() method. This way you can store a weak_ptr in you mock class and later convert it. Though do be careful and make sure the weak_ptr hasn't expired() .

#include <gmock/gmock.h>
#include <gtest/gtest.h>

struct Client;
struct Server
{
  virtual const std::vector<std::shared_ptr<Client>>& getClients() = 0;
};
struct Client
{
  virtual std::shared_ptr<Server> getServer() = 0;
};

struct MockServer : virtual Server
{
  MOCK_METHOD0(getClients, const std::vector<std::shared_ptr<Client>>&());
  std::vector<std::shared_ptr<Client>> clients;

protected:
  MockServer()
  {
    ON_CALL(*this, getClients()).WillByDefault(testing::ReturnRef(this->clients));
  }
  ~MockServer()
  {
    clients.clear();
    std::cout << __func__ << " - This is printed." << std::endl;
  }

public:
  static std::shared_ptr<MockServer> create()
  {
    return std::make_shared<testing::NiceMock<MockServer>>();
  }
};

ACTION_P(ReturnWeakToShared, wp)
{
  bool isWeakPtrExpired = wp.expired();
  EXPECT_FALSE(isWeakPtrExpired) << "Dangling pointer.";
  return wp.lock();
}

struct MockClient : virtual Client
{
  MOCK_METHOD0(getServer, std::shared_ptr<Server>());
  std::weak_ptr<Server> server;

protected:
  MockClient(std::shared_ptr<Server> server)
  {
    this->server = server;
    ON_CALL(*this, getServer()).WillByDefault(ReturnWeakToShared(this->server));
  }
  ~MockClient()
  {
    std::cout << __func__ << " - This is printed." << std::endl;
  }

public:
  static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server)
  {
    return std::make_shared<testing::NiceMock<MockClient>>(server);
  }
};

TEST(ExampleOfLeak, testLeakedMockObjects)
{
  auto server = MockServer::create();
  auto client1 = MockClient::create(server);
  auto client2 = MockClient::create(server);
  EXPECT_EQ(server, client1->getServer());
  EXPECT_EQ(server, client2->getServer());
  EXPECT_TRUE(server->getClients().empty());
  server->clients.push_back(client1);
  server->clients.push_back(client2);
  EXPECT_EQ(client1, server->getClients().front());
  EXPECT_EQ(client2, server->getClients().back());
}

the two mock objects to keep each other alive

One of them definitely should use a weak reference to the other.

For example

#include <gmock/gmock.h>
#include <gtest/gtest.h>

struct Client;
struct Server
{
  virtual const std::vector<std::weak_ptr<Client>>& getClients() = 0;
};
struct Client
{
  virtual std::shared_ptr<Server> getServer() = 0;
};

struct MockServer : virtual Server
{
  MOCK_METHOD0(getClients, const std::vector<std::weak_ptr<Client>>&());
  std::vector<std::weak_ptr<Client>> clients;  // I want to add this variable...

protected:
  MockServer()
  {
    ON_CALL(*this, getClients()).WillByDefault(testing::ReturnRef(this->clients));  // ...and return it whenever getClients is called.
  }
  ~MockServer()
  {
    clients.clear();  // This doesn't help...
    std::cout << __func__ << " - This is never printed." << std::endl;
  }

public:
  static std::shared_ptr<MockServer> create()
  {
    return std::make_shared<testing::NiceMock<MockServer>>();
  }
};
struct MockClient : virtual Client
{
  MOCK_METHOD0(getServer, std::shared_ptr<Server>());

protected:
  MockClient(std::shared_ptr<Server> server)
  {
    ON_CALL(*this, getServer()).WillByDefault(testing::Return(server));
  }
  ~MockClient()
  {
    std::cout << __func__ << " - This is never printed." << std::endl;
  }

public:
  static std::shared_ptr<MockClient> create(std::shared_ptr<Server> server)
  {
    return std::make_shared<testing::NiceMock<MockClient>>(server);
  }
};

TEST(ExampleOfLeak, testLeakedMockObjects)
{
  auto server = MockServer::create();
  auto client1 = MockClient::create(server);
  auto client2 = MockClient::create(server);
  EXPECT_EQ(server, client1->getServer());
  EXPECT_EQ(server, client2->getServer());
  EXPECT_TRUE(server->getClients().empty());
  server->clients.push_back(client1);  // This causes leaked mock objects!
  server->clients.push_back(client2);  // And this too...
  EXPECT_EQ(client1, server->getClients().front().lock());
  EXPECT_EQ(client2, server->getClients().back().lock());
  // server->clients.clear();  // This prevents mock objects from leaking, but doing this manually everywhere is highly unfeasible. 
}
Running main() from gmock_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from ExampleOfLeak
[ RUN      ] ExampleOfLeak.testLeakedMockObjects
~MockClient - This is never printed.
~MockClient - This is never printed.
~MockServer - This is never printed.
[       OK ] ExampleOfLeak.testLeakedMockObjects (0 ms)
[----------] 1 test from ExampleOfLeak (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM