Thursday, November 10, 2011

Simple C++ UnitTests with QtCreator

I wanted to have some simple unit tests for my project to speed up testing and to cover corner cases. I'll outline here what I settled with. This is, at least for me, a suitable solution for a small free-time project. The goal was to have minimum hassle and modifications needed to the stock QtCreator created project, but still allowing me to save manual testing time with unit tests.

Here is the directory structure I used:

project.pro
 - src/main.cpp
 - src/module/implementation.cpp
 - src/module/implementation.h
 - tests/testmain.cpp
 - tests/module/testimplementation.cpp
 - tests/module/testimplementation.h

Optimally when developing and running the application, I'd want the workflow to be:

  • Compile the program. Continue if successful.
  • Compile the unit tests. Continue if successful.
  • Run the unit tests against the program
  • If unit tests fail, abort and display the errors
  • If unit tests succeed, create the application package (but without the unit test code), deploy it and run)
Qt has a pretty nice unit test framework, QTest. Unfortunately creating the workflow described above doesn't seem to be that straightforward when using QtCreator and qmake. Basically, from what I have gathered, you would need to divide your project to sub-projects where the main application code you want to test is built into a library and then different sub-projects for the proper application and the application doing the unit tests, each of which use the library. I'd like to hear if someone has an easier setup with examples for Harmattan applications?

I didn't want to use that much time into setting up the project and working out the kinks, so I settled for something simpler. If I want to run unit tests, I compile the application and the unit test files, but I exclude the application's main.cpp. Instead I include testmain.cpp which creates the unit test cases to run. If I want to run the application properly I just compile the application files without the unit test files. Whether to run the unit tests version is controlled from the project file with one CONFIG line. This removes some of the advantages of unit tests, as I have to remember to run the unit tests myself every once in a while to make sure nothing broke. But for me it's a good compromise for small and simple projects as I can use the project pretty much as it was created by QtCreator.

In my project.pro I have a line to switch between running unit tests or the proper application. This switch actually instructs the build system to include Qt Testing Library, but I use it as a trigger to switch between the unit test version and normal application version.

# Uncomment if just unit tests are to be ran.
# CONFIG += qtestlib
To run the unit tests instead of the main application, add after OTHER_FILES in project.pro rules to remove the main application's main.cpp and instead include the unit test's testmain.cpp. Also, the unit test sources are added here. Here's example from above directory structure:
qtestlib {
    # If qtestlib is in CONFIG, replace application's main.cpp
    # with unit test main. Also add unit test sources.
    SOURCES -= src/main.cpp
    SOURCES += tests/testmain.cpp \
        tests/module/testimplementation.cpp
    HEADERS += tests/logic/testimplementation.h
}
The default QtTest framework is a bit cumbersome in my opinion. It basically assumes that you have one executable for each test. I like to run all tests from the same tester. So that's why I have the testmain.cpp there. It contains the code to run all the tests. In the example it contains only the code to test the class "Implementation", but it's easy to add more tests there. Here's an example of it:

tests/testmain.cpp:

#include 
#include "module/testimplementation.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    int retval(0);

    retval += QTest::qExec(&TestImplementation(), argc, argv);
    // Add more test like above here

    return (retval ? 1 : 0);
}
A skeleton of the TestImplementation class that tests the Implementation class could be like: tests/module/testimplementation.h
#ifndef TESTIMPLEMENTATION_H
#define TESTIMPLEMENTATION_H

#include 

#include "../../src/module/implementation.h"

class TestImplementation : public QObject
{
    Q_OBJECT

private slots:
    void dummyTest();
};

#endif // TESTIMPLEMENTATION_H
tests/module/testimplementation.h
void
TestImplementation::dummyTest() {
    // Always succeeds
    QVERIFY(true);
}

Final notes

As you may read between the lines, I'm not totally satisfied with this solution. So if you have concrete examples of better solutions, please post in the comments. But if you just want something simple for a small project, hopefully this will help get you started.

Further reading

Here's some sources I used, see them for more information and ideas for more advanced methods for unit testing with Qt:

