1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Copyright (C) 2016 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | #include <QtCore/QObject> |
30 | #include <QtCore/QVariant> |
31 | #include <QtCore/QList> |
32 | #include <QtCore/QThread> |
33 | #include <QtCore/QVector> |
34 | #include <QtTest/QtTest> |
35 | #include <QtDBus> |
36 | |
37 | #define TEST_INTERFACE_NAME "org.qtproject.QtDBus.MyObject" |
38 | |
39 | class MyObject : public QDBusAbstractAdaptor |
40 | { |
41 | Q_OBJECT |
42 | Q_CLASSINFO("D-Bus Interface" , "org.qtproject.QtDBus.MyObject" ) |
43 | |
44 | public: |
45 | MyObject(QObject* parent =0) |
46 | : QDBusAbstractAdaptor(parent) |
47 | {} |
48 | |
49 | public slots: |
50 | QStringList returnFoo() const |
51 | { return QStringList() << QString::fromLatin1(str: "foo" ); } |
52 | |
53 | void returnError(const QDBusMessage &msg) const |
54 | { |
55 | msg.setDelayedReply(true); |
56 | QDBusConnection::sessionBus().send(message: msg.createErrorReply(name: "dbuspendingcall_error" , msg: "" )); |
57 | } |
58 | }; |
59 | |
60 | class tst_QDBusPendingCall: public QObject |
61 | { |
62 | Q_OBJECT |
63 | |
64 | public: |
65 | tst_QDBusPendingCall(); |
66 | |
67 | public Q_SLOTS: |
68 | void callback(const QStringList &list); |
69 | void errorCallback(const QDBusError &error); |
70 | void finished(QDBusPendingCallWatcher *call); |
71 | void makeCall(); |
72 | |
73 | private Q_SLOTS: |
74 | void initTestCase(); |
75 | void waitForFinished(); |
76 | void waitForFinished_error(); |
77 | void watcher(); |
78 | void watcher_error(); |
79 | void watcher_waitForFinished(); |
80 | void watcher_waitForFinished_threaded(); |
81 | void watcher_waitForFinished_alreadyFinished(); |
82 | void watcher_waitForFinished_alreadyFinished_eventLoop(); |
83 | void watcher_waitForFinished_error(); |
84 | void callInsideWaitForFinished(); |
85 | |
86 | void callWithCallback_localLoop(); |
87 | void callWithCallback_localLoop_errorReply(); |
88 | |
89 | private: |
90 | QDBusPendingCall sendMessage(); |
91 | QDBusPendingCall sendError(); |
92 | |
93 | QDBusConnection conn; |
94 | |
95 | enum { CallbackCalled, ErrorCallbackCalled, FinishCalled, MakeCallCalled }; |
96 | int slotCalled; |
97 | int callCount; |
98 | QStringList callbackArgument; |
99 | QDBusError errorArgument; |
100 | QDBusPendingCallWatcher *watchArgument; |
101 | MyObject *obj; |
102 | }; |
103 | |
104 | tst_QDBusPendingCall::tst_QDBusPendingCall() |
105 | : conn(QDBusConnection::sessionBus()) |
106 | , obj(new MyObject(this)) |
107 | { |
108 | } |
109 | |
110 | void tst_QDBusPendingCall::finished(QDBusPendingCallWatcher *call) |
111 | { |
112 | slotCalled = FinishCalled; |
113 | ++callCount; |
114 | watchArgument = call; |
115 | if (QThread::currentThread() == thread()) |
116 | QTestEventLoop::instance().exitLoop(); |
117 | } |
118 | |
119 | void tst_QDBusPendingCall::callback(const QStringList &list) |
120 | { |
121 | slotCalled = CallbackCalled; |
122 | ++callCount; |
123 | callbackArgument = list; |
124 | QTestEventLoop::instance().exitLoop(); |
125 | } |
126 | |
127 | void tst_QDBusPendingCall::errorCallback(const QDBusError &error) |
128 | { |
129 | slotCalled = ErrorCallbackCalled; |
130 | ++callCount; |
131 | errorArgument = error; |
132 | QTestEventLoop::instance().exitLoop(); |
133 | } |
134 | |
135 | void tst_QDBusPendingCall::makeCall() |
136 | { |
137 | // make an external call to D-Bus to make sure we haven't left any locks |
138 | slotCalled = MakeCallCalled; |
139 | ++callCount; |
140 | |
141 | sendMessage().waitForFinished(); |
142 | } |
143 | |
144 | void tst_QDBusPendingCall::initTestCase() |
145 | { |
146 | QVERIFY(conn.isConnected()); |
147 | QVERIFY(conn.registerObject("/" , this)); |
148 | } |
149 | |
150 | QDBusPendingCall tst_QDBusPendingCall::sendMessage() |
151 | { |
152 | QDBusMessage msg = QDBusMessage::createMethodCall(destination: "org.freedesktop.DBus" , |
153 | path: "/" , |
154 | interface: "org.freedesktop.DBus" , |
155 | method: "ListNames" ); |
156 | return conn.asyncCall(message: msg); |
157 | } |
158 | |
159 | QDBusPendingCall tst_QDBusPendingCall::sendError() |
160 | { |
161 | QDBusMessage msg = QDBusMessage::createMethodCall(destination: "org.freedesktop.DBus" , |
162 | path: "/" , |
163 | interface: "org.freedesktop.DBus" , |
164 | method: "ThisNameWontExist" ); |
165 | return conn.asyncCall(message: msg); |
166 | } |
167 | |
168 | void tst_QDBusPendingCall::waitForFinished() |
169 | { |
170 | QDBusPendingCall ac = sendMessage(); |
171 | ac.waitForFinished(); |
172 | QVERIFY(ac.isFinished()); |
173 | QVERIFY(!ac.isError()); |
174 | |
175 | const QDBusMessage reply = ac.reply(); |
176 | QCOMPARE(reply.type(), QDBusMessage::ReplyMessage); |
177 | QCOMPARE(reply.signature(), QString("as" )); |
178 | |
179 | const QVariantList args = ac.reply().arguments(); |
180 | QCOMPARE(args.count(), 1); |
181 | |
182 | const QVariant &arg = args.at(i: 0); |
183 | QCOMPARE(arg.type(), QVariant::StringList); |
184 | QVERIFY(arg.toStringList().contains(conn.baseService())); |
185 | } |
186 | |
187 | void tst_QDBusPendingCall::waitForFinished_error() |
188 | { |
189 | QDBusPendingCall ac = sendError(); |
190 | ac.waitForFinished(); |
191 | QVERIFY(ac.isFinished()); |
192 | QVERIFY(ac.isError()); |
193 | |
194 | QDBusError error = ac.error(); |
195 | QVERIFY(error.isValid()); |
196 | QCOMPARE(error.name(), QString("org.freedesktop.DBus.Error.UnknownMethod" )); |
197 | QCOMPARE(error.type(), QDBusError::UnknownMethod); |
198 | } |
199 | |
200 | void tst_QDBusPendingCall::callWithCallback_localLoop() |
201 | { |
202 | // Verify that a callback actually gets called when the call is dispatched locally. |
203 | QDBusInterface iface(QDBusConnection::sessionBus().baseService(), QLatin1String("/" ), |
204 | TEST_INTERFACE_NAME); |
205 | QVERIFY(iface.isValid()); |
206 | |
207 | QVERIFY(iface.callWithCallback("returnFoo" , QVariantList(), this, SLOT(callback(QStringList)))); |
208 | |
209 | // May be called synchronously or asynchronously... |
210 | if (callbackArgument != (QStringList() << QString::fromLatin1(str: "foo" ))) { |
211 | QTestEventLoop::instance().enterLoop(secs: 2); |
212 | QVERIFY(!QTestEventLoop::instance().timeout()); |
213 | } |
214 | |
215 | QCOMPARE(callbackArgument, QStringList() << QString::fromLatin1("foo" )); |
216 | } |
217 | |
218 | void tst_QDBusPendingCall::callWithCallback_localLoop_errorReply() |
219 | { |
220 | // Verify that an error callback actually gets called when the call is |
221 | // dispatched locally and the called method returns an error |
222 | |
223 | QDBusInterface iface(QDBusConnection::sessionBus().baseService(), QLatin1String("/" ), |
224 | TEST_INTERFACE_NAME); |
225 | QVERIFY(iface.isValid()); |
226 | |
227 | callbackArgument.clear(); |
228 | |
229 | QVERIFY(iface.callWithCallback("returnError" , QVariantList(), this, |
230 | SLOT(callback(QStringList)), SLOT(errorCallback(QDBusError)))); |
231 | |
232 | // May be called synchronously or asynchronously... |
233 | if (errorArgument.name() != "dbuspendingcall_error" ) { |
234 | QTestEventLoop::instance().enterLoop(secs: 2); |
235 | QVERIFY(!QTestEventLoop::instance().timeout()); |
236 | } |
237 | |
238 | QCOMPARE(errorArgument.name(), QString::fromLatin1("dbuspendingcall_error" )); |
239 | QVERIFY(callbackArgument.isEmpty()); |
240 | } |
241 | |
242 | void tst_QDBusPendingCall::watcher() |
243 | { |
244 | QDBusPendingCall ac = sendMessage(); |
245 | callCount = 0; |
246 | watchArgument = 0; |
247 | |
248 | QDBusPendingCallWatcher watch(ac); |
249 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
250 | SLOT(finished(QDBusPendingCallWatcher*))); |
251 | |
252 | QTestEventLoop::instance().enterLoop(secs: 2); |
253 | QVERIFY(!QTestEventLoop::instance().timeout()); |
254 | |
255 | QVERIFY(ac.isFinished()); |
256 | QVERIFY(!ac.isError()); |
257 | |
258 | QCOMPARE(callCount, 1); |
259 | QCOMPARE(slotCalled, (int)FinishCalled); |
260 | QCOMPARE(watchArgument, &watch); |
261 | QVERIFY(!watch.isError()); |
262 | |
263 | const QVariantList args2 = ac.reply().arguments(); |
264 | QVERIFY(!args2.isEmpty()); |
265 | QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); |
266 | } |
267 | |
268 | void tst_QDBusPendingCall::watcher_error() |
269 | { |
270 | QDBusPendingCall ac = sendError(); |
271 | callCount = 0; |
272 | watchArgument = 0; |
273 | |
274 | QDBusPendingCallWatcher watch(ac); |
275 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
276 | SLOT(finished(QDBusPendingCallWatcher*))); |
277 | |
278 | QTestEventLoop::instance().enterLoop(secs: 2); |
279 | QVERIFY(!QTestEventLoop::instance().timeout()); |
280 | |
281 | QVERIFY(ac.isFinished()); |
282 | QVERIFY(ac.isError()); |
283 | |
284 | QCOMPARE(callCount, 1); |
285 | QCOMPARE(slotCalled, (int)FinishCalled); |
286 | QCOMPARE(watchArgument, &watch); |
287 | |
288 | QVERIFY(watch.isError()); |
289 | QVERIFY(watch.error().isValid()); |
290 | } |
291 | |
292 | void tst_QDBusPendingCall::watcher_waitForFinished() |
293 | { |
294 | QDBusPendingCall ac = sendMessage(); |
295 | callCount = 0; |
296 | watchArgument = 0; |
297 | |
298 | QDBusPendingCallWatcher watch(ac); |
299 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
300 | SLOT(finished(QDBusPendingCallWatcher*))); |
301 | |
302 | watch.waitForFinished(); |
303 | |
304 | QVERIFY(ac.isFinished()); |
305 | QVERIFY(!ac.isError()); |
306 | |
307 | QCOMPARE(callCount, 1); |
308 | QCOMPARE(slotCalled, (int)FinishCalled); |
309 | QCOMPARE(watchArgument, &watch); |
310 | QVERIFY(!watch.isError()); |
311 | |
312 | const QVariantList args2 = ac.reply().arguments(); |
313 | QVERIFY(!args2.isEmpty()); |
314 | QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); |
315 | } |
316 | |
317 | void tst_QDBusPendingCall::watcher_waitForFinished_threaded() |
318 | { |
319 | callCount = 0; |
320 | watchArgument = 0; |
321 | slotCalled = 0; |
322 | |
323 | class WorkerThread: public QThread { |
324 | public: |
325 | tst_QDBusPendingCall *tst; |
326 | WorkerThread(tst_QDBusPendingCall *tst) : tst(tst) {} |
327 | void run() |
328 | { |
329 | QDBusPendingCall ac = tst->sendMessage(); |
330 | // QVERIFY(!ac.isFinished()); |
331 | // QVERIFY(!ac.isError()); |
332 | // QCOMPARE(ac.reply().type(), QDBusMessage::InvalidMessage); |
333 | |
334 | QDBusPendingCallWatcher watch(ac); |
335 | tst->connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
336 | SLOT(finished(QDBusPendingCallWatcher*)), atype: Qt::DirectConnection); |
337 | |
338 | QTest::qSleep(ms: 100); // don't process events in this thread |
339 | |
340 | // QVERIFY(!ac.isFinished()); |
341 | // QVERIFY(!ac.isError()); |
342 | // QCOMPARE(ac.reply().type(), QDBusMessage::InvalidMessage); |
343 | QCOMPARE(tst->callCount, 0); |
344 | QCOMPARE(tst->slotCalled, 0); |
345 | |
346 | watch.waitForFinished(); |
347 | QVERIFY(ac.isFinished()); |
348 | QVERIFY(!ac.isError()); |
349 | |
350 | QCOMPARE(tst->callCount, 1); |
351 | QCOMPARE(tst->slotCalled, (int)FinishCalled); |
352 | QCOMPARE(tst->watchArgument, &watch); |
353 | QVERIFY(!watch.isError()); |
354 | |
355 | const QVariantList args2 = ac.reply().arguments(); |
356 | QVERIFY(!args2.isEmpty()); |
357 | QVERIFY(args2.at(0).toStringList().contains(tst->conn.baseService())); |
358 | } |
359 | } thread(this); |
360 | QTestEventLoop::instance().connect(asender: &thread, SIGNAL(finished()), SLOT(exitLoop())); |
361 | thread.start(); |
362 | QTestEventLoop::instance().enterLoop(secs: 10); |
363 | QVERIFY(thread.wait(3000)); |
364 | QVERIFY(!QTestEventLoop::instance().timeout()); |
365 | } |
366 | |
367 | void tst_QDBusPendingCall::watcher_waitForFinished_alreadyFinished() |
368 | { |
369 | QDBusPendingCall ac = sendMessage(); |
370 | ac.waitForFinished(); |
371 | QVERIFY(ac.isFinished()); |
372 | QVERIFY(!ac.isError()); |
373 | |
374 | callCount = 0; |
375 | watchArgument = 0; |
376 | |
377 | // create a watcher on an already-finished reply |
378 | QDBusPendingCallWatcher watch(ac); |
379 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
380 | SLOT(finished(QDBusPendingCallWatcher*))); |
381 | |
382 | watch.waitForFinished(); |
383 | |
384 | QVERIFY(ac.isFinished()); |
385 | QVERIFY(!ac.isError()); |
386 | |
387 | QCOMPARE(callCount, 1); |
388 | QCOMPARE(slotCalled, (int)FinishCalled); |
389 | QCOMPARE(watchArgument, &watch); |
390 | QVERIFY(!watch.isError()); |
391 | |
392 | const QVariantList args2 = ac.reply().arguments(); |
393 | QVERIFY(!args2.isEmpty()); |
394 | QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); |
395 | } |
396 | |
397 | void tst_QDBusPendingCall::watcher_waitForFinished_alreadyFinished_eventLoop() |
398 | { |
399 | QDBusPendingCall ac = sendMessage(); |
400 | ac.waitForFinished(); |
401 | QVERIFY(ac.isFinished()); |
402 | QVERIFY(!ac.isError()); |
403 | |
404 | callCount = 0; |
405 | watchArgument = 0; |
406 | |
407 | // create a watcher on an already-finished reply |
408 | QDBusPendingCallWatcher watch(ac); |
409 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
410 | SLOT(finished(QDBusPendingCallWatcher*))); |
411 | connect(sender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
412 | receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
413 | |
414 | QTestEventLoop::instance().enterLoop(secs: 1); |
415 | QVERIFY(!QTestEventLoop::instance().timeout()); |
416 | |
417 | QVERIFY(ac.isFinished()); |
418 | QVERIFY(!ac.isError()); |
419 | |
420 | QCOMPARE(callCount, 1); |
421 | QCOMPARE(slotCalled, (int)FinishCalled); |
422 | QCOMPARE(watchArgument, &watch); |
423 | QVERIFY(!watch.isError()); |
424 | |
425 | const QVariantList args2 = ac.reply().arguments(); |
426 | QVERIFY(!args2.isEmpty()); |
427 | QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); |
428 | } |
429 | |
430 | void tst_QDBusPendingCall::watcher_waitForFinished_error() |
431 | { |
432 | QDBusPendingCall ac = sendError(); |
433 | callCount = 0; |
434 | watchArgument = 0; |
435 | |
436 | QDBusPendingCallWatcher watch(ac); |
437 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
438 | SLOT(finished(QDBusPendingCallWatcher*))); |
439 | |
440 | watch.waitForFinished(); |
441 | |
442 | QVERIFY(ac.isFinished()); |
443 | QVERIFY(ac.isError()); |
444 | |
445 | QCOMPARE(callCount, 1); |
446 | QCOMPARE(slotCalled, (int)FinishCalled); |
447 | QCOMPARE(watchArgument, &watch); |
448 | |
449 | QVERIFY(watch.isError()); |
450 | QVERIFY(watch.error().isValid()); |
451 | } |
452 | |
453 | void tst_QDBusPendingCall::callInsideWaitForFinished() |
454 | { |
455 | QDBusPendingCall ac = sendMessage(); |
456 | QDBusPendingCallWatcher watch(ac); |
457 | |
458 | callCount = 0; |
459 | |
460 | connect(asender: &watch, SIGNAL(finished(QDBusPendingCallWatcher*)), |
461 | SLOT(makeCall())); |
462 | |
463 | watch.waitForFinished(); |
464 | |
465 | QCOMPARE(callCount, 1); |
466 | QCOMPARE(slotCalled, (int)MakeCallCalled); |
467 | QVERIFY(!watch.isError()); |
468 | |
469 | const QVariantList args2 = ac.reply().arguments(); |
470 | QVERIFY(!args2.isEmpty()); |
471 | QVERIFY(args2.at(0).toStringList().contains(conn.baseService())); |
472 | } |
473 | |
474 | QTEST_MAIN(tst_QDBusPendingCall) |
475 | #include "tst_qdbuspendingcall.moc" |
476 | |