aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp')
-rw-r--r--tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp404
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"