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 | |
29 | #include <QtTest/QtTest> |
30 | |
31 | #include <qabstracteventdispatcher.h> |
32 | #include <qcoreapplication.h> |
33 | #include <qcoreevent.h> |
34 | #include <qeventloop.h> |
35 | #include <private/qeventloop_p.h> |
36 | #if defined(Q_OS_UNIX) |
37 | #include <private/qeventdispatcher_unix_p.h> |
38 | #include <QtCore/private/qcore_unix_p.h> |
39 | #if defined(HAVE_GLIB) |
40 | #include <private/qeventdispatcher_glib_p.h> |
41 | #endif |
42 | #endif |
43 | #include <qmutex.h> |
44 | #include <qthread.h> |
45 | #include <qtimer.h> |
46 | #include <qwaitcondition.h> |
47 | #include <QTcpServer> |
48 | #include <QTcpSocket> |
49 | |
50 | class EventLoopExiter : public QObject |
51 | { |
52 | Q_OBJECT |
53 | QEventLoop *eventLoop; |
54 | public: |
55 | inline EventLoopExiter(QEventLoop *el) |
56 | : eventLoop(el) |
57 | { } |
58 | public slots: |
59 | void exit(); |
60 | void exit1(); |
61 | void exit2(); |
62 | }; |
63 | |
64 | void EventLoopExiter::exit() |
65 | { eventLoop->exit(); } |
66 | |
67 | void EventLoopExiter::exit1() |
68 | { eventLoop->exit(returnCode: 1); } |
69 | |
70 | void EventLoopExiter::exit2() |
71 | { eventLoop->exit(returnCode: 2); } |
72 | |
73 | class EventLoopThread : public QThread |
74 | { |
75 | Q_OBJECT |
76 | signals: |
77 | void checkPoint(); |
78 | public: |
79 | QEventLoop *eventLoop; |
80 | void run(); |
81 | }; |
82 | |
83 | void EventLoopThread::run() |
84 | { |
85 | eventLoop = new QEventLoop; |
86 | emit checkPoint(); |
87 | (void) eventLoop->exec(); |
88 | delete eventLoop; |
89 | eventLoop = 0; |
90 | } |
91 | |
92 | class MultipleExecThread : public QThread |
93 | { |
94 | Q_OBJECT |
95 | signals: |
96 | void checkPoint(); |
97 | public: |
98 | QMutex mutex; |
99 | QWaitCondition cond; |
100 | volatile int result1; |
101 | volatile int result2; |
102 | MultipleExecThread() : result1(0xdead), result2(0xbeef) {} |
103 | |
104 | void run() |
105 | { |
106 | QMutexLocker locker(&mutex); |
107 | // this exec should work |
108 | |
109 | cond.wakeOne(); |
110 | cond.wait(lockedMutex: &mutex); |
111 | |
112 | QTimer timer; |
113 | connect(asender: &timer, SIGNAL(timeout()), SLOT(quit()), atype: Qt::DirectConnection); |
114 | timer.setInterval(1000); |
115 | timer.start(); |
116 | result1 = exec(); |
117 | |
118 | // this should return immediately, since exit() has been called |
119 | cond.wakeOne(); |
120 | cond.wait(lockedMutex: &mutex); |
121 | QEventLoop eventLoop; |
122 | result2 = eventLoop.exec(); |
123 | } |
124 | }; |
125 | |
126 | class StartStopEvent: public QEvent |
127 | { |
128 | public: |
129 | explicit StartStopEvent(int type, QEventLoop *loop = 0) |
130 | : QEvent(Type(type)), el(loop) |
131 | { } |
132 | |
133 | QEventLoop *el; |
134 | }; |
135 | |
136 | class EventLoopExecutor : public QObject |
137 | { |
138 | Q_OBJECT |
139 | QEventLoop *eventLoop; |
140 | public: |
141 | int returnCode; |
142 | EventLoopExecutor(QEventLoop *eventLoop) |
143 | : QObject(), eventLoop(eventLoop), returnCode(-42) |
144 | { |
145 | } |
146 | public slots: |
147 | void exec() |
148 | { |
149 | QTimer::singleShot(msec: 100, receiver: eventLoop, SLOT(quit())); |
150 | // this should return immediately, and the timer event should be delivered to |
151 | // tst_QEventLoop::exec() test, letting the test complete |
152 | returnCode = eventLoop->exec(); |
153 | } |
154 | }; |
155 | |
156 | #ifdef QT_GUI_LIB |
157 | #define tst_QEventLoop tst_QGuiEventLoop |
158 | #endif |
159 | |
160 | class tst_QEventLoop : public QObject |
161 | { |
162 | Q_OBJECT |
163 | private slots: |
164 | // This test *must* run first. See the definition for why. |
165 | void processEvents(); |
166 | void exec(); |
167 | void reexec(); |
168 | void execAfterExit(); |
169 | void wakeUp(); |
170 | void quit(); |
171 | #if defined(Q_OS_UNIX) |
172 | void processEventsExcludeSocket(); |
173 | #endif |
174 | void processEventsExcludeTimers(); |
175 | void deliverInDefinedOrder(); |
176 | |
177 | // keep this test last: |
178 | void nestedLoops(); |
179 | |
180 | void testQuitLock(); |
181 | |
182 | protected: |
183 | void customEvent(QEvent *e); |
184 | }; |
185 | |
186 | void tst_QEventLoop::processEvents() |
187 | { |
188 | QSignalSpy aboutToBlockSpy(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock); |
189 | QSignalSpy awakeSpy(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::awake); |
190 | |
191 | QVERIFY(aboutToBlockSpy.isValid()); |
192 | QVERIFY(awakeSpy.isValid()); |
193 | |
194 | QEventLoop eventLoop; |
195 | |
196 | QCoreApplication::postEvent(receiver: &eventLoop, event: new QEvent(QEvent::User)); |
197 | |
198 | // process posted events, QEventLoop::processEvents() should return |
199 | // true |
200 | QVERIFY(eventLoop.processEvents()); |
201 | QCOMPARE(aboutToBlockSpy.count(), 0); |
202 | QCOMPARE(awakeSpy.count(), 1); |
203 | |
204 | // allow any session manager to complete its handshake, so that |
205 | // there are no pending events left. This tests that we are able |
206 | // to process all events from the queue, otherwise it will hang. |
207 | while (eventLoop.processEvents()) |
208 | ; |
209 | |
210 | // make sure the test doesn't block forever |
211 | int timerId = startTimer(interval: 100); |
212 | |
213 | // wait for more events to process, QEventLoop::processEvents() |
214 | // should return true |
215 | aboutToBlockSpy.clear(); |
216 | awakeSpy.clear(); |
217 | QVERIFY(eventLoop.processEvents(QEventLoop::WaitForMoreEvents)); |
218 | |
219 | // We should get one awake for each aboutToBlock, plus one awake when |
220 | // processEvents is entered. There is no guarantee that that the |
221 | // processEvents call actually blocked, since the OS may introduce |
222 | // native events at any time. |
223 | QVERIFY(awakeSpy.count() > 0); |
224 | QVERIFY(awakeSpy.count() >= aboutToBlockSpy.count()); |
225 | |
226 | killTimer(id: timerId); |
227 | } |
228 | |
229 | #define EXEC_TIMEOUT 100 |
230 | |
231 | void tst_QEventLoop::exec() |
232 | { |
233 | { |
234 | QEventLoop eventLoop; |
235 | EventLoopExiter exiter(&eventLoop); |
236 | int returnCode; |
237 | |
238 | QTimer::singleShot(EXEC_TIMEOUT, receiver: &exiter, SLOT(exit())); |
239 | returnCode = eventLoop.exec(); |
240 | QCOMPARE(returnCode, 0); |
241 | |
242 | QTimer::singleShot(EXEC_TIMEOUT, receiver: &exiter, SLOT(exit1())); |
243 | returnCode = eventLoop.exec(); |
244 | QCOMPARE(returnCode, 1); |
245 | |
246 | QTimer::singleShot(EXEC_TIMEOUT, receiver: &exiter, SLOT(exit2())); |
247 | returnCode = eventLoop.exec(); |
248 | QCOMPARE(returnCode, 2); |
249 | } |
250 | |
251 | { |
252 | // calling QEventLoop::exec() after a thread loop has exit()ed should return immediately |
253 | // Note: this behaviour differs from QCoreApplication and QEventLoop |
254 | // see tst_QCoreApplication::eventLoopExecAfterExit, tst_QEventLoop::reexec |
255 | MultipleExecThread thread; |
256 | |
257 | // start thread and wait for checkpoint |
258 | thread.mutex.lock(); |
259 | thread.start(); |
260 | thread.cond.wait(lockedMutex: &thread.mutex); |
261 | |
262 | // make sure the eventloop runs |
263 | QSignalSpy spy(QAbstractEventDispatcher::instance(thread: &thread), &QAbstractEventDispatcher::awake); |
264 | QVERIFY(spy.isValid()); |
265 | thread.cond.wakeOne(); |
266 | thread.cond.wait(lockedMutex: &thread.mutex); |
267 | QVERIFY(spy.count() > 0); |
268 | int v = thread.result1; |
269 | QCOMPARE(v, 0); |
270 | |
271 | // exec should return immediately |
272 | spy.clear(); |
273 | thread.cond.wakeOne(); |
274 | thread.mutex.unlock(); |
275 | thread.wait(); |
276 | QCOMPARE(spy.count(), 0); |
277 | v = thread.result2; |
278 | QCOMPARE(v, -1); |
279 | } |
280 | |
281 | { |
282 | // a single instance of QEventLoop should not be allowed to recurse into exec() |
283 | QEventLoop eventLoop; |
284 | EventLoopExecutor executor(&eventLoop); |
285 | |
286 | QTimer::singleShot(EXEC_TIMEOUT, receiver: &executor, SLOT(exec())); |
287 | int returnCode = eventLoop.exec(); |
288 | QCOMPARE(returnCode, 0); |
289 | QCOMPARE(executor.returnCode, -1); |
290 | } |
291 | } |
292 | |
293 | void tst_QEventLoop::reexec() |
294 | { |
295 | QEventLoop loop; |
296 | |
297 | // exec once |
298 | QMetaObject::invokeMethod(obj: &loop, member: "quit" , type: Qt::QueuedConnection); |
299 | QCOMPARE(loop.exec(), 0); |
300 | |
301 | // and again |
302 | QMetaObject::invokeMethod(obj: &loop, member: "quit" , type: Qt::QueuedConnection); |
303 | QCOMPARE(loop.exec(), 0); |
304 | } |
305 | |
306 | void tst_QEventLoop::execAfterExit() |
307 | { |
308 | QEventLoop loop; |
309 | EventLoopExiter obj(&loop); |
310 | |
311 | QMetaObject::invokeMethod(obj: &obj, member: "exit" , type: Qt::QueuedConnection); |
312 | loop.exit(returnCode: 1); |
313 | QCOMPARE(loop.exec(), 0); |
314 | } |
315 | |
316 | void tst_QEventLoop::wakeUp() |
317 | { |
318 | EventLoopThread thread; |
319 | QEventLoop eventLoop; |
320 | connect(sender: &thread, SIGNAL(checkPoint()), receiver: &eventLoop, SLOT(quit())); |
321 | connect(sender: &thread, SIGNAL(finished()), receiver: &eventLoop, SLOT(quit())); |
322 | |
323 | thread.start(); |
324 | (void) eventLoop.exec(); |
325 | |
326 | QSignalSpy spy(QAbstractEventDispatcher::instance(thread: &thread), &QAbstractEventDispatcher::awake); |
327 | QVERIFY(spy.isValid()); |
328 | thread.eventLoop->wakeUp(); |
329 | |
330 | // give the thread time to wake up |
331 | QTimer::singleShot(msec: 1000, receiver: &eventLoop, SLOT(quit())); |
332 | (void) eventLoop.exec(); |
333 | |
334 | QVERIFY(spy.count() > 0); |
335 | |
336 | thread.quit(); |
337 | (void) eventLoop.exec(); |
338 | } |
339 | |
340 | void tst_QEventLoop::quit() |
341 | { |
342 | QEventLoop eventLoop; |
343 | int returnCode; |
344 | |
345 | QTimer::singleShot(msec: 100, receiver: &eventLoop, SLOT(quit())); |
346 | returnCode = eventLoop.exec(); |
347 | QCOMPARE(returnCode, 0); |
348 | } |
349 | |
350 | |
351 | void tst_QEventLoop::nestedLoops() |
352 | { |
353 | QCoreApplication::postEvent(receiver: this, event: new StartStopEvent(QEvent::User)); |
354 | QCoreApplication::postEvent(receiver: this, event: new StartStopEvent(QEvent::User)); |
355 | QCoreApplication::postEvent(receiver: this, event: new StartStopEvent(QEvent::User)); |
356 | |
357 | // without the fix, this will *wedge* and never return |
358 | QTest::qWait(ms: 1000); |
359 | } |
360 | |
361 | void tst_QEventLoop::customEvent(QEvent *e) |
362 | { |
363 | if (e->type() == QEvent::User) { |
364 | QEventLoop loop; |
365 | QCoreApplication::postEvent(receiver: this, event: new StartStopEvent(int(QEvent::User) + 1, &loop)); |
366 | loop.exec(); |
367 | } else { |
368 | static_cast<StartStopEvent *>(e)->el->exit(); |
369 | } |
370 | } |
371 | |
372 | #if defined(Q_OS_UNIX) |
373 | class SocketEventsTester: public QObject |
374 | { |
375 | Q_OBJECT |
376 | public: |
377 | SocketEventsTester() |
378 | { |
379 | socket = 0; |
380 | server = 0; |
381 | dataSent = false; |
382 | dataReadable = false; |
383 | testResult = false; |
384 | dataArrived = false; |
385 | } |
386 | ~SocketEventsTester() |
387 | { |
388 | delete socket; |
389 | delete server; |
390 | } |
391 | bool init() |
392 | { |
393 | bool ret = false; |
394 | server = new QTcpServer(); |
395 | socket = new QTcpSocket(); |
396 | connect(sender: server, SIGNAL(newConnection()), receiver: this, SLOT(sendHello())); |
397 | connect(sender: socket, SIGNAL(readyRead()), receiver: this, SLOT(sendAck()), Qt::DirectConnection); |
398 | if((ret = server->listen(address: QHostAddress::LocalHost, port: 0))) { |
399 | socket->connectToHost(address: server->serverAddress(), port: server->serverPort()); |
400 | socket->waitForConnected(); |
401 | } |
402 | return ret; |
403 | } |
404 | |
405 | QTcpSocket *socket; |
406 | QTcpServer *server; |
407 | bool dataSent; |
408 | bool dataReadable; |
409 | bool testResult; |
410 | bool dataArrived; |
411 | public slots: |
412 | void sendAck() |
413 | { |
414 | dataArrived = true; |
415 | } |
416 | void sendHello() |
417 | { |
418 | char data[10] ="HELLO" ; |
419 | qint64 size = sizeof(data); |
420 | |
421 | QTcpSocket *serverSocket = server->nextPendingConnection(); |
422 | QCoreApplication::processEvents(); |
423 | serverSocket->write(data, len: size); |
424 | dataSent = serverSocket->waitForBytesWritten(msecs: -1); |
425 | |
426 | if (dataSent) { |
427 | pollfd pfd = qt_make_pollfd(fd: socket->socketDescriptor(), POLLIN); |
428 | dataReadable = (1 == qt_safe_poll(fds: &pfd, nfds: 1, timeout_ts: nullptr)); |
429 | } |
430 | |
431 | if (!dataReadable) { |
432 | testResult = dataArrived; |
433 | } else { |
434 | QCoreApplication::processEvents(flags: QEventLoop::ExcludeSocketNotifiers); |
435 | testResult = dataArrived; |
436 | // to check if the deferred event is processed |
437 | QCoreApplication::processEvents(); |
438 | } |
439 | serverSocket->close(); |
440 | QThread::currentThread()->exit(retcode: 0); |
441 | } |
442 | }; |
443 | |
444 | class SocketTestThread : public QThread |
445 | { |
446 | Q_OBJECT |
447 | public: |
448 | SocketTestThread():QThread(0),testResult(false){}; |
449 | void run() |
450 | { |
451 | SocketEventsTester *tester = new SocketEventsTester(); |
452 | if (tester->init()) |
453 | exec(); |
454 | dataSent = tester->dataSent; |
455 | dataReadable = tester->dataReadable; |
456 | testResult = tester->testResult; |
457 | dataArrived = tester->dataArrived; |
458 | delete tester; |
459 | } |
460 | bool dataSent; |
461 | bool dataReadable; |
462 | bool testResult; |
463 | bool dataArrived; |
464 | }; |
465 | |
466 | void tst_QEventLoop::processEventsExcludeSocket() |
467 | { |
468 | SocketTestThread thread; |
469 | thread.start(); |
470 | QVERIFY(thread.wait()); |
471 | QVERIFY(thread.dataSent); |
472 | QVERIFY(thread.dataReadable); |
473 | #if defined(HAVE_GLIB) |
474 | QAbstractEventDispatcher *eventDispatcher = QCoreApplication::eventDispatcher(); |
475 | if (qobject_cast<QEventDispatcherGlib *>(object: eventDispatcher)) |
476 | QEXPECT_FAIL("" , "ExcludeSocketNotifiers is currently broken in the Glib dispatchers" , Continue); |
477 | #endif |
478 | QVERIFY(!thread.testResult); |
479 | QVERIFY(thread.dataArrived); |
480 | } |
481 | #endif |
482 | |
483 | class TimerReceiver : public QObject |
484 | { |
485 | public: |
486 | int gotTimerEvent; |
487 | |
488 | TimerReceiver() |
489 | : QObject(), gotTimerEvent(-1) |
490 | { } |
491 | |
492 | void timerEvent(QTimerEvent *event) |
493 | { |
494 | gotTimerEvent = event->timerId(); |
495 | } |
496 | }; |
497 | |
498 | void tst_QEventLoop::processEventsExcludeTimers() |
499 | { |
500 | TimerReceiver timerReceiver; |
501 | int timerId = timerReceiver.startTimer(interval: 0); |
502 | |
503 | QEventLoop eventLoop; |
504 | |
505 | // normal process events will send timers |
506 | eventLoop.processEvents(); |
507 | QCOMPARE(timerReceiver.gotTimerEvent, timerId); |
508 | timerReceiver.gotTimerEvent = -1; |
509 | |
510 | // but not if we exclude timers |
511 | eventLoop.processEvents(flags: QEventLoop::X11ExcludeTimers); |
512 | |
513 | #if defined(Q_OS_UNIX) |
514 | QAbstractEventDispatcher *eventDispatcher = QCoreApplication::eventDispatcher(); |
515 | if (!qobject_cast<QEventDispatcherUNIX *>(object: eventDispatcher) |
516 | #if defined(HAVE_GLIB) |
517 | && !qobject_cast<QEventDispatcherGlib *>(object: eventDispatcher) |
518 | #endif |
519 | ) |
520 | #endif |
521 | QEXPECT_FAIL("" , "X11ExcludeTimers only supported in the UNIX/Glib dispatchers" , Continue); |
522 | |
523 | QCOMPARE(timerReceiver.gotTimerEvent, -1); |
524 | timerReceiver.gotTimerEvent = -1; |
525 | |
526 | // resume timer processing |
527 | eventLoop.processEvents(); |
528 | QCOMPARE(timerReceiver.gotTimerEvent, timerId); |
529 | timerReceiver.gotTimerEvent = -1; |
530 | } |
531 | |
532 | namespace DeliverInDefinedOrder { |
533 | enum { NbThread = 3, NbObject = 500, NbEventQueue = 5, NbEvent = 50 }; |
534 | |
535 | struct CustomEvent : public QEvent { |
536 | CustomEvent(int q, int v) : QEvent(Type(User + q)), value(v) {} |
537 | int value; |
538 | }; |
539 | |
540 | struct Object : public QObject { |
541 | Q_OBJECT |
542 | public: |
543 | Object() : count(0) { |
544 | for (int i = 0; i < NbEventQueue; i++) |
545 | lastReceived[i] = -1; |
546 | } |
547 | int lastReceived[NbEventQueue]; |
548 | int count; |
549 | virtual void customEvent(QEvent* e) { |
550 | QVERIFY(e->type() >= QEvent::User); |
551 | QVERIFY(e->type() < QEvent::User + 5); |
552 | uint idx = e->type() - QEvent::User; |
553 | int value = static_cast<CustomEvent *>(e)->value; |
554 | QVERIFY(lastReceived[idx] < value); |
555 | lastReceived[idx] = value; |
556 | count++; |
557 | } |
558 | |
559 | public slots: |
560 | void moveToThread(QThread *t) { |
561 | QObject::moveToThread(thread: t); |
562 | } |
563 | }; |
564 | |
565 | } |
566 | |
567 | void tst_QEventLoop::deliverInDefinedOrder() |
568 | { |
569 | using namespace DeliverInDefinedOrder; |
570 | qMetaTypeId<QThread*>(); |
571 | QThread threads[NbThread]; |
572 | Object objects[NbObject]; |
573 | for (int t = 0; t < NbThread; t++) { |
574 | threads[t].start(); |
575 | } |
576 | |
577 | int event = 0; |
578 | |
579 | for (int o = 0; o < NbObject; o++) { |
580 | objects[o].moveToThread(t: &threads[o % NbThread]); |
581 | for (int e = 0; e < NbEvent; e++) { |
582 | int q = e % NbEventQueue; |
583 | QCoreApplication::postEvent(receiver: &objects[o], event: new CustomEvent(q, ++event) , priority: q); |
584 | if (e % 7) |
585 | QMetaObject::invokeMethod(obj: &objects[o], member: "moveToThread" , type: Qt::QueuedConnection, Q_ARG(QThread*, &threads[(e+o)%NbThread])); |
586 | } |
587 | } |
588 | |
589 | for (int o = 0; o < NbObject; o++) { |
590 | QTRY_COMPARE(objects[o].count, int(NbEvent)); |
591 | } |
592 | |
593 | for (int t = 0; t < NbThread; t++) { |
594 | threads[t].quit(); |
595 | threads[t].wait(); |
596 | } |
597 | |
598 | } |
599 | |
600 | class JobObject : public QObject |
601 | { |
602 | Q_OBJECT |
603 | public: |
604 | |
605 | explicit JobObject(QEventLoop *loop, QObject *parent = 0) |
606 | : QObject(parent), locker(loop) |
607 | { |
608 | } |
609 | |
610 | explicit JobObject(QObject *parent = 0) |
611 | : QObject(parent) |
612 | { |
613 | } |
614 | |
615 | public slots: |
616 | void start(int timeout = 200) |
617 | { |
618 | QTimer::singleShot(msec: timeout, receiver: this, SLOT(timeout())); |
619 | } |
620 | |
621 | private slots: |
622 | void timeout() |
623 | { |
624 | emit done(); |
625 | deleteLater(); |
626 | } |
627 | |
628 | signals: |
629 | void done(); |
630 | |
631 | private: |
632 | QEventLoopLocker locker; |
633 | }; |
634 | |
635 | void tst_QEventLoop::testQuitLock() |
636 | { |
637 | QEventLoop eventLoop; |
638 | |
639 | QEventLoopPrivate* privateClass = static_cast<QEventLoopPrivate*>(QObjectPrivate::get(o: &eventLoop)); |
640 | |
641 | QCOMPARE(privateClass->quitLockRef.loadRelaxed(), 0); |
642 | |
643 | JobObject *job1 = new JobObject(&eventLoop, this); |
644 | job1->start(timeout: 500); |
645 | |
646 | QCOMPARE(privateClass->quitLockRef.loadRelaxed(), 1); |
647 | |
648 | eventLoop.exec(); |
649 | |
650 | QCOMPARE(privateClass->quitLockRef.loadRelaxed(), 0); |
651 | |
652 | |
653 | job1 = new JobObject(&eventLoop, this); |
654 | job1->start(timeout: 200); |
655 | |
656 | JobObject *previousJob = job1; |
657 | for (int i = 0; i < 9; ++i) { |
658 | JobObject *subJob = new JobObject(&eventLoop, this); |
659 | connect(sender: previousJob, SIGNAL(done()), receiver: subJob, SLOT(start())); |
660 | previousJob = subJob; |
661 | } |
662 | |
663 | eventLoop.exec(); |
664 | } |
665 | |
666 | QTEST_MAIN(tst_QEventLoop) |
667 | #include "tst_qeventloop.moc" |
668 | |