connector: Refactor and improve tests
This commit is contained in:
parent
04c9caf7d4
commit
55757b1e57
|
@ -21,6 +21,8 @@ QT_FORWARD_DECLARE_CLASS(QUrlQuery)
|
|||
QT_FORWARD_DECLARE_CLASS(QJsonObject)
|
||||
QT_FORWARD_DECLARE_CLASS(QJsonArray)
|
||||
|
||||
class ConnectionTests;
|
||||
|
||||
namespace Data {
|
||||
#undef Q_NAMESPACE
|
||||
#define Q_NAMESPACE
|
||||
|
@ -53,6 +55,8 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingLogEntry {
|
|||
};
|
||||
|
||||
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnection : public QObject {
|
||||
friend ConnectionTests;
|
||||
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString syncthingUrl READ syncthingUrl WRITE setSyncthingUrl)
|
||||
Q_PROPERTY(QByteArray apiKey READ apiKey WRITE setApiKey)
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
#include <cppunit/TestFixture.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QStringBuilder>
|
||||
#include <QJsonDocument>
|
||||
|
||||
using namespace std;
|
||||
using namespace Data;
|
||||
|
@ -30,18 +32,33 @@ public:
|
|||
|
||||
void testConnection();
|
||||
|
||||
void testErrorCases();
|
||||
void testInitialConnection();
|
||||
void checkDevices();
|
||||
void checkDirectories() const;
|
||||
void testReconnecting();
|
||||
void testResumingAllDevices();
|
||||
void testResumingDirectory();
|
||||
void testPausingDirectory();
|
||||
void testDisconnecting();
|
||||
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
private:
|
||||
template <typename Signal, typename Action, typename Handler = function<void(void)>>
|
||||
void waitForConnection(Signal signal, Action action, Handler handler = nullptr, bool *ok = nullptr, int timeout = 1000);
|
||||
template <typename Signal, typename Action, typename Handler = function<void(void)>>
|
||||
void waitForConnectionAnyAction(Signal signal, Action action, Handler handler = nullptr, bool *ok = nullptr, int timeout = 1000);
|
||||
template <typename Handler> QMetaObject::Connection handleNewDevices(Handler handler, bool *ok);
|
||||
template <typename Handler> QMetaObject::Connection handleNewDirs(Handler handler, bool *ok);
|
||||
template <typename Action, typename... SignalInfos>
|
||||
void waitForConnection(Action action, int timeout, const SignalInfos &... signalInfos);
|
||||
template<typename Signal, typename Handler = function<void(void)>>
|
||||
SignalInfo<Signal, Handler> connectionSignal(Signal signal, const Handler &handler = function<void(void)>(), bool *correctSignalEmitted = nullptr);
|
||||
static void (SyncthingConnection::*defaultConnect())(void);
|
||||
static void (SyncthingConnection::*defaultReconnect())(void);
|
||||
static void (SyncthingConnection::*defaultDisconnect())(void);
|
||||
|
||||
template <typename Handler> TemporaryConnection handleNewDevices(Handler handler, bool *ok);
|
||||
template <typename Handler> TemporaryConnection handleNewDirs(Handler handler, bool *ok);
|
||||
|
||||
SyncthingConnection m_connection;
|
||||
QString m_ownDevId;
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(ConnectionTests);
|
||||
|
@ -67,6 +84,10 @@ void ConnectionTests::setUp()
|
|||
// keep track of status changes
|
||||
QObject::connect(&m_connection, &SyncthingConnection::statusChanged,
|
||||
[this] { cerr << " - Connection status changed to: " << m_connection.statusText().toLocal8Bit().data() << endl; });
|
||||
|
||||
// log configuration change
|
||||
QObject::connect(&m_connection, &SyncthingConnection::newConfig,
|
||||
[] (const QJsonObject &config) { cerr << " - New config: " << QJsonDocument(config).toJson(QJsonDocument::Compact).data() << endl; });
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -84,25 +105,37 @@ void ConnectionTests::tearDown()
|
|||
/*!
|
||||
* \brief Variant of waitForSignal() where sender is the connection and the action is a method of the connection.
|
||||
*/
|
||||
template <typename Signal, typename Action, typename Handler>
|
||||
void ConnectionTests::waitForConnection(Signal signal, Action action, Handler handler, bool *ok, int timeout)
|
||||
template <typename Action, typename... SignalInfos>
|
||||
void ConnectionTests::waitForConnection(Action action, int timeout, const SignalInfos &...signalInfos)
|
||||
{
|
||||
waitForSignal(&m_connection, signal, bind(action, &m_connection), timeout, handler, ok);
|
||||
waitForSignals(bind(action, &m_connection), timeout, signalInfos...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Variant of waitForSignal() where sender is the connection.
|
||||
*/
|
||||
template <typename Signal, typename Action, typename Handler>
|
||||
void ConnectionTests::waitForConnectionAnyAction(Signal signal, Action action, Handler handler, bool *ok, int timeout)
|
||||
template<typename Signal, typename Handler>
|
||||
SignalInfo<Signal, Handler> ConnectionTests::connectionSignal(Signal signal, const Handler &handler, bool *correctSignalEmitted)
|
||||
{
|
||||
waitForSignal(&m_connection, signal, action, timeout, handler, ok);
|
||||
return SignalInfo<Signal, Handler>(&m_connection, signal, handler, correctSignalEmitted);
|
||||
}
|
||||
|
||||
void (SyncthingConnection::*ConnectionTests::defaultConnect())(void)
|
||||
{
|
||||
return static_cast<void(SyncthingConnection::*)(void)>(&SyncthingConnection::connect);
|
||||
}
|
||||
|
||||
void (SyncthingConnection::*ConnectionTests::defaultReconnect())(void)
|
||||
{
|
||||
return static_cast<void(SyncthingConnection::*)(void)>(&SyncthingConnection::reconnect);
|
||||
}
|
||||
|
||||
void (SyncthingConnection::*ConnectionTests::defaultDisconnect())(void)
|
||||
{
|
||||
return static_cast<void(SyncthingConnection::*)(void)>(&SyncthingConnection::disconnect);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Helps handling newDevices() signal when waiting for device change.
|
||||
*/
|
||||
template <typename Handler> QMetaObject::Connection ConnectionTests::handleNewDevices(Handler handler, bool *ok)
|
||||
template <typename Handler> TemporaryConnection ConnectionTests::handleNewDevices(Handler handler, bool *ok)
|
||||
{
|
||||
return QObject::connect(&m_connection, &SyncthingConnection::newDevices, [ok, &handler](const std::vector<SyncthingDev> &devs) {
|
||||
for (const SyncthingDev &dev : devs) {
|
||||
|
@ -114,7 +147,7 @@ template <typename Handler> QMetaObject::Connection ConnectionTests::handleNewDe
|
|||
/*!
|
||||
* \brief Helps handling newDirs() signal when waiting for directory change.
|
||||
*/
|
||||
template <typename Handler> QMetaObject::Connection ConnectionTests::handleNewDirs(Handler handler, bool *ok)
|
||||
template <typename Handler> TemporaryConnection ConnectionTests::handleNewDirs(Handler handler, bool *ok)
|
||||
{
|
||||
return QObject::connect(&m_connection, &SyncthingConnection::newDirs, [ok, &handler](const std::vector<SyncthingDir> &dirs) {
|
||||
for (const SyncthingDir &dir : dirs) {
|
||||
|
@ -132,9 +165,26 @@ template <typename Handler> QMetaObject::Connection ConnectionTests::handleNewDi
|
|||
*/
|
||||
void ConnectionTests::testConnection()
|
||||
{
|
||||
cerr << "\n - Connecting initially ..." << endl;
|
||||
testErrorCases();
|
||||
testInitialConnection();
|
||||
checkDevices();
|
||||
checkDirectories();
|
||||
testReconnecting();
|
||||
testResumingAllDevices();
|
||||
testResumingDirectory();
|
||||
testPausingDirectory();
|
||||
testDisconnecting();
|
||||
}
|
||||
|
||||
// error in case of inavailability and wrong API key
|
||||
void ConnectionTests::testErrorCases()
|
||||
{
|
||||
cerr << "\n - Error handling in case of insufficient conficuration ..." << endl;
|
||||
waitForConnection(defaultConnect(), 1000,
|
||||
connectionSignal(&SyncthingConnection::error, [](const QString &errorMessage) {
|
||||
CPPUNIT_ASSERT_EQUAL(QStringLiteral("Connection configuration is insufficient."), errorMessage);
|
||||
}));
|
||||
|
||||
cerr << "\n - Error handling in case of inavailability and wrong API key ..." << endl;
|
||||
m_connection.setApiKey(QByteArray("wrong API key"));
|
||||
bool syncthingAvailable = false;
|
||||
const function<void(const QString &errorMessage)> errorHandler = [this, &syncthingAvailable](const QString &errorMessage) {
|
||||
|
@ -153,24 +203,29 @@ void ConnectionTests::testConnection()
|
|||
}
|
||||
};
|
||||
while (!syncthingAvailable) {
|
||||
waitForConnection(&SyncthingConnection::error, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect), errorHandler);
|
||||
waitForConnection(defaultConnect(), 5000, connectionSignal(&SyncthingConnection::error, errorHandler));
|
||||
}
|
||||
}
|
||||
|
||||
// initial connection
|
||||
void ConnectionTests::testInitialConnection()
|
||||
{
|
||||
cerr << "\n - Connecting initially ..." << endl;
|
||||
m_connection.setApiKey(apiKey().toUtf8());
|
||||
waitForConnection(&SyncthingConnection::statusChanged, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
|
||||
waitForConnection(defaultConnect(), 5000, connectionSignal(&SyncthingConnection::statusChanged));
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(
|
||||
"connected and paused (one dev is initially paused)", QStringLiteral("connected, paused"), m_connection.statusText());
|
||||
CPPUNIT_ASSERT_MESSAGE("no dirs out-of-sync", !m_connection.hasOutOfSyncDirs());
|
||||
}
|
||||
|
||||
// devs present
|
||||
void ConnectionTests::checkDevices()
|
||||
{
|
||||
const auto &devInfo = m_connection.devInfo();
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("3 devs present", 3_st, devInfo.size());
|
||||
QString ownDevId;
|
||||
for (const SyncthingDev &dev : devInfo) {
|
||||
if (dev.id != QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7")
|
||||
&& dev.id != QStringLiteral("6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4")) {
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("own device", QStringLiteral("own device"), dev.statusString());
|
||||
ownDevId = dev.id;
|
||||
m_ownDevId = dev.id;
|
||||
}
|
||||
}
|
||||
for (const SyncthingDev &dev : devInfo) {
|
||||
|
@ -188,8 +243,10 @@ void ConnectionTests::testConnection()
|
|||
CPPUNIT_ASSERT_EQUAL(QStringLiteral("dynamic"), dev.addresses.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dirs present
|
||||
void ConnectionTests::checkDirectories() const
|
||||
{
|
||||
const auto &dirInfo = m_connection.dirInfo();
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("2 dirs present", 2_st, dirInfo.size());
|
||||
const SyncthingDir &dir1 = dirInfo.front();
|
||||
|
@ -200,7 +257,7 @@ void ConnectionTests::testConnection()
|
|||
CPPUNIT_ASSERT(!dir1.readOnly);
|
||||
CPPUNIT_ASSERT(!dir1.paused);
|
||||
CPPUNIT_ASSERT_EQUAL(dir1.devices.toSet(), QSet<QString>({ QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7"),
|
||||
QStringLiteral("6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4"), ownDevId }));
|
||||
QStringLiteral("6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4"), m_ownDevId }));
|
||||
const SyncthingDir &dir2 = dirInfo.back();
|
||||
CPPUNIT_ASSERT_EQUAL(QStringLiteral("test2"), dir2.id);
|
||||
CPPUNIT_ASSERT_EQUAL(QStringLiteral("Test dir 2"), dir2.label);
|
||||
|
@ -209,58 +266,83 @@ void ConnectionTests::testConnection()
|
|||
CPPUNIT_ASSERT(!dir2.readOnly);
|
||||
CPPUNIT_ASSERT(dir2.paused);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
dir2.devices.toSet(), QSet<QString>({ QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7"), ownDevId }));
|
||||
// reconnecting
|
||||
dir2.devices.toSet(), QSet<QString>({ QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7"), m_ownDevId }));
|
||||
}
|
||||
|
||||
void ConnectionTests::testReconnecting()
|
||||
{
|
||||
cerr << "\n - Reconnecting ..." << endl;
|
||||
waitForConnection(&SyncthingConnection::statusChanged, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::reconnect));
|
||||
waitForConnection(defaultReconnect(), 1000, connectionSignal(&SyncthingConnection::statusChanged));
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("reconnecting", QStringLiteral("reconnecting"), m_connection.statusText());
|
||||
waitForConnectionAnyAction(&SyncthingConnection::statusChanged, noop);
|
||||
waitForSignals(noop, 1000, connectionSignal(&SyncthingConnection::statusChanged));
|
||||
if (m_connection.isConnected() && m_connection.status() != SyncthingStatus::Paused) {
|
||||
// FIXME: Maybe it takes one further update to recon paused dev?
|
||||
waitForConnectionAnyAction(&SyncthingConnection::statusChanged, noop);
|
||||
// FIXME: Maybe it takes one further update to recognize paused dev?
|
||||
waitForSignals(noop, 1000, connectionSignal(&SyncthingConnection::statusChanged));
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("connected again", QStringLiteral("connected, paused"), m_connection.statusText());
|
||||
}
|
||||
|
||||
cerr << "\n - Pausing/resuming devs/dirs ..." << endl;
|
||||
// resume all devs
|
||||
void ConnectionTests::testResumingAllDevices()
|
||||
{
|
||||
cerr << "\n - Resuming all devices ..." << endl;
|
||||
bool devResumed = false;
|
||||
const function<void(const SyncthingDev &, int)> devResumedHandler = [&devResumed](const SyncthingDev &dev, int) {
|
||||
if (dev.name == QStringLiteral("Test dev 2") && !dev.paused) {
|
||||
devResumed = true;
|
||||
}
|
||||
};
|
||||
const auto newDevsConnection1 = handleNewDevices(devResumedHandler, &devResumed);
|
||||
waitForConnection(&SyncthingConnection::devStatusChanged, &SyncthingConnection::resumeAllDevs, devResumedHandler, &devResumed);
|
||||
QObject::disconnect(newDevsConnection1);
|
||||
const function<void(const QStringList &)> devResumedTriggeredHandler = [this](const QStringList &devIds) {
|
||||
CPPUNIT_ASSERT_EQUAL(m_connection.deviceIds(), devIds);
|
||||
};
|
||||
const auto newDevsConnection = handleNewDevices(devResumedHandler, &devResumed);
|
||||
waitForConnection(&SyncthingConnection::resumeAllDevs, 1000,
|
||||
connectionSignal(&SyncthingConnection::devStatusChanged, devResumedHandler, &devResumed),
|
||||
connectionSignal(&SyncthingConnection::deviceResumeTriggered, devResumedTriggeredHandler));
|
||||
CPPUNIT_ASSERT(devResumed);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("not paused anymore", QStringLiteral("connected"), m_connection.statusText());
|
||||
for (const QJsonValue &devValue : m_connection.m_rawConfig.value(QStringLiteral("devices")).toArray()) {
|
||||
const QJsonObject &devObj(devValue.toObject());
|
||||
CPPUNIT_ASSERT(!devObj.isEmpty());
|
||||
CPPUNIT_ASSERT_MESSAGE("raw config updated accordingly", !devObj.value(QStringLiteral("paused")).toBool(true));
|
||||
}
|
||||
}
|
||||
|
||||
// resume all dirs
|
||||
void ConnectionTests::testResumingDirectory()
|
||||
{
|
||||
cerr << "\n - Resuming all dirs ..." << endl;
|
||||
bool dirResumed = false;
|
||||
const function<void(const SyncthingDir &, int)> dirResumedHandler = [&dirResumed](const SyncthingDir &dir, int) {
|
||||
if (dir.id == QStringLiteral("test2") && !dir.paused) {
|
||||
dirResumed = true;
|
||||
}
|
||||
};
|
||||
const auto newDirsConnection1 = handleNewDirs(dirResumedHandler, &dirResumed);
|
||||
waitForConnection(&SyncthingConnection::dirStatusChanged, &SyncthingConnection::resumeAllDirs, dirResumedHandler, &dirResumed);
|
||||
QObject::disconnect(newDirsConnection1);
|
||||
const auto newDirsConnection = handleNewDirs(dirResumedHandler, &dirResumed);
|
||||
waitForConnection(&SyncthingConnection::resumeAllDirs, 1000, connectionSignal(&SyncthingConnection::dirStatusChanged, dirResumedHandler, &dirResumed));
|
||||
CPPUNIT_ASSERT(dirResumed);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("still 2 dirs present", 2_st, m_connection.dirInfo().size());
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("still not paused anymore", QStringLiteral("connected"), m_connection.statusText());
|
||||
}
|
||||
|
||||
// pause dir 1
|
||||
void ConnectionTests::testPausingDirectory()
|
||||
{
|
||||
cerr << "\n - Pause dir 1 ..." << endl;
|
||||
bool dirPaused = false;
|
||||
const function<void(const SyncthingDir &, int)> dirPausedHandler = [&dirPaused](const SyncthingDir &dir, int) {
|
||||
if (dir.id == QStringLiteral("test1") && dir.paused) {
|
||||
dirPaused = true;
|
||||
}
|
||||
};
|
||||
const auto newDirsConnection2 = handleNewDirs(dirPausedHandler, &dirPaused);
|
||||
waitForConnectionAnyAction(&SyncthingConnection::dirStatusChanged,
|
||||
bind(&SyncthingConnection::pauseDirectories, &m_connection, QStringList({ QStringLiteral("test1") })), dirPausedHandler, &dirPaused);
|
||||
QObject::disconnect(newDirsConnection2);
|
||||
const auto newDirsConnection = handleNewDirs(dirPausedHandler, &dirPaused);
|
||||
waitForSignals(bind(&SyncthingConnection::pauseDirectories, &m_connection, QStringList({ QStringLiteral("test1") })), 1000,
|
||||
connectionSignal(&SyncthingConnection::dirStatusChanged, dirPausedHandler, &dirPaused));
|
||||
CPPUNIT_ASSERT(dirPaused);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("still 2 dirs present", 2_st, m_connection.dirInfo().size());
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("still not paused anymore", QStringLiteral("connected"), m_connection.statusText());
|
||||
}
|
||||
|
||||
// disconnecting
|
||||
void ConnectionTests::testDisconnecting()
|
||||
{
|
||||
cerr << "\n - Disconnecting ..." << endl;
|
||||
waitForConnection(&SyncthingConnection::statusChanged, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::disconnect));
|
||||
waitForConnection(defaultDisconnect(), 1000, connectionSignal(&SyncthingConnection::statusChanged));
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("disconnected", QStringLiteral("disconnected"), m_connection.statusText());
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ inline std::ostream &operator<<(std::ostream &o, const QSet<QString> &qstringset
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Waits for the in ms specified \a duration keeping the event loop running.
|
||||
* \brief Waits for the\a duration specified in ms while keeping the event loop running.
|
||||
*/
|
||||
inline void wait(int duration)
|
||||
{
|
||||
|
@ -53,62 +53,227 @@ inline void wait(int duration)
|
|||
loop.exec();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Does nothing - meant to be used in waitForSignals() if no action needs to be triggered.
|
||||
*/
|
||||
inline void noop()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Waits until the \a signal is emitted by \a sender when performing \a action and connects \a signal with \a handler if specified.
|
||||
* \arg sender Specifies the sender which is assumed to emit \a signal.
|
||||
* \arg signal Specifies the signal.
|
||||
* \arg action Specifies the action to be invoked when waiting.
|
||||
* \arg timeout Specifies the max. time to wait.
|
||||
* \arg handler Specifies a handler which will be also connected to \a signal.
|
||||
* \arg ok Specifies whether the correct signal has been emitted. Should be set in \a handler to indicate that the emitted signal is actually the one
|
||||
* the test is waiting for (and not just one which has been emitted as side-effect).
|
||||
* \throws Fails if \a signal is not emitted in at least \a timeout milliseconds or when at least one of the required
|
||||
* connections can not be established.
|
||||
* \remarks The handler is disconnected before the function returns.
|
||||
* \brief The TemporaryConnection class disconnects a QMetaObject::Connection when being destroyed.
|
||||
*/
|
||||
template <typename Signal, typename Action, typename Handler = std::function<void(void)>>
|
||||
void waitForSignal(typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal, Action action, int timeout = 2500,
|
||||
Handler handler = nullptr, bool *ok = nullptr)
|
||||
class TemporaryConnection
|
||||
{
|
||||
// determine name of the signal for error messages
|
||||
const QByteArray signalName(QMetaMethod::fromSignal(signal).name());
|
||||
public:
|
||||
TemporaryConnection(QMetaObject::Connection connection) :
|
||||
m_connection(connection)
|
||||
{
|
||||
}
|
||||
|
||||
// use loop for waiting
|
||||
QEventLoop loop;
|
||||
~TemporaryConnection()
|
||||
{
|
||||
QObject::disconnect(m_connection);
|
||||
}
|
||||
|
||||
// if specified, connect handler to signal
|
||||
QMetaObject::Connection handlerConnection;
|
||||
if (handler) {
|
||||
handlerConnection = QObject::connect(sender, signal, sender, handler, Qt::DirectConnection);
|
||||
if (!handlerConnection) {
|
||||
CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName.data(), " to handler"));
|
||||
private:
|
||||
QMetaObject::Connection m_connection;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The SignalInfo class represents a connection of a signal with a handler.
|
||||
*
|
||||
* SignalInfo objects are meant to be passed to waitForSignals() so the function can keep track
|
||||
* of emitted signals.
|
||||
*/
|
||||
template<typename Signal, typename Handler>
|
||||
class SignalInfo
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructs a SignalInfo with handler and automatically connects the handler to the signal.
|
||||
* \param sender Specifies the object which will emit \a signal.
|
||||
* \param signal Specifies the signal.
|
||||
* \param handler Specifies a handler to be connected to \a signal.
|
||||
* \param correctSignalEmitted Specifies whether the correct signal has been emitted. Should be set in \a handler to indicate that the emitted signal is actually the one
|
||||
* the test is waiting for (and not just one which has been emitted as side-effect).
|
||||
*/
|
||||
SignalInfo(typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal, const Handler &handler, bool *correctSignalEmitted = nullptr) :
|
||||
sender(sender),
|
||||
signal(signal),
|
||||
correctSignalEmitted(correctSignalEmitted),
|
||||
signalEmitted(false)
|
||||
{
|
||||
// register handler if specified
|
||||
if(handler) {
|
||||
handlerConnection = QObject::connect(sender, signal, sender, handler, Qt::DirectConnection);
|
||||
if (!handlerConnection) {
|
||||
CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName().data(), " to handler"));
|
||||
}
|
||||
}
|
||||
// register own handler to detect whether signal has been emitted
|
||||
emittedConnection = QObject::connect(sender, signal, sender, [this] {
|
||||
signalEmitted = true;
|
||||
}, Qt::DirectConnection);
|
||||
if (!emittedConnection) {
|
||||
CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName().data(), " to check for signal emmitation"));
|
||||
}
|
||||
}
|
||||
|
||||
// connect the signal to the quit slot of the loop
|
||||
if (!QObject::connect(sender, signal, &loop, &QEventLoop::quit, Qt::DirectConnection)) {
|
||||
CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName.data(), " for waiting"));
|
||||
SignalInfo(const SignalInfo &other) = delete;
|
||||
|
||||
SignalInfo(SignalInfo &&other) :
|
||||
sender(other.sender),
|
||||
signal(other.signal),
|
||||
handlerConnection(other.handlerConnection),
|
||||
emittedConnection(other.emittedConnection),
|
||||
loopConnection(other.loopConnection),
|
||||
correctSignalEmitted(other.correctSignalEmitted),
|
||||
signalEmitted(other.signalEmitted)
|
||||
{
|
||||
other.handlerConnection = other.emittedConnection = other.loopConnection = QMetaObject::Connection();
|
||||
}
|
||||
|
||||
// handle case when signal is directly emitted
|
||||
bool signalDirectlyEmitted = false;
|
||||
QMetaObject::Connection signalDirectlyEmittedConnection
|
||||
= QObject::connect(sender, signal, sender, [&signalDirectlyEmitted] { signalDirectlyEmitted = true; }, Qt::DirectConnection);
|
||||
if (!signalDirectlyEmittedConnection) {
|
||||
CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName.data(), " to check for direct emmitation"));
|
||||
/*!
|
||||
* \brief Disconnects any established connections.
|
||||
*/
|
||||
~SignalInfo()
|
||||
{
|
||||
QObject::disconnect(handlerConnection);
|
||||
QObject::disconnect(emittedConnection);
|
||||
QObject::disconnect(loopConnection);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether the signal has been emitted.
|
||||
*/
|
||||
operator bool() const
|
||||
{
|
||||
return (correctSignalEmitted && *correctSignalEmitted) || (!correctSignalEmitted && signalEmitted);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the name of the signal as string.
|
||||
*/
|
||||
QByteArray signalName() const
|
||||
{
|
||||
return QMetaMethod::fromSignal(signal).name();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Connects the signal to the specified \a loop so the loop is being interrupted when the signal
|
||||
* has been emitted.
|
||||
*/
|
||||
void connectToLoop(QEventLoop *loop) const
|
||||
{
|
||||
QObject::disconnect(loopConnection);
|
||||
loopConnection = QObject::connect(sender, signal, loop, &QEventLoop::quit, Qt::DirectConnection);
|
||||
if (!loopConnection) {
|
||||
CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName().data(), " for waiting"));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typename QtPrivate::FunctionPointer<Signal>::Object *sender;
|
||||
Signal signal;
|
||||
QMetaObject::Connection handlerConnection;
|
||||
QMetaObject::Connection emittedConnection;
|
||||
mutable QMetaObject::Connection loopConnection;
|
||||
bool *correctSignalEmitted = nullptr;
|
||||
bool signalEmitted;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new SignalInfo.
|
||||
*/
|
||||
template<typename Signal, typename Handler>
|
||||
inline SignalInfo<Signal, Handler> signalInfo(typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal, const Handler &handler = Handler(), bool *correctSignalEmitted = nullptr)
|
||||
{
|
||||
return SignalInfo<Signal, Handler>(sender, signal, handler, correctSignalEmitted);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Connects the specified signal infos the \a loop via SignalInfo::connectToLoop().
|
||||
*/
|
||||
template <typename SignalInfo>
|
||||
inline void connectSignalInfosToLoop(QEventLoop *loop, const SignalInfo &signalInfo)
|
||||
{
|
||||
signalInfo.connectToLoop(loop);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Connects the specified signal infos the \a loop via SignalInfo::connectToLoop().
|
||||
*/
|
||||
template <typename SignalInfo, typename... SignalInfos>
|
||||
inline void connectSignalInfosToLoop(QEventLoop *loop, const SignalInfo &firstSignalInfo, const SignalInfos &... remainingSignalInfos)
|
||||
{
|
||||
connectSignalInfosToLoop(loop, firstSignalInfo);
|
||||
connectSignalInfosToLoop(loop, remainingSignalInfos...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Checks whether all specified signals have been emitted.
|
||||
*/
|
||||
template <typename SignalInfo>
|
||||
inline bool checkWhetherAllSignalsEmitted(const SignalInfo &signalInfo)
|
||||
{
|
||||
return signalInfo;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Checks whether all specified signals have been emitted.
|
||||
*/
|
||||
template <typename SignalInfo, typename... SignalInfos>
|
||||
inline bool checkWhetherAllSignalsEmitted(const SignalInfo &firstSignalInfo, const SignalInfos &... remainingSignalInfos)
|
||||
{
|
||||
return firstSignalInfo && checkWhetherAllSignalsEmitted(remainingSignalInfos...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the names of all specified signal infos which haven't been emitted yet as comma-separated string.
|
||||
*/
|
||||
template <typename SignalInfo>
|
||||
inline QByteArray failedSignalNames(const SignalInfo &signalInfo)
|
||||
{
|
||||
return !signalInfo ? signalInfo.signalName() : QByteArray();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the names of all specified signal infos which haven't been emitted yet as comma-separated string.
|
||||
*/
|
||||
template <typename SignalInfo, typename... SignalInfos>
|
||||
inline QByteArray failedSignalNames(const SignalInfo &firstSignalInfo, const SignalInfos &... remainingSignalInfos)
|
||||
{
|
||||
const QByteArray firstSignalName = failedSignalNames(firstSignalInfo);
|
||||
if(!firstSignalName.isEmpty()) {
|
||||
return firstSignalName + ", " + failedSignalNames(remainingSignalInfos...);
|
||||
} else {
|
||||
return failedSignalNames(remainingSignalInfos...);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Waits until the specified signals have been emitted when performing async operations triggered by \a action.
|
||||
* \arg action Specifies a method to trigger the action to run when waiting.
|
||||
* \arg timeout Specifies the max. time to wait. Set to zero to wait forever.
|
||||
* \arg signalInfos Specifies the signals to wait for.
|
||||
* \throws Fails if not all signals have been emitted in at least \a timeout milliseconds or when at least one of the
|
||||
* required connections can not be established.
|
||||
*/
|
||||
template <typename Action, typename... SignalInfos>
|
||||
void waitForSignals(Action action, int timeout, const SignalInfos &... signalInfos)
|
||||
{
|
||||
// use loop for waiting
|
||||
QEventLoop loop;
|
||||
|
||||
// connect all signals to loop so loop is interrupted when one of the signals is emitted
|
||||
connectSignalInfosToLoop(&loop, signalInfos...);
|
||||
|
||||
// perform specified action
|
||||
action();
|
||||
|
||||
// no reason to enter event loop when signal has been emitted directly
|
||||
if ((!ok || *ok) && signalDirectlyEmitted) {
|
||||
QObject::disconnect(signalDirectlyEmittedConnection);
|
||||
QObject::disconnect(handlerConnection);
|
||||
// no reason to enter event loop when all signals have been emitted directly
|
||||
if(checkWhetherAllSignalsEmitted(signalInfos...)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -122,21 +287,15 @@ void waitForSignal(typename QtPrivate::FunctionPointer<Signal>::Object *sender,
|
|||
}
|
||||
|
||||
// exec event loop as long as the right signal has not been emitted yet and there is still time
|
||||
if (!ok) {
|
||||
bool allSignalsEmitted = false;
|
||||
do {
|
||||
loop.exec();
|
||||
} else {
|
||||
while (!*ok && (!timeout || timer.isActive())) {
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
} while (!(allSignalsEmitted = checkWhetherAllSignalsEmitted(signalInfos...)) && (!timeout || timer.isActive()));
|
||||
|
||||
// check whether a timeout occured
|
||||
if ((!ok || !*ok) && timeout && !timer.isActive()) {
|
||||
CPPUNIT_FAIL(argsToString("Signal ", signalName.data(), " has not emmitted within at least ", timeout, " ms."));
|
||||
if (!allSignalsEmitted && timeout && !timer.isActive()) {
|
||||
CPPUNIT_FAIL(argsToString("Signal(s) ", failedSignalNames(signalInfos...).data(), " has/have not emmitted within at least ", timeout, " ms."));
|
||||
}
|
||||
|
||||
QObject::disconnect(signalDirectlyEmittedConnection);
|
||||
QObject::disconnect(handlerConnection);
|
||||
}
|
||||
|
||||
#endif // SYNCTHINGTESTHELPER_H
|
||||
|
|
Loading…
Reference in New Issue