0

I'm building the tests for my application. Currently, the code delegates all database calls to a separate worker thread. When trying to test the functions, I see no call for the thread executes.

I saw that I can call QCoreApplication::exec() for executing the Event Loop calls, but this executes some and then hangs forever.

Here, anda_skoa suggests "using a test-local QEventLoop instance that quits on the result signal", but I have no idea how to do this.

Below is a sample of current code:

tests.cpp

#include <QtTest>
#include <QCoreApplication>
#include <QDebug>

#include "dbmanager.h"
#include "exception.h"

class Test : public QObject
{
    Q_OBJECT

    const QString dbPath = "test_DB.db";

private slots:
    void initTestCase()
    {
        DbManager* db = new DbManager();
        QFuture<void> connection = db->connect(dbPath);

        connection
            .then([](){
                qInfo() << "Database Connected!";
            }).onFailed([](Exception e){
                qDebug() << "Error initializing database" << e.what();
            }); // None of this ever runs.
    }

    void isTrueTrue()
    {
        QCOMPARE(true, true);
    }
};

QTEST_MAIN(Test)

#include "tests.moc"

dbmanager.h

#include <QSqlQuery>
#include <QFuture>
#include <QtConcurrent/QtConcurrent>

#include "dbthread.h"
#include "exception.h"

class DbManager : public QObject
{
    Q_OBJECT
public:
    QFuture<void> connect(const QString &path)
    {
        return QtConcurrent::run(DbThread::instance()->pool(), [path]() {
            QSqlDatabase m_db = QSqlDatabase::addDatabase("QSQLITE", "main");
            m_db.setDatabaseName(path);

            if (!m_db.open())
            {
                throw Exception("Error connecting to the database");
            }
        });
    }
};

dbthread.h

#ifndef DBTHREAD_H
#define DBTHREAD_H

#include <QThreadPool>

class DbThread
{
    DbThread() {
        m_pool = new QThreadPool();
        m_pool->setMaxThreadCount(1);
        m_pool->setExpiryTimeout(-1);
        m_pool->setObjectName("Database ThreadPool");
    };

    virtual ~DbThread() {
        m_pool->deleteLater();
    };

public:
    DbThread( const DbThread& ) = delete;               // singletons should not be copy-constructed
    DbThread& operator=( const DbThread& ) = delete;    // singletons should not be assignable

    static DbThread* instance() {

        if ( !m_instance )
            m_instance = new DbThread();

        return m_instance;
    }

    QThreadPool* pool() const { return m_pool; };

private:
    inline static DbThread* m_instance = nullptr; // https://stackoverflow.com/a/61519399/12172630
    QThreadPool* m_pool;

};

#endif // DBTHREAD_H

exception.h

#ifndef EXCEPTION_H
#define EXCEPTION_H

#include <QException>

class Exception : public QException
{

public:
    Exception(QString text)
    {
        m_text = text.toLocal8Bit();
    }

    const char* what() const noexcept
    {
        return m_text.data(); // https://wiki.qt.io/Technical_FAQ#How_can_I_convert_a_QString_to_char.2A_and_vice_versa.3F
    }

    void raise() const
    {
        Exception e = *this;
        throw e;
    }

    Exception *clone() const
    {
        return new Exception(*this);
    }


private:
    QByteArray m_text;

};

#endif // EXCEPTION_H

Many of the subsequent tests in this test are also methods wrapped on a Concurrent run that return a QFuture. However, I can see that none of the lambdas are executed as I can see that no Debug log is produced, and all the tests fail, as the values are not changed because their logic was not executed.

If I add a QCoreApplication::exec() at the end of initTestCase(), the lambdas are executed, but it hangs there forever, and nothing more is executed.

Everything works fine on my application, so I'm sure something is missing for testing only.

What to do?

2 Answers 2

0

Because you question doesn't contain a minimal reproducible example I'm not even trying to answer to it but I'll try to answer to the title of your question including what you were wondering about the linked discussion in Qt Centre:

"using a test-local QEventLoop instance that quits on the result signal", but I have no idea how to do this

You could familiarize with QFutureWatcher which allows monitoring a QFuture using signals and slots. I'm showing below a code snippet that uses local event loop in a Qt test app. It may not be directly applicable to your problem but hopefully gives you ideas to get forward.

QFutureWatcher<int> watcher;
QEventLoop loop;
QSignalSpy spy(&watcher, &QFutureWatcher<int>::finished);
QObject::connect(&watcher, &QFutureWatcherBase::finished, 
                 &loop, &QEventLoop::quit, Qt::QueuedConnection);
auto future = QtConcurrent::run([](){
    return 10;
});
watcher.setFuture(future);
QCOMPARE(spy.count(), 0);

loop.exec();

QCOMPARE(watcher.result(), 10);
QCOMPARE(spy.count(), 1);
1
  • Sorry, I added a minimal reproducible exemple now Commented Dec 28, 2023 at 14:48
0

I figured it out! If you have the future variable you can call [varaible with the future].waitForFinished(); and it will have it executed. With this I was able to get the database up on the initTestCase()!

I'm still having issues when I don't have the future variable, e.g. when the concurrent is being called inside a method that do not return the future, but I will open a new stack for it. Thanks!

Not the answer you're looking for? Browse other questions tagged or ask your own question.