Thursday, 4 August 2016

Injeqt 1.1 and testing Qt applications

I've developed Injeqt as a way to improve Kadu quality. Its code was filled by singletons and hidden dependencies. Now, with Injeqt 1.1 released and Kadu 4.0 getting close to release I can tell that I've fulfilled my goals.

First step in making Kadu testable was to get rid of singletons and replace them with injected objects. With quick grep I discovered that the phase ::instance() had more than 2600 occurrences with more than 150 singleton classes in core alone (not counting plugins). It took me almost two months just to add Injeqt setters to each class that used these singletons and fix things that broke during that phase.

One of immediately visible benefit of this effort was that dependencies between classes were suddenly obvious (just look at header file for INJEQT_SET setters). And what follows - realization that some of them do not make any sense at all. And some classes have way too much dependencies (several classes with 10 or more dependencies and tens of classes with over 5). So its a good starting point for refactoring.

But you can't just get a 500000 lines project and refactor it. So I did what I think is the most reasonable way to it - refactor as you go (and add unit tests!).

This brings us to the core of this post - how easy it is to test classes and services using Injeqt.

First feature that I've added with this mindset was JumpList support on Windows. JumpList are additional menu items that shows in context menu over taskbar buttons, just like here:



Idea was simple - for Kadu it should display all currently open and recently used chats in two groups. This feature requires two services - OpenChatRepository and RecentChatRepository - these holds lists of open/recent chats (just wrapped sets with signals in reality). As these classes didn't exist at this time, I've implemented them and added tests (as these classes do not depend on anything, Injeqt is not used there).

Then more interesting things came - JumpList abstract class to act as an adaptor for Qt's QWinJumpList and WindowsJumpListService to to handle the chat repositories and the JumpList instance. Thanks to JumpList being an abstract interface it was easy to create JumpListStub and test the whole thing in windows-jump-list-service.test.cpp file. The core of this tests is in makeInjector method that contains private class module with all classes required to execute tests - including our stub class:


injeqt::injector
WindowsJumpListServiceTest::makeInjector() const
{
  class module : public injeqt::module
  {
  public:
    module()
    {
      add_type<JumpListStub>();
      add_type<OpenChatRepository>();
      add_type<RecentChatRepository>();
      add_type<WindowsJumpListService>();
    }
  };

  auto modules =
    std::vector<std::unique_ptr<injeqt::module>>{};
  modules.emplace_back(std::make_unique<module>());

  return injeqt::injector{std::move(modules)};
}

So I was able to recreate all required dependencies for WindowsJumpListService with ease. If OpenChatRepository or RecentChatRepository had their own dependencies it would be neccessary to mock them too, but fortunately it was not the case.

If you would like to test Injeqt if your own project or if you just have any questions, feel free to send me email and I'll be happy to answer.

Friday, 8 January 2016

Injeqt 1.1 is coming

After some hiatus (summer is time for riding a bike, not for spending evenings at home writing open-source code) I've moved Kadu few steps forward. New version - 4.0 - will require some more functionalities from Injeqt dependency injection library. So a few new had been added. Most of them for plugin support. I'll give a short description of each of them and then show you how these works together.

Subinjectors

I've promised subinjectors when Injeqt 1.0 was released. And now these are available. In short - you can create new injector with new modules and classes that also gains all knowledge and instance of another injector (or injectors).

In Kadu this means that each plugin can new have its own injector and use all of Kadu classes and services in a nice way (using INJEQT_SET setters).

INJEQT_INIT and INJEQT_DONE methods

It is quite frequent to run some code after all required objects were injected into newly created one. Think of that as of two-step initialization (in that case it is not as bad as most people describe it, do not be scared). Slots marked with INJEQT_INIT tag will do just that - be called after all INJEQT_SET/INJEQT_SETTER slots.

INJEQT_DONE marked slots that will be called before any object created by injector is destructed. It allows for cleanup, writing files do disc and do other stuff without worrying about availability of other objects (such code can not be run from destructors, because Injeqt does not guarantee anything about order of object destruction).

In Kadu INJEQT_INIT methods are used to connects slots and signals of services and to do loading/storing data.

INJEQT_SET tag

It is a new name for INJEQT_SETTER. Just to match grammar of INJEQT_INIT and INJEQT_DONE.

INJEQT_INSTANCE_ON_DEMAND and INJEQT_INSTANCE_IMMEDIATE

Now Injeqt allows you to select when given object will be instantiated. If it is tagged with INJEQT_INSTANCE_ON_DEMAND (default) the behavior will be the same as with Injeqt 1.0 - object will be first instantiated when it is needed. If class is tagged with INJEQT_INSTANCE_IMMEDIATE - object will be instantiated as soon as possible (before injeqt::injector constructor finishes). Useful for services that works on their own using signals from other services. In Kadu all main plugin objects are marked with INJEQT_INSTANCE_IMMEDIATE, so there is no need to manually create them after plugin is loaded and plugin's injector is created.