Tuesday, November 1, 2011

QmlApplicationViewer and setContextProperty

Qt SDK 1.1.3 application wizard creates a nice stub for starting the application, QmlApplicationViewer. It takes care of some of the differences in starting the application in different environments like Desktop, Symbian and Harmattan. It also handles Harmattan booster for improving application start-up time and sets-up QML debugging.

So using it, and even transferring an existing application to using it, is worth to do.

Unfortunately, the stub QmlApplicationViewer created by QtCreator does not work correctly in Harmattan if you want to set context properties to access C++ functionality from QML. Here's how I got it working without breaking the application in Desktop and Harmattan environments.

First of all, here's the basic skeleton program created by QtCreator where I want "myContextProperty" to be accessible from QML:

main.cpp:

#include <QtGui/QApplication>
#include <QtDeclarative>

#include "qmlapplicationviewer.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> 
       app(createApplication(argc, argv));
    QScopedPointer<QmlApplicationViewer> 
       viewer(QmlApplicationViewer::create());

    // Here you create "myContextProperty" that 
    // can be assigned as context property for QML.
    // See Qt documents or my earlier postings 
    // for examples.
    // 
    // After that, assign it as context property:

    QDeclarativeContext *ctxt = viewer->rootContext();
    ctxt->setContextProperty("myContextProperty", 
                             &myContextProperty);

    viewer->setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
    viewer->setMainQmlFile(QLatin1String("qml/main.qml"));
    viewer->showExpanded();

    return app->exec();
}
This is how it should work out of the box. But not with the QmlApplicationViewer created by the current QtQuick application wizard. You can see the problem yourself from the source of QmlApplicationViewer, but the basic gist of the issue is that for Harmattan the QmlApplicationViewer::create() constructs a view from MDeclarativeCache, whose root context can not be accessed from QmlApplicationViewer. So above, you are setting a context properties for something that's not really used in QML.

To fix this, we add a function to QmlApplicationViewer to access it.

qmlapplicationviewer/qmlapplicationviewer.h:

public:
    QDeclarativeContext *rootContext();

qmlapplicationviewer/qmlapplicationviewer.cpp:

QDeclarativeContext *QmlApplicationViewer::rootContext()
{
    return d->view->rootContext();
}
Note that since qmlapplicationviewer is created by the wizard, the comments in it quite correctly warn against modifying. So when updating the SDK or QtCreator, check if some automatic updates happened add/remove above code if necessary.

One more thing... the working of the new qmlapplicationviewer also depends on updating the project file. There's some nice things anyway compared to the previous SDK, so well worth the effort in my opinion.

Tuesday, July 26, 2011

Changing and accessing profiles programmatically in Harmattan

Qt's mobility APIs have currently quite restricted support for any profile based operations. For example, you can not change the current active profile. Luckily you can do so by using D-Bus, as Harmattan uses as a backend MeeGo's profile daemon.

In this posting I'll show the code to do the following:

  • Iterating all available profiles
  • Setting the used active profile
  • Catching a notification when the active profile changes

A little background information first. Harmattan's profiles that you can see (Silent, Beep, Ringing), are not really profiles. Instead they match existing profiles (silent, meeting, general) like this:

Harmattan profile type Real profile name
Silent silent
Beep meeting
Ringing general

The profile backend specifies all profiles, and each profile contains properties as key/value pairs. The Harmattan profile types are defined in each profile type under a string key of "ringing.alert.type". So when selecting "Beep" profile from N9/N950, in the background really the "Silent" profile is selected because that profile has as a property "ringing.alert.type=Beep". What this means in practice is best shown with code.

For easier comprehension, these code samples are minimal and lack any error checking (ie. they assume the D-Bus queries always succeed).

Common defines

