using GoogleTest from a dynamically opened library without access to caller's main

Viewed 44

I have a project (service) where my code is in a library which will be dynamically opened by a process from another project (framework) that I can't modify. I want to find a way to trigger the tests even though I can't modify the main().

The framework (not my code) implements a fmk_process which finds a service_lib.so (containing my code) from a configuration. Many such service libraries can exist for many different applications/backends. As part of fmk_process's initialization, it will call an extern "C" ServiceInit(...); from service_lib.so which purpose is to register callbacks using templates provided by the framework. It will complete other initialization tasks after that.

Then, the normal interaction with fmk_process is to send a payload on a port it listens to, the internal logic will find the callback and invoke it with the payload. Also, the payload is custom and has all its constructors private or = delete in the framework...

how am I supposed to test my service ?


simplified example code to understand:

framework.h (visible to service projects)

class Service;

class CustomPayload {
public:
  CustomPayload() = delete; // other constructors/factories are not available either
};

class PayloadInvoker {
public:
  virtual void invoke(Service& service, CustomPayload& payload) = 0;
  virtual std::shared_ptr<Service> createService() = 0;
};

class IRegistry {
public:
  virtual ~IRegistry() = default;
  virtual void register(std::string& serviceName, std::shared_ptr<PayloadInvoker> pInvoker) = 0;
  virtual void invoke(std::string& serviceName, CustomPayload& payload) = 0;
};

class Registry {
public:
  Registry();
  ~Registry() {}
  template <typename T> register(std::string& serviceName, void (T::*method)(CustomPayload& payload));
  void invoke(std::string& serviceName, CustomPayload& payload);

private:
  template <typename> class PayloadInvokerTemplate;
  std:shared_ptr<IRegistry> _pImpl;
};

template <typename T> Registry::PayloadInvokerTemplate : public PayloadInvoker {
public:
  PayloadInvokerTemplate(void (T::*method)(CustomPayload& payload)) : _method(method) {}
  std::shared_ptr<Service> createService() { return std::make_shared<T>(); }

protected:
  void invoke(Service& service, CustomPayload& payload) {
    (static_cast<T&>(service).*_method)(payload);
  }
  void (T::*_method)(CustomPayload& payload);
};

// the template which is actually used by the service_lib.so code
template <typename T> Registry::register(std::string& serviceName, void (T::*method)(CustomPayload& payload)) {
  this->_pImpl->register(serviceName, std::make_shared<PayloadInvokerTemplate<T>>(method));
}

framework.cpp (not visible to service projects)

#include "framework.h" // there are other includes obviously

class RegistryImpl : public IRegistry {
public:
  void register(std::string& serviceName, std::shared_ptr<PayloadInvoker> pInvoker) override {
    this->_map[serviceName] = pInvoker;
  }
  void invoke(std::string& serviceName, CustomPayload& payload) override {
    if (this->_map.contains(serviceName)) {
      PayloadInvoker* pInvoker = this->_map[serviceName].get();
      std::shared_ptr<Service> pService = pInvoker->createService();
      pInvoker->invoke(*pService, payload);
    }
  }

private:
  std::unordered_map<std::string, std::shared_ptr<PayloadInvoker> _map;
};

Registry::Registry() { _pImpl = std::shared_ptr<IRegistry>(new RegistryImpl()); }
void Registry::invoke(std::string& serviceName, CustomPayload& payload) {
  this->_pImpl->invoke(serviceName, payload);
}

other hidden framework code for fmk_process

int main() {
  // dlopen on service_lib.so
  // dlsym on "ServiceInit"
  // call ServiceInit() with an instance of Registry
  // other init

  // listen on port
  // call registryInstance.invoke(...)
}

now my code service.cpp, resulting in service_lib.so

class MyService1 : public Service {
public:
  void MyCallback1(std::string& serviceName, CustomPayload& payload) { /* do something */ }
  void MyCallback2(std::string& serviceName, CustomPayload& payload) { /* do something else */ }
};

class MyService2 : public Service {
public:
  void MyCallback3(std::string& serviceName, CustomPayload& payload) { /* do something */ }
  void MyCallback4(std::string& serviceName, CustomPayload& payload) { /* do something else */ }
};

extern "C" ServiceInit(Registry registry) {
  registry.register<MyService1>("service1", &MyService1::MyCallback1);
  registry.register<MyService1>("service2", &MyService1::MyCallback2);
  registry.register<MyService2>("service3", &MyService2::MyCallback3);
  registry.register<MyService2>("service4", &MyService2::MyCallback4);
}

// somewhere, somehow
TEST(MyServices) {
  EXPECT_TRUE(/* all works */);
}

// particularly this part
::testing::InitGoogleTest();
RUN_ALL_TESTS()

So how can I trigger those tests ? In my CI, I could start fmk_process and make it find service_lib.so, but how am I supposed to call RUN_ALL_TESTS() ? If I have to send a payload on the port using an external process like curl, I don't consider it a unit test, and I don't want to write a testing library relying on exchanging requests/responses.

What I'm not showing here is that I'm actually extending the framework with my own templates using the framework templates. So I need to test that callbacks registered through my templates can be reached properly by fmk_process, and particularly, I need to test new versions of the framework and make sure that the templates in my lib are still compatible. If I were to reproduce and mock the framework behavior, I would not be able to test that they are still compatible when the framework evolves (which happens quite often).

0 Answers
Related