aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qmlcachegen
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qml/qmlcachegen')
-rw-r--r--tests/auto/qml/qmlcachegen/Enums.qml9
-rw-r--r--tests/auto/qml/qmlcachegen/Retain.qml2
-rw-r--r--tests/auto/qml/qmlcachegen/jsimport.qml6
-rw-r--r--tests/auto/qml/qmlcachegen/jsmoduleimport.qml6
-rw-r--r--tests/auto/qml/qmlcachegen/library.js4
-rw-r--r--tests/auto/qml/qmlcachegen/qmlcachegen.pro22
-rw-r--r--tests/auto/qml/qmlcachegen/retain.qrc5
-rw-r--r--tests/auto/qml/qmlcachegen/script.js6
-rw-r--r--tests/auto/qml/qmlcachegen/script.mjs4
-rw-r--r--tests/auto/qml/qmlcachegen/trickypaths.qml4
-rw-r--r--tests/auto/qml/qmlcachegen/trickypaths.qrc7
-rw-r--r--tests/auto/qml/qmlcachegen/trickypaths_umlaut.qrc5
-rw-r--r--tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp465
-rw-r--r--tests/auto/qml/qmlcachegen/umlaut.qml4
-rw-r--r--tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-core-yc.qml4
-rw-r--r--tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-more.qml4
-rw-r--r--tests/auto/qml/qmlcachegen/versionchecks.qml4
-rw-r--r--tests/auto/qml/qmlcachegen/worker.js3
-rw-r--r--tests/auto/qml/qmlcachegen/worker.qml12
19 files changed, 571 insertions, 5 deletions
diff --git a/tests/auto/qml/qmlcachegen/Enums.qml b/tests/auto/qml/qmlcachegen/Enums.qml
new file mode 100644
index 0000000000..830babb73e
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/Enums.qml
@@ -0,0 +1,9 @@
+import QtQml 2.0
+QtObject {
+ enum Test {
+ First = 100,
+ Second = 200
+ }
+ property int value: 0
+ Component.onCompleted: value = Enums.Second
+}
diff --git a/tests/auto/qml/qmlcachegen/Retain.qml b/tests/auto/qml/qmlcachegen/Retain.qml
new file mode 100644
index 0000000000..0e69012662
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/Retain.qml
@@ -0,0 +1,2 @@
+import QtQml 2.0
+QtObject {}
diff --git a/tests/auto/qml/qmlcachegen/jsimport.qml b/tests/auto/qml/qmlcachegen/jsimport.qml
new file mode 100644
index 0000000000..9c40878e60
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/jsimport.qml
@@ -0,0 +1,6 @@
+import QtQml 2.0
+import "script.js" as Script
+
+QtObject {
+ property int value: Script.getter()
+}
diff --git a/tests/auto/qml/qmlcachegen/jsmoduleimport.qml b/tests/auto/qml/qmlcachegen/jsmoduleimport.qml
new file mode 100644
index 0000000000..c1fad7fee2
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/jsmoduleimport.qml
@@ -0,0 +1,6 @@
+import QtQml 2.0
+import "script.mjs" as Script
+
+QtObject {
+ property bool ok: Script.ok()
+}
diff --git a/tests/auto/qml/qmlcachegen/library.js b/tests/auto/qml/qmlcachegen/library.js
new file mode 100644
index 0000000000..51fb41dc23
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/library.js
@@ -0,0 +1,4 @@
+
+function getter() {
+ return 42;
+}
diff --git a/tests/auto/qml/qmlcachegen/qmlcachegen.pro b/tests/auto/qml/qmlcachegen/qmlcachegen.pro
index 8d8b37be15..c7820ac1cd 100644
--- a/tests/auto/qml/qmlcachegen/qmlcachegen.pro
+++ b/tests/auto/qml/qmlcachegen/qmlcachegen.pro
@@ -1,7 +1,27 @@
-CONFIG += testcase
+CONFIG += testcase qtquickcompiler
TARGET = tst_qmlcachegen
macos:CONFIG -= app_bundle
SOURCES += tst_qmlcachegen.cpp
+workerscripts_test.files = worker.js worker.qml
+workerscripts_test.prefix = /workerscripts
+RESOURCES += workerscripts_test
+
+RESOURCES += versionchecks.qml
+
+RESOURCES += trickypaths.qrc
+
+RESOURCES += jsimport.qml script.js library.js
+
+RESOURCES += Enums.qml
+
+# QTBUG-46375
+!win32: RESOURCES += trickypaths_umlaut.qrc
+
+RESOURCES += jsmoduleimport.qml script.mjs
+
+RESOURCES += retain.qrc
+QTQUICK_COMPILER_RETAINED_RESOURCES += retain.qrc
+
QT += core-private qml-private testlib
diff --git a/tests/auto/qml/qmlcachegen/retain.qrc b/tests/auto/qml/qmlcachegen/retain.qrc
new file mode 100644
index 0000000000..af042b25d8
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/retain.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Retain.qml</file>
+ </qresource>
+</RCC>
diff --git a/tests/auto/qml/qmlcachegen/script.js b/tests/auto/qml/qmlcachegen/script.js
new file mode 100644
index 0000000000..fa55f9069e
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/script.js
@@ -0,0 +1,6 @@
+
+.import "library.js" as Library
+
+function getter() {
+ return Library.getter()
+}
diff --git a/tests/auto/qml/qmlcachegen/script.mjs b/tests/auto/qml/qmlcachegen/script.mjs
new file mode 100644
index 0000000000..459c336125
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/script.mjs
@@ -0,0 +1,4 @@
+
+export function ok() {
+ return true
+}
diff --git a/tests/auto/qml/qmlcachegen/trickypaths.qml b/tests/auto/qml/qmlcachegen/trickypaths.qml
new file mode 100644
index 0000000000..0836808dc2
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/trickypaths.qml
@@ -0,0 +1,4 @@
+import QtQml 2.0
+QtObject {
+ property int success: 42
+}
diff --git a/tests/auto/qml/qmlcachegen/trickypaths.qrc b/tests/auto/qml/qmlcachegen/trickypaths.qrc
new file mode 100644
index 0000000000..57977ccf6d
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/trickypaths.qrc
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/directory with spaces">
+<file alias="file name with spaces.qml">trickypaths.qml</file>
+<file>versionStyleSuffix-1.2-core-yc.qml</file>
+<file>versionStyleSuffix-1.2-more.qml</file>
+</qresource>
+</RCC>
diff --git a/tests/auto/qml/qmlcachegen/trickypaths_umlaut.qrc b/tests/auto/qml/qmlcachegen/trickypaths_umlaut.qrc
new file mode 100644
index 0000000000..9ca889d692
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/trickypaths_umlaut.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/">
+<file alias="Bäh.qml">umlaut.qml</file>
+</qresource>
+</RCC>
diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
index b7e616a050..8cfa4cb6af 100644
--- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
+++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
@@ -32,7 +32,11 @@
#include <QQmlEngine>
#include <QProcess>
#include <QLibraryInfo>
+#include <QStandardPaths>
#include <QSysInfo>
+#include <QLoggingCategory>
+#include <private/qqmlcomponent_p.h>
+#include <qtranslator.h>
class tst_qmlcachegen: public QObject
{
@@ -43,6 +47,25 @@ private slots:
void loadGeneratedFile();
void translationExpressionSupport();
+ void signalHandlerParameters();
+ void errorOnArgumentsInSignalHandler();
+ void aheadOfTimeCompilation();
+ void functionExpressions();
+ void versionChecksForAheadOfTimeUnits();
+ void retainedResources();
+
+ void workerScripts();
+
+ void trickyPaths_data();
+ void trickyPaths();
+
+ void qrcScriptImport();
+ void fsScriptImport();
+ void moduleScriptImport();
+
+ void enums();
+
+ void sourceFileIndices();
};
// A wrapper around QQmlComponent to ensure the temporary reference counts
@@ -67,15 +90,20 @@ public:
}
};
-static bool generateCache(const QString &qmlFileName)
+static bool generateCache(const QString &qmlFileName, QByteArray *capturedStderr = nullptr)
{
QProcess proc;
- proc.setProcessChannelMode(QProcess::ForwardedChannels);
+ if (capturedStderr == nullptr)
+ proc.setProcessChannelMode(QProcess::ForwardedChannels);
proc.setProgram(QLibraryInfo::location(QLibraryInfo::BinariesPath) + QDir::separator() + QLatin1String("qmlcachegen"));
- proc.setArguments(QStringList() << (QLatin1String("--target-architecture=") + QSysInfo::buildCpuArchitecture()) << (QLatin1String("--target-abi=") + QSysInfo::buildAbi()) << qmlFileName);
+ proc.setArguments(QStringList() << qmlFileName);
proc.start();
if (!proc.waitForFinished())
return false;
+
+ if (capturedStderr)
+ *capturedStderr = proc.readAllStandardError();
+
if (proc.exitStatus() != QProcess::NormalExit)
return false;
return proc.exitCode() == 0;
@@ -84,6 +112,13 @@ static bool generateCache(const QString &qmlFileName)
void tst_qmlcachegen::initTestCase()
{
qputenv("QML_FORCE_DISK_CACHE", "1");
+ QStandardPaths::setTestModeEnabled(true);
+
+ // make sure there's no pre-existing cache dir
+ QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+ if (!cacheDir.isEmpty())
+ //QDir(cacheDir).removeRecursively();
+ qDebug() << cacheDir;
}
void tst_qmlcachegen::loadGeneratedFile()
@@ -108,6 +143,17 @@ void tst_qmlcachegen::loadGeneratedFile()
const QString cacheFilePath = testFilePath + QLatin1Char('c');
QVERIFY(QFile::exists(cacheFilePath));
+
+ {
+ QFile cache(cacheFilePath);
+ QVERIFY(cache.open(QIODevice::ReadOnly));
+ const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, sizeof(QV4::CompiledData::Unit)));
+ QVERIFY(cacheUnit);
+ QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::StaticData);
+ QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::PendingTypeCompilation);
+ QCOMPARE(uint(cacheUnit->sourceFileIndex), uint(0));
+ }
+
QVERIFY(QFile::remove(testFilePath));
QQmlEngine engine;
@@ -115,13 +161,36 @@ void tst_qmlcachegen::loadGeneratedFile()
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
QCOMPARE(obj->property("value").toInt(), 42);
+
+ auto componentPrivate = QQmlComponentPrivate::get(&component);
+ QVERIFY(componentPrivate);
+ auto compilationUnit = componentPrivate->compilationUnit;
+ QVERIFY(compilationUnit);
+ auto unitData = compilationUnit->unitData();
+ QVERIFY(unitData);
+ QVERIFY(unitData->flags & QV4::CompiledData::Unit::StaticData);
}
+class QTestTranslator : public QTranslator
+{
+public:
+ QString translate(const char *context, const char *sourceText, const char */*disambiguation*/, int /*n*/) const override
+ {
+ m_lastContext = QString::fromUtf8(context);
+ return QString::fromUtf8(sourceText).toUpper();
+ }
+ bool isEmpty() const override { return true; }
+ mutable QString m_lastContext;
+};
+
void tst_qmlcachegen::translationExpressionSupport()
{
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());
+ QTestTranslator translator;
+ qApp->installTranslator(&translator);
+
const auto writeTempFile = [&tempDir](const QString &fileName, const char *contents) {
QFile f(tempDir.path() + '/' + fileName);
const bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate);
@@ -155,7 +224,395 @@ void tst_qmlcachegen::translationExpressionSupport()
CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
- QCOMPARE(obj->property("text").toString(), QString("All Ok"));
+ QCOMPARE(obj->property("text").toString(), QString("ALL Ok"));
+ QCOMPARE(translator.m_lastContext, QStringLiteral("test"));
+}
+
+void tst_qmlcachegen::signalHandlerParameters()
+{
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ const auto writeTempFile = [&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();
+ };
+
+ const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.0\n"
+ "QtObject {\n"
+ " property real result: 0\n"
+ " signal testMe(real value);\n"
+ " onTestMe: result = value;\n"
+ " function runTest() { testMe(42); }\n"
+ "}");
+
+ QVERIFY(generateCache(testFilePath));
+
+ const QString cacheFilePath = testFilePath + QLatin1Char('c');
+ QVERIFY(QFile::exists(cacheFilePath));
+ QVERIFY(QFile::remove(testFilePath));
+
+ {
+ QFile cache(cacheFilePath);
+ QVERIFY(cache.open(QIODevice::ReadOnly));
+ const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, sizeof(QV4::CompiledData::Unit)));
+ QVERIFY(cacheUnit);
+ }
+
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QMetaObject::invokeMethod(obj.data(), "runTest");
+ QCOMPARE(obj->property("result").toInt(), 42);
+
+ {
+ auto componentPrivate = QQmlComponentPrivate::get(&component);
+ QVERIFY(componentPrivate);
+ auto compilationUnit = componentPrivate->compilationUnit;
+ QVERIFY(compilationUnit);
+ QVERIFY(compilationUnit->unitData());
+
+ // Verify that the QML objects don't come from the original data.
+ QVERIFY(compilationUnit->objectAt(0) != compilationUnit->unitData()->qmlUnit()->objectAt(0));
+
+ // Typically the final file name is one of those strings that is not in the original
+ // pre-compiled qml file's string table, while for example the signal parameter
+ // name ("value") is.
+ const auto isStringIndexInStringTable = [compilationUnit](uint index) {
+ return index < compilationUnit->unitData()->stringTableSize;
+ };
+
+ QVERIFY(isStringIndexInStringTable(compilationUnit->objectAt(0)->signalAt(0)->parameterAt(0)->nameIndex));
+ QVERIFY(!compilationUnit->dynamicStrings.isEmpty());
+ }
+}
+
+void tst_qmlcachegen::errorOnArgumentsInSignalHandler()
+{
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ const auto writeTempFile = [&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();
+ };
+
+ const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.2\n"
+ "QtObject {\n"
+ " signal mySignal(var arguments);\n"
+ " onMySignal: console.log(arguments);\n"
+ "}");
+
+
+ QByteArray errorOutput;
+ QVERIFY(!generateCache(testFilePath, &errorOutput));
+ QVERIFY2(errorOutput.contains("error: The use of eval() or the use of the arguments object in signal handlers is"), errorOutput);
+}
+
+void tst_qmlcachegen::aheadOfTimeCompilation()
+{
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ const auto writeTempFile = [&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();
+ };
+
+ const QString testFilePath = writeTempFile("test.qml", "import QtQml 2.0\n"
+ "QtObject {\n"
+ " function runTest() { var x = 0; while (x < 42) { ++x }; return x; }\n"
+ "}");
+
+ QVERIFY(generateCache(testFilePath));
+
+ const QString cacheFilePath = testFilePath + QLatin1Char('c');
+ QVERIFY(QFile::exists(cacheFilePath));
+ QVERIFY(QFile::remove(testFilePath));
+
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QVariant result;
+ QMetaObject::invokeMethod(obj.data(), "runTest", Q_RETURN_ARG(QVariant, result));
+ QCOMPARE(result.toInt(), 42);
+}
+
+static QQmlPrivate::CachedQmlUnit *temporaryModifiedCachedUnit = nullptr;
+
+void tst_qmlcachegen::versionChecksForAheadOfTimeUnits()
+{
+ QVERIFY(QFile::exists(":/versionchecks.qml"));
+ QCOMPARE(QFileInfo(":/versionchecks.qml").size(), 0);
+
+ Q_ASSERT(!temporaryModifiedCachedUnit);
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ const QV4::CompiledData::Unit *originalUnit = QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/versionchecks.qml"), &error);
+ QVERIFY(originalUnit);
+ QV4::CompiledData::Unit *tweakedUnit = (QV4::CompiledData::Unit *)malloc(originalUnit->unitSize);
+ memcpy(reinterpret_cast<void *>(tweakedUnit), reinterpret_cast<const void *>(originalUnit), originalUnit->unitSize);
+ tweakedUnit->version = QV4_DATA_STRUCTURE_VERSION - 1;
+ temporaryModifiedCachedUnit = new QQmlPrivate::CachedQmlUnit{tweakedUnit, nullptr, nullptr};
+
+ auto testHandler = [](const QUrl &url) -> const QQmlPrivate::CachedQmlUnit * {
+ if (url == QUrl("qrc:/versionchecks.qml"))
+ return temporaryModifiedCachedUnit;
+ return nullptr;
+ };
+ QQmlMetaType::prependCachedUnitLookupFunction(testHandler);
+
+ {
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ QVERIFY(!QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/versionchecks.qml"), &error));
+ QCOMPARE(error, QQmlMetaType::CachedUnitLookupError::VersionMismatch);
+ }
+
+ {
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl("qrc:/versionchecks.qml"));
+ QCOMPARE(component.status(), QQmlComponent::Error);
+ QCOMPARE(component.errorString(), QString("qrc:/versionchecks.qml:-1 File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile\n"));
+ }
+
+ Q_ASSERT(temporaryModifiedCachedUnit);
+ free(const_cast<QV4::CompiledData::Unit *>(temporaryModifiedCachedUnit->qmlData));
+ delete temporaryModifiedCachedUnit;
+ temporaryModifiedCachedUnit = nullptr;
+
+ QQmlMetaType::removeCachedUnitLookupFunction(testHandler);
+}
+
+void tst_qmlcachegen::retainedResources()
+{
+ QFile file(":/Retain.qml");
+ QVERIFY(file.open(QIODevice::ReadOnly));
+ QVERIFY(file.readAll().startsWith("import QtQml 2.0"));
+}
+
+void tst_qmlcachegen::workerScripts()
+{
+ QVERIFY(QFile::exists(":/workerscripts/worker.js"));
+ QVERIFY(QFile::exists(":/workerscripts/worker.qml"));
+ QCOMPARE(QFileInfo(":/workerscripts/worker.js").size(), 0);
+
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl("qrc:///workerscripts/worker.qml"));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QTRY_VERIFY(obj->property("success").toBool());
+}
+
+void tst_qmlcachegen::functionExpressions()
+{
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ const auto writeTempFile = [&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();
+ };
+
+ const QString testFilePath = writeTempFile(
+ "test.qml",
+ "import QtQuick 2.0\n"
+ "Item {\n"
+ " id: di\n"
+ " \n"
+ " property var f\n"
+ " property bool f_called: false\n"
+ " f : function() { f_called = true }\n"
+ " \n"
+ " signal g\n"
+ " property bool g_handler_called: false\n"
+ " onG: function() { g_handler_called = true }\n"
+ " \n"
+ " signal h(int i)\n"
+ " property bool h_connections_handler_called: false\n"
+ " Connections {\n"
+ " target: di\n"
+ " onH: function(magic) { h_connections_handler_called = (magic == 42)\n }\n"
+ " }\n"
+ " \n"
+ " function runTest() { \n"
+ " f()\n"
+ " g()\n"
+ " h(42)\n"
+ " }\n"
+ "}");
+
+ QVERIFY(generateCache(testFilePath));
+
+ const QString cacheFilePath = testFilePath + QLatin1Char('c');
+ QVERIFY(QFile::exists(cacheFilePath));
+ QVERIFY(QFile::remove(testFilePath));
+
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+
+ QCOMPARE(obj->property("f_called").toBool(), false);
+ QCOMPARE(obj->property("g_handler_called").toBool(), false);
+ QCOMPARE(obj->property("h_connections_handler_called").toBool(), false);
+
+ QMetaObject::invokeMethod(obj.data(), "runTest");
+
+ QCOMPARE(obj->property("f_called").toBool(), true);
+ QCOMPARE(obj->property("g_handler_called").toBool(), true);
+ QCOMPARE(obj->property("h_connections_handler_called").toBool(), true);
+}
+
+void tst_qmlcachegen::trickyPaths_data()
+{
+ QTest::addColumn<QString>("filePath");
+ QTest::newRow("path with spaces") << QStringLiteral(":/directory with spaces/file name with spaces.qml");
+ QTest::newRow("version style suffix 1") << QStringLiteral(":/directory with spaces/versionStyleSuffix-1.2-core-yc.qml");
+ QTest::newRow("version style suffix 2") << QStringLiteral(":/directory with spaces/versionStyleSuffix-1.2-more.qml");
+
+ // QTBUG-46375
+#if !defined(Q_OS_WIN)
+ QTest::newRow("path with umlaut") << QStringLiteral(":/Bäh.qml");
+#endif
+}
+
+void tst_qmlcachegen::trickyPaths()
+{
+ QFETCH(QString, filePath);
+ QVERIFY2(QFile::exists(filePath), qPrintable(filePath));
+ QCOMPARE(QFileInfo(filePath).size(), 0);
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl("qrc" + filePath));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QCOMPARE(obj->property("success").toInt(), 42);
+}
+
+void tst_qmlcachegen::qrcScriptImport()
+{
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl("qrc:///jsimport.qml"));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QTRY_COMPARE(obj->property("value").toInt(), 42);
+}
+
+void tst_qmlcachegen::fsScriptImport()
+{
+ QTemporaryDir tempDir;
+ QVERIFY(tempDir.isValid());
+
+ const auto writeTempFile = [&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();
+ };
+
+ const QString testFilePath = writeTempFile(
+ "test.qml",
+ "import QtQml 2.0\n"
+ "import \"test.js\" as ScriptTest\n"
+ "QtObject {\n"
+ " property int value: ScriptTest.value\n"
+ "}\n");
+
+ const QString scriptFilePath = writeTempFile(
+ "test.js",
+ "var value = 42"
+ );
+
+ QVERIFY(generateCache(scriptFilePath));
+ QVERIFY(generateCache(testFilePath));
+
+ const QString scriptCacheFilePath = scriptFilePath + QLatin1Char('c');
+ QVERIFY(QFile::exists(scriptFilePath));
+
+ {
+ QFile cache(scriptCacheFilePath);
+ QVERIFY(cache.open(QIODevice::ReadOnly));
+ const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, sizeof(QV4::CompiledData::Unit)));
+ QVERIFY(cacheUnit);
+ QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::StaticData);
+ QVERIFY(!(cacheUnit->flags & QV4::CompiledData::Unit::PendingTypeCompilation));
+ QCOMPARE(uint(cacheUnit->sourceFileIndex), uint(0));
+ }
+
+ // Remove source code to make sure that when loading succeeds, it is because we loaded
+ // the existing cache files.
+ QVERIFY(QFile::remove(testFilePath));
+ QVERIFY(QFile::remove(scriptFilePath));
+
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QCOMPARE(obj->property("value").toInt(), 42);
+}
+
+void tst_qmlcachegen::moduleScriptImport()
+{
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl("qrc:///jsmoduleimport.qml"));
+ QVERIFY2(!component.isError(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QTRY_VERIFY(obj->property("ok").toBool());
+
+ QVERIFY(QFile::exists(":/script.mjs"));
+ QCOMPARE(QFileInfo(":/script.mjs").size(), 0);
+
+ {
+ auto componentPrivate = QQmlComponentPrivate::get(&component);
+ QVERIFY(componentPrivate);
+ auto compilationUnit = componentPrivate->compilationUnit->dependentScripts.first()->compilationUnit();
+ QVERIFY(compilationUnit);
+ auto unitData = compilationUnit->unitData();
+ QVERIFY(unitData);
+ QVERIFY(unitData->flags & QV4::CompiledData::Unit::StaticData);
+ QVERIFY(unitData->flags & QV4::CompiledData::Unit::IsESModule);
+
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ const QV4::CompiledData::Unit *unitFromResources = QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/script.mjs"), &error);
+ QVERIFY(unitFromResources);
+
+ QCOMPARE(unitFromResources, compilationUnit->unitData());
+ }
+}
+
+void tst_qmlcachegen::enums()
+{
+ QQmlEngine engine;
+ CleanlyLoadingComponent component(&engine, QUrl("qrc:///Enums.qml"));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QTRY_COMPARE(obj->property("value").toInt(), 200);
+}
+
+void tst_qmlcachegen::sourceFileIndices()
+{
+ QVERIFY(QFile::exists(":/versionchecks.qml"));
+ QCOMPARE(QFileInfo(":/versionchecks.qml").size(), 0);
+
+ QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
+ const QV4::CompiledData::Unit *unitFromResources = QQmlMetaType::findCachedCompilationUnit(QUrl("qrc:/versionchecks.qml"), &error);
+ QVERIFY(unitFromResources);
+ QVERIFY(unitFromResources->flags & QV4::CompiledData::Unit::PendingTypeCompilation);
+ QCOMPARE(uint(unitFromResources->sourceFileIndex), uint(0));
}
QTEST_GUILESS_MAIN(tst_qmlcachegen)
diff --git a/tests/auto/qml/qmlcachegen/umlaut.qml b/tests/auto/qml/qmlcachegen/umlaut.qml
new file mode 100644
index 0000000000..0836808dc2
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/umlaut.qml
@@ -0,0 +1,4 @@
+import QtQml 2.0
+QtObject {
+ property int success: 42
+}
diff --git a/tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-core-yc.qml b/tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-core-yc.qml
new file mode 100644
index 0000000000..0836808dc2
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-core-yc.qml
@@ -0,0 +1,4 @@
+import QtQml 2.0
+QtObject {
+ property int success: 42
+}
diff --git a/tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-more.qml b/tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-more.qml
new file mode 100644
index 0000000000..0836808dc2
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/versionStyleSuffix-1.2-more.qml
@@ -0,0 +1,4 @@
+import QtQml 2.0
+QtObject {
+ property int success: 42
+}
diff --git a/tests/auto/qml/qmlcachegen/versionchecks.qml b/tests/auto/qml/qmlcachegen/versionchecks.qml
new file mode 100644
index 0000000000..77d67e7da4
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/versionchecks.qml
@@ -0,0 +1,4 @@
+import QtQml 2.0
+QtObject {
+ property bool ok: true
+}
diff --git a/tests/auto/qml/qmlcachegen/worker.js b/tests/auto/qml/qmlcachegen/worker.js
new file mode 100644
index 0000000000..dd2d0b843d
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/worker.js
@@ -0,0 +1,3 @@
+WorkerScript.onMessage = function(message) {
+ WorkerScript.sendMessage({ reply: message });
+}
diff --git a/tests/auto/qml/qmlcachegen/worker.qml b/tests/auto/qml/qmlcachegen/worker.qml
new file mode 100644
index 0000000000..1f1c9d1ac7
--- /dev/null
+++ b/tests/auto/qml/qmlcachegen/worker.qml
@@ -0,0 +1,12 @@
+import QtQml 2.0
+import QtQuick 2.0
+QtObject {
+ property bool success: false
+ property WorkerScript worker: WorkerScript {
+ source: "worker.js"
+ onMessage: {
+ success = true
+ }
+ }
+ Component.onCompleted: worker.sendMessage("Hello")
+}