1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <qtest.h>
30
31#include <private/qv4compileddata_p.h>
32#include <private/qv4compiler_p.h>
33#include <private/qv4engine_p.h>
34#include <private/qv4codegen_p.h>
35#include <private/qqmlcomponent_p.h>
36#include <private/qv4executablecompilationunit_p.h>
37#include <private/qqmlscriptdata_p.h>
38#include <QQmlComponent>
39#include <QQmlEngine>
40#include <QQmlFileSelector>
41#include <QThread>
42#include <QCryptographicHash>
43#include <QStandardPaths>
44#include <QDirIterator>
45
46class tst_qmldiskcache: public QObject
47{
48 Q_OBJECT
49
50private slots:
51 void initTestCase();
52 void cleanupTestCase();
53
54 void loadLocalAsFallback();
55 void regenerateAfterChange();
56 void registerImportForImplicitComponent();
57 void basicVersionChecks();
58 void recompileAfterChange();
59 void recompileAfterDirectoryChange();
60 void fileSelectors();
61 void localAliases();
62 void cacheResources();
63 void stableOrderOfDependentCompositeTypes();
64 void singletonDependency();
65 void cppRegisteredSingletonDependency();
66 void cacheModuleScripts();
67 void reuseStaticMappings();
68
69private:
70 QDir m_qmlCacheDirectory;
71};
72
73// A wrapper around QQmlComponent to ensure the temporary reference counts
74// on the type data as a result of the main thread <> loader thread communication
75// are dropped. Regular Synchronous loading will leave us with an event posted
76// to the gui thread and an extra refcount that will only be dropped after the
77// event delivery. A plain sendPostedEvents() however is insufficient because
78// we can't be sure that the event is posted after the constructor finished.
79class CleanlyLoadingComponent : public QQmlComponent
80{
81public:
82 CleanlyLoadingComponent(QQmlEngine *engine, const QUrl &url)
83 : QQmlComponent(engine, url, QQmlComponent::Asynchronous)
84 { waitForLoad(); }
85 CleanlyLoadingComponent(QQmlEngine *engine, const QString &fileName)
86 : QQmlComponent(engine, fileName, QQmlComponent::Asynchronous)
87 { waitForLoad(); }
88
89 void waitForLoad()
90 {
91 QTRY_VERIFY_WITH_TIMEOUT(
92 status() == QQmlComponent::Ready || status() == QQmlComponent::Error,
93 32768);
94 }
95};
96
97static void waitForFileSystem()
98{
99 // On macOS with HFS+ the precision of file times is measured in seconds, so to ensure that
100 // the newly written file has a modification date newer than an existing cache file, we must
101 // wait.
102 // Similar effects of lacking precision have been observed on some Linux systems.
103 static const bool fsHasSubSecondResolution = []() {
104 QDateTime mtime = QFileInfo(QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation)).lastModified();
105 // 1:1000 chance of a false negative
106 return mtime.toMSecsSinceEpoch() % 1000;
107 }();
108 if (!fsHasSubSecondResolution)
109 QThread::sleep(1);
110}
111
112struct TestCompiler
113{
114 TestCompiler(QQmlEngine *engine)
115 : engine(engine)
116 , tempDir()
117 , currentMapping(nullptr)
118 {
119 init(baseDirectory: tempDir.path());
120 }
121
122 void init(const QString &baseDirectory)
123 {
124 closeMapping();
125 testFilePath = baseDirectory + QStringLiteral("/test.qml");
126 cacheFilePath = QV4::ExecutableCompilationUnit::localCacheFilePath(
127 url: QUrl::fromLocalFile(localfile: testFilePath));
128 mappedFile.setFileName(cacheFilePath);
129 }
130
131 void reset()
132 {
133 closeMapping();
134 engine->clearComponentCache();
135 waitForFileSystem();
136 }
137
138 bool writeTestFile(const QByteArray &contents)
139 {
140 QFile f(testFilePath);
141 if (!f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
142 lastErrorString = f.errorString();
143 return false;
144 }
145 if (f.write(data: contents) != contents.size()) {
146 lastErrorString = f.errorString();
147 return false;
148 }
149 return true;
150 }
151
152 bool loadTestFile()
153 {
154 CleanlyLoadingComponent component(engine, testFilePath);
155 if (!component.isReady()) {
156 lastErrorString = component.errorString();
157 return false;
158 }
159 return true;
160 }
161
162 bool compile(const QByteArray &contents)
163 {
164 reset();
165 return writeTestFile(contents) && loadTestFile();
166 }
167
168 const QV4::CompiledData::Unit *mapUnit()
169 {
170 if (!mappedFile.open(flags: QIODevice::ReadOnly)) {
171 lastErrorString = mappedFile.errorString();
172 return nullptr;
173 }
174
175 currentMapping = mappedFile.map(/*offset*/0, size: mappedFile.size());
176 if (!currentMapping) {
177 lastErrorString = mappedFile.errorString();
178 return nullptr;
179 }
180 QV4::CompiledData::Unit *unitPtr;
181 memcpy(dest: &unitPtr, src: &currentMapping, n: sizeof(unitPtr));
182 return unitPtr;
183 }
184
185 typedef void (*HeaderTweakFunction)(QV4::CompiledData::Unit *header);
186 bool tweakHeader(HeaderTweakFunction function, const QString &newName)
187 {
188 closeMapping();
189
190 const QString targetTestFilePath = tempDir.path() + "/" + newName;
191
192 {
193 QFile testFile(testFilePath);
194 if (!testFile.copy(newName: targetTestFilePath))
195 return false;
196 }
197
198 const QString targetCacheFilePath = QV4::ExecutableCompilationUnit::localCacheFilePath(
199 url: QUrl::fromLocalFile(localfile: targetTestFilePath));
200
201 QFile source(cacheFilePath);
202 if (!source.copy(newName: targetCacheFilePath))
203 return false;
204
205 if (!source.open(flags: QIODevice::ReadOnly))
206 return false;
207
208 QFile target(targetCacheFilePath);
209 if (!target.open(flags: QIODevice::WriteOnly))
210 return false;
211
212 QV4::CompiledData::Unit header;
213 if (source.read(data: reinterpret_cast<char *>(&header), maxlen: sizeof(header)) != sizeof(header))
214 return false;
215 function(&header);
216
217 return target.write(data: reinterpret_cast<const char *>(&header), len: sizeof(header))
218 == sizeof(header);
219 }
220
221 bool verify(const QString &fileName = QString())
222 {
223 const QString path = fileName.isEmpty() ? testFilePath : tempDir.path() + "/" + fileName;
224
225 QQmlRefPointer<QV4::ExecutableCompilationUnit> unit
226 = QV4::ExecutableCompilationUnit::create();
227 return unit->loadFromDisk(url: QUrl::fromLocalFile(localfile: path),
228 sourceTimeStamp: QFileInfo(path).lastModified(), errorString: &lastErrorString);
229 }
230
231 quintptr unitData()
232 {
233 QQmlRefPointer<QV4::ExecutableCompilationUnit> unit
234 = QV4::ExecutableCompilationUnit::create();
235 return unit->loadFromDisk(url: QUrl::fromLocalFile(localfile: testFilePath),
236 sourceTimeStamp: QFileInfo(testFilePath).lastModified(), errorString: &lastErrorString)
237 ? quintptr(unit->unitData())
238 : 0;
239 }
240
241
242 void closeMapping()
243 {
244 if (currentMapping) {
245 mappedFile.unmap(address: currentMapping);
246 currentMapping = nullptr;
247 }
248 mappedFile.close();
249 }
250
251 void clearCache(const QString &fileName = QString())
252 {
253 const QString path = fileName.isEmpty() ? testFilePath : tempDir.path() + "/" + fileName;
254 closeMapping();
255 QFile::remove(fileName: path);
256 }
257
258 QQmlEngine *engine;
259 const QTemporaryDir tempDir;
260 QString testFilePath;
261 QString cacheFilePath;
262 QString lastErrorString;
263 QFile mappedFile;
264 uchar *currentMapping;
265};
266
267void tst_qmldiskcache::initTestCase()
268{
269 qputenv(varName: "QML_FORCE_DISK_CACHE", value: "1");
270 QStandardPaths::setTestModeEnabled(true);
271
272 const QString cacheDirectory = QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation);
273 m_qmlCacheDirectory.setPath(cacheDirectory + QLatin1String("/qmlcache"));
274 if (m_qmlCacheDirectory.exists())
275 QVERIFY(m_qmlCacheDirectory.removeRecursively());
276 QVERIFY(QDir::root().mkpath(m_qmlCacheDirectory.absolutePath()));
277}
278
279void tst_qmldiskcache::cleanupTestCase()
280{
281 m_qmlCacheDirectory.removeRecursively();
282}
283
284void tst_qmldiskcache::loadLocalAsFallback()
285{
286 QQmlEngine engine;
287 TestCompiler testCompiler(&engine);
288
289 QVERIFY(testCompiler.tempDir.isValid());
290
291 const QByteArray contents = QByteArrayLiteral("import QtQml 2.0\n"
292 "QtObject {\n"
293 " property string blah: Qt.platform;\n"
294 "}");
295
296 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
297
298 // Create an invalid side-by-side .qmlc
299 {
300 QFile f(testCompiler.tempDir.path() + "/test.qmlc");
301 QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate));
302 QV4::CompiledData::Unit unit = {};
303 memcpy(dest: unit.magic, src: QV4::CompiledData::magic_str, n: sizeof(unit.magic));
304 unit.version = QV4_DATA_STRUCTURE_VERSION;
305 unit.qtVersion = QT_VERSION;
306 unit.sourceTimeStamp = testCompiler.mappedFile.fileTime(time: QFile::FileModificationTime).toMSecsSinceEpoch();
307 unit.unitSize = ~0U; // make the size a silly number
308 // write something to the library hash that should cause it not to be loaded
309 memset(s: unit.libraryVersionHash, c: 'z', n: sizeof(unit.libraryVersionHash));
310 memset(s: unit.md5Checksum, c: 0, n: sizeof(unit.md5Checksum));
311
312 // leave the other fields unset, since they don't matter
313
314 f.write(data: reinterpret_cast<const char *>(&unit), len: sizeof(unit));
315 }
316
317 QQmlRefPointer<QV4::ExecutableCompilationUnit> unit = QV4::ExecutableCompilationUnit::create();
318 bool loaded = unit->loadFromDisk(url: QUrl::fromLocalFile(localfile: testCompiler.testFilePath),
319 sourceTimeStamp: QFileInfo(testCompiler.testFilePath).lastModified(),
320 errorString: &testCompiler.lastErrorString);
321 QVERIFY2(loaded, qPrintable(testCompiler.lastErrorString));
322 QCOMPARE(unit->objectCount(), 1);
323}
324
325void tst_qmldiskcache::regenerateAfterChange()
326{
327 QQmlEngine engine;
328 TestCompiler testCompiler(&engine);
329
330 QVERIFY(testCompiler.tempDir.isValid());
331
332 const QByteArray contents = QByteArrayLiteral("import QtQml 2.0\n"
333 "QtObject {\n"
334 " property string blah: Qt.platform;\n"
335 "}");
336
337 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
338
339 {
340 const QV4::CompiledData::Unit *testUnit = testCompiler.mapUnit();
341 QVERIFY2(testUnit, qPrintable(testCompiler.lastErrorString));
342
343 const QV4::CompiledData::QmlUnit *qmlUnit = testUnit->qmlUnit();
344
345 QCOMPARE(quint32(qmlUnit->nObjects), quint32(1));
346
347 const QV4::CompiledData::Object *obj = qmlUnit->objectAt(idx: 0);
348 QCOMPARE(quint32(obj->nBindings), quint32(1));
349 QCOMPARE(quint32(obj->bindingTable()->type), quint32(QV4::CompiledData::Binding::Type_Script));
350 QCOMPARE(quint32(obj->bindingTable()->value.compiledScriptIndex), quint32(0));
351
352 QCOMPARE(quint32(testUnit->functionTableSize), quint32(1));
353
354 const QV4::CompiledData::Function *bindingFunction = testUnit->functionAt(idx: 0);
355 QCOMPARE(testUnit->stringAtInternal(bindingFunction->nameIndex), QString("expression for blah")); // check if we have the correct function
356 QVERIFY(bindingFunction->codeSize > 0);
357 QVERIFY(bindingFunction->codeOffset < testUnit->unitSize);
358 }
359
360 {
361 const QByteArray newContents = QByteArrayLiteral("import QtQml 2.0\n"
362 "QtObject {\n"
363 " property string blah: Qt.platform;\n"
364 " property int secondProperty: 42;\n"
365 "}");
366
367 QVERIFY2(testCompiler.compile(newContents), qPrintable(testCompiler.lastErrorString));
368 const QV4::CompiledData::Unit *testUnit = testCompiler.mapUnit();
369 QVERIFY2(testUnit, qPrintable(testCompiler.lastErrorString));
370
371 const QV4::CompiledData::QmlUnit *qmlUnit = testUnit->qmlUnit();
372
373 QCOMPARE(quint32(qmlUnit->nObjects), quint32(1));
374
375 const QV4::CompiledData::Object *obj = qmlUnit->objectAt(idx: 0);
376 QCOMPARE(quint32(obj->nBindings), quint32(2));
377 QCOMPARE(quint32(obj->bindingTable()->type), quint32(QV4::CompiledData::Binding::Type_Number));
378
379 QCOMPARE(reinterpret_cast<const QV4::Value *>(testUnit->constants())
380 [obj->bindingTable()->value.constantValueIndex].doubleValue(),
381 double(42));
382
383 QCOMPARE(quint32(testUnit->functionTableSize), quint32(1));
384
385 const QV4::CompiledData::Function *bindingFunction = testUnit->functionAt(idx: 0);
386 QCOMPARE(testUnit->stringAtInternal(bindingFunction->nameIndex), QString("expression for blah")); // check if we have the correct function
387 QVERIFY(bindingFunction->codeSize > 0);
388 QVERIFY(bindingFunction->codeOffset < testUnit->unitSize);
389 }
390}
391
392void tst_qmldiskcache::registerImportForImplicitComponent()
393{
394 QQmlEngine engine;
395
396 TestCompiler testCompiler(&engine);
397 QVERIFY(testCompiler.tempDir.isValid());
398
399 const QByteArray contents = QByteArrayLiteral("import QtQuick 2.0\n"
400 "Loader {\n"
401 " sourceComponent: Item {}\n"
402 "}");
403
404 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
405 {
406 const QV4::CompiledData::Unit *testUnit = testCompiler.mapUnit();
407 QVERIFY2(testUnit, qPrintable(testCompiler.lastErrorString));
408
409 const QV4::CompiledData::QmlUnit *qmlUnit = testUnit->qmlUnit();
410 QCOMPARE(quint32(qmlUnit->nImports), quint32(2));
411 QCOMPARE(testUnit->stringAtInternal(qmlUnit->importAt(0)->uriIndex), QStringLiteral("QtQuick"));
412
413 QQmlType componentType = QQmlMetaType::qmlType(&QQmlComponent::staticMetaObject);
414
415 QCOMPARE(testUnit->stringAtInternal(qmlUnit->importAt(1)->uriIndex), QString(componentType.module()));
416 QCOMPARE(testUnit->stringAtInternal(qmlUnit->importAt(1)->qualifierIndex), QStringLiteral("QmlInternals"));
417
418 QCOMPARE(quint32(qmlUnit->nObjects), quint32(3));
419
420 const QV4::CompiledData::Object *obj = qmlUnit->objectAt(idx: 0);
421 QCOMPARE(quint32(obj->nBindings), quint32(1));
422 QCOMPARE(quint32(obj->bindingTable()->type), quint32(QV4::CompiledData::Binding::Type_Object));
423
424 const QV4::CompiledData::Object *implicitComponent = qmlUnit->objectAt(idx: obj->bindingTable()->value.objectIndex);
425 QCOMPARE(testUnit->stringAtInternal(implicitComponent->inheritedTypeNameIndex), QStringLiteral("QmlInternals.") + componentType.elementName());
426 }
427}
428
429void tst_qmldiskcache::basicVersionChecks()
430{
431 QQmlEngine engine;
432
433 TestCompiler testCompiler(&engine);
434 QVERIFY(testCompiler.tempDir.isValid());
435
436 const QByteArray contents = QByteArrayLiteral("import QtQml 2.0\n"
437 "QtObject {\n"
438 " property string blah: Qt.platform;\n"
439 "}");
440
441 {
442 testCompiler.clearCache();
443 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
444 QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString));
445 }
446
447 {
448 testCompiler.clearCache();
449 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
450
451 const QString qtVersionFile = QStringLiteral("qtversion.qml");
452 QVERIFY(testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
453 header->qtVersion = 0;
454 }, qtVersionFile));
455
456 QVERIFY(!testCompiler.verify(qtVersionFile));
457 QCOMPARE(testCompiler.lastErrorString, QString::fromUtf8("Qt version mismatch. Found 0 expected %1").arg(QT_VERSION, 0, 16));
458 testCompiler.clearCache(fileName: qtVersionFile);
459 }
460
461 {
462 testCompiler.clearCache();
463 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
464
465 const QString versionFile = QStringLiteral("version.qml");
466 QVERIFY(testCompiler.tweakHeader([](QV4::CompiledData::Unit *header) {
467 header->version = 0;
468 }, versionFile));
469
470 QVERIFY(!testCompiler.verify(versionFile));
471 QCOMPARE(testCompiler.lastErrorString, QString::fromUtf8("V4 data structure version mismatch. Found 0 expected %1").arg(QV4_DATA_STRUCTURE_VERSION, 0, 16));
472 testCompiler.clearCache(fileName: versionFile);
473 }
474}
475
476class TypeVersion1 : public QObject
477{
478 Q_OBJECT
479 Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged);
480public:
481
482
483 int m_value = 0;
484 int value() const { return m_value; }
485 void setValue(int v) { m_value = v; emit valueChanged(); }
486
487signals:
488 void valueChanged();
489};
490
491// Same as TypeVersion1 except the property type changed!
492class TypeVersion2 : public QObject
493{
494 Q_OBJECT
495 Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged);
496public:
497
498
499 QString m_value;
500 QString value() const { return m_value; }
501 void setValue(QString v) { m_value = v; emit valueChanged(); }
502
503signals:
504 void valueChanged();
505};
506
507void tst_qmldiskcache::recompileAfterChange()
508{
509 QQmlEngine engine;
510
511 TestCompiler testCompiler(&engine);
512 QVERIFY(testCompiler.tempDir.isValid());
513
514 const QByteArray contents = QByteArrayLiteral("import TypeTest 1.0\n"
515 "TypeThatWillChange {\n"
516 "}");
517
518 qmlRegisterType<TypeVersion1>(uri: "TypeTest", versionMajor: 1, versionMinor: 0, qmlName: "TypeThatWillChange");
519
520 {
521 testCompiler.clearCache();
522 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
523 QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString));
524 }
525
526 QDateTime initialCacheTimeStamp = QFileInfo(testCompiler.cacheFilePath).lastModified();
527
528 {
529 CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
530 QScopedPointer<TypeVersion1> obj(qobject_cast<TypeVersion1*>(object: component.create()));
531 QVERIFY(!obj.isNull());
532 QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), initialCacheTimeStamp);
533 }
534
535 engine.clearComponentCache();
536
537 {
538 CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
539 QScopedPointer<TypeVersion1> obj(qobject_cast<TypeVersion1*>(object: component.create()));
540 QVERIFY(!obj.isNull());
541 QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), initialCacheTimeStamp);
542 }
543
544 engine.clearComponentCache();
545 qmlClearTypeRegistrations();
546 qmlRegisterType<TypeVersion2>(uri: "TypeTest", versionMajor: 1, versionMinor: 0, qmlName: "TypeThatWillChange");
547
548 waitForFileSystem();
549
550 {
551 CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
552 QScopedPointer<TypeVersion2> obj(qobject_cast<TypeVersion2*>(object: component.create()));
553 QVERIFY(!obj.isNull());
554 QVERIFY(QFileInfo(testCompiler.cacheFilePath).lastModified() > initialCacheTimeStamp);
555 }
556}
557
558void tst_qmldiskcache::recompileAfterDirectoryChange()
559{
560 QQmlEngine engine;
561 TestCompiler testCompiler(&engine);
562
563 QVERIFY(testCompiler.tempDir.isValid());
564
565 QVERIFY(QDir(testCompiler.tempDir.path()).mkdir("source1"));
566 testCompiler.init(baseDirectory: testCompiler.tempDir.path() + QLatin1String("/source1"));
567
568 {
569 const QByteArray contents = QByteArrayLiteral("import QtQml 2.0\n"
570 "QtObject {\n"
571 " property int blah: 42;\n"
572 "}");
573
574 testCompiler.clearCache();
575 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
576 QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString));
577 const QV4::CompiledData::Unit *unit = testCompiler.mapUnit();
578 QVERIFY(unit->sourceFileIndex != 0);
579 const QString expectedPath = QUrl::fromLocalFile(localfile: testCompiler.testFilePath).toString();
580 QCOMPARE(unit->stringAtInternal(unit->sourceFileIndex), expectedPath);
581 testCompiler.closeMapping();
582 }
583
584 const QDateTime initialCacheTimeStamp = QFileInfo(testCompiler.cacheFilePath).lastModified();
585
586 QDir(testCompiler.tempDir.path()).rename(QStringLiteral("source1"), QStringLiteral("source2"));
587 waitForFileSystem();
588
589 testCompiler.init(baseDirectory: testCompiler.tempDir.path() + QLatin1String("/source2"));
590
591 {
592 CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
593 QScopedPointer<QObject> obj(component.create());
594 QVERIFY(!obj.isNull());
595 QCOMPARE(obj->property("blah").toInt(), 42);
596 }
597
598 QFile cacheFile(testCompiler.cacheFilePath);
599 QVERIFY2(cacheFile.exists(), qPrintable(cacheFile.fileName()));
600 QVERIFY(QFileInfo(testCompiler.cacheFilePath).lastModified() > initialCacheTimeStamp);
601}
602
603void tst_qmldiskcache::fileSelectors()
604{
605 QQmlEngine engine;
606
607 QTemporaryDir tempDir;
608 QVERIFY(tempDir.isValid());
609
610 const QString testFilePath = tempDir.path() + "/test.qml";
611 {
612 QFile f(testFilePath);
613 QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
614 f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 42 }"));
615 }
616
617 const QString selector = QStringLiteral("testSelector");
618 const QString selectorPath = tempDir.path() + "/+" + selector;
619 const QString selectedTestFilePath = selectorPath + "/test.qml";
620 {
621 QVERIFY(QDir::root().mkpath(selectorPath));
622 QFile f(selectorPath + "/test.qml");
623 QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
624 f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 100 }"));
625 }
626
627 {
628 QQmlComponent component(&engine, testFilePath);
629 QScopedPointer<QObject> obj(component.create());
630 QVERIFY(!obj.isNull());
631 QCOMPARE(obj->property("value").toInt(), 42);
632
633 QFile cacheFile(QV4::ExecutableCompilationUnit::localCacheFilePath(
634 url: QUrl::fromLocalFile(localfile: testFilePath)));
635 QVERIFY2(cacheFile.exists(), qPrintable(cacheFile.fileName()));
636 }
637
638 QQmlFileSelector qmlSelector(&engine);
639 qmlSelector.setExtraSelectors(QStringList() << selector);
640
641 engine.clearComponentCache();
642
643 {
644 QQmlComponent component(&engine, testFilePath);
645 QScopedPointer<QObject> obj(component.create());
646 QVERIFY(!obj.isNull());
647 QCOMPARE(obj->property("value").toInt(), 100);
648
649 QFile cacheFile(QV4::ExecutableCompilationUnit::localCacheFilePath(
650 url: QUrl::fromLocalFile(localfile: selectedTestFilePath)));
651 QVERIFY2(cacheFile.exists(), qPrintable(cacheFile.fileName()));
652 }
653}
654
655void tst_qmldiskcache::localAliases()
656{
657 QQmlEngine engine;
658
659 TestCompiler testCompiler(&engine);
660 QVERIFY(testCompiler.tempDir.isValid());
661
662 const QByteArray contents = QByteArrayLiteral("import QtQml 2.0\n"
663 "QtObject {\n"
664 " id: root\n"
665 " property int prop: 100\n"
666 " property alias dummy1: root.prop\n"
667 " property alias dummy2: root.prop\n"
668 " property alias dummy3: root.prop\n"
669 " property alias dummy4: root.prop\n"
670 " property alias dummy5: root.prop\n"
671 " property alias foo: root.prop\n"
672 " property alias bar: root.foo\n"
673 "}");
674
675 {
676 testCompiler.clearCache();
677 QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString));
678 QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString));
679 }
680
681 {
682 CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
683 QScopedPointer<QObject> obj(component.create());
684 QVERIFY(!obj.isNull());
685 QCOMPARE(obj->property("bar").toInt(), 100);
686 }
687
688 engine.clearComponentCache();
689
690 {
691 CleanlyLoadingComponent component(&engine, testCompiler.testFilePath);
692 QScopedPointer<QObject> obj(component.create());
693 QVERIFY(!obj.isNull());
694 QCOMPARE(obj->property("bar").toInt(), 100);
695 }
696}
697
698static QSet<QString> entrySet(const QDir &dir)
699{
700 const auto &list = dir.entryList(filters: QDir::Files | QDir::NoDotAndDotDot);
701 return QSet<QString>(list.cbegin(), list.cend());
702}
703
704static QSet<QString> entrySet(const QDir &dir, const QStringList &filters)
705{
706 const auto &list = dir.entryList(nameFilters: filters);
707 return QSet<QString>(list.cbegin(), list.cend());
708}
709
710void tst_qmldiskcache::cacheResources()
711{
712 const QSet<QString> existingFiles = entrySet(dir: m_qmlCacheDirectory);
713
714 QQmlEngine engine;
715
716 {
717 CleanlyLoadingComponent component(&engine, QUrl("qrc:/test.qml"));
718 QScopedPointer<QObject> obj(component.create());
719 QVERIFY(!obj.isNull());
720 QCOMPARE(obj->property("value").toInt(), 20);
721 }
722
723 const QSet<QString> entries = entrySet(dir: m_qmlCacheDirectory).subtract(other: existingFiles);
724 QCOMPARE(entries.count(), 1);
725
726 QDateTime cacheFileTimeStamp;
727
728 {
729 QFile cacheFile(m_qmlCacheDirectory.absoluteFilePath(fileName: *entries.cbegin()));
730 QVERIFY2(cacheFile.open(QIODevice::ReadOnly), qPrintable(cacheFile.errorString()));
731 QV4::CompiledData::Unit unit;
732 QVERIFY(cacheFile.read(reinterpret_cast<char *>(&unit), sizeof(unit)) == sizeof(unit));
733
734 cacheFileTimeStamp = QFileInfo(cacheFile.fileName()).lastModified();
735
736 QDateTime referenceTimeStamp = QFileInfo(":/test.qml").lastModified();
737 if (!referenceTimeStamp.isValid())
738 referenceTimeStamp = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
739 QCOMPARE(qint64(unit.sourceTimeStamp), referenceTimeStamp.toMSecsSinceEpoch());
740 }
741
742 waitForFileSystem();
743
744 {
745 CleanlyLoadingComponent component(&engine, QUrl("qrc:///test.qml"));
746 QScopedPointer<QObject> obj(component.create());
747 QVERIFY(!obj.isNull());
748 QCOMPARE(obj->property("value").toInt(), 20);
749 }
750
751 {
752 const QSet<QString> entries = entrySet(dir: m_qmlCacheDirectory).subtract(other: existingFiles);
753 QCOMPARE(entries.count(), 1);
754
755 QCOMPARE(QFileInfo(m_qmlCacheDirectory.absoluteFilePath(*entries.cbegin())).lastModified().toMSecsSinceEpoch(),
756 cacheFileTimeStamp.toMSecsSinceEpoch());
757 }
758}
759
760void tst_qmldiskcache::stableOrderOfDependentCompositeTypes()
761{
762 QQmlEngine engine;
763
764 QTemporaryDir tempDir;
765 QVERIFY(tempDir.isValid());
766
767 {
768 const QString depFilePath = tempDir.path() + "/FirstDependentType.qml";
769 QFile f(depFilePath);
770 QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
771 f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 42 }"));
772 }
773
774 {
775 const QString depFilePath = tempDir.path() + "/SecondDependentType.qml";
776 QFile f(depFilePath);
777 QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
778 f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 100 }"));
779 }
780
781 const QString testFilePath = tempDir.path() + "/main.qml";
782 {
783 QFile f(testFilePath);
784 QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
785 f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject {\n"
786 " property QtObject dep1: FirstDependentType{}\n"
787 " property QtObject dep2: SecondDependentType{}\n"
788 " property int value: dep1.value + dep2.value\n"
789 "}"));
790 }
791
792 QByteArray firstDependentTypeClassName;
793 QByteArray secondDependentTypeClassName;
794
795 {
796 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
797 QScopedPointer<QObject> obj(component.create());
798 QVERIFY(!obj.isNull());
799 QCOMPARE(obj->property("value").toInt(), 142);
800
801 firstDependentTypeClassName = qvariant_cast<QObject *>(v: obj->property(name: "dep1"))->metaObject()->className();
802 secondDependentTypeClassName = qvariant_cast<QObject *>(v: obj->property(name: "dep2"))->metaObject()->className();
803 }
804
805 QVERIFY(firstDependentTypeClassName != secondDependentTypeClassName);
806 QVERIFY2(firstDependentTypeClassName.contains("QMLTYPE"), firstDependentTypeClassName.constData());
807 QVERIFY2(secondDependentTypeClassName.contains("QMLTYPE"), secondDependentTypeClassName.constData());
808
809 const QString testFileCachePath = QV4::ExecutableCompilationUnit::localCacheFilePath(
810 url: QUrl::fromLocalFile(localfile: testFilePath));
811 QVERIFY(QFile::exists(testFileCachePath));
812 QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
813
814 engine.clearComponentCache();
815 waitForFileSystem();
816
817 // Creating the test component a second time should load it from the cache (same time stamp),
818 // despite the class names of the dependent composite types differing.
819 {
820 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
821 QScopedPointer<QObject> obj(component.create());
822 QVERIFY(!obj.isNull());
823 QCOMPARE(obj->property("value").toInt(), 142);
824
825 QVERIFY(qvariant_cast<QObject *>(obj->property("dep1"))->metaObject()->className() != firstDependentTypeClassName);
826 QVERIFY(qvariant_cast<QObject *>(obj->property("dep2"))->metaObject()->className() != secondDependentTypeClassName);
827 }
828
829 {
830 QVERIFY(QFile::exists(testFileCachePath));
831 QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
832 QCOMPARE(newCacheTimeStamp, initialCacheTimeStamp);
833 }
834
835 // Now change the first dependent QML type and see if we correctly re-generate the
836 // caches.
837 engine.clearComponentCache();
838 waitForFileSystem();
839 {
840 const QString depFilePath = tempDir.path() + "/FirstDependentType.qml";
841 QFile f(depFilePath);
842 QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString()));
843 f.write(QByteArrayLiteral("import QtQml 2.0\nQtObject { property int value: 40 }"));
844 }
845
846 {
847 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
848 QScopedPointer<QObject> obj(component.create());
849 QVERIFY(!obj.isNull());
850 QCOMPARE(obj->property("value").toInt(), 140);
851 }
852
853 {
854 QVERIFY(QFile::exists(testFileCachePath));
855 QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
856 QVERIFY2(newCacheTimeStamp > initialCacheTimeStamp, qPrintable(newCacheTimeStamp.toString()));
857 }
858}
859
860void tst_qmldiskcache::singletonDependency()
861{
862 QScopedPointer<QQmlEngine> engine(new QQmlEngine);
863
864 QTemporaryDir tempDir;
865 QVERIFY(tempDir.isValid());
866
867 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
868 QFile f(tempDir.path() + '/' + fileName);
869 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
870 Q_ASSERT(ok);
871 f.write(data: contents);
872 return f.fileName();
873 };
874
875 writeTempFile("MySingleton.qml", "import QtQml 2.0\npragma Singleton\nQtObject { property int value: 42 }");
876 writeTempFile("qmldir", "singleton MySingleton 1.0 MySingleton.qml");
877 const QString testFilePath = writeTempFile("main.qml", "import QtQml 2.0\nimport \".\"\nQtObject {\n"
878 " property int value: MySingleton.value\n"
879 "}");
880
881 {
882 CleanlyLoadingComponent component(engine.data(), QUrl::fromLocalFile(localfile: testFilePath));
883 QScopedPointer<QObject> obj(component.create());
884 QVERIFY(!obj.isNull());
885 QCOMPARE(obj->property("value").toInt(), 42);
886 }
887
888 const QString testFileCachePath = QV4::ExecutableCompilationUnit::localCacheFilePath(
889 url: QUrl::fromLocalFile(localfile: testFilePath));
890 QVERIFY(QFile::exists(testFileCachePath));
891 QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
892
893 engine.reset(other: new QQmlEngine);
894 waitForFileSystem();
895
896 writeTempFile("MySingleton.qml", "import QtQml 2.0\npragma Singleton\nQtObject { property int value: 100 }");
897 waitForFileSystem();
898
899 {
900 CleanlyLoadingComponent component(engine.data(), QUrl::fromLocalFile(localfile: testFilePath));
901 QScopedPointer<QObject> obj(component.create());
902 QVERIFY(!obj.isNull());
903 QCOMPARE(obj->property("value").toInt(), 100);
904 }
905
906 {
907 QVERIFY(QFile::exists(testFileCachePath));
908 QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
909 QVERIFY2(newCacheTimeStamp > initialCacheTimeStamp, qPrintable(newCacheTimeStamp.toString()));
910 }
911}
912
913void tst_qmldiskcache::cppRegisteredSingletonDependency()
914{
915 qmlClearTypeRegistrations();
916 QScopedPointer<QQmlEngine> engine(new QQmlEngine);
917
918 QTemporaryDir tempDir;
919 QVERIFY(tempDir.isValid());
920
921 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
922 QFile f(tempDir.path() + '/' + fileName);
923 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
924 Q_ASSERT(ok);
925 f.write(data: contents);
926 return f.fileName();
927 };
928
929 writeTempFile("MySingleton.qml", "import QtQml 2.0\npragma Singleton\nQtObject { property int value: 42 }");
930
931 qmlRegisterSingletonType(url: QUrl::fromLocalFile(localfile: tempDir.path() + QLatin1String("/MySingleton.qml")), uri: "CppRegisteredSingletonDependency", versionMajor: 1, versionMinor: 0, qmlName: "Singly");
932
933 const QString testFilePath = writeTempFile("main.qml", "import QtQml 2.0\nimport CppRegisteredSingletonDependency 1.0\nQtObject {\n"
934 " function getValue() { return Singly.value; }\n"
935 "}");
936
937 {
938 CleanlyLoadingComponent component(engine.data(), QUrl::fromLocalFile(localfile: testFilePath));
939 QScopedPointer<QObject> obj(component.create());
940 QVERIFY(!obj.isNull());
941 QVariant value;
942 QVERIFY(QMetaObject::invokeMethod(obj.data(), "getValue", Q_RETURN_ARG(QVariant, value)));
943 QCOMPARE(value.toInt(), 42);
944 }
945
946 const QString testFileCachePath = QV4::ExecutableCompilationUnit::localCacheFilePath(
947 url: QUrl::fromLocalFile(localfile: testFilePath));
948 QVERIFY(QFile::exists(testFileCachePath));
949 QDateTime initialCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
950
951 engine.reset(other: new QQmlEngine);
952 waitForFileSystem();
953
954 writeTempFile("MySingleton.qml", "import QtQml 2.0\npragma Singleton\nQtObject { property int value: 100 }");
955 waitForFileSystem();
956
957 {
958 CleanlyLoadingComponent component(engine.data(), QUrl::fromLocalFile(localfile: testFilePath));
959 QScopedPointer<QObject> obj(component.create());
960 QVERIFY(!obj.isNull());
961
962 {
963 QVERIFY(QFile::exists(testFileCachePath));
964 QDateTime newCacheTimeStamp = QFileInfo(testFileCachePath).lastModified();
965 QVERIFY2(newCacheTimeStamp > initialCacheTimeStamp, qPrintable(newCacheTimeStamp.toString()));
966 }
967
968 QVariant value;
969 QVERIFY(QMetaObject::invokeMethod(obj.data(), "getValue", Q_RETURN_ARG(QVariant, value)));
970 QCOMPARE(value.toInt(), 100);
971 }
972}
973
974void tst_qmldiskcache::cacheModuleScripts()
975{
976 const QSet<QString> existingFiles = entrySet(dir: m_qmlCacheDirectory);
977
978 QQmlEngine engine;
979
980 {
981 CleanlyLoadingComponent component(&engine, QUrl("qrc:/importmodule.qml"));
982 QScopedPointer<QObject> obj(component.create());
983 QVERIFY(!obj.isNull());
984 QVERIFY(obj->property("ok").toBool());
985
986 auto componentPrivate = QQmlComponentPrivate::get(c: &component);
987 QVERIFY(componentPrivate);
988 auto compilationUnit = componentPrivate->compilationUnit->dependentScripts.first()->compilationUnit();
989 QVERIFY(compilationUnit);
990 auto unitData = compilationUnit->unitData();
991 QVERIFY(unitData);
992 QVERIFY(unitData->flags & QV4::CompiledData::Unit::StaticData);
993 QVERIFY(unitData->flags & QV4::CompiledData::Unit::IsESModule);
994 QVERIFY(!compilationUnit->backingFile.isNull());
995 }
996
997 const QSet<QString> entries = entrySet(dir: m_qmlCacheDirectory, filters: QStringList("*.mjsc"));
998
999 QCOMPARE(entries.count(), 1);
1000
1001 QDateTime cacheFileTimeStamp;
1002
1003 {
1004 QFile cacheFile(m_qmlCacheDirectory.absoluteFilePath(fileName: *entries.cbegin()));
1005 QVERIFY2(cacheFile.open(QIODevice::ReadOnly), qPrintable(cacheFile.errorString()));
1006 QV4::CompiledData::Unit unit;
1007 QVERIFY(cacheFile.read(reinterpret_cast<char *>(&unit), sizeof(unit)) == sizeof(unit));
1008
1009 QVERIFY(unit.flags & QV4::CompiledData::Unit::IsESModule);
1010 }
1011}
1012
1013void tst_qmldiskcache::reuseStaticMappings()
1014{
1015 QQmlEngine engine;
1016
1017 TestCompiler testCompiler(&engine);
1018 QVERIFY(testCompiler.tempDir.isValid());
1019
1020 QVERIFY2(testCompiler.compile("import QtQml 2.15\nQtObject { objectName: 'foobar' }\n"),
1021 qPrintable(testCompiler.lastErrorString));
1022
1023 const quintptr data1 = testCompiler.unitData();
1024 QVERIFY(data1 != 0);
1025 QCOMPARE(testCompiler.unitData(), data1);
1026
1027 testCompiler.reset();
1028 QVERIFY(testCompiler.loadTestFile());
1029
1030 QCOMPARE(testCompiler.unitData(), data1);
1031}
1032
1033QTEST_MAIN(tst_qmldiskcache)
1034
1035#include "tst_qmldiskcache.moc"
1036

source code of qtdeclarative/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp