diff options
Diffstat (limited to 'tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp')
-rw-r--r-- | tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | 404 |
1 files changed, 389 insertions, 15 deletions
diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp index 19a6731ff7..810fdecafd 100644 --- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp +++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> @@ -34,12 +34,18 @@ private slots: void recompileAfterDirectoryChange(); void fileSelectors(); void localAliases(); + void aliasToAlias(); void cacheResources(); void stableOrderOfDependentCompositeTypes(); void singletonDependency(); void cppRegisteredSingletonDependency(); void cacheModuleScripts(); void reuseStaticMappings(); + void invalidateSaveLoadCache(); + void duplicateIdsInInlineComponents(); + + void inlineComponentDoesNotCauseConstantInvalidation_data(); + void inlineComponentDoesNotCauseConstantInvalidation(); private: QDir m_qmlCacheDirectory; @@ -98,7 +104,7 @@ struct TestCompiler { closeMapping(); testFilePath = baseDirectory + QStringLiteral("/test.qml"); - cacheFilePath = QV4::ExecutableCompilationUnit::localCacheFilePath( + cacheFilePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(testFilePath)); mappedFile.setFileName(cacheFilePath); } @@ -170,7 +176,7 @@ struct TestCompiler return false; } - const QString targetCacheFilePath = QV4::ExecutableCompilationUnit::localCacheFilePath( + const QString targetCacheFilePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(targetTestFilePath)); QFile source(cacheFilePath); @@ -197,16 +203,14 @@ struct TestCompiler { const QString path = fileName.isEmpty() ? testFilePath : tempDir.path() + "/" + fileName; - QQmlRefPointer<QV4::ExecutableCompilationUnit> unit - = QV4::ExecutableCompilationUnit::create(); + auto unit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(); return unit->loadFromDisk(QUrl::fromLocalFile(path), QFileInfo(path).lastModified(), &lastErrorString); } quintptr unitData() { - QQmlRefPointer<QV4::ExecutableCompilationUnit> unit - = QV4::ExecutableCompilationUnit::create(); + auto unit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(); return unit->loadFromDisk(QUrl::fromLocalFile(testFilePath), QFileInfo(testFilePath).lastModified(), &lastErrorString) ? quintptr(unit->unitData()) @@ -289,12 +293,12 @@ void tst_qmldiskcache::loadLocalAsFallback() f.write(reinterpret_cast<const char *>(&unit), sizeof(unit)); } - QQmlRefPointer<QV4::ExecutableCompilationUnit> unit = QV4::ExecutableCompilationUnit::create(); + auto unit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(); bool loaded = unit->loadFromDisk(QUrl::fromLocalFile(testCompiler.testFilePath), QFileInfo(testCompiler.testFilePath).lastModified(), &testCompiler.lastErrorString); QVERIFY2(loaded, qPrintable(testCompiler.lastErrorString)); - QCOMPARE(unit->objectCount(), 1); + QCOMPARE(unit->qmlData->nObjects, 1u); } void tst_qmldiskcache::regenerateAfterChange() @@ -606,7 +610,7 @@ void tst_qmldiskcache::fileSelectors() QVERIFY(!obj.isNull()); QCOMPARE(obj->property("value").toInt(), 42); - QFile cacheFile(QV4::ExecutableCompilationUnit::localCacheFilePath( + QFile cacheFile(QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(testFilePath))); QVERIFY2(cacheFile.exists(), qPrintable(cacheFile.fileName())); } @@ -622,7 +626,7 @@ void tst_qmldiskcache::fileSelectors() QVERIFY(!obj.isNull()); QCOMPARE(obj->property("value").toInt(), 100); - QFile cacheFile(QV4::ExecutableCompilationUnit::localCacheFilePath( + QFile cacheFile(QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(selectedTestFilePath))); QVERIFY2(cacheFile.exists(), qPrintable(cacheFile.fileName())); } @@ -671,6 +675,55 @@ void tst_qmldiskcache::localAliases() } } +void tst_qmldiskcache::aliasToAlias() +{ + QQmlEngine engine; + + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + const QByteArray contents = QByteArrayLiteral(R"( + import QML + QtObject { + id: foo + readonly property alias myAlias: bar.prop + + property QtObject o: QtObject { + id: bar + + property QtObject o: QtObject { + id: baz + readonly property int value: 100 + } + + readonly property alias prop: baz.value + } + } + )"); + + { + testCompiler.clearCache(); + QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString)); + QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString)); + } + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("myAlias").toInt(), 100); + } + + engine.clearComponentCache(); + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->property("myAlias").toInt(), 100); + } +} + static QSet<QString> entrySet(const QDir &dir) { const auto &list = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); @@ -782,7 +835,7 @@ void tst_qmldiskcache::stableOrderOfDependentCompositeTypes() QVERIFY2(firstDependentTypeClassName.contains("QMLTYPE"), firstDependentTypeClassName.constData()); QVERIFY2(secondDependentTypeClassName.contains("QMLTYPE"), secondDependentTypeClassName.constData()); - const QString testFileCachePath = QV4::ExecutableCompilationUnit::localCacheFilePath( + const QString testFileCachePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(testFilePath)); QVERIFY(QFile::exists(testFileCachePath)); QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified(); @@ -861,7 +914,7 @@ void tst_qmldiskcache::singletonDependency() QCOMPARE(obj->property("value").toInt(), 42); } - const QString testFileCachePath = QV4::ExecutableCompilationUnit::localCacheFilePath( + const QString testFileCachePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(testFilePath)); QVERIFY(QFile::exists(testFileCachePath)); QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified(); @@ -919,7 +972,7 @@ void tst_qmldiskcache::cppRegisteredSingletonDependency() QCOMPARE(value.toInt(), 42); } - const QString testFileCachePath = QV4::ExecutableCompilationUnit::localCacheFilePath( + const QString testFileCachePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( QUrl::fromLocalFile(testFilePath)); QVERIFY(QFile::exists(testFileCachePath)); QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified(); @@ -961,7 +1014,8 @@ void tst_qmldiskcache::cacheModuleScripts() auto componentPrivate = QQmlComponentPrivate::get(&component); QVERIFY(componentPrivate); - auto compilationUnit = componentPrivate->compilationUnit->dependentScripts.first()->compilationUnit(); + auto compilationUnit = componentPrivate->compilationUnit->dependentScriptsPtr() + ->first()->compilationUnit(); QVERIFY(compilationUnit); auto unitData = compilationUnit->unitData(); QVERIFY(unitData); @@ -1007,6 +1061,326 @@ void tst_qmldiskcache::reuseStaticMappings() QCOMPARE(testCompiler.unitData(), data1); } +class AParent : public QObject +{ + Q_OBJECT + Q_PROPERTY(int x MEMBER x) +public: + AParent(QObject *parent = nullptr) : QObject(parent) {} + int x = 25; +}; + +class BParent : public QObject +{ + Q_OBJECT + + // Insert y before x, to change the property index of x + Q_PROPERTY(int y MEMBER y) + + Q_PROPERTY(int x MEMBER x) +public: + BParent(QObject *parent = nullptr) : QObject(parent) {} + int y = 13; + int x = 25; +}; + +static QString writeTempFile( + const QTemporaryDir &tempDir, const QString &fileName, const char *contents) { + QFile f(tempDir.path() + '/' + fileName); + const bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate); + Q_ASSERT(ok); + f.write(contents); + return f.fileName(); +}; + +void tst_qmldiskcache::invalidateSaveLoadCache() +{ + qmlRegisterType<AParent>("Base", 1, 0, "Parent"); + std::unique_ptr<QQmlEngine> e = std::make_unique<QQmlEngine>(); + + // If you store a CU to a .qmlc file at run time, the .qmlc file will contain + // alias entries with the encodedMetaPropertyIndex pre-resolved. That's in + // contrast to .qmlc files generated ahead of time. Exploit that to cause + // a need to recompile the file. + + QTemporaryDir tempDir; + writeTempFile( + tempDir, QLatin1String("B.qml"), + R"( + import QML + QtObject { + component C: QtObject {} + } + )"); + + const QString fileName = writeTempFile( + tempDir, QLatin1String("a.qml"), + R"( + import Base + Parent { + id: self + property alias z: self.x + component C: Parent {} + property C c: C {} + property B.C d: B.C {} + } + )"); + const QUrl url = QUrl::fromLocalFile(fileName); + waitForFileSystem(); + + { + QQmlComponent a(e.get(), url); + QVERIFY2(a.isReady(), qPrintable(a.errorString())); + QScopedPointer<QObject> ao(a.create()); + QVERIFY(!ao.isNull()); + AParent *ap = qobject_cast<AParent *>(ao.data()); + QCOMPARE(ap->property("z").toInt(), ap->x); + } + + QString errorString; + auto oldUnit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(); + QVERIFY2(oldUnit->loadFromDisk(url, QFileInfo(fileName).lastModified(), &errorString), qPrintable(errorString)); + + // Produce a checksum mismatch. + e->clearComponentCache(); + qmlClearTypeRegistrations(); + qmlRegisterType<BParent>("Base", 1, 0, "Parent"); + e = std::make_unique<QQmlEngine>(); + + { + QQmlComponent b(e.get(), url); + QVERIFY2(b.isReady(), qPrintable(b.errorString())); + QScopedPointer<QObject> bo(b.create()); + QVERIFY(!bo.isNull()); + BParent *bp = qobject_cast<BParent *>(bo.data()); + QCOMPARE(bp->property("z").toInt(), bp->x); + } + + // Make it recompile again. If we ever get rid of the metaobject indices in compilation units, + // the above test will not test the save/load cache anymore. Therefore, in order to make really + // sure that we get a new CU that invalidates the save/load cache, modify the file in place. + + e->clearComponentCache(); + { + QFile file(fileName); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Append)); + file.write(" "); + } + waitForFileSystem(); + + { + QQmlComponent b(e.get(), url); + QVERIFY2(b.isReady(), qPrintable(b.errorString())); + QScopedPointer<QObject> bo(b.create()); + QVERIFY(!bo.isNull()); + BParent *bp = qobject_cast<BParent *>(bo.data()); + QCOMPARE(bp->property("z").toInt(), bp->x); + } + + // Verify that the mapped unit data is actually different now. + // The cache should have been invalidated after all. + // So, now we should be able to load a freshly written CU. + + auto unit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(); + QVERIFY2(unit->loadFromDisk(url, QFileInfo(fileName).lastModified(), &errorString), qPrintable(errorString)); + + QVERIFY(unit->unitData() != oldUnit->unitData()); +} + +void tst_qmldiskcache::duplicateIdsInInlineComponents() +{ + // Exercise the case of loading strange generalized group properties from .qmlc. + + QQmlEngine engine; + + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + const QByteArray contents = QByteArrayLiteral(R"( + import QtQml + QtObject { + component First : QtObject { + property QtObject aa: QtObject { + id: a + } + property Binding bb: Binding { + a.objectName: "test1" + } + } + + component Second : QtObject { + property QtObject aa: QtObject { + id: a + } + property Binding bb: Binding { + a.objectName: "test2" + } + + property Component cc: QtObject { + property QtObject aa: QtObject { + id: a + } + property Binding bb: Binding { + a.objectName: "test3" + } + } + } + + property First first: First {} + property Second second: Second {} + property QtObject third: second.cc.createObject(); + + objectName: first.aa.objectName + second.aa.objectName + third.aa.objectName; + } + )"); + + { + testCompiler.clearCache(); + QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString)); + QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString)); + } + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->objectName(), "test1test2test3"); + } + + engine.clearComponentCache(); + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + QScopedPointer<QObject> obj(component.create()); + QVERIFY(!obj.isNull()); + QCOMPARE(obj->objectName(), "test1test2test3"); + } +} + +void tst_qmldiskcache::inlineComponentDoesNotCauseConstantInvalidation_data() +{ + QTest::addColumn<QByteArray>("code"); + + QTest::addRow("simple") << QByteArray(R"( + import QtQml + QtObject { + component Test: QtObject { + property int i: 28 + } + property Test test: Test { + objectName: "foobar" + } + property int k: test.i + } + )"); + + QTest::addRow("with function") << QByteArray(R"( + import QtQml + QtObject { + component Test : QtObject { + id: self + property int i: 2 + property alias j: self.i + } + property Test test: Test { + function updateValue() {} + objectName: 'foobar' + j: 28 + } + property int k: test.j + } + )"); + + QTest::addRow("in nested") << QByteArray(R"( + import QtQuick + Item { + Item { + component Line: Item { + property alias endY: pathLine.y + Item { + Item { + id: pathLine + } + } + } + } + Line { + id: primaryLine + endY: 28 + } + property int k: primaryLine.endY + } + )"); + + QTest::addRow("with revision") << QByteArray(R"( + import QtQuick + ListView { + Item { + id: scrollBar + } + delegate: Image { + mipmap: true + } + Item { + id: refreshNodesIndicator + } + property int k: delegate.createObject().mipmap ? 28 : 4 + } + )"); +} + +void tst_qmldiskcache::inlineComponentDoesNotCauseConstantInvalidation() +{ + QFETCH(QByteArray, code); + + QQmlEngine engine; + + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + auto check = [&](){ + QQmlComponent c(&engine, QUrl::fromLocalFile(testCompiler.testFilePath)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("k"), QVariant::fromValue<int>(28)); + }; + + testCompiler.reset(); + QVERIFY(testCompiler.writeTestFile(code)); + + QVERIFY(testCompiler.loadTestFile()); + + const quintptr data1 = testCompiler.unitData(); + QVERIFY(data1 != 0); + QCOMPARE(testCompiler.unitData(), data1); + check(); + + engine.clearComponentCache(); + + // inline component does not invalidate cache + QVERIFY(testCompiler.loadTestFile()); + QCOMPARE(testCompiler.unitData(), data1); + check(); + + testCompiler.reset(); + QVERIFY(testCompiler.writeTestFile(R"( + import QtQml + QtObject { + component Test : QtObject { + property double d: 2 + } + property Test test: Test { + objectName: 'foobar' + } + })")); + QVERIFY(testCompiler.loadTestFile()); + const quintptr data2 = testCompiler.unitData(); + QVERIFY(data2); + QVERIFY(data1 != data2); +} + QTEST_MAIN(tst_qmldiskcache) #include "tst_qmldiskcache.moc" |