/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "testtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include "../../shared/util.h" #include #include #include class tst_qqmlincubator : public QQmlDataTest { Q_OBJECT public: tst_qqmlincubator() {} private slots: void initTestCase(); void incubationMode(); void objectDeleted(); void clear(); void noIncubationController(); void forceCompletion(); void setInitialState(); void clearDuringCompletion(); void objectDeletionAfterInit(); void recursiveClear(); void statusChanged(); void asynchronousIfNested(); void nestedComponent(); void chainedAsynchronousIfNested(); void chainedAsynchronousIfNestedOnCompleted(); void chainedAsynchronousClear(); void selfDelete(); void contextDelete(); void garbageCollection(); void requiredProperties(); private: QQmlIncubationController controller; QQmlEngine engine; }; #define VERIFY_ERRORS(component, errorfile) \ if (!errorfile) { \ if (qgetenv("DEBUG") != "" && !component.errors().isEmpty()) \ qWarning() << "Unexpected Errors:" << component.errors(); \ QVERIFY(!component.isError()); \ QVERIFY(component.errors().isEmpty()); \ } else { \ QFile file(QQmlDataTest::instance()->testFile(errorfile)); \ QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); \ QByteArray data = file.readAll(); \ file.close(); \ QList expected = data.split('\n'); \ expected.removeAll(QByteArray("")); \ QList errors = component.errors(); \ QList actual; \ for (int ii = 0; ii < errors.count(); ++ii) { \ const QQmlError &error = errors.at(ii); \ QByteArray errorStr = QByteArray::number(error.line()) + ':' + \ QByteArray::number(error.column()) + ':' + \ error.description().toUtf8(); \ actual << errorStr; \ } \ if (qgetenv("DEBUG") != "" && expected != actual) \ qWarning() << "Expected:" << expected << "Actual:" << actual; \ QCOMPARE(expected, actual); \ } void tst_qqmlincubator::initTestCase() { QQmlDataTest::initTestCase(); registerTypes(); engine.setIncubationController(&controller); } void tst_qqmlincubator::incubationMode() { { QQmlIncubator incubator; QCOMPARE(incubator.incubationMode(), QQmlIncubator::Asynchronous); } { QQmlIncubator incubator(QQmlIncubator::Asynchronous); QCOMPARE(incubator.incubationMode(), QQmlIncubator::Asynchronous); } { QQmlIncubator incubator(QQmlIncubator::Synchronous); QCOMPARE(incubator.incubationMode(), QQmlIncubator::Synchronous); } { QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested); QCOMPARE(incubator.incubationMode(), QQmlIncubator::AsynchronousIfNested); } } void tst_qqmlincubator::objectDeleted() { { QQmlEngine engine; QQmlIncubationController controller; engine.setIncubationController(&controller); SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("objectDeleted.qml")); QVERIFY(component.isReady()); QQmlIncubator incubator; component.create(incubator); QCOMPARE(incubator.status(), QQmlIncubator::Loading); QVERIFY(!SelfRegisteringType::me()); while (SelfRegisteringOuterType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringOuterType::me() != nullptr); QVERIFY(incubator.isLoading()); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } delete SelfRegisteringType::me(); { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(incubator.isError()); VERIFY_ERRORS(incubator, "objectDeleted.errors.txt"); QVERIFY(!incubator.object()); } QVERIFY(SelfRegisteringOuterType::beenDeleted); } void tst_qqmlincubator::clear() { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("clear.qml")); QVERIFY(component.isReady()); // Clear in null state { QQmlIncubator incubator; QVERIFY(incubator.isNull()); incubator.clear(); // no effect QVERIFY(incubator.isNull()); } // Clear in loading state { QQmlIncubator incubator; component.create(incubator); QVERIFY(incubator.isLoading()); incubator.clear(); QVERIFY(incubator.isNull()); } // Clear mid load { QQmlIncubator incubator; component.create(incubator); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(SelfRegisteringType::me() != nullptr); QPointer srt = SelfRegisteringType::me(); incubator.clear(); QVERIFY(incubator.isNull()); QVERIFY(srt.isNull()); } // Clear in ready state { QQmlIncubator incubator; component.create(incubator); { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(incubator.isReady()); QVERIFY(incubator.object() != nullptr); QPointer obj = incubator.object(); incubator.clear(); QVERIFY(incubator.isNull()); QVERIFY(!incubator.object()); QVERIFY(!obj.isNull()); delete obj; QVERIFY(obj.isNull()); } } void tst_qqmlincubator::noIncubationController() { // All incubators should behave synchronously when there is no controller QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("noIncubationController.qml")); QVERIFY(component.isReady()); { QQmlIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("testValue").toInt(), 1913); delete incubator.object(); } { QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested); component.create(incubator); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("testValue").toInt(), 1913); delete incubator.object(); } { QQmlIncubator incubator(QQmlIncubator::Synchronous); component.create(incubator); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("testValue").toInt(), 1913); delete incubator.object(); } } void tst_qqmlincubator::forceCompletion() { QQmlComponent component(&engine, testFileUrl("forceCompletion.qml")); QVERIFY(component.isReady()); { // forceCompletion on a null incubator does nothing QQmlIncubator incubator; QVERIFY(incubator.isNull()); incubator.forceCompletion(); QVERIFY(incubator.isNull()); } { // forceCompletion immediately after creating an asynchronous object completes it QQmlIncubator incubator; QVERIFY(incubator.isNull()); component.create(incubator); QVERIFY(incubator.isLoading()); incubator.forceCompletion(); QVERIFY(incubator.isReady()); QVERIFY(incubator.object() != nullptr); QCOMPARE(incubator.object()->property("testValue").toInt(), 3499); delete incubator.object(); } { // forceCompletion during creation completes it SelfRegisteringType::clearMe(); QQmlIncubator incubator; QVERIFY(incubator.isNull()); component.create(incubator); QVERIFY(incubator.isLoading()); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator.isLoading()); incubator.forceCompletion(); QVERIFY(incubator.isReady()); QVERIFY(incubator.object() != nullptr); QCOMPARE(incubator.object()->property("testValue").toInt(), 3499); delete incubator.object(); } { // forceCompletion on a ready incubator has no effect QQmlIncubator incubator; QVERIFY(incubator.isNull()); component.create(incubator); QVERIFY(incubator.isLoading()); incubator.forceCompletion(); QVERIFY(incubator.isReady()); QVERIFY(incubator.object() != nullptr); QCOMPARE(incubator.object()->property("testValue").toInt(), 3499); incubator.forceCompletion(); QVERIFY(incubator.isReady()); QVERIFY(incubator.object() != nullptr); QCOMPARE(incubator.object()->property("testValue").toInt(), 3499); delete incubator.object(); } } void tst_qqmlincubator::setInitialState() { QQmlComponent component(&engine, testFileUrl("setInitialState.qml")); QVERIFY(component.isReady()); struct MyIncubator : public QQmlIncubator { MyIncubator(QQmlIncubator::IncubationMode mode) : QQmlIncubator(mode) {} virtual void setInitialState(QObject *o) { QQmlProperty::write(o, "test2", 19); QQmlProperty::write(o, "testData1", 201); } }; { MyIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); QVERIFY(incubator.isLoading()); std::atomic b{true}; controller.incubateWhile(&b); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("myValueFunctionCalled").toBool(), false); QCOMPARE(incubator.object()->property("test1").toInt(), 502); QCOMPARE(incubator.object()->property("test2").toInt(), 19); delete incubator.object(); } { MyIncubator incubator(QQmlIncubator::Synchronous); component.create(incubator); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("myValueFunctionCalled").toBool(), false); QCOMPARE(incubator.object()->property("test1").toInt(), 502); QCOMPARE(incubator.object()->property("test2").toInt(), 19); delete incubator.object(); } } void tst_qqmlincubator::clearDuringCompletion() { CompletionRegisteringType::clearMe(); SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("clearDuringCompletion.qml")); QVERIFY(component.isReady()); QQmlIncubator incubator; component.create(incubator); QCOMPARE(incubator.status(), QQmlIncubator::Loading); QVERIFY(!CompletionRegisteringType::me()); while (CompletionRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(CompletionRegisteringType::me() != nullptr); QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator.isLoading()); QPointer srt = SelfRegisteringType::me(); incubator.clear(); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QCoreApplication::processEvents(); QVERIFY(incubator.isNull()); QVERIFY(srt.isNull()); } void tst_qqmlincubator::objectDeletionAfterInit() { QQmlComponent component(&engine, testFileUrl("clear.qml")); QVERIFY(component.isReady()); struct MyIncubator : public QQmlIncubator { MyIncubator(QQmlIncubator::IncubationMode mode) : QQmlIncubator(mode), obj(nullptr) {} virtual void setInitialState(QObject *o) { obj = o; } QObject *obj; }; SelfRegisteringType::clearMe(); MyIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); while (!incubator.obj && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(SelfRegisteringType::me() != nullptr); delete incubator.obj; incubator.clear(); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QCoreApplication::processEvents(); QVERIFY(incubator.isNull()); } class Switcher : public QObject { Q_OBJECT public: Switcher(QQmlEngine *e) : QObject(), engine(e) { } ~Switcher() { if (component) component->deleteLater(); if (incubator && incubator->object()) incubator->object()->deleteLater(); } struct MyIncubator : public QQmlIncubator { MyIncubator(QQmlIncubator::IncubationMode mode, QObject *s) : QQmlIncubator(mode), switcher(s) {} virtual void setInitialState(QObject *o) { if (o->objectName() == "switchMe") connect(o, SIGNAL(switchMe()), switcher, SLOT(switchIt())); } QObject *switcher; }; void start() { incubator.reset(new MyIncubator(QQmlIncubator::Synchronous, this)); component = new QQmlComponent(engine, QQmlDataTest::instance()->testFileUrl("recursiveClear.1.qml")); component->create(*incubator); } QQmlEngine *engine; QScopedPointer incubator; QQmlComponent *component; public slots: void switchIt() { component->deleteLater(); incubator->clear(); component = new QQmlComponent(engine, QQmlDataTest::instance()->testFileUrl("recursiveClear.2.qml")); component->create(*incubator); } }; void tst_qqmlincubator::recursiveClear() { Switcher switcher(&engine); switcher.start(); } void tst_qqmlincubator::statusChanged() { class MyIncubator : public QQmlIncubator { public: MyIncubator(QQmlIncubator::IncubationMode mode = QQmlIncubator::Asynchronous) : QQmlIncubator(mode) {} QList statuses; protected: virtual void statusChanged(Status s) { statuses << s; } virtual void setInitialState(QObject *) { statuses << -1; } }; { QQmlComponent component(&engine, testFileUrl("statusChanged.qml")); QVERIFY(component.isReady()); MyIncubator incubator(QQmlIncubator::Synchronous); component.create(incubator); QVERIFY(incubator.isReady()); QCOMPARE(incubator.statuses.count(), 3); QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading)); QCOMPARE(incubator.statuses.at(1), -1); QCOMPARE(incubator.statuses.at(2), int(QQmlIncubator::Ready)); delete incubator.object(); } { QQmlComponent component(&engine, testFileUrl("statusChanged.qml")); QVERIFY(component.isReady()); MyIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); QVERIFY(incubator.isLoading()); QCOMPARE(incubator.statuses.count(), 1); QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading)); { std::atomic b{true}; controller.incubateWhile(&b); } QCOMPARE(incubator.statuses.count(), 3); QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading)); QCOMPARE(incubator.statuses.at(1), -1); QCOMPARE(incubator.statuses.at(2), int(QQmlIncubator::Ready)); delete incubator.object(); } { QQmlComponent component2(&engine, testFileUrl("statusChanged.nested.qml")); QVERIFY(component2.isReady()); MyIncubator incubator(QQmlIncubator::Asynchronous); component2.create(incubator); QVERIFY(incubator.isLoading()); QCOMPARE(incubator.statuses.count(), 1); QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading)); { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(incubator.isReady()); QCOMPARE(incubator.statuses.count(), 3); QCOMPARE(incubator.statuses.at(0), int(QQmlIncubator::Loading)); QCOMPARE(incubator.statuses.at(1), -1); QCOMPARE(incubator.statuses.at(2), int(QQmlIncubator::Ready)); delete incubator.object(); } } void tst_qqmlincubator::asynchronousIfNested() { // Asynchronous if nested within a finalized context behaves synchronously { QQmlComponent component(&engine, testFileUrl("asynchronousIfNested.1.qml")); QVERIFY(component.isReady()); QScopedPointer object { component.create() }; QVERIFY(object); QCOMPARE(object->property("a").toInt(), 10); QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested); component.create(incubator, nullptr, qmlContext(object.get())); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("a").toInt(), 10); delete incubator.object(); } // Asynchronous if nested within an executing context behaves asynchronously, but prevents // the parent from finishing { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("asynchronousIfNested.2.qml")); QVERIFY(component.isReady()); QQmlIncubator incubator; component.create(incubator); QVERIFY(incubator.isLoading()); QVERIFY(!SelfRegisteringType::me()); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator.isLoading()); QQmlIncubator nested(QQmlIncubator::AsynchronousIfNested); component.create(nested, nullptr, qmlContext(SelfRegisteringType::me())); QVERIFY(nested.isLoading()); while (nested.isLoading()) { QVERIFY(incubator.isLoading()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(nested.isReady()); QVERIFY(incubator.isLoading()); { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(nested.isReady()); QVERIFY(incubator.isReady()); delete nested.object(); delete incubator.object(); } // AsynchronousIfNested within a synchronous AsynchronousIfNested behaves synchronously { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("asynchronousIfNested.3.qml")); QVERIFY(component.isReady()); struct CallbackData { CallbackData(QQmlEngine *e) : engine(e), pass(false) {} QQmlEngine *engine; bool pass; static void callback(CallbackRegisteringType *o, void *data) { CallbackData *d = (CallbackData *)data; QQmlComponent c(d->engine, QQmlDataTest::instance()->testFileUrl("asynchronousIfNested.1.qml")); if (!c.isReady()) return; QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested); c.create(incubator, nullptr, qmlContext(o)); if (!incubator.isReady()) return; if (incubator.object()->property("a").toInt() != 10) return; d->pass = true; incubator.object()->deleteLater(); } }; CallbackData cd(&engine); CallbackRegisteringType::registerCallback(&CallbackData::callback, &cd); QQmlIncubator incubator(QQmlIncubator::AsynchronousIfNested); component.create(incubator); QVERIFY(incubator.isReady()); QCOMPARE(cd.pass, true); delete incubator.object(); } } void tst_qqmlincubator::nestedComponent() { QQmlComponent component(&engine, testFileUrl("nestedComponent.qml")); QVERIFY(component.isReady()); QObject *object = component.create(); QQmlComponent *nested = object->property("c").value(); QVERIFY(nested); QVERIFY(nested->isReady()); // Test without incubator { QObject *nestedObject = nested->create(); QCOMPARE(nestedObject->property("value").toInt(), 19988); delete nestedObject; } // Test with incubator { QQmlIncubator incubator(QQmlIncubator::Synchronous); nested->create(incubator); QVERIFY(incubator.isReady()); QVERIFY(incubator.object()); QCOMPARE(incubator.object()->property("value").toInt(), 19988); delete incubator.object(); } delete object; } // Checks that a new AsynchronousIfNested incubator can be correctly started in the // statusChanged() callback of another. void tst_qqmlincubator::chainedAsynchronousIfNested() { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("chainedAsynchronousIfNested.qml")); QVERIFY(component.isReady()); QQmlIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); QVERIFY(incubator.isLoading()); QVERIFY(!SelfRegisteringType::me()); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator.isLoading()); struct MyIncubator : public QQmlIncubator { MyIncubator(MyIncubator *next, QQmlComponent *component, QQmlContext *ctxt) : QQmlIncubator(AsynchronousIfNested), next(next), component(component), ctxt(ctxt) {} protected: virtual void statusChanged(Status s) { if (s == Ready && next) component->create(*next, nullptr, ctxt); } private: MyIncubator *next; QQmlComponent *component; QQmlContext *ctxt; }; MyIncubator incubator2(nullptr, &component, nullptr); MyIncubator incubator1(&incubator2, &component, qmlContext(SelfRegisteringType::me())); component.create(incubator1, nullptr, qmlContext(SelfRegisteringType::me())); QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isLoading()); QVERIFY(incubator2.isNull()); while (incubator1.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isLoading()); QVERIFY(incubator2.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isLoading()); while (incubator2.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isLoading()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); if (incubator.isLoading()) { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(incubator.isReady()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); incubator.object()->deleteLater(); incubator1.object()->deleteLater(); incubator2.object()->deleteLater(); } // Checks that new AsynchronousIfNested incubators can be correctly chained if started in // componentCompleted(). void tst_qqmlincubator::chainedAsynchronousIfNestedOnCompleted() { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("chainInCompletion.qml")); QVERIFY(component.isReady()); QQmlComponent c1(&engine, testFileUrl("chainedAsynchronousIfNested.qml")); QVERIFY(c1.isReady()); struct MyIncubator : public QQmlIncubator { MyIncubator(MyIncubator *next, QQmlComponent *component, QQmlContext *ctxt) : QQmlIncubator(AsynchronousIfNested), next(next), component(component), ctxt(ctxt) {} protected: virtual void statusChanged(Status s) { if (s == Ready && next) { component->create(*next, nullptr, ctxt); } } private: MyIncubator *next; QQmlComponent *component; QQmlContext *ctxt; }; struct CallbackData { CallbackData(QQmlComponent *c, MyIncubator *i, QQmlContext *ct) : component(c), incubator(i), ctxt(ct) {} QQmlComponent *component; MyIncubator *incubator; QQmlContext *ctxt; static void callback(CompletionCallbackType *, void *data) { CallbackData *d = (CallbackData *)data; d->component->create(*d->incubator, nullptr, d->ctxt); } }; QQmlIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); QVERIFY(incubator.isLoading()); QVERIFY(!SelfRegisteringType::me()); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator.isLoading()); MyIncubator incubator3(nullptr, &c1, qmlContext(SelfRegisteringType::me())); MyIncubator incubator2(&incubator3, &c1, qmlContext(SelfRegisteringType::me())); MyIncubator incubator1(&incubator2, &c1, qmlContext(SelfRegisteringType::me())); // start incubator1 in componentComplete CallbackData cd(&c1, &incubator1, qmlContext(SelfRegisteringType::me())); CompletionCallbackType::registerCallback(&CallbackData::callback, &cd); while (!incubator1.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator2.isNull()); QVERIFY(incubator3.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isLoading()); QVERIFY(incubator2.isNull()); QVERIFY(incubator3.isNull()); while (incubator1.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isLoading()); QVERIFY(incubator2.isNull()); QVERIFY(incubator3.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isLoading()); QVERIFY(incubator3.isNull()); while (incubator2.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isLoading()); QVERIFY(incubator3.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); QVERIFY(incubator3.isLoading()); while (incubator3.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); QVERIFY(incubator3.isLoading()); std::atomic b{false}; controller.incubateWhile(&b); } { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(incubator.isReady()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); QVERIFY(incubator3.isReady()); incubator.object()->deleteLater(); incubator1.object()->deleteLater(); incubator2.object()->deleteLater(); incubator3.object()->deleteLater(); } // Checks that new AsynchronousIfNested incubators can be correctly cleared if started in // componentCompleted(). void tst_qqmlincubator::chainedAsynchronousClear() { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("chainInCompletion.qml")); QVERIFY(component.isReady()); QQmlComponent c1(&engine, testFileUrl("chainedAsynchronousIfNested.qml")); QVERIFY(c1.isReady()); struct MyIncubator : public QQmlIncubator { MyIncubator(MyIncubator *next, QQmlComponent *component, QQmlContext *ctxt) : QQmlIncubator(AsynchronousIfNested), next(next), component(component), ctxt(ctxt) {} protected: virtual void statusChanged(Status s) { if (s == Ready && next) { component->create(*next, nullptr, ctxt); } } private: MyIncubator *next; QQmlComponent *component; QQmlContext *ctxt; }; struct CallbackData { CallbackData(QQmlComponent *c, MyIncubator *i, QQmlContext *ct) : component(c), incubator(i), ctxt(ct) {} QQmlComponent *component; MyIncubator *incubator; QQmlContext *ctxt; static void callback(CompletionCallbackType *, void *data) { CallbackData *d = (CallbackData *)data; d->component->create(*d->incubator, nullptr, d->ctxt); } }; QQmlIncubator incubator(QQmlIncubator::Asynchronous); component.create(incubator); QVERIFY(incubator.isLoading()); QVERIFY(!SelfRegisteringType::me()); while (SelfRegisteringType::me() == nullptr && incubator.isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator.isLoading()); MyIncubator incubator3(nullptr, &c1, qmlContext(SelfRegisteringType::me())); MyIncubator incubator2(&incubator3, &c1, qmlContext(SelfRegisteringType::me())); MyIncubator incubator1(&incubator2, &c1, qmlContext(SelfRegisteringType::me())); // start incubator1 in componentComplete CallbackData cd(&c1, &incubator1, qmlContext(SelfRegisteringType::me())); CompletionCallbackType::registerCallback(&CallbackData::callback, &cd); while (!incubator1.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator2.isNull()); QVERIFY(incubator3.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isLoading()); QVERIFY(incubator2.isNull()); QVERIFY(incubator3.isNull()); while (incubator1.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isLoading()); QVERIFY(incubator2.isNull()); QVERIFY(incubator3.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isLoading()); QVERIFY(incubator3.isNull()); while (incubator2.isLoading()) { QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isLoading()); QVERIFY(incubator3.isNull()); std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(incubator.isLoading()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); QVERIFY(incubator3.isLoading()); // Any in loading state will become null when cleared. incubator.clear(); QVERIFY(incubator.isNull()); QVERIFY(incubator1.isReady()); QVERIFY(incubator2.isReady()); QVERIFY(incubator3.isNull()); incubator1.object()->deleteLater(); incubator2.object()->deleteLater(); } void tst_qqmlincubator::selfDelete() { struct MyIncubator : public QQmlIncubator { MyIncubator(bool *done, Status status, IncubationMode mode) : QQmlIncubator(mode), done(done), status(status) {} protected: virtual void statusChanged(Status s) { if (s == status) { *done = true; if (s == Ready) delete object(); delete this; } } private: bool *done; Status status; }; { QQmlComponent component(&engine, testFileUrl("selfDelete.qml")); #define DELETE_TEST(status, mode) { \ bool done = false; \ component.create(*(new MyIncubator(&done, status, mode))); \ std::atomic True{true}; \ controller.incubateWhile(&True); \ QVERIFY(done == true); \ } DELETE_TEST(QQmlIncubator::Loading, QQmlIncubator::Synchronous); DELETE_TEST(QQmlIncubator::Ready, QQmlIncubator::Synchronous); DELETE_TEST(QQmlIncubator::Loading, QQmlIncubator::Asynchronous); DELETE_TEST(QQmlIncubator::Ready, QQmlIncubator::Asynchronous); #undef DELETE_TEST } // Delete within error status { SelfRegisteringType::clearMe(); QQmlComponent component(&engine, testFileUrl("objectDeleted.qml")); QVERIFY(component.isReady()); bool done = false; MyIncubator *incubator = new MyIncubator(&done, QQmlIncubator::Error, QQmlIncubator::Asynchronous); component.create(*incubator); QCOMPARE(incubator->QQmlIncubator::status(), QQmlIncubator::Loading); QVERIFY(!SelfRegisteringType::me()); while (SelfRegisteringType::me() == nullptr && incubator->isLoading()) { std::atomic b{false}; controller.incubateWhile(&b); } QVERIFY(SelfRegisteringType::me() != nullptr); QVERIFY(incubator->isLoading()); // We have to cheat and manually remove it from the creator->allCreatedObjects // otherwise we will do a double delete QQmlIncubatorPrivate *incubatorPriv = QQmlIncubatorPrivate::get(incubator); incubatorPriv->creator->allCreatedObjects().pop(); delete SelfRegisteringType::me(); { std::atomic b{true}; controller.incubateWhile(&b); } QVERIFY(done); } } // Test that QML doesn't crash if the context is deleted prior to the incubator // first executing. void tst_qqmlincubator::contextDelete() { QQmlContext *context = new QQmlContext(engine.rootContext()); QQmlComponent component(&engine, testFileUrl("contextDelete.qml")); QQmlIncubator incubator; component.create(incubator, context); delete context; { std::atomic b{false}; controller.incubateWhile(&b); } } // QTBUG-53111 void tst_qqmlincubator::garbageCollection() { QQmlComponent component(&engine, testFileUrl("garbageCollection.qml")); QScopedPointer obj(component.create()); engine.collectGarbage(); std::atomic b{true}; controller.incubateWhile(&b); // verify incubation completed (the incubator was not prematurely collected) QVariant incubatorVariant; QMetaObject::invokeMethod(obj.data(), "getAndClearIncubator", Q_RETURN_ARG(QVariant, incubatorVariant)); QJSValue strongRef = incubatorVariant.value(); QVERIFY(!strongRef.isNull() && !strongRef.isUndefined()); // turn the last strong reference to the incubator into a weak one and collect QV4::WeakValue weakIncubatorRef; weakIncubatorRef.set(QQmlEnginePrivate::getV4Engine(&engine), QJSValuePrivate::asReturnedValue(&strongRef)); strongRef = QJSValue(); incubatorVariant.clear(); // verify incubator is correctly collected now that incubation is complete and all references are gone engine.collectGarbage(); QVERIFY(weakIncubatorRef.isNullOrUndefined()); } void tst_qqmlincubator::requiredProperties() { { QQmlComponent component(&engine, testFileUrl("requiredProperty.qml")); QVERIFY(component.isReady()); // forceCompletion immediately after creating an asynchronous object completes it QQmlIncubator incubator; incubator.setInitialProperties({{"requiredProperty", 42}}); QVERIFY(incubator.isNull()); component.create(incubator); QVERIFY(incubator.isLoading()); incubator.forceCompletion(); QVERIFY(incubator.isReady()); QVERIFY(incubator.object() != nullptr); QCOMPARE(incubator.object()->property("requiredProperty").toInt(), 42); delete incubator.object(); } { QQmlComponent component(&engine, testFileUrl("requiredProperty.qml")); QVERIFY(component.isReady()); // forceCompletion immediately after creating an asynchronous object completes it QQmlIncubator incubator; QVERIFY(incubator.isNull()); component.create(incubator); QVERIFY(incubator.isLoading()); incubator.forceCompletion(); QVERIFY(incubator.isError()); auto error = incubator.errors().first(); QVERIFY(error.description().contains(QLatin1String("Required property requiredProperty was not initialized"))); QVERIFY(incubator.object() == nullptr); } } QTEST_MAIN(tst_qqmlincubator) #include "tst_qqmlincubator.moc"