1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <QtTest>
29#include <QtDBus>
30#include <QtCore/QVarLengthArray>
31#include <QtCore/QThread>
32#include <QtCore/QObject>
33#include <QtCore/QSemaphore>
34#include <QtCore/QMutex>
35#include <QtCore/QWaitCondition>
36#include <QtCore/QMap>
37
38class Thread : public QThread
39{
40 Q_OBJECT
41 static int counter;
42public:
43 Thread(bool automatic = true);
44 void run();
45
46 using QThread::exec;
47};
48int Thread::counter;
49
50class tst_QDBusThreading : public QObject
51{
52 Q_OBJECT
53 static tst_QDBusThreading *_self;
54 QAtomicInt threadJoinCount;
55 QSemaphore threadJoin;
56public:
57 QSemaphore sem1, sem2;
58 volatile bool success;
59 QEventLoop *loop;
60 enum FunctionSpy {
61 NoMethod = 0,
62 Adaptor_method,
63 Object_method
64 } functionSpy;
65
66 QThread *threadSpy;
67 int signalSpy;
68
69 tst_QDBusThreading();
70 static inline tst_QDBusThreading *self() { return _self; }
71
72 void joinThreads();
73 bool waitForSignal(QObject *obj, const char *signal, int delay = 1);
74
75public Q_SLOTS:
76 void cleanup();
77 void signalSpySlot() { ++signalSpy; }
78 void threadStarted() { threadJoinCount.ref(); }
79 void threadFinished() { threadJoin.release(); }
80
81 void dyingThread_thread();
82 void lastInstanceInOtherThread_thread();
83 void concurrentCreation_thread();
84 void disconnectAnothersConnection_thread();
85 void accessMainsConnection_thread();
86 void accessOthersConnection_thread();
87 void registerObjectInOtherThread_thread();
88 void registerAdaptorInOtherThread_thread();
89 void callbackInMainThread_thread();
90 void callbackInAuxThread_thread();
91 void callbackInAnotherAuxThread_thread();
92
93private Q_SLOTS:
94 void dyingThread();
95 void lastInstanceInOtherThread();
96 void concurrentCreation();
97 void disconnectAnothersConnection();
98 void accessMainsConnection();
99 void accessOthersConnection();
100 void registerObjectInOtherThread();
101 void registerAdaptorInOtherThread();
102 void callbackInMainThread();
103 void callbackInAuxThread();
104 void callbackInAnotherAuxThread();
105};
106tst_QDBusThreading *tst_QDBusThreading::_self;
107
108class Adaptor : public QDBusAbstractAdaptor
109{
110 Q_OBJECT
111 Q_CLASSINFO("D-Bus Interface", "local.Adaptor")
112public:
113 Adaptor(QObject *parent)
114 : QDBusAbstractAdaptor(parent)
115 {
116 }
117
118public Q_SLOTS:
119 void method()
120 {
121 tst_QDBusThreading::self()->functionSpy = tst_QDBusThreading::Adaptor_method;
122 tst_QDBusThreading::self()->threadSpy = QThread::currentThread();
123 emit signal();
124 }
125
126Q_SIGNALS:
127 void signal();
128};
129
130class Object : public QObject
131{
132 Q_OBJECT
133 Q_CLASSINFO("D-Bus Interface", "local.Object")
134public:
135 Object(bool useAdaptor)
136 {
137 if (useAdaptor)
138 new Adaptor(this);
139 }
140
141 ~Object()
142 {
143 QMetaObject::invokeMethod(obj: QThread::currentThread(), member: "quit", type: Qt::QueuedConnection);
144 }
145
146public Q_SLOTS:
147 void method()
148 {
149 tst_QDBusThreading::self()->functionSpy = tst_QDBusThreading::Object_method;
150 tst_QDBusThreading::self()->threadSpy = QThread::currentThread();
151 emit signal();
152 deleteLater();
153 }
154
155Q_SIGNALS:
156 void signal();
157};
158
159#if 0
160typedef void (*qdbusThreadDebugFunc)(int, int, QDBusConnectionPrivate *);
161QDBUS_EXPORT void qdbusDefaultThreadDebug(int, int, QDBusConnectionPrivate *);
162extern QDBUS_EXPORT qdbusThreadDebugFunc qdbusThreadDebug;
163
164static void threadDebug(int action, int condition, QDBusConnectionPrivate *p)
165{
166 qdbusDefaultThreadDebug(action, condition, p);
167}
168#endif
169
170Thread::Thread(bool automatic)
171{
172 setObjectName(QString::fromLatin1(str: "Aux thread %1").arg(a: ++counter));
173 connect(sender: this, SIGNAL(started()), receiver: tst_QDBusThreading::self(), SLOT(threadStarted()));
174 connect(sender: this, SIGNAL(finished()), receiver: tst_QDBusThreading::self(), SLOT(threadFinished()),
175 Qt::DirectConnection);
176 connect(sender: this, SIGNAL(finished()), receiver: this, SLOT(deleteLater()), Qt::DirectConnection);
177 if (automatic)
178 start();
179}
180
181void Thread::run()
182{
183 QVarLengthArray<char, 56> name;
184 name.append(abuf: QTest::currentTestFunction(), increment: qstrlen(str: QTest::currentTestFunction()));
185 name.append(abuf: "_thread", increment: sizeof "_thread");
186 QMetaObject::invokeMethod(obj: tst_QDBusThreading::self(), member: name.constData(), type: Qt::DirectConnection);
187}
188
189static const char myConnectionName[] = "connection";
190
191tst_QDBusThreading::tst_QDBusThreading()
192 : loop(0), functionSpy(NoMethod), threadSpy(0)
193{
194 _self = this;
195 QCoreApplication::instance()->thread()->setObjectName("Main thread");
196}
197
198void tst_QDBusThreading::joinThreads()
199{
200 threadJoin.acquire(n: threadJoinCount.loadRelaxed());
201 threadJoinCount.storeRelaxed(newValue: 0);
202}
203
204bool tst_QDBusThreading::waitForSignal(QObject *obj, const char *signal, int delay)
205{
206 QObject::connect(sender: obj, signal, receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
207 QPointer<QObject> safe = obj;
208
209 QTestEventLoop::instance().enterLoop(secs: delay);
210 if (!safe.isNull())
211 QObject::disconnect(sender: safe, signal, receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
212 return QTestEventLoop::instance().timeout();
213}
214
215void tst_QDBusThreading::cleanup()
216{
217 joinThreads();
218
219 if (sem1.available())
220 sem1.acquire(n: sem1.available());
221 if (sem2.available())
222 sem2.acquire(n: sem2.available());
223
224 if (QDBusConnection(myConnectionName).isConnected())
225 QDBusConnection::disconnectFromBus(name: myConnectionName);
226
227 delete loop;
228 loop = 0;
229
230 QTest::qWait(ms: 500);
231}
232
233void tst_QDBusThreading::dyingThread_thread()
234{
235 QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: myConnectionName);
236}
237
238void tst_QDBusThreading::dyingThread()
239{
240 Thread *th = new Thread(false);
241 QTestEventLoop::instance().connect(asender: th, SIGNAL(destroyed(QObject*)), SLOT(exitLoop()));
242 th->start();
243
244 QTestEventLoop::instance().enterLoop(secs: 10);
245 QVERIFY(!QTestEventLoop::instance().timeout());
246
247 QDBusConnection con(myConnectionName);
248 QDBusConnection::disconnectFromBus(name: myConnectionName);
249
250 QVERIFY(con.isConnected());
251 QDBusReply<QStringList> reply = con.interface()->registeredServiceNames();
252 QVERIFY(reply.isValid());
253 QVERIFY(!reply.value().isEmpty());
254 QVERIFY(reply.value().contains(con.baseService()));
255
256 con.interface()->callWithCallback(method: "ListNames", args: QVariantList(),
257 receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
258
259 QTestEventLoop::instance().enterLoop(secs: 1);
260 QVERIFY(!QTestEventLoop::instance().timeout());
261}
262
263void tst_QDBusThreading::lastInstanceInOtherThread_thread()
264{
265 QDBusConnection con(myConnectionName);
266 QVERIFY(con.isConnected());
267
268 QDBusConnection::disconnectFromBus(name: myConnectionName);
269
270 // con is being destroyed in the wrong thread
271}
272
273void tst_QDBusThreading::lastInstanceInOtherThread()
274{
275 Thread *th = new Thread(false);
276 // create the connection:
277 QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: myConnectionName);
278
279 th->start();
280 th->wait();
281}
282
283void tst_QDBusThreading::concurrentCreation_thread()
284{
285 sem1.acquire();
286 QDBusConnection con = QDBusConnection::connectToBus(type: QDBusConnection::SessionBus,
287 name: myConnectionName);
288 sem2.release();
289}
290
291void tst_QDBusThreading::concurrentCreation()
292{
293 Thread *th = new Thread;
294
295 {
296 sem1.release();
297 QDBusConnection con = QDBusConnection::connectToBus(type: QDBusConnection::SessionBus,
298 name: myConnectionName);
299 QVERIFY(con.isConnected());
300 sem2.acquire();
301 }
302 waitForSignal(obj: th, SIGNAL(finished()));
303 QDBusConnection::disconnectFromBus(name: myConnectionName);
304
305 QVERIFY(!QDBusConnection(myConnectionName).isConnected());
306}
307
308void tst_QDBusThreading::disconnectAnothersConnection_thread()
309{
310 QDBusConnection con = QDBusConnection::connectToBus(type: QDBusConnection::SessionBus,
311 name: myConnectionName);
312 sem2.release();
313}
314
315void tst_QDBusThreading::disconnectAnothersConnection()
316{
317 new Thread;
318 sem2.acquire();
319
320 QVERIFY(QDBusConnection(myConnectionName).isConnected());
321 QDBusConnection::disconnectFromBus(name: myConnectionName);
322}
323
324void tst_QDBusThreading::accessMainsConnection_thread()
325{
326 sem1.acquire();
327 QDBusConnection con = QDBusConnection::sessionBus();
328 con.interface()->registeredServiceNames();
329 sem2.release();
330}
331
332void tst_QDBusThreading::accessMainsConnection()
333{
334 QVERIFY(QDBusConnection::sessionBus().isConnected());
335
336 new Thread;
337 sem1.release();
338 sem2.acquire();
339};
340
341void tst_QDBusThreading::accessOthersConnection_thread()
342{
343 QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: myConnectionName);
344 sem2.release();
345
346 // wait for main thread to be done
347 sem1.acquire();
348 QDBusConnection::disconnectFromBus(name: myConnectionName);
349 sem2.release();
350}
351
352void tst_QDBusThreading::accessOthersConnection()
353{
354 new Thread;
355
356 // wait for the connection to be created
357 sem2.acquire();
358
359 {
360 QDBusConnection con(myConnectionName);
361 QVERIFY(con.isConnected());
362 QVERIFY(con.baseService() != QDBusConnection::sessionBus().baseService());
363
364 QDBusReply<QStringList> reply = con.interface()->registeredServiceNames();
365 if (!reply.isValid())
366 qDebug() << reply.error().name() << reply.error().message();
367 QVERIFY(reply.isValid());
368 QVERIFY(!reply.value().isEmpty());
369 QVERIFY(reply.value().contains(con.baseService()));
370 QVERIFY(reply.value().contains(QDBusConnection::sessionBus().baseService()));
371 }
372
373 // tell it to destroy:
374 sem1.release();
375 sem2.acquire();
376
377 QDBusConnection con(myConnectionName);
378 QVERIFY(!con.isConnected());
379}
380
381void tst_QDBusThreading::registerObjectInOtherThread_thread()
382{
383 {
384 Object *obj = new Object(false);
385 QDBusConnection::sessionBus().registerObject(path: "/", object: obj, options: QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
386
387 sem2.release();
388 static_cast<Thread *>(QThread::currentThread())->exec();
389 }
390
391 sem2.release();
392}
393
394void tst_QDBusThreading::registerObjectInOtherThread()
395{
396 QVERIFY(QDBusConnection::sessionBus().isConnected());
397 QThread *th = new Thread;
398 sem2.acquire();
399
400 signalSpy = 0;
401
402 QDBusInterface iface(QDBusConnection::sessionBus().baseService(), "/", "local.Object");
403 QVERIFY(iface.isValid());
404
405 connect(asender: &iface, SIGNAL(signal()), SLOT(signalSpySlot()));
406
407 QTest::qWait(ms: 100);
408 QCOMPARE(signalSpy, 0);
409
410 functionSpy = NoMethod;
411 threadSpy = 0;
412 QDBusReply<void> reply = iface.call(method: "method");
413 QVERIFY(reply.isValid());
414 QCOMPARE(functionSpy, Object_method);
415 QCOMPARE(threadSpy, th);
416
417 QTRY_COMPARE(signalSpy, 1);
418
419 sem2.acquire(); // the object is gone
420 functionSpy = NoMethod;
421 threadSpy = 0;
422 reply = iface.call(method: "method");
423 QVERIFY(!reply.isValid());
424 QCOMPARE(functionSpy, NoMethod);
425 QCOMPARE(threadSpy, (QThread*)0);
426}
427
428void tst_QDBusThreading::registerAdaptorInOtherThread_thread()
429{
430 {
431 Object *obj = new Object(true);
432 QDBusConnection::sessionBus().registerObject(path: "/", object: obj, options: QDBusConnection::ExportAdaptors |
433 QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
434
435 sem2.release();
436 static_cast<Thread *>(QThread::currentThread())->exec();
437 }
438
439 sem2.release();
440}
441
442void tst_QDBusThreading::registerAdaptorInOtherThread()
443{
444 QVERIFY(QDBusConnection::sessionBus().isConnected());
445 QThread *th = new Thread;
446 sem2.acquire();
447
448 QDBusInterface object(QDBusConnection::sessionBus().baseService(), "/", "local.Object");
449 QDBusInterface adaptor(QDBusConnection::sessionBus().baseService(), "/", "local.Adaptor");
450 QVERIFY(object.isValid());
451 QVERIFY(adaptor.isValid());
452
453 signalSpy = 0;
454 connect(asender: &adaptor, SIGNAL(signal()), SLOT(signalSpySlot()));
455 QCOMPARE(signalSpy, 0);
456
457 functionSpy = NoMethod;
458 threadSpy = 0;
459 QDBusReply<void> reply = adaptor.call(method: "method");
460 QVERIFY(reply.isValid());
461 QCOMPARE(functionSpy, Adaptor_method);
462 QCOMPARE(threadSpy, th);
463
464 QTRY_COMPARE(signalSpy, 1);
465
466 functionSpy = NoMethod;
467 threadSpy = 0;
468 reply = object.call(method: "method");
469 QVERIFY(reply.isValid());
470 QCOMPARE(functionSpy, Object_method);
471 QCOMPARE(threadSpy, th);
472
473 QTest::qWait(ms: 100);
474 QCOMPARE(signalSpy, 1);
475
476 sem2.acquire(); // the object is gone
477 functionSpy = NoMethod;
478 threadSpy = 0;
479 reply = adaptor.call(method: "method");
480 QVERIFY(!reply.isValid());
481 QCOMPARE(functionSpy, NoMethod);
482 QCOMPARE(threadSpy, (QThread*)0);
483 reply = object.call(method: "method");
484 QVERIFY(!reply.isValid());
485 QCOMPARE(functionSpy, NoMethod);
486 QCOMPARE(threadSpy, (QThread*)0);
487}
488
489void tst_QDBusThreading::callbackInMainThread_thread()
490{
491 QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: myConnectionName);
492 sem2.release();
493
494 static_cast<Thread *>(QThread::currentThread())->exec();
495 QDBusConnection::disconnectFromBus(name: myConnectionName);
496}
497
498void tst_QDBusThreading::callbackInMainThread()
499{
500 Thread *th = new Thread;
501
502 // wait for it to be connected
503 sem2.acquire();
504
505 QDBusConnection con(myConnectionName);
506 con.interface()->callWithCallback(method: "ListNames", args: QVariantList(),
507 receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
508 QTestEventLoop::instance().enterLoop(secs: 10);
509 QVERIFY(!QTestEventLoop::instance().timeout());
510
511 QMetaObject::invokeMethod(obj: th, member: "quit");
512 waitForSignal(obj: th, SIGNAL(finished()));
513}
514
515void tst_QDBusThreading::callbackInAuxThread_thread()
516{
517 QDBusConnection con(QDBusConnection::sessionBus());
518 QTestEventLoop ownLoop;
519 con.interface()->callWithCallback(method: "ListNames", args: QVariantList(),
520 receiver: &ownLoop, SLOT(exitLoop()));
521 ownLoop.enterLoop(secs: 10);
522 loop->exit(returnCode: ownLoop.timeout() ? 1 : 0);
523}
524
525void tst_QDBusThreading::callbackInAuxThread()
526{
527 QVERIFY(QDBusConnection::sessionBus().isConnected());
528
529 loop = new QEventLoop;
530
531 new Thread;
532 QCOMPARE(loop->exec(), 0);
533}
534
535void tst_QDBusThreading::callbackInAnotherAuxThread_thread()
536{
537 sem1.acquire();
538 if (!loop) {
539 // first thread
540 // create the connection and just wait
541 QDBusConnection con = QDBusConnection::connectToBus(type: QDBusConnection::SessionBus, name: myConnectionName);
542 loop = new QEventLoop;
543
544 // tell the main thread we have created the loop and connection
545 sem2.release();
546
547 // wait for the main thread to connect its signal
548 sem1.acquire();
549 success = loop->exec() == 0;
550 sem2.release();
551
552 // clean up
553 QDBusConnection::disconnectFromBus(name: myConnectionName);
554 } else {
555 // second thread
556 // try waiting for a message
557 QDBusConnection con(myConnectionName);
558 QTestEventLoop ownLoop;
559 con.interface()->callWithCallback(method: "ListNames", args: QVariantList(),
560 receiver: &ownLoop, SLOT(exitLoop()));
561 ownLoop.enterLoop(secs: 1);
562 loop->exit(returnCode: ownLoop.timeout() ? 1 : 0);
563 }
564}
565
566void tst_QDBusThreading::callbackInAnotherAuxThread()
567{
568 // create first thread
569 success = false;
570 new Thread;
571
572 // wait for the event loop
573 sem1.release();
574 sem2.acquire();
575 QVERIFY(loop);
576
577 // create the second thread
578 new Thread;
579 sem1.release(n: 2);
580
581 // wait for loop thread to finish executing:
582 sem2.acquire();
583
584 QVERIFY(success);
585}
586
587// Next tests:
588// - unexport an object at the moment the call is being delivered
589// - delete an object at the moment the call is being delivered
590// - keep a global-static QDBusConnection for a thread-created connection
591
592QTEST_MAIN(tst_QDBusThreading)
593#include "tst_qdbusthreading.moc"
594

source code of qtbase/tests/auto/dbus/qdbusthreading/tst_qdbusthreading.cpp