inject_into method

Sometimes you create object manually, but still want to put injectable objects into it. Just call injeqt::injector::injeqt_into(QObjec *) on it and you are done.

In Kadu this is mainly used to allow access to Kadu services by dynamically created widgets.

Usage in Kadu

Now I'll show you real-life example of how it all works together.

Lets get a quick overview on how Kadu plugins were loaded in the past (like 3.x releases).

class PluginRootComponent
{
public:
  virtual bool init() = 0;
  virtual void done() = 0;
};

Q_DECLARE_INTERFACE(PluginRootComponent, "im.kadu.PluginRootComponent")
This is the class that each plugin must implement and export. You can see example implementation for OTR encryption plugin in otr-plugin.cpp file. As you can see it uses init() method to initialize its own injector, connect some of its signals and slots (unfortunately, Injeqt can not do it for you yet). Some objects from Kadu core are retrieved using Core::instance() calls (this is one thing that I'm fixing currently). And the done() method is used to remove OTR objects from Kadu services. Fortunately the Injeqt takes care of objects created with OTR injector. The last important part are PluginRootComponentHandler class:
PluginRootComponentHandler::PluginRootComponentHandler(
  PluginRootComponent *pluginRootComponent) :
  m_pluginRootComponent(pluginRootComponent)
{
  m_pluginRootComponent->init(firstLoad));
}

PluginRootComponentHandler::~PluginRootComponentHandler() noexcept
{
  m_pluginRootComponent->done();
}
And the ActivePlugin class:
ActivePlugin::ActivePlugin(const QString &pluginName) :
  m_pluginLoader{make_unique<PluginLoader>pluginName)},
  m_pluginRootComponentHandler{make_unique(m_pluginLoader->instance())}
{
}
So, finally, the steps are:
  1. load .dll or .so file
  2. create PluginRootComponent instance from it using Qt QPluginLoader
  3. call init() method on the instance, it creates its own objects and connects to Kadu core classes with awkward ::instance() calls (either Core::instance()->className() or className::instance()
  4. before unloading call done() method
In comparison, Kadu 4.x uses different method for that. PluginRootComponent is replaced with PluginModulesFactory:
class KADUAPI PluginModulesFactory : public QObject
{
  Q_OBJECT

public:
  explicit PluginModulesFactory(QObject *parent = nullptr);
  virtual ~PluginModulesFactory();

  virtual std::vector<std::unique_ptr<injeqt::module>>
    createPluginModules() const = 0;
  virtual QString parentInjectorName() const;

};

Q_DECLARE_INTERFACE(PluginModulesFactory, "im.kadu.PluginModulesFactory")
Its only job is to create list of Injeqt modules. Implementation of this class is usually fairly simple:
std::vector<std::unique_ptr<injeqt::module>>
OtrPluginModulesFactory::createPluginModules() const
{
  auto modules = std::vector<std::unique_ptr<injeqt::module>>{};
  modules.emplace_back(make_unique<OtrModule>());

  return modules;
}
Then the injector is created and stored in PluginLoader class using following code:
injeqt::injector{std::vector<injeqt::injector *>{parentInjector},
  pluginModulesFactory->createPluginModules()};
And that is all that is needed from Kadu core. Now please look at new otr-plugin-object.h and otr-plugin-object.cpp files. There are few things to be seen in OtrPluginObject class:
  1. it is marked with INJEQT_INSTANCE_IMMEDIATE - it means it will be instantiated as soon as plugin is loaded and injector is created
  2. it has INJEQT_SET slots with Otr* objects (that come from OtrModule) and with other objects that comes from Kadu core - it is possible because of adding parentInjector into injeqt::injector constructor - we got rid of some of ::instance() calls
  3. it has INJEQT_INIT and INJEQT_DONE methods to set up and shut down the plugin - these two are called by Injeqt and replaces manually called init() and done() methods of PluginRootComponent


Summary

I think that Injeqt is going into good direction. It makes working with such a big project as Kadu a bit easier each time it is itself improved. In future I hope for two more things to be implemented in it: INJEQT_SIGNAL and INJEQT_SLOT tags that will allow to automatically connects slots and signals of Injeqt-created objects and something like INJEQT_ADD and INJEQT_REMOVE to simplify creating repository/registry-like classes. Maybe one of these feature will be added to Injeqt 1.2. After adding both of them, I could remove most of INJEQT_INIT and INJEQT_DONE methods from plugins.