Here's some common defines used in the code samples. They are for using the D-Bus invocations
#define PROFILED_SERVICE "com.nokia.profiled"
#define PROFILED_PATH "/com/nokia/profiled"
#define PROFILED_INTERFACE "com.nokia.profiled"
#define PROFILED_GET_PROFILES "get_profiles"
#define PROFILED_SET_PROFILE "set_profile"
#define PROFILED_GET_VALUE "get_value"
#define PROFILED_PROFILE_CHANGED "profile_changed"
// The key for accessing Harmattan's profile type under profile
#define PROFILED_TYPE_VALUE "ringing.alert.type"

Iterating all known profiles

This piece of code can be used to iterate all known profiles. These are not the Harmattan profile types that you want to show if targetting N9/N950, but it is something that is anyway needed when constructing the Harmattan profile types list:
QStringList
ProfileClient::profiles() const
{
    QDBusInterface dbus_iface(PROFILED_SERVICE, PROFILED_PATH,
                              PROFILED_INTERFACE);

    QDBusReply<QStringList> reply = 
        dbus_iface.call(PROFILED_GET_PROFILES);
    return reply.value();
}
Here's the function that should be used with Harmattan if you want to show the same values as in Harmattan UI. This function basically iterates through the profiles and takes from each profile the property that defines the Harmattan profile type (Silent, Beep or Ringing):
QStringList
ProfileClient::profileTypes() const
{
    QDBusInterface dbus_iface(PROFILED_SERVICE, PROFILED_PATH,
                              PROFILED_INTERFACE);

    QStringList profileTypes;
    QString profile;
    foreach (profile, profiles())
    {
        QDBusReply<QString> reply = 
            dbus_iface.call(PROFILED_GET_VALUE, profile, 
                            PROFILED_TYPE_VALUE);
        profileTypes.append(reply.value());
    }

    // In Harmattan at least, profile type Ringing is 
    // attached to  "general" and "outdoors" profiles. 
    // The "general" profile is the one that's used for
    // "ringing", profile "outdoors" should not be used
    // when setting a profile.
    profileTypes.removeDuplicates();
    return profileTypes;
}

Setting the used active profile

Here's the code to set the active profile to the named one. Note that this function requires the real profile (silent, meeting, general). In Harmattan you probably want to set the profile by Harmattan's profile type. Since it's an easy mapping from Harmattan profile type to the real profile name, I'll leave that detail as a fun little exercise for anyone interested.
bool
ProfileClient::setProfile(QString profileName)
{
    QDBusInterface dbus_iface(PROFILED_SERVICE, PROFILED_PATH,
                              PROFILED_INTERFACE);

    // Returns true if success
    QDBusReply<bool> reply = 
        dbus_iface.call(PROFILED_SET_PROFILE, profileName);
    return reply.value();
}

Catching a notification when the active profile changes

If there's a need to do something when active profile changes, you can attach a signal handler to using the following code sample. This catches changes done either by your program or by anything else that uses the profile backend (like changing it from N9/N950 menu)
ProfileClient::ProfileClient(QObject *parent) : QObject(parent)
{
   QDBusConnection::sessionBus().connect("", "",
       PROFILED_INTERFACE, 
       PROFILED_PROFILE_CHANGED,
       this, 
       SLOT(profileChanged(bool, bool, QString))))
}

void
ProfileClient::profileChanged(bool changed, bool active, QString profile)
{
    if (changed && active) {
        qDebug("Profile changed to %s", qPrintable(profile));
    }
}

Further reading

That's the basics for getting started, but if you want more look for hints here:

Profile backend's configuration files

See in the device (or in QEMU emulator) the files stored under /etc/profiled. These are the configuration files, and there you can see under each profile the properties that they define and that you can access like I did above with the Harmattan profile type.

Profile backend's D-Bus API

MeeGo's profile backend that Harmattan uses is open source (luckily, or I would not have been able to figure out these things), and you can see other D-Bus functions that are available for use here: MeeGo profile_dbus.h

Tuesday, July 19, 2011

Populating SelectionDialog from C++ backend

I have what I would imagine a pretty common need to create a data-set in C++ backend, and use that data to populate the values that can be selected in QML's SelectionDialog.

Since SelectionDialog seems to follow the QML Data model pattern, and it has its own delegate defined that handles the data for the platform, I thought it would be as easy as this:

