Reputation: 118
I'm currently developing the unit tests for a QT application. This application use a single worker thread that handles all database calls, so all database methods execute a concurrent call and returns a QFuture. The service class gets the Qfuture and usually emits a signal when it's completed(but I'm unsure it's always the case). But for some reason the thread seems to only execute once tests have finished.
I have solved this for instances when the methods return the QFuture and I can call .waitforfinished()
on it so it wait till it's finished (WOW). But there are methods that process the QFutures internally and those seem to only execute once all tests have finished.
My question is, how can I force or wait till all is executed? (I found out I can use spy.wait() for this specific case but I'm unsure all methods really emit a signal so are there more ways to wait for the thread inside a method to finishing executing?)
Here is an example, first the log and then the code:
log
********* Start testing of Test *********
Config: Using QtTest library 6.5.3, Qt 6.5.3
QINFO : Test::initTestCase() Database Connected!
PASS : Test::initTestCase()
FAIL! : Test::isDataInserted() Compared values are not the same
Actual (spy.count()): 0
Expected (1) : 1
/test/tests.cpp(36) : failure location
PASS : Test::cleanupTestCase()
QDEBUG : Test::isDataInserted() Running query on QThreadPoolThread(0x168, name = "Database ThreadPool")
Totals: 2 passed, 1 failed, 0 skipped, 0 blacklisted, 10ms
********* Finished testing of Test *********
Query executed on QThreadPoolThread(0x16828586790, name = "Database ThreadPool")
Query result: QVariant(qlonglong, 4)
A crash occurred \tests.exe.
Function time: 133ms Total time: 142ms
Exception address: 0x00007ff8fa1612e7
Exception code : 0xc0000005
tests.cpp
#include <QCoreApplication>
#include <QDebug>
#include <QtTest>
#include "exception.h"
#include "service.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);
QFuture test = connection.then([]() { qInfo() << "Database Connected!"; })
.onFailed([](Exception e) {
qDebug() << "Error initializing database" << e.what();
}); // None of this ever runs.
test.waitForFinished();
}
// void cleanupTestCase() {
// QFile file(dbPath);
// file.remove();
// }
void isDataInserted() {
Service service;
QSignalSpy spy(&service, SIGNAL(dataInserted()));
service.insertData("test 1");
QCOMPARE(spy.count(), 1);
}
};
QTEST_MAIN(Test)
#include "tests.moc"
dbmanager.h
#ifndef DBMANAGER_H
#define DBMANAGER_H
#include <QFuture>
#include <QSqlQuery>
#include <QtConcurrent/QtConcurrent>
#include "exception.h"
#include "thread.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");
}
QSqlQuery query(m_db);
query.prepare(
"CREATE TABLE IF NOT EXISTS TEST "
"(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT)");
bool success = query.exec();
if (!success) {
throw Exception("Error creating structure");
}
});
}
QFuture<QSqlQuery> executeQuery(QString queryString, QList<QVariant> args) {
return QtConcurrent::run(
DbThread::instance()->pool(), [this, queryString, args]() {
qDebug() << "Running query on " << QThread::currentThread();
QSqlQuery query(QSqlDatabase::database("main"));
query.prepare(queryString);
for (int i = 0; i < args.length(); i++) {
query.addBindValue(args[i]);
}
if (query.exec()) {
return query;
} else {
throw Exception("Error executing query (" + queryString + ")");
}
});
}
};
#endif // DBMANAGER_H
service.h
#ifndef SERVICE_H
#define SERVICE_H
#include <QObject>
#include "dbmanager.h"
class Service : public QObject {
Q_OBJECT
public:
void insertData(QString data) {
QString queryString = "INSERT INTO TEST (data) VALUES (?)";
auto args = QList<QVariant>{data};
DbManager db;
QFuture<QSqlQuery> result = db.executeQuery(queryString, args);
result
.then([this](QSqlQuery query) {
qDebug() << "Query executed on " << QThread::currentThread();
qDebug() << "Query result: " << query.lastInsertId();
emit dataInserted();
})
.onFailed([this](Exception e) {
qDebug() << "Error executing query: " << e.what();
emit dataInsertedFailed();
});
}
signals:
void dataInserted();
void dataInsertedFailed();
};
#endif // SERVICE_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
thread.h
#ifndef THREAD_H
#define THREAD_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 // THREAD_H
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(tests LANGUAGES CXX)
enable_testing()
find_package(Qt6 6.5 REQUIRED COMPONENTS Sql Gui Quick Test)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(sources STATIC
dbmanager.h thread.h exception.h
service.h
)
add_executable(tests tests.cpp)
add_test(NAME tests COMMAND tests)
target_link_libraries(sources
Qt6::Sql
)
target_link_libraries(tests
PRIVATE Qt6::Quick
PRIVATE Qt6::Gui
Qt6::Test
sources
)
Upvotes: 0
Views: 73