diff -r 000000000000 -r 1918ee327afb tests/auto/qdbusthreading/tst_qdbusthreading.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/auto/qdbusthreading/tst_qdbusthreading.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,608 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Thread : public QThread +{ + Q_OBJECT + static int counter; +public: + Thread(bool automatic = true); + void run(); + + 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; + const char *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 initTestCase(); + 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 = Q_FUNC_INFO; + 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 = Q_FUNC_INFO; + 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 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(0), threadSpy(0) +{ + _self = this; + QCoreApplication::instance()->thread()->setObjectName("Main thread"); +} + +void tst_QDBusThreading::joinThreads() +{ + threadJoin.acquire(threadJoinCount); + threadJoinCount = 0; +} + +bool tst_QDBusThreading::waitForSignal(QObject *obj, const char *signal, int delay) +{ + QObject::connect(obj, signal, &QTestEventLoop::instance(), SLOT(exitLoop())); + QPointer 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::initTestCase() +{ +} + +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 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 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(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 = 0; + threadSpy = 0; + QDBusReply reply = iface.call("method"); + QVERIFY(reply.isValid()); + QCOMPARE(functionSpy, "void Object::method()"); + QCOMPARE(threadSpy, th); + + QTest::qWait(100); + QCOMPARE(signalSpy, 1); + + sem2.acquire(); // the object is gone + functionSpy = 0; + threadSpy = 0; + reply = iface.call("method"); + QVERIFY(!reply.isValid()); + QCOMPARE(functionSpy, (const char*)0); + 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(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 = 0; + threadSpy = 0; + QDBusReply reply = adaptor.call("method"); + QVERIFY(reply.isValid()); + QCOMPARE(functionSpy, "void Adaptor::method()"); + QCOMPARE(threadSpy, th); + + QTest::qWait(100); + QCOMPARE(signalSpy, 1); + + functionSpy = 0; + threadSpy = 0; + reply = object.call("method"); + QVERIFY(reply.isValid()); + QCOMPARE(functionSpy, "void Object::method()"); + QCOMPARE(threadSpy, th); + + QTest::qWait(100); + QCOMPARE(signalSpy, 1); + + sem2.acquire(); // the object is gone + functionSpy = 0; + threadSpy = 0; + reply = adaptor.call("method"); + QVERIFY(!reply.isValid()); + QCOMPARE(functionSpy, (const char*)0); + QCOMPARE(threadSpy, (QThread*)0); + reply = object.call("method"); + QVERIFY(!reply.isValid()); + QCOMPARE(functionSpy, (const char*)0); + QCOMPARE(threadSpy, (QThread*)0); +} + +void tst_QDBusThreading::callbackInMainThread_thread() +{ + QDBusConnection::connectToBus(QDBusConnection::SessionBus, myConnectionName); + sem2.release(); + + static_cast(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(); + Q_ASSERT(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"