PageStackWindow {
    id: appWindow

    initialPage: mainPage

    MainPage{id: mainPage}

    ToolBarLayout {
        id: commonTools
        visible: true
        ToolIcon { platformIconId: "toolbar-view-menu";
             anchors.right: parent===undefined ? undefined : parent.right
             onClicked: (myMenu.status == DialogStatus.Closed) ? myMenu.open() : myMenu.close()
        }
    }

    Menu {
        id: myMenu
        visualParent: pageStack
        MenuLayout {
            MenuItem { text: "Sample menu item" }
        }
    }
}

MainPage.qml:

Page {
    id: mainPage
    tools: commonTools

    SelectionDialog {
        id: selectionDialog
        titleText: "Select item"

        model: selectionModel
    }

    Button {
        text: "Select"

        onClicked: selectionDialog.open()
    }
}

main.cpp:

#include <QtGui/QApplication>
#include <QtDeclarative>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QStringList dataList;
    dataList.append("Item 1");
    dataList.append("Item 2");
    dataList.append("Item 3");
    dataList.append("Item 4");
    dataList.append("Item 5");

    QStringListModel dataListModel(dataList);

    QDeclarativeView view;
    QDeclarativeContext *ctxt = view.rootContext();
    ctxt->setContextProperty("selectionModel", &dataListModel);
    view.setSource(QUrl("qrc:/qml/main.qml"));
    view.showFullScreen();

    return app.exec();
}

In these code samples, only MainPage.qml and main.cpp are interesting. I create a list of items I want to show in C++, assign them to a model so that it can be shown in QML, register the model as id so that it's available in QML and then load and show the QML. When pressing the "Select" button, the items should be shown.

But they don't show, instead I got an empty list. And there is at least two separate reasons why the items are not shown:

  • Qt's models have "roles" which are used to access data in the model. SelectionDialog tries to display data from roles that are not set by QStringListModel. SelectionDialog uses data from role "name", but QStringListModel gives access to the data in the role "display".
  • SelectionDialog's model property does not accept just any model, it expects QML ListModel type. QStringListModel can not be assigned to the QML property, trying to assign it will give an error.

If you want more information and discussion on those issues, see a posting on MeeGo Forum.

These issues, especially the second one, may be temporary problems that will be fixed in future. But for now, I'll show the cleanest way that I could figure out to handle this.

For mixed C++/QML programs I think the best practice is to have the C++ part of the code to be common as much as possible across platforms, and the QML part will be what is customized for each platform as necessary. So the C++ code I represent here hopefully works in MeeGo/Harmattan as well as in Symbian. For the QML part I will only represent the Harmattan part, as that's what I'm interested in and that's what I can test.

In a nut shell, I create in QML an empty ListModel, and signal from C++ to QML side to add elements to the ListModel using JavaScript. In the C++ code I create a wrapper for QStringListModel that sets the displayed role to "name" so that Meego Harmattan and Symbian Qt Component delegates can use it. In Symbian platforms, there should be no need for the "Connections" block in the QML, as you should be able to just assign the "selectionModel" to SelectionDialog's model directly.

There is one more caveat on Meego 1.2 Harmattan API. You need to set SelectionDialog's selectedIndex property to some value. If you do not set the selected index, initially the dialog does not show the first item. So for now displaying a dialog with this code is not possible without having a default selection.

I'll represent the code here, included with comments on the parts that need explaining. If you've figured a cleaner way to do this please post in the comments.

main.cpp:

#include <QtGui/QApplication>
#include <QtDeclarative>

