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 <QQmlComponent>
32#include <QQmlEngine>
33#include <QProcess>
34#include <QLibraryInfo>
35#include <QStandardPaths>
36#include <QSysInfo>
37#include <QLoggingCategory>
38#include <private/qqmlcomponent_p.h>
39#include <private/qqmlscriptdata_p.h>
40#include <private/qv4compileddata_p.h>
41#include <qtranslator.h>
42
43#include "../../shared/util.h"
44
45class tst_qmlcachegen: public QQmlDataTest
46{
47 Q_OBJECT
48
49private slots:
50 void initTestCase();
51
52 void loadGeneratedFile();
53 void translationExpressionSupport();
54 void signalHandlerParameters();
55 void errorOnArgumentsInSignalHandler();
56 void aheadOfTimeCompilation();
57 void functionExpressions();
58 void versionChecksForAheadOfTimeUnits();
59 void retainedResources();
60 void skippedResources();
61
62 void workerScripts();
63
64 void trickyPaths_data();
65 void trickyPaths();
66
67 void qrcScriptImport();
68 void fsScriptImport();
69 void moduleScriptImport();
70 void esModulesViaQJSEngine();
71
72 void enums();
73
74 void sourceFileIndices();
75
76 void reproducibleCache_data();
77 void reproducibleCache();
78
79 void parameterAdjustment();
80 void inlineComponent();
81 void posthocRequired();
82
83 void saveableUnitPointer();
84};
85
86// A wrapper around QQmlComponent to ensure the temporary reference counts
87// on the type data as a result of the main thread <> loader thread communication
88// are dropped. Regular Synchronous loading will leave us with an event posted
89// to the gui thread and an extra refcount that will only be dropped after the
90// event delivery. A plain sendPostedEvents() however is insufficient because
91// we can't be sure that the event is posted after the constructor finished.
92class CleanlyLoadingComponent : public QQmlComponent
93{
94public:
95 CleanlyLoadingComponent(QQmlEngine *engine, const QUrl &url)
96 : QQmlComponent(engine, url, QQmlComponent::Asynchronous)
97 { waitForLoad(); }
98 CleanlyLoadingComponent(QQmlEngine *engine, const QString &fileName)
99 : QQmlComponent(engine, fileName, QQmlComponent::Asynchronous)
100 { waitForLoad(); }
101
102 void waitForLoad()
103 {
104 QTRY_VERIFY(status() == QQmlComponent::Ready || status() == QQmlComponent::Error);
105 }
106};
107
108static bool generateCache(const QString &qmlFileName, QByteArray *capturedStderr = nullptr)
109{
110 QProcess proc;
111 if (capturedStderr == nullptr)
112 proc.setProcessChannelMode(QProcess::ForwardedChannels);
113 proc.setProgram(QLibraryInfo::location(QLibraryInfo::BinariesPath) + QDir::separator() + QLatin1String("qmlcachegen"));
114 proc.setArguments(QStringList() << qmlFileName);
115 proc.start();
116 if (!proc.waitForFinished())
117 return false;
118
119 if (capturedStderr)
120 *capturedStderr = proc.readAllStandardError();
121
122 if (proc.exitStatus() != QProcess::NormalExit)
123 return false;
124 return proc.exitCode() == 0;
125}
126
127void tst_qmlcachegen::initTestCase()
128{
129 qputenv(varName: "QML_FORCE_DISK_CACHE", value: "1");
130 QStandardPaths::setTestModeEnabled(true);
131
132 // make sure there's no pre-existing cache dir
133 QString cacheDir = QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation);
134 if (!cacheDir.isEmpty())
135 //QDir(cacheDir).removeRecursively();
136 qDebug() << cacheDir;
137 QQmlDataTest::initTestCase();
138}
139
140void tst_qmlcachegen::loadGeneratedFile()
141{
142 QTemporaryDir tempDir;
143 QVERIFY(tempDir.isValid());
144
145 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
146 QFile f(tempDir.path() + '/' + fileName);
147 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
148 Q_ASSERT(ok);
149 f.write(data: contents);
150 return f.fileName();
151 };
152
153 const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.0\n"
154 "QtObject {\n"
155 " property int value: Math.min(100, 42);\n"
156 "}");
157
158 QVERIFY(generateCache(testFilePath));
159
160 const QString cacheFilePath = testFilePath + QLatin1Char('c');
161 QVERIFY(QFile::exists(cacheFilePath));
162
163 {
164 QFile cache(cacheFilePath);
165 QVERIFY(cache.open(QIODevice::ReadOnly));
166 const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, size: sizeof(QV4::CompiledData::Unit)));
167 QVERIFY(cacheUnit);
168 QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::StaticData);
169 QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::PendingTypeCompilation);
170 QCOMPARE(uint(cacheUnit->sourceFileIndex), uint(0));
171 }
172
173 QVERIFY(QFile::remove(testFilePath));
174
175 QQmlEngine engine;
176 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
177 QScopedPointer<QObject> obj(component.create());
178 QVERIFY(!obj.isNull());
179 QCOMPARE(obj->property("value").toInt(), 42);
180
181 auto componentPrivate = QQmlComponentPrivate::get(c: &component);
182 QVERIFY(componentPrivate);
183 auto compilationUnit = componentPrivate->compilationUnit;
184 QVERIFY(compilationUnit);
185 auto unitData = compilationUnit->unitData();
186 QVERIFY(unitData);
187 QVERIFY(unitData->flags & QV4::CompiledData::Unit::StaticData);
188}
189
190class QTestTranslator : public QTranslator
191{
192public:
193 QString translate(const char *context, const char *sourceText, const char */*disambiguation*/, int /*n*/) const override
194 {
195 m_lastContext = QString::fromUtf8(str: context);
196 return QString::fromUtf8(str: sourceText).toUpper();
197 }
198 bool isEmpty() const override { return true; }
199 mutable QString m_lastContext;
200};
201
202void tst_qmlcachegen::translationExpressionSupport()
203{
204 QTemporaryDir tempDir;
205 QVERIFY(tempDir.isValid());
206
207 QTestTranslator translator;
208 qApp->installTranslator(messageFile: &translator);
209
210 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
211 QFile f(tempDir.path() + '/' + fileName);
212 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
213 Q_ASSERT(ok);
214 f.write(data: contents);
215 return f.fileName();
216 };
217
218 const QString testFilePath = writeTempFile("test.qml", "import QtQml.Models 2.2\n"
219 "import QtQml 2.2\n"
220 "QtObject {\n"
221 " property ListModel model: ListModel {\n"
222 " ListElement {\n"
223 " text: qsTr(\"All\")\n"
224 " }\n"
225 " ListElement {\n"
226 " text: QT_TR_NOOP(\"Ok\")\n"
227 " }\n"
228 " }\n"
229 " property string text: model.get(0).text + \" \" + model.get(1).text\n"
230 "}");
231
232
233 QVERIFY(generateCache(testFilePath));
234
235 const QString cacheFilePath = testFilePath + QLatin1Char('c');
236 QVERIFY(QFile::exists(cacheFilePath));
237 QVERIFY(QFile::remove(testFilePath));
238
239 QQmlEngine engine;
240 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
241 QScopedPointer<QObject> obj(component.create());
242 QVERIFY(!obj.isNull());
243 QCOMPARE(obj->property("text").toString(), QString("ALL Ok"));
244 QCOMPARE(translator.m_lastContext, QStringLiteral("test"));
245}
246
247void tst_qmlcachegen::signalHandlerParameters()
248{
249 QTemporaryDir tempDir;
250 QVERIFY(tempDir.isValid());
251
252 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
253 QFile f(tempDir.path() + '/' + fileName);
254 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
255 Q_ASSERT(ok);
256 f.write(data: contents);
257 return f.fileName();
258 };
259
260 const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.0\n"
261 "QtObject {\n"
262 " property real result: 0\n"
263 " signal testMe(real value);\n"
264 " onTestMe: result = value;\n"
265 " function runTest() { testMe(42); }\n"
266 "}");
267
268 QVERIFY(generateCache(testFilePath));
269
270 const QString cacheFilePath = testFilePath + QLatin1Char('c');
271 QVERIFY(QFile::exists(cacheFilePath));
272 QVERIFY(QFile::remove(testFilePath));
273
274 {
275 QFile cache(cacheFilePath);
276 QVERIFY(cache.open(QIODevice::ReadOnly));
277 const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, size: sizeof(QV4::CompiledData::Unit)));
278 QVERIFY(cacheUnit);
279 }
280
281 QQmlEngine engine;
282 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
283 QScopedPointer<QObject> obj(component.create());
284 QVERIFY(!obj.isNull());
285 QMetaObject::invokeMethod(obj: obj.data(), member: "runTest");
286 QCOMPARE(obj->property("result").toInt(), 42);
287
288 {
289 auto componentPrivate = QQmlComponentPrivate::get(c: &component);
290 QVERIFY(componentPrivate);
291 auto compilationUnit = componentPrivate->compilationUnit;
292 QVERIFY(compilationUnit);
293 QVERIFY(compilationUnit->unitData());
294
295 // Verify that the QML objects don't come from the original data.
296 QVERIFY(compilationUnit->objectAt(0) != compilationUnit->unitData()->qmlUnit()->objectAt(0));
297
298 // Typically the final file name is one of those strings that is not in the original
299 // pre-compiled qml file's string table, while for example the signal parameter
300 // name ("value") is.
301 const auto isStringIndexInStringTable = [compilationUnit](uint index) {
302 return index < compilationUnit->unitData()->stringTableSize;
303 };
304
305 QVERIFY(isStringIndexInStringTable(compilationUnit->objectAt(0)->signalAt(0)->parameterAt(0)->nameIndex));
306 QVERIFY(!compilationUnit->dynamicStrings.isEmpty());
307 }
308}
309
310void tst_qmlcachegen::errorOnArgumentsInSignalHandler()
311{
312 QTemporaryDir tempDir;
313 QVERIFY(tempDir.isValid());
314
315 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
316 QFile f(tempDir.path() + '/' + fileName);
317 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
318 Q_ASSERT(ok);
319 f.write(data: contents);
320 return f.fileName();
321 };
322
323 const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.2\n"
324 "QtObject {\n"
325 " signal mySignal(var arguments);\n"
326 " onMySignal: console.log(arguments);\n"
327 "}");
328
329
330 QByteArray errorOutput;
331 QVERIFY(!generateCache(testFilePath, &errorOutput));
332 QVERIFY2(errorOutput.contains("error: The use of eval() or the use of the arguments object in signal handlers is"), errorOutput);
333}
334
335void tst_qmlcachegen::aheadOfTimeCompilation()
336{
337 QTemporaryDir tempDir;
338 QVERIFY(tempDir.isValid());
339
340 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
341 QFile f(tempDir.path() + '/' + fileName);
342 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
343 Q_ASSERT(ok);
344 f.write(data: contents);
345 return f.fileName();
346 };
347
348 const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.0\n"
349 "QtObject {\n"
350 " function runTest() { var x = 0; while (x < 42) { ++x }; return x; }\n"
351 "}");
352
353 QVERIFY(generateCache(testFilePath));
354
355 const QString cacheFilePath = testFilePath + QLatin1Char('c');
356 QVERIFY(QFile::exists(cacheFilePath));
357 QVERIFY(QFile::remove(testFilePath));
358
359 QQmlEngine engine;
360 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
361 QScopedPointer<QObject> obj(component.create());
362 QVERIFY(!obj.isNull());
363 QVariant result;
364 QMetaObject::invokeMethod(obj: obj.data(), member: "runTest", Q_RETURN_ARG(QVariant, result));
365 QCOMPARE(result.toInt(), 42);
366}
367
368static QQmlPrivate::CachedQmlUnit *temporaryModifiedCachedUnit = nullptr;
369
370void tst_qmlcachegen::versionChecksForAheadOfTimeUnits()
371{
372 QVERIFY(QFile::exists(":/data/versionchecks.qml"));
373 QVERIFY(QFileInfo(":/data/versionchecks.qml").size() > 0);
374
375 Q_ASSERT(!temporaryModifiedCachedUnit);
376 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
377 const QV4::CompiledData::Unit *originalUnit = QQmlMetaType::findCachedCompilationUnit(
378 uri: QUrl("qrc:/data/versionchecks.qml"), status: &error);
379 QVERIFY(originalUnit);
380 QV4::CompiledData::Unit *tweakedUnit = (QV4::CompiledData::Unit *)malloc(size: originalUnit->unitSize);
381 memcpy(dest: reinterpret_cast<void *>(tweakedUnit), src: reinterpret_cast<const void *>(originalUnit), n: originalUnit->unitSize);
382 tweakedUnit->version = QV4_DATA_STRUCTURE_VERSION - 1;
383 temporaryModifiedCachedUnit = new QQmlPrivate::CachedQmlUnit{.qmlData: tweakedUnit, .unused1: nullptr, .unused2: nullptr};
384
385 auto testHandler = [](const QUrl &url) -> const QQmlPrivate::CachedQmlUnit * {
386 if (url == QUrl("qrc:/data/versionchecks.qml"))
387 return temporaryModifiedCachedUnit;
388 return nullptr;
389 };
390 QQmlMetaType::prependCachedUnitLookupFunction(handler: testHandler);
391
392 {
393 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
394 QVERIFY(!QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/data/versionchecks.qml"), &error));
395 QCOMPARE(error, QQmlMetaType::CachedUnitLookupError::VersionMismatch);
396 }
397
398 {
399 QQmlEngine engine;
400 CleanlyLoadingComponent component(&engine, QUrl("qrc:/data/versionchecks.qml"));
401 QCOMPARE(component.status(), QQmlComponent::Ready);
402 }
403
404 Q_ASSERT(temporaryModifiedCachedUnit);
405 free(ptr: const_cast<QV4::CompiledData::Unit *>(temporaryModifiedCachedUnit->qmlData));
406 delete temporaryModifiedCachedUnit;
407 temporaryModifiedCachedUnit = nullptr;
408
409 QQmlMetaType::removeCachedUnitLookupFunction(handler: testHandler);
410}
411
412void tst_qmlcachegen::retainedResources()
413{
414 QFile file(":/Retain.qml");
415 QVERIFY(file.open(QIODevice::ReadOnly));
416 QVERIFY(file.readAll().startsWith("import QtQml 2.0"));
417}
418
419void tst_qmlcachegen::skippedResources()
420{
421 QFile file(":/not/Skip.qml");
422 QVERIFY(file.open(QIODevice::ReadOnly));
423 QVERIFY(file.readAll().startsWith("import QtQml 2.0"));
424
425 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
426 const auto *unit = QQmlMetaType::findCachedCompilationUnit(uri: QUrl("qrc:/not/Skip.qml"), status: &error);
427 QCOMPARE(unit, nullptr);
428 QCOMPARE(error, QQmlMetaType::CachedUnitLookupError::NoUnitFound);
429}
430
431void tst_qmlcachegen::workerScripts()
432{
433 QVERIFY(QFile::exists(":/workerscripts/data/worker.js"));
434 QVERIFY(QFile::exists(":/workerscripts/data/worker.qml"));
435 QVERIFY(QFileInfo(":/workerscripts/data/worker.js").size() > 0);
436
437 QQmlEngine engine;
438 CleanlyLoadingComponent component(&engine, QUrl("qrc:///workerscripts/data/worker.qml"));
439 QScopedPointer<QObject> obj(component.create());
440 QVERIFY(!obj.isNull());
441 QTRY_VERIFY(obj->property("success").toBool());
442}
443
444void tst_qmlcachegen::functionExpressions()
445{
446 QTemporaryDir tempDir;
447 QVERIFY(tempDir.isValid());
448
449 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
450 QFile f(tempDir.path() + '/' + fileName);
451 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
452 Q_ASSERT(ok);
453 f.write(data: contents);
454 return f.fileName();
455 };
456
457 const QString testFilePath = writeTempFile(
458 "test.qml",
459 "import QtQuick 2.0\n"
460 "Item {\n"
461 " id: di\n"
462 " \n"
463 " property var f\n"
464 " property bool f_called: false\n"
465 " f : function() { f_called = true }\n"
466 " \n"
467 " signal g\n"
468 " property bool g_handler_called: false\n"
469 " onG: function() { g_handler_called = true }\n"
470 " \n"
471 " signal h(int i)\n"
472 " property bool h_connections_handler_called: false\n"
473 " Connections {\n"
474 " target: di\n"
475 " onH: function(magic) { h_connections_handler_called = (magic == 42)\n }\n"
476 " }\n"
477 " \n"
478 " function runTest() { \n"
479 " f()\n"
480 " g()\n"
481 " h(42)\n"
482 " }\n"
483 "}");
484
485 QVERIFY(generateCache(testFilePath));
486
487 const QString cacheFilePath = testFilePath + QLatin1Char('c');
488 QVERIFY(QFile::exists(cacheFilePath));
489 QVERIFY(QFile::remove(testFilePath));
490
491 QQmlEngine engine;
492 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
493 QScopedPointer<QObject> obj(component.create());
494 QVERIFY(!obj.isNull());
495
496 QCOMPARE(obj->property("f_called").toBool(), false);
497 QCOMPARE(obj->property("g_handler_called").toBool(), false);
498 QCOMPARE(obj->property("h_connections_handler_called").toBool(), false);
499
500 QMetaObject::invokeMethod(obj: obj.data(), member: "runTest");
501
502 QCOMPARE(obj->property("f_called").toBool(), true);
503 QCOMPARE(obj->property("g_handler_called").toBool(), true);
504 QCOMPARE(obj->property("h_connections_handler_called").toBool(), true);
505}
506
507void tst_qmlcachegen::trickyPaths_data()
508{
509 QTest::addColumn<QString>(name: "filePath");
510 QTest::newRow(dataTag: "path with spaces") << QStringLiteral(":/directory with spaces/file name with spaces.qml");
511 QTest::newRow(dataTag: "version style suffix 1") << QStringLiteral(":/directory with spaces/versionStyleSuffix-1.2-core-yc.qml");
512 QTest::newRow(dataTag: "version style suffix 2") << QStringLiteral(":/directory with spaces/versionStyleSuffix-1.2-more.qml");
513
514 // QTBUG-46375
515#if !defined(Q_OS_WIN)
516 QTest::newRow(dataTag: "path with umlaut") << QStringLiteral(":/Bäh.qml");
517#endif
518}
519
520void tst_qmlcachegen::trickyPaths()
521{
522 QFETCH(QString, filePath);
523 QVERIFY2(QFile::exists(filePath), qPrintable(filePath));
524 QVERIFY(QFileInfo(filePath).size() > 0);
525 QQmlEngine engine;
526 QQmlComponent component(&engine, QUrl("qrc" + filePath));
527 QScopedPointer<QObject> obj(component.create());
528 QVERIFY(!obj.isNull());
529 QCOMPARE(obj->property("success").toInt(), 42);
530}
531
532void tst_qmlcachegen::qrcScriptImport()
533{
534 QQmlEngine engine;
535 CleanlyLoadingComponent component(&engine, QUrl("qrc:///data/jsimport.qml"));
536 QScopedPointer<QObject> obj(component.create());
537 QVERIFY(!obj.isNull());
538 QTRY_COMPARE(obj->property("value").toInt(), 42);
539}
540
541void tst_qmlcachegen::fsScriptImport()
542{
543 QTemporaryDir tempDir;
544 QVERIFY(tempDir.isValid());
545
546 const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
547 QFile f(tempDir.path() + '/' + fileName);
548 const bool ok = f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate);
549 Q_ASSERT(ok);
550 f.write(data: contents);
551 return f.fileName();
552 };
553
554 const QString testFilePath = writeTempFile(
555 "test.qml",
556 "import QtQml 2.0\n"
557 "import \"test.js\" as ScriptTest\n"
558 "QtObject {\n"
559 " property int value: ScriptTest.value\n"
560 "}\n");
561
562 const QString scriptFilePath = writeTempFile(
563 "test.js",
564 "var value = 42"
565 );
566
567 QVERIFY(generateCache(scriptFilePath));
568 QVERIFY(generateCache(testFilePath));
569
570 const QString scriptCacheFilePath = scriptFilePath + QLatin1Char('c');
571 QVERIFY(QFile::exists(scriptFilePath));
572
573 {
574 QFile cache(scriptCacheFilePath);
575 QVERIFY(cache.open(QIODevice::ReadOnly));
576 const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, size: sizeof(QV4::CompiledData::Unit)));
577 QVERIFY(cacheUnit);
578 QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::StaticData);
579 QVERIFY(!(cacheUnit->flags & QV4::CompiledData::Unit::PendingTypeCompilation));
580 QCOMPARE(uint(cacheUnit->sourceFileIndex), uint(0));
581 }
582
583 // Remove source code to make sure that when loading succeeds, it is because we loaded
584 // the existing cache files.
585 QVERIFY(QFile::remove(testFilePath));
586 QVERIFY(QFile::remove(scriptFilePath));
587
588 QQmlEngine engine;
589 CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(localfile: testFilePath));
590 QScopedPointer<QObject> obj(component.create());
591 QVERIFY(!obj.isNull());
592 QCOMPARE(obj->property("value").toInt(), 42);
593}
594
595void tst_qmlcachegen::moduleScriptImport()
596{
597 QQmlEngine engine;
598 CleanlyLoadingComponent component(&engine, QUrl("qrc:///data/jsmoduleimport.qml"));
599 QVERIFY2(!component.isError(), qPrintable(component.errorString()));
600 QScopedPointer<QObject> obj(component.create());
601 QVERIFY(!obj.isNull());
602 QTRY_VERIFY(obj->property("ok").toBool());
603
604 QVERIFY(QFile::exists(":/data/script.mjs"));
605 QVERIFY(QFileInfo(":/data/script.mjs").size() > 0);
606
607 {
608 auto componentPrivate = QQmlComponentPrivate::get(c: &component);
609 QVERIFY(componentPrivate);
610 auto compilationUnit = componentPrivate->compilationUnit->dependentScripts.first()->compilationUnit();
611 QVERIFY(compilationUnit);
612 auto unitData = compilationUnit->unitData();
613 QVERIFY(unitData);
614 QVERIFY(unitData->flags & QV4::CompiledData::Unit::StaticData);
615 QVERIFY(unitData->flags & QV4::CompiledData::Unit::IsESModule);
616
617 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
618 const QV4::CompiledData::Unit *unitFromResources = QQmlMetaType::findCachedCompilationUnit(
619 uri: QUrl("qrc:/data/script.mjs"), status: &error);
620 QVERIFY(unitFromResources);
621
622 QCOMPARE(unitFromResources, compilationUnit->unitData());
623 }
624}
625
626void tst_qmlcachegen::esModulesViaQJSEngine()
627{
628 QJSEngine engine;
629 QJSValue module = engine.importModule(fileName: ":/data/module.mjs");
630 QJSValue result = module.property(name: "entry").call();
631 QCOMPARE(result.toString(), "ok");
632}
633
634void tst_qmlcachegen::enums()
635{
636 QQmlEngine engine;
637 CleanlyLoadingComponent component(&engine, QUrl("qrc:///data/Enums.qml"));
638 QScopedPointer<QObject> obj(component.create());
639 QVERIFY(!obj.isNull());
640 QTRY_COMPARE(obj->property("value").toInt(), 200);
641}
642
643void tst_qmlcachegen::sourceFileIndices()
644{
645 QVERIFY(QFile::exists(":/data/versionchecks.qml"));
646 QVERIFY(QFileInfo(":/data/versionchecks.qml").size() > 0);
647
648 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
649 const QV4::CompiledData::Unit *unitFromResources = QQmlMetaType::findCachedCompilationUnit(
650 uri: QUrl("qrc:/data/versionchecks.qml"), status: &error);
651 QVERIFY(unitFromResources);
652 QVERIFY(unitFromResources->flags & QV4::CompiledData::Unit::PendingTypeCompilation);
653 QCOMPARE(uint(unitFromResources->sourceFileIndex), uint(0));
654}
655
656void tst_qmlcachegen::reproducibleCache_data()
657{
658 QTest::addColumn<QString>(name: "filePath");
659
660 QDir dir(dataDirectory());
661 for (const QString &entry : dir.entryList(nameFilters: (QStringList() << "*.qml" << "*.js" << "*.mjs"), filters: QDir::Files)) {
662 QTest::newRow(dataTag: entry.toUtf8().constData()) << dir.filePath(fileName: entry);
663 }
664}
665
666void tst_qmlcachegen::reproducibleCache()
667{
668 QFETCH(QString, filePath);
669
670 QFile file(filePath);
671 QVERIFY(file.exists());
672
673 auto generate = [](const QString &path) {
674 if (!generateCache(qmlFileName: path))
675 return QByteArray();
676 QFile generated(path + 'c');
677 [&](){ QVERIFY(generated.open(QIODevice::ReadOnly)); }();
678 const QByteArray result = generated.readAll();
679 generated.remove();
680 return result;
681 };
682
683 const QByteArray contents1 = generate(file.fileName());
684 const QByteArray contents2 = generate(file.fileName());
685 QCOMPARE(contents1, contents2);
686}
687
688void tst_qmlcachegen::parameterAdjustment()
689{
690 QQmlEngine engine;
691 CleanlyLoadingComponent component(&engine, QUrl("qrc:///data/parameterAdjustment.qml"));
692 QScopedPointer<QObject> obj(component.create());
693 QVERIFY(!obj.isNull()); // Doesn't crash
694}
695
696
697void tst_qmlcachegen::inlineComponent()
698{
699 bool ok = generateCache(qmlFileName: testFile(fileName: "inlineComponentWithId.qml"));
700 QVERIFY(ok);
701 QQmlEngine engine;
702 CleanlyLoadingComponent component(&engine, testFileUrl(fileName: "inlineComponentWithId.qml"));
703 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "42");
704 QScopedPointer<QObject> obj(component.create());
705 QVERIFY(!obj.isNull());
706}
707
708void tst_qmlcachegen::posthocRequired()
709{
710 bool ok = generateCache(qmlFileName: testFile(fileName: "posthocrequired.qml"));
711 QVERIFY(ok);
712 QQmlEngine engine;
713 CleanlyLoadingComponent component(&engine, testFileUrl(fileName: "posthocrequired.qml"));
714 QScopedPointer<QObject> obj(component.create());
715 QVERIFY(obj.isNull() && component.isError());
716 QVERIFY(component.errorString().contains(QStringLiteral("Required property x was not initialized")));
717}
718
719void tst_qmlcachegen::saveableUnitPointer()
720{
721 QV4::CompiledData::Unit unit;
722 unit.flags = QV4::CompiledData::Unit::StaticData | QV4::CompiledData::Unit::IsJavascript;
723 const auto flags = unit.flags;
724
725 QV4::CompiledData::SaveableUnitPointer pointer(&unit);
726
727 QVERIFY(pointer.saveToDisk<char>([](const char *, quint32) { return true; }));
728 QCOMPARE(unit.flags, flags);
729}
730
731QTEST_GUILESS_MAIN(tst_qmlcachegen)
732
733#include "tst_qmlcachegen.moc"
734

source code of qtdeclarative/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp