Friday, 21 April 2017

Injeqt 1.2 and plans for 2.0

Injeqt 1.2 will be the last release of 1.x series. It includes one bugfix (for invalid test results in release mode) and one more feature - get_all_with_type_role method.

I do not have any ideas for new features for Injeqt 2.0. It may mean that it is feature-complete or that my use cases are very simple and not representative for wider audiences. However, for each thing I wanted to add to the library, I have come up with better (and simpler) solution that works best outside of library.

For example, I wanted a Repository feature to automatically add object that are subclasses of given class to another object. In Kadu that would be sublasses of Action added to repository named Actions. Core has a lot of them and most of plugins provides additional ones. After lots of thinking a InjectorRegisteredActions RAII class was created inside of Kadu that takes care of this without any kind of new class annotations.

Here it is:

InjectorRegisteredActions::InjectorRegisteredActions(
    Actions &actions,
    injeqt::injector &injector) : m_actions{actions}
{
    for (auto const &o : injector.get_all_with_type_role(ACTION))
    {
        auto action = qobject_cast<ActionDescription *>(o);
        if (action && m_actions.insert(action))
            m_registeredActions.push_back(action);
    }
}

InjectorRegisteredActions::~InjectorRegisteredActions()
{
    for (auto const &a : m_registeredActions)
        m_actions.remove(a);
}

It can be easily turned to template, and it will be as soon as need arise.

As I can think of any new features, Injeqt 2.0 will be more of API improvement release. There are things I need to do:

  • get rid of instantiate_all_with_type_role()
  • get rid of just-introduced get_all_with_type_role()
  • rename type role to tag
  • replace it with a types() method that will return all types with their tag information
  • then use all the data to implement instantiate_all_with_type_role() and get_all_with_type_role() outside of library or as free helper functions (with better names, of course)
  • get rid of version namespace, it is not needed as nobody will ever use it
  • check if ranges library will be useful (injeqt uses lots of containers and algorithms)

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.

Tuesday, 31 March 2015

End of Facebook Chat support

This is sad announcement. Kadu 3 will have its Facebook Chat support removed. There will be no longer possibility to add or use Facebook accounts and existing accounts will stop working. It is not Kadu fault. Facebook is closing its XMPP gates on 1.05.2015 [1] and their own protocol is not accessible for third party applications.

[1] https://developers.facebook.com/docs/chat

Tuesday, 3 March 2015

Moved to Gitlab

As you may know, Gitorious is merging with Gitlab. That means Kadu repository was also moved there: https://gitlab.com/kadu/kadu. Github repository will be kept as mirror.

Saturday, 7 February 2015

Kadu nightly for Windows

If you are interested in testing Kadu we have great news for you: since today nightly Windows builds of 2.0 and 3.0 are available under http://download.kadu.im/nightly/. Enjoy!

Thursday, 5 February 2015

New development model

Since Kadu 1.0 I'm working with new development model. It was first described in On versioning post. Its basic idea is very simple: release often, underpromise and overdeliver. Thanks to this 1.0 series had already 5 bugfix releases (last one, 1.5, was released just two weeks ago), 2.0 is almost released and work on 3.0 is underway.

Work on 2.0 was started very quickly around 1.0 release (to be completely honest, most of Qt5 port work was done by beevvy a long time ago). I didn't made big plans for it - just finish Qt5 port and add a few small features (and, of course, fix as much bugs as possible). And now it is almost release ready (rc2 will be available this weekend) and work on Kadu 3.0 is already progressing very nicely (this time with 100% support of file transfers in Gadu Gadu).

This is possible thanks to change of my own mentality (underpromise instead of overpromise) and to much improved source code quality - it is easier to make changes than 10 to 5 years ago. Also openSUSE Build Service is a great help - working Windows installer can be available just a hour after any commit.

So, get ready for 2.0 and for 3.0 testing!