#include "qstringlistmodelforqtcomponent.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QStringList dataList;
    QStringListModelForQtComponent dataListModel;

    dataList.append("Item 1");
    dataList.append("Item 2");
    dataList.append("Item 3");
    dataList.append("Item 4");
    dataList.append("Item 5");

    QDeclarativeView view;
    QDeclarativeContext *ctxt = view.rootContext();
    ctxt->setContextProperty("selectionModel", &dataListModel);
    view.setSource(QUrl("qrc:/qml/main.qml"));

    // The displayed elements must be set after the QML has been
    // loaded, as on MeeGo/Harmattan adding the elements to 
    // lists is done by emitting signals. The signal handlers 
    // are not instantiated before the QML has been loaded.
    // Doing it this way should also work on Symbian  
    // Qt Components and the stock ListView because QML models 
    // are updated on the fly if they are changed.
    dataListModel.setStringList(dataList);

    view.showFullScreen();

    dataListModel.setStringList(dataList);

    return app.exec();
}
qstringlistmodelforqtcomponent.h:
#ifndef Q_STRING_LIST_MODEL_FOR_QT_COMPONENT_H
#define Q_STRING_LIST_MODEL_FOR_QT_COMPONENT_H

#include <QObject>
#include <QStringListModel>

class QStringListModelForQtComponent : public QStringListModel
{
    Q_OBJECT

public:
    // Contrary to normal QStringListModel, this class does not
    // allow giving the strings in the model in constructor. 
    // The reason is that for this model to work also on 
    // MeeGo/Harmattan, the strings must be set after the
    // QML template has been loaded. Otherwise the signal 
    // handlers in QML have not been registered. The default 
    // role name used to display the roles is "name", which is 
    // used both by Symbian and Harmattan Qt Compontents.
    QStringListModelForQtComponent(const QByteArray &displayRoleName = "name", QObject *parent = 0);

    // Overrides QStringListModel's function. Setting this will
    // emit stringAdded(QString) for each string in the list. If
    // there was non empty string list set to this model before,
    // stringsReset() signal is emitted before 
    // stringAdded(QString) signals.
    void setStringList(const QStringList &strings);

signals:
    void stringAdded(const QString &newString);
    // Emitted when setStringList is called if already contained strings
    void stringsReset();
};

#endif // Q_STRING_LIST_MODEL_FOR_QT_COMPONENT_H

qstringlistmodelforqtcomponent.cpp:
#include "qstringlistmodelforqtcomponent.h"

QStringListModelForQtComponent::QStringListModelForQtComponent(const QByteArray &displayRoleName, QObject *parent)
    : QStringListModel(parent)
{
    QHash<int, QByteArray> roleNames;
    roleNames.insert(Qt::DisplayRole, displayRoleName);
    setRoleNames(roleNames);
}

void
QStringListModelForQtComponent::setStringList(const QStringList &strings)
{
    if (!stringList().isEmpty())
    {
        emit stringsReset();
    }
    QStringListModel::setStringList(strings);
    QStringList::const_iterator i = strings.constBegin();
    for (; i != strings.constEnd(); ++i) {
        QString s = *i;
        emit stringAdded(s);
    }
}
MainPage.qml:
import QtQuick 1.1
import com.meego 1.0

Page {
    id: mainPage
    tools: commonTools

    SelectionDialog {
        id: selectionDialog
        titleText: "Select item"
        selectedIndex: 0

        model: ListModel {
            // Populated from C++ backend
        }
    }

    Connections {
        target: selectionModel
        // Only onStringAdded signal is needed, since the C++ backend
        // needs only to initialize the strings once.
        onStringAdded: {
            selectionDialog.model.append({name: newString})
        }
    }
}

Tuesday, July 12, 2011

Setting up Qt SDK for Meego Harmattan development on Ubuntu 10.04

Meego Harmattan support in the current Qt SDK release 1.1.2 is experimental. That means there can be some quirks have to worked around. I expect these quirks to be fixed in up-coming SDK releases, but I'll list here for now the steps I had to do to make the development environment suite my preliminary needs:
  1. Running the application in Harmattan emulator (Qemu)
  2. Running the application in desktop environment (for faster testing)
First thing that had to be done when installing Qt SDK is selecting custom installation and tick the Experimental / Harmattan module. That can be done later also from Help / Start Updater.

When creating a new project I chose Harmattan target. It creates a "Hello world" stub that can be run on Harmattan emulator. Compiling that worked well, but when trying to run it from Qt I got an error:

