qt6windows7/tests/auto/dbus/qdbusthreading/tst_qdbusthreading.cpp
2023-10-29 23:33:08 +01:00

575 lines
16 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QTestEventLoop>
#include <QVarLengthArray>
#include <QThread>
#include <QObject>
#include <QSemaphore>
#include <QMutex>
#include <QWaitCondition>
#include <QMap>
#include <QDBusAbstractAdaptor>
#include <QDBusConnection>
#include <QDBusReply>
#include <QDBusConnectionInterface>
#include <QDBusInterface>
class Thread : public QThread
{
Q_OBJECT
static int counter;
public:
Thread(bool automatic = true);
void run() override;
using QThread::exec;
};
int Thread::counter;
class tst_QDBusThreading : public QObject
{
Q_OBJECT
static tst_QDBusThreading *_self;
QAtomicInt threadJoinCount;
QSemaphore threadJoin;
public:
QSemaphore sem1, sem2;
volatile bool success;
QEventLoop *loop;
enum FunctionSpy {
NoMethod = 0,
Adaptor_method,
Object_method
} functionSpy;
QThread *threadSpy;
int signalSpy;
tst_QDBusThreading();
static inline tst_QDBusThreading *self() { return _self; }
void joinThreads();
bool waitForSignal(QObject *obj, const char *signal, int delay = 1);
public Q_SLOTS:
void cleanup();
void signalSpySlot() { ++signalSpy; }
void threadStarted() { threadJoinCount.ref(); }
void threadFinished() { threadJoin.release(); }
void dyingThread_thread();
void lastInstanceInOtherThread_thread();
void concurrentCreation_thread();
void disconnectAnothersConnection_thread();
void accessMainsConnection_thread();
void accessOthersConnection_thread();
void registerObjectInOtherThread_thread();
void registerAdaptorInOtherThread_thread();
void callbackInMainThread_thread();
void callbackInAuxThread_thread();
void callbackInAnotherAuxThread_thread();
private Q_SLOTS:
void dyingThread();
void lastInstanceInOtherThread();
void concurrentCreation();
void disconnectAnothersConnection();
void accessMainsConnection();
void accessOthersConnection();
void registerObjectInOtherThread();
void registerAdaptorInOtherThread();
void callbackInMainThread();
void callbackInAuxThread();
void callbackInAnotherAuxThread();
};
tst_QDBusThreading *tst_QDBusThreading::_self;
class Adaptor : public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "local.Adaptor")
public:
Adaptor(QObject *parent)
: QDBusAbstractAdaptor(parent)
{
}
public Q_SLOTS:
void method()
{
tst_QDBusThreading::self()->functionSpy = tst_QDBusThreading::Adaptor_method;
tst_QDBusThreading::self()->threadSpy = QThread::currentThread();
emit signal();
}
Q_SIGNALS:
void signal();
};
class Object : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "local.Object")
public:
Object(bool useAdaptor)
{
if (useAdaptor)
new Adaptor(this);
}
~Object()
{
QMetaObject::invokeMethod(QThread::currentThread(), "quit", Qt::QueuedConnection);
}
public Q_SLOTS:
void method()
{
tst_QDBusThreading::self()->functionSpy = tst_QDBusThreading::Object_method;
tst_QDBusThreading::self()->threadSpy = QThread::currentThread();
emit signal();
deleteLater();
}
Q_SIGNALS:
void signal();
};
#if 0
typedef void (*qdbusThreadDebugFunc)(int, int, QDBusConnectionPrivate *);
QDBUS_EXPORT void qdbusDefaultThreadDebug(int, int, QDBusConnectionPrivate *);
extern QDBUS_EXPORT qdbusThreadDebugFunc qdbusThreadDebug;
static void threadDebug(int action, int condition, QDBusConnectionPrivate *p)
{
qdbusDefaultThreadDebug(action, condition, p);
}
#endif
Thread::Thread(bool automatic)
{
setObjectName(QString::fromLatin1("Aux thread %1").arg(++counter));
connect(this, SIGNAL(started()), tst_QDBusThreading::self(), SLOT(threadStarted()));
connect(this, SIGNAL(finished()), tst_QDBusThreading::self(), SLOT(threadFinished()),
Qt::DirectConnection);
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()), Qt::DirectConnection);
if (automatic)
start();
}
void Thread::run()
{
QVarLengthArray<char, 56> name;
name.append(QTest::currentTestFunction(), qstrlen(QTest::currentTestFunction()));
name.append("_thread", sizeof "_thread");
QMetaObject::invokeMethod(tst_QDBusThreading::self(), name.constData(), Qt::DirectConnection);
}
static const char myConnectionName[] = "connection";
tst_QDBusThreading::tst_QDBusThreading()
: loop(0), functionSpy(NoMethod), threadSpy(0)
{
_self = this;
QCoreApplication::instance()->thread()->setObjectName("Main thread");
}
void tst_QDBusThreading::joinThreads()
{
threadJoin.acquire(threadJoinCount.loadRelaxed());
threadJoinCount.storeRelaxed(0);
}
bool tst_QDBusThreading::waitForSignal(QObject *obj, const char *signal, int delay)
{
QObject::connect(obj, signal, &QTestEventLoop::instance(), SLOT(exitLoop()));
QPointer<QObject> safe = obj;
QTestEventLoop::instance().enterLoop(delay);
if (!safe.isNull())
QObject::disconnect(safe, signal, &QTestEventLoop::instance(), SLOT(exitLoop()));
return QTestEventLoop::instance().timeout();
}
void tst_QDBusThreading::cleanup()
{
joinThreads();
if (sem1.available())
sem1.acquire(sem1.available());
if (sem2.available())
sem2.acquire(sem2.available());
if (QDBusConnection(myConnectionName).isConnected())
QDBusConnection::disconnectFromBus(myConnectionName);
delete loop;
loop = 0;
QTest::qWait(500);
}
void tst_QDBusThreading::dyingThread_thread()
{
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
}
void tst_QDBusThreading::dyingThread()
{
Thread *th = new Thread(false);
QTestEventLoop::instance().connect(th, SIGNAL(destroyed(QObject*)), SLOT(exitLoop()));
th->start();
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QDBusConnection con(myConnectionName);
QDBusConnection::disconnectFromBus(myConnectionName);
QVERIFY(con.isConnected());
QDBusReply<QStringList> reply = con.interface()->registeredServiceNames();
QVERIFY(reply.isValid());
QVERIFY(!reply.value().isEmpty());
QVERIFY(reply.value().contains(con.baseService()));
con.interface()->callWithCallback("ListNames", QVariantList(),
&QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(1);
QVERIFY(!QTestEventLoop::instance().timeout());
}
void tst_QDBusThreading::lastInstanceInOtherThread_thread()
{
QDBusConnection con(myConnectionName);
QVERIFY(con.isConnected());
QDBusConnection::disconnectFromBus(myConnectionName);
// con is being destroyed in the wrong thread
}
void tst_QDBusThreading::lastInstanceInOtherThread()
{
Thread *th = new Thread(false);
// create the connection:
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
th->start();
th->wait();
}
void tst_QDBusThreading::concurrentCreation_thread()
{
sem1.acquire();
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus,
myConnectionName);
sem2.release();
}
void tst_QDBusThreading::concurrentCreation()
{
Thread *th = new Thread;
{
sem1.release();
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus,
myConnectionName);
QVERIFY(con.isConnected());
sem2.acquire();
}
waitForSignal(th, SIGNAL(finished()));
QDBusConnection::disconnectFromBus(myConnectionName);
QVERIFY(!QDBusConnection(myConnectionName).isConnected());
}
void tst_QDBusThreading::disconnectAnothersConnection_thread()
{
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus,
myConnectionName);
sem2.release();
}
void tst_QDBusThreading::disconnectAnothersConnection()
{
new Thread;
sem2.acquire();
QVERIFY(QDBusConnection(myConnectionName).isConnected());
QDBusConnection::disconnectFromBus(myConnectionName);
}
void tst_QDBusThreading::accessMainsConnection_thread()
{
sem1.acquire();
QDBusConnection con = QDBusConnection::sessionBus();
con.interface()->registeredServiceNames();
sem2.release();
}
void tst_QDBusThreading::accessMainsConnection()
{
QVERIFY(QDBusConnection::sessionBus().isConnected());
new Thread;
sem1.release();
sem2.acquire();
};
void tst_QDBusThreading::accessOthersConnection_thread()
{
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
sem2.release();
// wait for main thread to be done
sem1.acquire();
QDBusConnection::disconnectFromBus(myConnectionName);
sem2.release();
}
void tst_QDBusThreading::accessOthersConnection()
{
new Thread;
// wait for the connection to be created
sem2.acquire();
{
QDBusConnection con(myConnectionName);
QVERIFY(con.isConnected());
QVERIFY(con.baseService() != QDBusConnection::sessionBus().baseService());
QDBusReply<QStringList> reply = con.interface()->registeredServiceNames();
if (!reply.isValid())
qDebug() << reply.error().name() << reply.error().message();
QVERIFY(reply.isValid());
QVERIFY(!reply.value().isEmpty());
QVERIFY(reply.value().contains(con.baseService()));
QVERIFY(reply.value().contains(QDBusConnection::sessionBus().baseService()));
}
// tell it to destroy:
sem1.release();
sem2.acquire();
QDBusConnection con(myConnectionName);
QVERIFY(!con.isConnected());
}
void tst_QDBusThreading::registerObjectInOtherThread_thread()
{
{
Object *obj = new Object(false);
QDBusConnection::sessionBus().registerObject("/", obj, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
sem2.release();
static_cast<Thread *>(QThread::currentThread())->exec();
}
sem2.release();
}
void tst_QDBusThreading::registerObjectInOtherThread()
{
QVERIFY(QDBusConnection::sessionBus().isConnected());
QThread *th = new Thread;
sem2.acquire();
signalSpy = 0;
QDBusInterface iface(QDBusConnection::sessionBus().baseService(), "/", "local.Object");
QVERIFY(iface.isValid());
connect(&iface, SIGNAL(signal()), SLOT(signalSpySlot()));
QTest::qWait(100);
QCOMPARE(signalSpy, 0);
functionSpy = NoMethod;
threadSpy = 0;
QDBusReply<void> reply = iface.call("method");
QVERIFY(reply.isValid());
QCOMPARE(functionSpy, Object_method);
QCOMPARE(threadSpy, th);
QTRY_COMPARE(signalSpy, 1);
sem2.acquire(); // the object is gone
functionSpy = NoMethod;
threadSpy = 0;
reply = iface.call("method");
QVERIFY(!reply.isValid());
QCOMPARE(functionSpy, NoMethod);
QCOMPARE(threadSpy, (QThread*)0);
}
void tst_QDBusThreading::registerAdaptorInOtherThread_thread()
{
{
Object *obj = new Object(true);
QDBusConnection::sessionBus().registerObject("/", obj, QDBusConnection::ExportAdaptors |
QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
sem2.release();
static_cast<Thread *>(QThread::currentThread())->exec();
}
sem2.release();
}
void tst_QDBusThreading::registerAdaptorInOtherThread()
{
QVERIFY(QDBusConnection::sessionBus().isConnected());
QThread *th = new Thread;
sem2.acquire();
QDBusInterface object(QDBusConnection::sessionBus().baseService(), "/", "local.Object");
QDBusInterface adaptor(QDBusConnection::sessionBus().baseService(), "/", "local.Adaptor");
QVERIFY(object.isValid());
QVERIFY(adaptor.isValid());
signalSpy = 0;
connect(&adaptor, SIGNAL(signal()), SLOT(signalSpySlot()));
QCOMPARE(signalSpy, 0);
functionSpy = NoMethod;
threadSpy = 0;
QDBusReply<void> reply = adaptor.call("method");
QVERIFY(reply.isValid());
QCOMPARE(functionSpy, Adaptor_method);
QCOMPARE(threadSpy, th);
QTRY_COMPARE(signalSpy, 1);
functionSpy = NoMethod;
threadSpy = 0;
reply = object.call("method");
QVERIFY(reply.isValid());
QCOMPARE(functionSpy, Object_method);
QCOMPARE(threadSpy, th);
QTest::qWait(100);
QCOMPARE(signalSpy, 1);
sem2.acquire(); // the object is gone
functionSpy = NoMethod;
threadSpy = 0;
reply = adaptor.call("method");
QVERIFY(!reply.isValid());
QCOMPARE(functionSpy, NoMethod);
QCOMPARE(threadSpy, (QThread*)0);
reply = object.call("method");
QVERIFY(!reply.isValid());
QCOMPARE(functionSpy, NoMethod);
QCOMPARE(threadSpy, (QThread*)0);
}
void tst_QDBusThreading::callbackInMainThread_thread()
{
QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
sem2.release();
static_cast<Thread *>(QThread::currentThread())->exec();
QDBusConnection::disconnectFromBus(myConnectionName);
}
void tst_QDBusThreading::callbackInMainThread()
{
Thread *th = new Thread;
// wait for it to be connected
sem2.acquire();
QDBusConnection con(myConnectionName);
con.interface()->callWithCallback("ListNames", QVariantList(),
&QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QMetaObject::invokeMethod(th, "quit");
waitForSignal(th, SIGNAL(finished()));
}
void tst_QDBusThreading::callbackInAuxThread_thread()
{
QDBusConnection con(QDBusConnection::sessionBus());
QTestEventLoop ownLoop;
con.interface()->callWithCallback("ListNames", QVariantList(),
&ownLoop, SLOT(exitLoop()));
ownLoop.enterLoop(10);
loop->exit(ownLoop.timeout() ? 1 : 0);
}
void tst_QDBusThreading::callbackInAuxThread()
{
QVERIFY(QDBusConnection::sessionBus().isConnected());
loop = new QEventLoop;
new Thread;
QCOMPARE(loop->exec(), 0);
}
void tst_QDBusThreading::callbackInAnotherAuxThread_thread()
{
sem1.acquire();
if (!loop) {
// first thread
// create the connection and just wait
QDBusConnection con = QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName);
loop = new QEventLoop;
// tell the main thread we have created the loop and connection
sem2.release();
// wait for the main thread to connect its signal
sem1.acquire();
success = loop->exec() == 0;
sem2.release();
// clean up
QDBusConnection::disconnectFromBus(myConnectionName);
} else {
// second thread
// try waiting for a message
QDBusConnection con(myConnectionName);
QTestEventLoop ownLoop;
con.interface()->callWithCallback("ListNames", QVariantList(),
&ownLoop, SLOT(exitLoop()));
ownLoop.enterLoop(1);
loop->exit(ownLoop.timeout() ? 1 : 0);
}
}
void tst_QDBusThreading::callbackInAnotherAuxThread()
{
// create first thread
success = false;
new Thread;
// wait for the event loop
sem1.release();
sem2.acquire();
QVERIFY(loop);
// create the second thread
new Thread;
sem1.release(2);
// wait for loop thread to finish executing:
sem2.acquire();
QVERIFY(success);
}
// Next tests:
// - unexport an object at the moment the call is being delivered
// - delete an object at the moment the call is being delivered
// - keep a global-static QDBusConnection for a thread-created connection
QTEST_MAIN(tst_QDBusThreading)
#include "tst_qdbusthreading.moc"