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 "testtypes.h"
29
30#include <QUrl>
31#include <QDir>
32#include <QDebug>
33#include <qtest.h>
34#include <QPointer>
35#include <QFileInfo>
36#include <QQmlEngine>
37#include <QQmlContext>
38#include <QQmlProperty>
39#include <QQmlComponent>
40#include <QQmlIncubator>
41#include "../../shared/util.h"
42#include <private/qjsvalue_p.h>
43#include <private/qqmlincubator_p.h>
44#include <private/qqmlobjectcreator_p.h>
45
46class tst_qqmlincubator : public QQmlDataTest
47{
48 Q_OBJECT
49public:
50 tst_qqmlincubator() {}
51
52private slots:
53 void initTestCase();
54
55 void incubationMode();
56 void objectDeleted();
57 void clear();
58 void noIncubationController();
59 void forceCompletion();
60 void setInitialState();
61 void clearDuringCompletion();
62 void objectDeletionAfterInit();
63 void recursiveClear();
64 void statusChanged();
65 void asynchronousIfNested();
66 void nestedComponent();
67 void chainedAsynchronousIfNested();
68 void chainedAsynchronousIfNestedOnCompleted();
69 void chainedAsynchronousClear();
70 void selfDelete();
71 void contextDelete();
72 void garbageCollection();
73 void requiredProperties();
74 void deleteInSetInitialState();
75
76private:
77 QQmlIncubationController controller;
78 QQmlEngine engine;
79};
80
81#define VERIFY_ERRORS(component, errorfile) \
82 if (!errorfile) { \
83 if (qgetenv("DEBUG") != "" && !component.errors().isEmpty()) \
84 qWarning() << "Unexpected Errors:" << component.errors(); \
85 QVERIFY(!component.isError()); \
86 QVERIFY(component.errors().isEmpty()); \
87 } else { \
88 QFile file(QQmlDataTest::instance()->testFile(errorfile)); \
89 QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); \
90 QByteArray data = file.readAll(); \
91 file.close(); \
92 QList<QByteArray> expected = data.split('\n'); \
93 expected.removeAll(QByteArray("")); \
94 QList<QQmlError> errors = component.errors(); \
95 QList<QByteArray> actual; \
96 for (int ii = 0; ii < errors.count(); ++ii) { \
97 const QQmlError &error = errors.at(ii); \
98 QByteArray errorStr = QByteArray::number(error.line()) + ':' + \
99 QByteArray::number(error.column()) + ':' + \
100 error.description().toUtf8(); \
101 actual << errorStr; \
102 } \
103 if (qgetenv("DEBUG") != "" && expected != actual) \
104 qWarning() << "Expected:" << expected << "Actual:" << actual; \
105 QCOMPARE(expected, actual); \
106 }
107
108void tst_qqmlincubator::initTestCase()
109{
110 QQmlDataTest::initTestCase();
111 registerTypes();
112 engine.setIncubationController(&controller);
113}
114
115void tst_qqmlincubator::incubationMode()
116{
117 {
118 QQmlIncubator incubator;
119 QCOMPARE(incubator.incubationMode(), QQmlIncubator::Asynchronous);
120 }
121 {
122 QQmlIncubator incubator(QQmlIncubator::Asynchronous);
123 QCOMPARE(incubator.incubationMode(), QQmlIncubator::Asynchronous);
124 }
125 {
126 QQmlIncubator incubator(QQmlIncubator::Synchronous);
127 QCOMPARE(incubator.incubationMode(), QQmlIncubator::Synchronous);
128 }
129 {
130 QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested);
131 QCOMPARE(incubator.incubationMode(), QQmlIncubator::AsynchronousIfNested);
132 }
133}
134
135void tst_qqmlincubator::objectDeleted()
136{
137 {
138 QQmlEngine engine;
139 QQmlIncubationController controller;
140 engine.setIncubationController(&controller);
141 SelfRegisteringType::clearMe();
142
143 QQmlComponent component(&engine, testFileUrl(fileName: "objectDeleted.qml"));
144 QVERIFY(component.isReady());
145
146 QQmlIncubator incubator;
147 component.create(incubator);
148
149 QCOMPARE(incubator.status(), QQmlIncubator::Loading);
150 QVERIFY(!SelfRegisteringType::me());
151
152 while (SelfRegisteringOuterType::me() == nullptr && incubator.isLoading()) {
153 std::atomic<bool> b{false};
154 controller.incubateWhile(flag: &b);
155 }
156
157 QVERIFY(SelfRegisteringOuterType::me() != nullptr);
158 QVERIFY(incubator.isLoading());
159
160 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
161 std::atomic<bool> b{false};
162 controller.incubateWhile(flag: &b);
163 }
164
165 delete SelfRegisteringType::me();
166
167 {
168 std::atomic<bool> b{true};
169 controller.incubateWhile(flag: &b);
170 }
171
172 QVERIFY(incubator.isError());
173 VERIFY_ERRORS(incubator, "objectDeleted.errors.txt");
174 QVERIFY(!incubator.object());
175 }
176 QVERIFY(SelfRegisteringOuterType::beenDeleted);
177}
178
179void tst_qqmlincubator::clear()
180{
181 SelfRegisteringType::clearMe();
182
183 QQmlComponent component(&engine, testFileUrl(fileName: "clear.qml"));
184 QVERIFY(component.isReady());
185
186 // Clear in null state
187 {
188 QQmlIncubator incubator;
189 QVERIFY(incubator.isNull());
190 incubator.clear(); // no effect
191 QVERIFY(incubator.isNull());
192 }
193
194 // Clear in loading state
195 {
196 QQmlIncubator incubator;
197 component.create(incubator);
198 QVERIFY(incubator.isLoading());
199 incubator.clear();
200 QVERIFY(incubator.isNull());
201 }
202
203 // Clear mid load
204 {
205 QQmlIncubator incubator;
206 component.create(incubator);
207
208 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
209 std::atomic<bool> b{false};
210 controller.incubateWhile(flag: &b);
211 }
212
213 QVERIFY(incubator.isLoading());
214 QVERIFY(SelfRegisteringType::me() != nullptr);
215 QPointer<SelfRegisteringType> srt = SelfRegisteringType::me();
216
217 incubator.clear();
218 QVERIFY(incubator.isNull());
219 QVERIFY(srt.isNull());
220 }
221
222 // Clear in ready state
223 {
224 QQmlIncubator incubator;
225 component.create(incubator);
226
227 {
228 std::atomic<bool> b{true};
229 controller.incubateWhile(flag: &b);
230 }
231
232 QVERIFY(incubator.isReady());
233 QVERIFY(incubator.object() != nullptr);
234 QPointer<QObject> obj = incubator.object();
235
236 incubator.clear();
237 QVERIFY(incubator.isNull());
238 QVERIFY(!incubator.object());
239 QVERIFY(!obj.isNull());
240
241 delete obj;
242 QVERIFY(obj.isNull());
243 }
244}
245
246void tst_qqmlincubator::noIncubationController()
247{
248 // All incubators should behave synchronously when there is no controller
249
250 QQmlEngine engine;
251 QQmlComponent component(&engine, testFileUrl(fileName: "noIncubationController.qml"));
252
253 QVERIFY(component.isReady());
254
255 {
256 QQmlIncubator incubator(QQmlIncubator::Asynchronous);
257 component.create(incubator);
258 QVERIFY(incubator.isReady());
259 QVERIFY(incubator.object());
260 QCOMPARE(incubator.object()->property("testValue").toInt(), 1913);
261 delete incubator.object();
262 }
263
264 {
265 QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested);
266 component.create(incubator);
267 QVERIFY(incubator.isReady());
268 QVERIFY(incubator.object());
269 QCOMPARE(incubator.object()->property("testValue").toInt(), 1913);
270 delete incubator.object();
271 }
272
273 {
274 QQmlIncubator incubator(QQmlIncubator::Synchronous);
275 component.create(incubator);
276 QVERIFY(incubator.isReady());
277 QVERIFY(incubator.object());
278 QCOMPARE(incubator.object()->property("testValue").toInt(), 1913);
279 delete incubator.object();
280 }
281}
282
283void tst_qqmlincubator::forceCompletion()
284{
285 QQmlComponent component(&engine, testFileUrl(fileName: "forceCompletion.qml"));
286 QVERIFY(component.isReady());
287
288 {
289 // forceCompletion on a null incubator does nothing
290 QQmlIncubator incubator;
291 QVERIFY(incubator.isNull());
292 incubator.forceCompletion();
293 QVERIFY(incubator.isNull());
294 }
295
296 {
297 // forceCompletion immediately after creating an asynchronous object completes it
298 QQmlIncubator incubator;
299 QVERIFY(incubator.isNull());
300 component.create(incubator);
301 QVERIFY(incubator.isLoading());
302
303 incubator.forceCompletion();
304
305 QVERIFY(incubator.isReady());
306 QVERIFY(incubator.object() != nullptr);
307 QCOMPARE(incubator.object()->property("testValue").toInt(), 3499);
308
309 delete incubator.object();
310 }
311
312 {
313 // forceCompletion during creation completes it
314 SelfRegisteringType::clearMe();
315
316 QQmlIncubator incubator;
317 QVERIFY(incubator.isNull());
318 component.create(incubator);
319 QVERIFY(incubator.isLoading());
320
321 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
322 std::atomic<bool> b{false};
323 controller.incubateWhile(flag: &b);
324 }
325
326 QVERIFY(SelfRegisteringType::me() != nullptr);
327 QVERIFY(incubator.isLoading());
328
329 incubator.forceCompletion();
330
331 QVERIFY(incubator.isReady());
332 QVERIFY(incubator.object() != nullptr);
333 QCOMPARE(incubator.object()->property("testValue").toInt(), 3499);
334
335 delete incubator.object();
336 }
337
338 {
339 // forceCompletion on a ready incubator has no effect
340 QQmlIncubator incubator;
341 QVERIFY(incubator.isNull());
342 component.create(incubator);
343 QVERIFY(incubator.isLoading());
344
345 incubator.forceCompletion();
346
347 QVERIFY(incubator.isReady());
348 QVERIFY(incubator.object() != nullptr);
349 QCOMPARE(incubator.object()->property("testValue").toInt(), 3499);
350
351 incubator.forceCompletion();
352
353 QVERIFY(incubator.isReady());
354 QVERIFY(incubator.object() != nullptr);
355 QCOMPARE(incubator.object()->property("testValue").toInt(), 3499);
356
357 delete incubator.object();
358 }
359}
360
361void tst_qqmlincubator::setInitialState()
362{
363 QQmlComponent component(&engine, testFileUrl(fileName: "setInitialState.qml"));
364 QVERIFY(component.isReady());
365
366 struct MyIncubator : public QQmlIncubator
367 {
368 MyIncubator(QQmlIncubator::IncubationMode mode)
369 : QQmlIncubator(mode) {}
370
371 virtual void setInitialState(QObject *o) {
372 QQmlProperty::write(o, "test2", 19);
373 QQmlProperty::write(o, "testData1", 201);
374 }
375 };
376
377 {
378 MyIncubator incubator(QQmlIncubator::Asynchronous);
379 component.create(incubator);
380 QVERIFY(incubator.isLoading());
381 std::atomic<bool> b{true};
382 controller.incubateWhile(flag: &b);
383 QVERIFY(incubator.isReady());
384 QVERIFY(incubator.object());
385 QCOMPARE(incubator.object()->property("myValueFunctionCalled").toBool(), false);
386 QCOMPARE(incubator.object()->property("test1").toInt(), 502);
387 QCOMPARE(incubator.object()->property("test2").toInt(), 19);
388 delete incubator.object();
389 }
390
391 {
392 MyIncubator incubator(QQmlIncubator::Synchronous);
393 component.create(incubator);
394 QVERIFY(incubator.isReady());
395 QVERIFY(incubator.object());
396 QCOMPARE(incubator.object()->property("myValueFunctionCalled").toBool(), false);
397 QCOMPARE(incubator.object()->property("test1").toInt(), 502);
398 QCOMPARE(incubator.object()->property("test2").toInt(), 19);
399 delete incubator.object();
400 }
401}
402
403void tst_qqmlincubator::clearDuringCompletion()
404{
405 CompletionRegisteringType::clearMe();
406 SelfRegisteringType::clearMe();
407
408 QQmlComponent component(&engine, testFileUrl(fileName: "clearDuringCompletion.qml"));
409 QVERIFY(component.isReady());
410
411 QQmlIncubator incubator;
412 component.create(incubator);
413
414 QCOMPARE(incubator.status(), QQmlIncubator::Loading);
415 QVERIFY(!CompletionRegisteringType::me());
416
417 while (CompletionRegisteringType::me() == nullptr && incubator.isLoading()) {
418 std::atomic<bool> b{false};
419 controller.incubateWhile(flag: &b);
420 }
421
422 QVERIFY(CompletionRegisteringType::me() != nullptr);
423 QVERIFY(SelfRegisteringType::me() != nullptr);
424 QVERIFY(incubator.isLoading());
425
426 QPointer<QObject> srt = SelfRegisteringType::me();
427
428 incubator.clear();
429 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
430 QCoreApplication::processEvents();
431 QVERIFY(incubator.isNull());
432 QVERIFY(srt.isNull());
433}
434
435void tst_qqmlincubator::objectDeletionAfterInit()
436{
437 QQmlComponent component(&engine, testFileUrl(fileName: "clear.qml"));
438 QVERIFY(component.isReady());
439
440 struct MyIncubator : public QQmlIncubator
441 {
442 MyIncubator(QQmlIncubator::IncubationMode mode)
443 : QQmlIncubator(mode), obj(nullptr) {}
444
445 virtual void setInitialState(QObject *o) {
446 obj = o;
447 }
448
449 QObject *obj;
450 };
451
452 SelfRegisteringType::clearMe();
453 MyIncubator incubator(QQmlIncubator::Asynchronous);
454 component.create(incubator);
455
456 while (!incubator.obj && incubator.isLoading()) {
457 std::atomic<bool> b{false};
458 controller.incubateWhile(flag: &b);
459 }
460
461 QVERIFY(incubator.isLoading());
462 QVERIFY(SelfRegisteringType::me() != nullptr);
463
464 delete incubator.obj;
465
466 incubator.clear();
467 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
468 QCoreApplication::processEvents();
469 QVERIFY(incubator.isNull());
470}
471
472class Switcher : public QObject
473{
474 Q_OBJECT
475public:
476 Switcher(QQmlEngine *e) : QObject(), engine(e) { }
477
478 struct MyIncubator : public QQmlIncubator
479 {
480 MyIncubator(QQmlIncubator::IncubationMode mode, QObject *s)
481 : QQmlIncubator(mode), switcher(s) {}
482
483 virtual void setInitialState(QObject *o) {
484 if (o->objectName() == "switchMe")
485 connect(sender: o, SIGNAL(switchMe()), receiver: switcher, SLOT(switchIt()));
486 }
487
488 QObject *switcher;
489 };
490
491 void start()
492 {
493 incubator = new MyIncubator(QQmlIncubator::Synchronous, this);
494 component = new QQmlComponent(engine, QQmlDataTest::instance()->testFileUrl(fileName: "recursiveClear.1.qml"));
495 component->create(*incubator);
496 }
497
498 QQmlEngine *engine;
499 MyIncubator *incubator;
500 QQmlComponent *component;
501
502public slots:
503 void switchIt() {
504 component->deleteLater();
505 incubator->clear();
506 component = new QQmlComponent(engine, QQmlDataTest::instance()->testFileUrl(fileName: "recursiveClear.2.qml"));
507 component->create(*incubator);
508 }
509};
510
511void tst_qqmlincubator::recursiveClear()
512{
513 Switcher switcher(&engine);
514 switcher.start();
515}
516
517void tst_qqmlincubator::statusChanged()
518{
519 class MyIncubator : public QQmlIncubator
520 {
521 public:
522 MyIncubator(QQmlIncubator::IncubationMode mode = QQmlIncubator::Asynchronous)
523 : QQmlIncubator(mode) {}
524
525 QList<int> statuses;
526 protected:
527 virtual void statusChanged(Status s) { statuses << s; }
528 virtual void setInitialState(QObject *) { statuses << -1; }
529 };
530
531 {
532 QQmlComponent component(&engine, testFileUrl(fileName: "statusChanged.qml"));
533 QVERIFY(component.isReady());
534
535 MyIncubator incubator(QQmlIncubator::Synchronous);
536 component.create(incubator);
537 QVERIFY(incubator.isReady());
538 QCOMPARE(incubator.statuses.count(), 3);
539 QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading));
540 QCOMPARE(incubator.statuses.at(1), -1);
541 QCOMPARE(incubator.statuses.at(2), int(QQmlIncubator::Ready));
542 delete incubator.object();
543 }
544
545 {
546 QQmlComponent component(&engine, testFileUrl(fileName: "statusChanged.qml"));
547 QVERIFY(component.isReady());
548
549 MyIncubator incubator(QQmlIncubator::Asynchronous);
550 component.create(incubator);
551 QVERIFY(incubator.isLoading());
552 QCOMPARE(incubator.statuses.count(), 1);
553 QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading));
554
555 {
556 std::atomic<bool> b{true};
557 controller.incubateWhile(flag: &b);
558 }
559
560 QCOMPARE(incubator.statuses.count(), 3);
561 QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading));
562 QCOMPARE(incubator.statuses.at(1), -1);
563 QCOMPARE(incubator.statuses.at(2), int(QQmlIncubator::Ready));
564 delete incubator.object();
565 }
566
567 {
568 QQmlComponent component2(&engine, testFileUrl(fileName: "statusChanged.nested.qml"));
569 QVERIFY(component2.isReady());
570
571 MyIncubator incubator(QQmlIncubator::Asynchronous);
572 component2.create(incubator);
573 QVERIFY(incubator.isLoading());
574 QCOMPARE(incubator.statuses.count(), 1);
575 QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading));
576
577 {
578 std::atomic<bool> b{true};
579 controller.incubateWhile(flag: &b);
580 }
581
582 QVERIFY(incubator.isReady());
583 QCOMPARE(incubator.statuses.count(), 3);
584 QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading));
585 QCOMPARE(incubator.statuses.at(1), -1);
586 QCOMPARE(incubator.statuses.at(2), int(QQmlIncubator::Ready));
587 delete incubator.object();
588 }
589}
590
591void tst_qqmlincubator::asynchronousIfNested()
592{
593 // Asynchronous if nested within a finalized context behaves synchronously
594 {
595 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronousIfNested.1.qml"));
596 QVERIFY(component.isReady());
597
598 QObject *object = component.create();
599 QVERIFY(object != nullptr);
600 QCOMPARE(object->property("a").toInt(), 10);
601
602 QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested);
603 component.create(incubator, context: nullptr, forContext: qmlContext(object));
604
605 QVERIFY(incubator.isReady());
606 QVERIFY(incubator.object());
607 QCOMPARE(incubator.object()->property("a").toInt(), 10);
608 delete incubator.object();
609 delete object;
610 }
611
612 // Asynchronous if nested within an executing context behaves asynchronously, but prevents
613 // the parent from finishing
614 {
615 SelfRegisteringType::clearMe();
616
617 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronousIfNested.2.qml"));
618 QVERIFY(component.isReady());
619
620 QQmlIncubator incubator;
621 component.create(incubator);
622
623 QVERIFY(incubator.isLoading());
624 QVERIFY(!SelfRegisteringType::me());
625 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
626 std::atomic<bool> b{false};
627 controller.incubateWhile(flag: &b);
628 }
629
630 QVERIFY(SelfRegisteringType::me() != nullptr);
631 QVERIFY(incubator.isLoading());
632
633 QQmlIncubator nested(QQmlIncubator::AsynchronousIfNested);
634 component.create(nested, context: nullptr, forContext: qmlContext(SelfRegisteringType::me()));
635 QVERIFY(nested.isLoading());
636
637 while (nested.isLoading()) {
638 QVERIFY(incubator.isLoading());
639 std::atomic<bool> b{false};
640 controller.incubateWhile(flag: &b);
641 }
642
643 QVERIFY(nested.isReady());
644 QVERIFY(incubator.isLoading());
645
646 {
647 std::atomic<bool> b{true};
648 controller.incubateWhile(flag: &b);
649 }
650
651 QVERIFY(nested.isReady());
652 QVERIFY(incubator.isReady());
653
654 delete nested.object();
655 delete incubator.object();
656 }
657
658 // AsynchronousIfNested within a synchronous AsynchronousIfNested behaves synchronously
659 {
660 SelfRegisteringType::clearMe();
661
662 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronousIfNested.3.qml"));
663 QVERIFY(component.isReady());
664
665 struct CallbackData {
666 CallbackData(QQmlEngine *e) : engine(e), pass(false) {}
667 QQmlEngine *engine;
668 bool pass;
669 static void callback(CallbackRegisteringType *o, void *data) {
670 CallbackData *d = (CallbackData *)data;
671
672 QQmlComponent c(d->engine, QQmlDataTest::instance()->testFileUrl(fileName: "asynchronousIfNested.1.qml"));
673 if (!c.isReady()) return;
674
675 QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested);
676 c.create(incubator, context: nullptr, forContext: qmlContext(o));
677
678 if (!incubator.isReady()) return;
679
680 if (incubator.object()->property(name: "a").toInt() != 10) return;
681
682 d->pass = true;
683 }
684 };
685
686 CallbackData cd(&engine);
687 CallbackRegisteringType::registerCallback(&CallbackData::callback, &cd);
688
689 QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested);
690 component.create(incubator);
691
692 QVERIFY(incubator.isReady());
693 QCOMPARE(cd.pass, true);
694
695 delete incubator.object();
696 }
697}
698
699void tst_qqmlincubator::nestedComponent()
700{
701 QQmlComponent component(&engine, testFileUrl(fileName: "nestedComponent.qml"));
702 QVERIFY(component.isReady());
703
704 QObject *object = component.create();
705
706 QQmlComponent *nested = object->property(name: "c").value<QQmlComponent*>();
707 QVERIFY(nested);
708 QVERIFY(nested->isReady());
709
710 // Test without incubator
711 {
712 QObject *nestedObject = nested->create();
713 QCOMPARE(nestedObject->property("value").toInt(), 19988);
714 delete nestedObject;
715 }
716
717 // Test with incubator
718 {
719 QQmlIncubator incubator(QQmlIncubator::Synchronous);
720 nested->create(incubator);
721 QVERIFY(incubator.isReady());
722 QVERIFY(incubator.object());
723 QCOMPARE(incubator.object()->property("value").toInt(), 19988);
724 delete incubator.object();
725 }
726
727 delete object;
728}
729
730// Checks that a new AsynchronousIfNested incubator can be correctly started in the
731// statusChanged() callback of another.
732void tst_qqmlincubator::chainedAsynchronousIfNested()
733{
734 SelfRegisteringType::clearMe();
735
736 QQmlComponent component(&engine, testFileUrl(fileName: "chainedAsynchronousIfNested.qml"));
737 QVERIFY(component.isReady());
738
739 QQmlIncubator incubator(QQmlIncubator::Asynchronous);
740 component.create(incubator);
741
742 QVERIFY(incubator.isLoading());
743 QVERIFY(!SelfRegisteringType::me());
744
745 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
746 std::atomic<bool> b{false};
747 controller.incubateWhile(flag: &b);
748 }
749
750 QVERIFY(SelfRegisteringType::me() != nullptr);
751 QVERIFY(incubator.isLoading());
752
753 struct MyIncubator : public QQmlIncubator {
754 MyIncubator(MyIncubator *next, QQmlComponent *component, QQmlContext *ctxt)
755 : QQmlIncubator(AsynchronousIfNested), next(next), component(component), ctxt(ctxt) {}
756
757 protected:
758 virtual void statusChanged(Status s) {
759 if (s == Ready && next)
760 component->create(*next, context: nullptr, forContext: ctxt);
761 }
762
763 private:
764 MyIncubator *next;
765 QQmlComponent *component;
766 QQmlContext *ctxt;
767 };
768
769 MyIncubator incubator2(nullptr, &component, nullptr);
770 MyIncubator incubator1(&incubator2, &component, qmlContext(SelfRegisteringType::me()));
771
772 component.create(incubator1, context: nullptr, forContext: qmlContext(SelfRegisteringType::me()));
773
774 QVERIFY(incubator.isLoading());
775 QVERIFY(incubator1.isLoading());
776 QVERIFY(incubator2.isNull());
777
778 while (incubator1.isLoading()) {
779 QVERIFY(incubator.isLoading());
780 QVERIFY(incubator1.isLoading());
781 QVERIFY(incubator2.isNull());
782
783 std::atomic<bool> b{false};
784 controller.incubateWhile(flag: &b);
785 }
786
787 QVERIFY(incubator.isLoading());
788 QVERIFY(incubator1.isReady());
789 QVERIFY(incubator2.isLoading());
790
791 while (incubator2.isLoading()) {
792 QVERIFY(incubator.isLoading());
793 QVERIFY(incubator1.isReady());
794 QVERIFY(incubator2.isLoading());
795
796 std::atomic<bool> b{false};
797 controller.incubateWhile(flag: &b);
798 }
799
800 QVERIFY(incubator1.isReady());
801 QVERIFY(incubator2.isReady());
802 if (incubator.isLoading()) {
803 std::atomic<bool> b{true};
804 controller.incubateWhile(flag: &b);
805 }
806
807 QVERIFY(incubator.isReady());
808 QVERIFY(incubator1.isReady());
809 QVERIFY(incubator2.isReady());
810}
811
812// Checks that new AsynchronousIfNested incubators can be correctly chained if started in
813// componentCompleted().
814void tst_qqmlincubator::chainedAsynchronousIfNestedOnCompleted()
815{
816 SelfRegisteringType::clearMe();
817
818 QQmlComponent component(&engine, testFileUrl(fileName: "chainInCompletion.qml"));
819 QVERIFY(component.isReady());
820
821 QQmlComponent c1(&engine, testFileUrl(fileName: "chainedAsynchronousIfNested.qml"));
822 QVERIFY(c1.isReady());
823
824 struct MyIncubator : public QQmlIncubator {
825 MyIncubator(MyIncubator *next, QQmlComponent *component, QQmlContext *ctxt)
826 : QQmlIncubator(AsynchronousIfNested), next(next), component(component), ctxt(ctxt) {}
827
828 protected:
829 virtual void statusChanged(Status s) {
830 if (s == Ready && next) {
831 component->create(*next, context: nullptr, forContext: ctxt);
832 }
833 }
834
835 private:
836 MyIncubator *next;
837 QQmlComponent *component;
838 QQmlContext *ctxt;
839 };
840
841 struct CallbackData {
842 CallbackData(QQmlComponent *c, MyIncubator *i, QQmlContext *ct)
843 : component(c), incubator(i), ctxt(ct) {}
844 QQmlComponent *component;
845 MyIncubator *incubator;
846 QQmlContext *ctxt;
847 static void callback(CompletionCallbackType *, void *data) {
848 CallbackData *d = (CallbackData *)data;
849 d->component->create(*d->incubator, context: nullptr, forContext: d->ctxt);
850 }
851 };
852
853 QQmlIncubator incubator(QQmlIncubator::Asynchronous);
854 component.create(incubator);
855
856 QVERIFY(incubator.isLoading());
857 QVERIFY(!SelfRegisteringType::me());
858
859 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
860 std::atomic<bool> b{false};
861 controller.incubateWhile(flag: &b);
862 }
863
864 QVERIFY(SelfRegisteringType::me() != nullptr);
865 QVERIFY(incubator.isLoading());
866
867 MyIncubator incubator3(nullptr, &c1, qmlContext(SelfRegisteringType::me()));
868 MyIncubator incubator2(&incubator3, &c1, qmlContext(SelfRegisteringType::me()));
869 MyIncubator incubator1(&incubator2, &c1, qmlContext(SelfRegisteringType::me()));
870
871 // start incubator1 in componentComplete
872 CallbackData cd(&c1, &incubator1, qmlContext(SelfRegisteringType::me()));
873 CompletionCallbackType::registerCallback(&CallbackData::callback, &cd);
874
875 while (!incubator1.isLoading()) {
876 QVERIFY(incubator.isLoading());
877 QVERIFY(incubator2.isNull());
878 QVERIFY(incubator3.isNull());
879
880 std::atomic<bool> b{false};
881 controller.incubateWhile(flag: &b);
882 }
883
884 QVERIFY(incubator.isLoading());
885 QVERIFY(incubator1.isLoading());
886 QVERIFY(incubator2.isNull());
887 QVERIFY(incubator3.isNull());
888
889 while (incubator1.isLoading()) {
890 QVERIFY(incubator.isLoading());
891 QVERIFY(incubator1.isLoading());
892 QVERIFY(incubator2.isNull());
893 QVERIFY(incubator3.isNull());
894
895 std::atomic<bool> b{false};
896 controller.incubateWhile(flag: &b);
897 }
898
899 QVERIFY(incubator.isLoading());
900 QVERIFY(incubator1.isReady());
901 QVERIFY(incubator2.isLoading());
902 QVERIFY(incubator3.isNull());
903
904 while (incubator2.isLoading()) {
905 QVERIFY(incubator.isLoading());
906 QVERIFY(incubator1.isReady());
907 QVERIFY(incubator2.isLoading());
908 QVERIFY(incubator3.isNull());
909
910 std::atomic<bool> b{false};
911 controller.incubateWhile(flag: &b);
912 }
913
914 QVERIFY(incubator.isLoading());
915 QVERIFY(incubator1.isReady());
916 QVERIFY(incubator2.isReady());
917 QVERIFY(incubator3.isLoading());
918
919 while (incubator3.isLoading()) {
920 QVERIFY(incubator.isLoading());
921 QVERIFY(incubator1.isReady());
922 QVERIFY(incubator2.isReady());
923 QVERIFY(incubator3.isLoading());
924
925 std::atomic<bool> b{false};
926 controller.incubateWhile(flag: &b);
927 }
928
929 {
930 std::atomic<bool> b{true};
931 controller.incubateWhile(flag: &b);
932 }
933
934 QVERIFY(incubator.isReady());
935 QVERIFY(incubator1.isReady());
936 QVERIFY(incubator2.isReady());
937 QVERIFY(incubator3.isReady());
938}
939
940// Checks that new AsynchronousIfNested incubators can be correctly cleared if started in
941// componentCompleted().
942void tst_qqmlincubator::chainedAsynchronousClear()
943{
944 SelfRegisteringType::clearMe();
945
946 QQmlComponent component(&engine, testFileUrl(fileName: "chainInCompletion.qml"));
947 QVERIFY(component.isReady());
948
949 QQmlComponent c1(&engine, testFileUrl(fileName: "chainedAsynchronousIfNested.qml"));
950 QVERIFY(c1.isReady());
951
952 struct MyIncubator : public QQmlIncubator {
953 MyIncubator(MyIncubator *next, QQmlComponent *component, QQmlContext *ctxt)
954 : QQmlIncubator(AsynchronousIfNested), next(next), component(component), ctxt(ctxt) {}
955
956 protected:
957 virtual void statusChanged(Status s) {
958 if (s == Ready && next) {
959 component->create(*next, context: nullptr, forContext: ctxt);
960 }
961 }
962
963 private:
964 MyIncubator *next;
965 QQmlComponent *component;
966 QQmlContext *ctxt;
967 };
968
969 struct CallbackData {
970 CallbackData(QQmlComponent *c, MyIncubator *i, QQmlContext *ct)
971 : component(c), incubator(i), ctxt(ct) {}
972 QQmlComponent *component;
973 MyIncubator *incubator;
974 QQmlContext *ctxt;
975 static void callback(CompletionCallbackType *, void *data) {
976 CallbackData *d = (CallbackData *)data;
977 d->component->create(*d->incubator, context: nullptr, forContext: d->ctxt);
978 }
979 };
980
981 QQmlIncubator incubator(QQmlIncubator::Asynchronous);
982 component.create(incubator);
983
984 QVERIFY(incubator.isLoading());
985 QVERIFY(!SelfRegisteringType::me());
986
987 while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) {
988 std::atomic<bool> b{false};
989 controller.incubateWhile(flag: &b);
990 }
991
992 QVERIFY(SelfRegisteringType::me() != nullptr);
993 QVERIFY(incubator.isLoading());
994
995 MyIncubator incubator3(nullptr, &c1, qmlContext(SelfRegisteringType::me()));
996 MyIncubator incubator2(&incubator3, &c1, qmlContext(SelfRegisteringType::me()));
997 MyIncubator incubator1(&incubator2, &c1, qmlContext(SelfRegisteringType::me()));
998
999 // start incubator1 in componentComplete
1000 CallbackData cd(&c1, &incubator1, qmlContext(SelfRegisteringType::me()));
1001 CompletionCallbackType::registerCallback(&CallbackData::callback, &cd);
1002
1003 while (!incubator1.isLoading()) {
1004 QVERIFY(incubator.isLoading());
1005 QVERIFY(incubator2.isNull());
1006 QVERIFY(incubator3.isNull());
1007
1008 std::atomic<bool> b{false};
1009 controller.incubateWhile(flag: &b);
1010 }
1011
1012 QVERIFY(incubator.isLoading());
1013 QVERIFY(incubator1.isLoading());
1014 QVERIFY(incubator2.isNull());
1015 QVERIFY(incubator3.isNull());
1016
1017 while (incubator1.isLoading()) {
1018 QVERIFY(incubator.isLoading());
1019 QVERIFY(incubator1.isLoading());
1020 QVERIFY(incubator2.isNull());
1021 QVERIFY(incubator3.isNull());
1022
1023 std::atomic<bool> b{false};
1024 controller.incubateWhile(flag: &b);
1025 }
1026
1027 QVERIFY(incubator.isLoading());
1028 QVERIFY(incubator1.isReady());
1029 QVERIFY(incubator2.isLoading());
1030 QVERIFY(incubator3.isNull());
1031
1032 while (incubator2.isLoading()) {
1033 QVERIFY(incubator.isLoading());
1034 QVERIFY(incubator1.isReady());
1035 QVERIFY(incubator2.isLoading());
1036 QVERIFY(incubator3.isNull());
1037
1038 std::atomic<bool> b{false};
1039 controller.incubateWhile(flag: &b);
1040 }
1041
1042 QVERIFY(incubator.isLoading());
1043 QVERIFY(incubator1.isReady());
1044 QVERIFY(incubator2.isReady());
1045 QVERIFY(incubator3.isLoading());
1046
1047 // Any in loading state will become null when cleared.
1048 incubator.clear();
1049
1050 QVERIFY(incubator.isNull());
1051 QVERIFY(incubator1.isReady());
1052 QVERIFY(incubator2.isReady());
1053 QVERIFY(incubator3.isNull());
1054}
1055
1056void tst_qqmlincubator::selfDelete()
1057{
1058 struct MyIncubator : public QQmlIncubator {
1059 MyIncubator(bool *done, Status status, IncubationMode mode)
1060 : QQmlIncubator(mode), done(done), status(status) {}
1061
1062 protected:
1063 virtual void statusChanged(Status s) {
1064 if (s == status) {
1065 *done = true;
1066 if (s == Ready) delete object();
1067 delete this;
1068 }
1069 }
1070
1071 private:
1072 bool *done;
1073 Status status;
1074 };
1075
1076 {
1077 QQmlComponent component(&engine, testFileUrl(fileName: "selfDelete.qml"));
1078
1079#define DELETE_TEST(status, mode) { \
1080 bool done = false; \
1081 component.create(*(new MyIncubator(&done, status, mode))); \
1082 std::atomic<bool> True{true}; \
1083 controller.incubateWhile(&True); \
1084 QVERIFY(done == true); \
1085 }
1086
1087 DELETE_TEST(QQmlIncubator::Loading, QQmlIncubator::Synchronous);
1088 DELETE_TEST(QQmlIncubator::Ready, QQmlIncubator::Synchronous);
1089 DELETE_TEST(QQmlIncubator::Loading, QQmlIncubator::Asynchronous);
1090 DELETE_TEST(QQmlIncubator::Ready, QQmlIncubator::Asynchronous);
1091
1092#undef DELETE_TEST
1093 }
1094
1095 // Delete within error status
1096 {
1097 SelfRegisteringType::clearMe();
1098
1099 QQmlComponent component(&engine, testFileUrl(fileName: "objectDeleted.qml"));
1100 QVERIFY(component.isReady());
1101
1102 bool done = false;
1103 MyIncubator *incubator = new MyIncubator(&done, QQmlIncubator::Error,
1104 QQmlIncubator::Asynchronous);
1105 component.create(*incubator);
1106
1107 QCOMPARE(incubator->QQmlIncubator::status(), QQmlIncubator::Loading);
1108 QVERIFY(!SelfRegisteringType::me());
1109
1110 while (SelfRegisteringType::me() == nullptr && incubator->isLoading()) {
1111 std::atomic<bool> b{false};
1112 controller.incubateWhile(flag: &b);
1113 }
1114
1115 QVERIFY(SelfRegisteringType::me() != nullptr);
1116 QVERIFY(incubator->isLoading());
1117
1118 // We have to cheat and manually remove it from the creator->allCreatedObjects
1119 // otherwise we will do a double delete
1120 QQmlIncubatorPrivate *incubatorPriv = QQmlIncubatorPrivate::get(incubator);
1121 incubatorPriv->creator->allCreatedObjects().pop();
1122 delete SelfRegisteringType::me();
1123
1124 {
1125 std::atomic<bool> b{true};
1126 controller.incubateWhile(flag: &b);
1127 }
1128
1129 QVERIFY(done);
1130 }
1131}
1132
1133// Test that QML doesn't crash if the context is deleted prior to the incubator
1134// first executing.
1135void tst_qqmlincubator::contextDelete()
1136{
1137 QQmlContext *context = new QQmlContext(engine.rootContext());
1138 QQmlComponent component(&engine, testFileUrl(fileName: "contextDelete.qml"));
1139
1140 QQmlIncubator incubator;
1141 component.create(incubator, context);
1142
1143 delete context;
1144
1145 {
1146 std::atomic<bool> b{false};
1147 controller.incubateWhile(flag: &b);
1148 }
1149}
1150
1151// QTBUG-53111
1152void tst_qqmlincubator::garbageCollection()
1153{
1154 QQmlComponent component(&engine, testFileUrl(fileName: "garbageCollection.qml"));
1155 QScopedPointer<QObject> obj(component.create());
1156
1157 engine.collectGarbage();
1158
1159 std::atomic<bool> b{true};
1160 controller.incubateWhile(flag: &b);
1161
1162 // verify incubation completed (the incubator was not prematurely collected)
1163 QVariant incubatorVariant;
1164 QMetaObject::invokeMethod(obj: obj.data(), member: "getAndClearIncubator", Q_RETURN_ARG(QVariant, incubatorVariant));
1165 QJSValue strongRef = incubatorVariant.value<QJSValue>();
1166 QVERIFY(!strongRef.isNull() && !strongRef.isUndefined());
1167
1168 // turn the last strong reference to the incubator into a weak one and collect
1169 QV4::WeakValue weakIncubatorRef;
1170 weakIncubatorRef.set(engine: QQmlEnginePrivate::getV4Engine(e: &engine), value: *QJSValuePrivate::getValue(jsval: &strongRef));
1171 strongRef = QJSValue();
1172 incubatorVariant.clear();
1173
1174 // verify incubator is correctly collected now that incubation is complete and all references are gone
1175 engine.collectGarbage();
1176 QVERIFY(weakIncubatorRef.isNullOrUndefined());
1177}
1178
1179void tst_qqmlincubator::requiredProperties()
1180{
1181 {
1182 QQmlComponent component(&engine, testFileUrl(fileName: "requiredProperty.qml"));
1183 QVERIFY(component.isReady());
1184 // forceCompletion immediately after creating an asynchronous object completes it
1185 QQmlIncubator incubator;
1186 incubator.setInitialProperties({{"requiredProperty", 42}});
1187 QVERIFY(incubator.isNull());
1188 component.create(incubator);
1189 QVERIFY(incubator.isLoading());
1190
1191 incubator.forceCompletion();
1192
1193 QVERIFY(incubator.isReady());
1194 QVERIFY(incubator.object() != nullptr);
1195 QCOMPARE(incubator.object()->property("requiredProperty").toInt(), 42);
1196
1197 delete incubator.object();
1198 }
1199 {
1200 QQmlComponent component(&engine, testFileUrl(fileName: "requiredProperty.qml"));
1201 QVERIFY(component.isReady());
1202 // forceCompletion immediately after creating an asynchronous object completes it
1203 QQmlIncubator incubator;
1204 QVERIFY(incubator.isNull());
1205 component.create(incubator);
1206 QVERIFY(incubator.isLoading());
1207
1208 incubator.forceCompletion();
1209
1210 QVERIFY(incubator.isError());
1211 auto error = incubator.errors().first();
1212 QVERIFY(error.description().contains(QLatin1String("Required property requiredProperty was not initialized")));
1213 QVERIFY(incubator.object() == nullptr);
1214 }
1215}
1216
1217class DeletingIncubator : public QQmlIncubator
1218{
1219
1220
1221 // QQmlIncubator interface
1222protected:
1223 void statusChanged(Status) override
1224 {
1225
1226 }
1227 void setInitialState(QObject *obj) override
1228 {
1229 delete obj;
1230 clear();
1231 }
1232};
1233
1234void tst_qqmlincubator::deleteInSetInitialState()
1235{
1236 QQmlComponent component(&engine, testFileUrl(fileName: "requiredProperty.qml"));
1237 QVERIFY(component.isReady());
1238 // forceCompletion immediately after creating an asynchronous object completes it
1239 DeletingIncubator incubator;
1240 incubator.setInitialProperties({{"requiredProperty", 42}});
1241 QVERIFY(incubator.isNull());
1242 component.create(incubator);
1243 QVERIFY(incubator.isLoading());
1244 incubator.forceCompletion(); // no crash
1245 QVERIFY(incubator.isNull());
1246 QCOMPARE(incubator.object(), nullptr); // object was deleted
1247}
1248
1249QTEST_MAIN(tst_qqmlincubator)
1250
1251#include "tst_qqmlincubator.moc"
1252

source code of qtdeclarative/tests/auto/qml/qqmlincubator/tst_qqmlincubator.cpp