Deployment failed: Qemu was not running. It has now been started up for you, but it will take a bit of time until it is ready
But nothing happens, Qemu is not started. Turns out that by default Qt Creator uses wrong build configuration for the Harmattan target. After creating Harmattan project, it is "Harmattan Platform API". When I changed it to "Meego 1.2 Harmattan API" the Harmattan emulator started up when running the application.

Qemu is very slow however. It's probably ok for doing final testing, but during development I want faster way to test the software and its UI. So it is helpful to be able to compile and run the application as native desktop application. Unfortunately, Harmattan uses QtQuick 1.1 which comes as part of Qt 4.7.4, which hasn't been released yet. And as it's not released, the SDK does not contain it for desktop builds. Instead it contains QtQuick 1.0 and Qt 4.7.3.

The situation should be much easier when Qt SDK with Qt 4.7.4 is released. For now though, I thought it is best to compile Qt 4.7.4 to use in the SDK for development. Note: you might be better off just downloading and installing QtCreator snapshot version, if it contains Qt 4.7.4 you'll save lots of time and trial-and-error. I didn't think this thoroughly and went the way of compiling Qt myself, so I don't know if the snapshot version of QtCreator includes Qt 4.7.4. If it does, you could also just copy Qt 4.7.4 from there to your stable Qt SDK installation, thus avoiding possible instabilities in the QtCreator snapshot.

Compiling Qt snapshot

First the source code is needed. The project is hosted in gitorious, these instructions are directly from the official documentation. First create a directory where the sources should be fetched, go there, and in the terminal do:
git config --global core.autocrlf input
git clone git://gitorious.org/qt/qt.git
Qt needs several libraries' development versions installed, or some modules are not compiled and the result won't work for applications using Harmattan UI. In my system the only thing I needed was dbus development libraries:
sudo apt-get install libdbus-1-dev
I also had a problem with the GL libraries, which is due to some package management problem if NVidia drivers are installed. In my system /usr/lib/libGL.so pointed to non-existing mesa version of the library, which will cause Qt's OpenGL library not to be built. It could be corrected by simply linking it to point to NVidia's GL implementation.

Then I configured the compilation so that it will install into QtSDK:

./configure -v -prefix [path to QtSDK installation directory]/Desktop/Qt/474/gcc -opengl desktop
Compile and install:
make install
The compilation takes quite some time.

Installing Qt Components

The following is based on Kate Alhola's helpful blog post.

Before Harmattan applications can be run on desktop, the Qt components Harmattan uses need to be installed. Forum Nokia PPA has the needed package qt-components. Since I want to keep my OS environment "pure" and only need these under QtCreator, I didn't do the usual apt-get install qt-components. Instead, I downloaded the package, used "dpkg -x" to extract it and then copied the relevant files to SDK (run this in the directory where you extracted the package):

cp -r usr/lib/qt4/imports [path to QtSDK installation directory]/Desktop/Qt/474/gcc/imports

Installing Harmattan theme

To use the Qt Compontents on desktop, you will also need the theme installed. To do this, I copied the theme straight from the Harmattan emulator. Start Harmattan emulator, for example by running the "Hello world" application created by new project wizard. When the emulator is up and running, run the following command in your host terminal (not in Harmattan emulator):
sudo scp -r -P 6666 'developer@localhost:/usr/share/themes/*' /usr/share/themes/

Running Harmattan application from QtCreator in desktop

First I had to tell QtCreator about the installed Qt 4.7.4 version. This can be done from Tools / Options / Qt4. In the Qt4 Versions tab, click Add. In the version name I put "Desktop Qt 4.7.4 for GCC (Qt SDK)" and pointed qmake location to the new installation at [path to QtSDK installation directory]/Desktop/Qt/474/gcc/bin/qmake

Now opening the Harmattan "Hello World" project that was created using new project wizard, I added a Desktop build and selected it to use Qt version 4.7.4.

After these steps I could run the "Hello World" in desktop environment and the application looked just like in Harmattan emulator.