1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 Canonical Limited and/or its subsidiary(-ies). |
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 <QtTest/QtTest> |
30 | #include <QtQml/qqmlengine.h> |
31 | #include <QtQml/qqmlfile.h> |
32 | #include <QtQml/qqmlnetworkaccessmanagerfactory.h> |
33 | #include <QtQuick/qquickview.h> |
34 | #include <QtQuick/qquickitem.h> |
35 | #if QT_CONFIG(process) |
36 | #include <QtCore/qprocess.h> |
37 | #endif |
38 | #include <QtQml/private/qqmlengine_p.h> |
39 | #include <QtQml/private/qqmltypedata_p.h> |
40 | #include <QtQml/private/qqmltypeloader_p.h> |
41 | #include "../../shared/testhttpserver.h" |
42 | #include "../../shared/util.h" |
43 | |
44 | class tst_QQMLTypeLoader : public QQmlDataTest |
45 | { |
46 | Q_OBJECT |
47 | |
48 | private slots: |
49 | void testLoadComplete(); |
50 | void loadComponentSynchronously(); |
51 | void trimCache(); |
52 | void trimCache2(); |
53 | void trimCache3(); |
54 | void keepSingleton(); |
55 | void keepRegistrations(); |
56 | void intercept(); |
57 | void redirect(); |
58 | void qmlSingletonWithinModule(); |
59 | void multiSingletonModule(); |
60 | void implicitComponentModule(); |
61 | void customDiskCachePath(); |
62 | void qrcRootPathUrl(); |
63 | void implicitImport(); |
64 | void compositeSingletonCycle(); |
65 | void declarativeCppType(); |
66 | void circularDependency(); |
67 | }; |
68 | |
69 | void tst_QQMLTypeLoader::testLoadComplete() |
70 | { |
71 | QQuickView *window = new QQuickView(); |
72 | window->engine()->addImportPath(QT_TESTCASE_BUILDDIR); |
73 | qDebug() << window->engine()->importPathList(); |
74 | window->setGeometry(posx: 0,posy: 0,w: 240,h: 320); |
75 | window->setSource(testFileUrl(fileName: "test_load_complete.qml" )); |
76 | window->show(); |
77 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
78 | |
79 | QObject *rootObject = window->rootObject(); |
80 | QTRY_VERIFY(rootObject != nullptr); |
81 | QTRY_COMPARE(rootObject->property("created" ).toInt(), 2); |
82 | QTRY_COMPARE(rootObject->property("loaded" ).toInt(), 2); |
83 | delete window; |
84 | } |
85 | |
86 | void tst_QQMLTypeLoader::loadComponentSynchronously() |
87 | { |
88 | QQmlEngine engine; |
89 | QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression( |
90 | QLatin1String(".*nonprotocol::1:1: QtObject is not a type.*" ))); |
91 | QQmlComponent component(&engine, testFileUrl(fileName: "load_synchronous.qml" )); |
92 | QScopedPointer<QObject> o(component.create()); |
93 | QVERIFY(o); |
94 | } |
95 | |
96 | void tst_QQMLTypeLoader::trimCache() |
97 | { |
98 | QQmlEngine engine; |
99 | QQmlTypeLoader &loader = QQmlEnginePrivate::get(e: &engine)->typeLoader; |
100 | QVector<QQmlTypeData *> releaseLater; |
101 | QVector<QV4::ExecutableCompilationUnit *> releaseCompilationUnitLater; |
102 | for (int i = 0; i < 256; ++i) { |
103 | QUrl url = testFileUrl(fileName: "trim_cache.qml" ); |
104 | url.setQuery(query: QString::number(i)); |
105 | |
106 | QQmlTypeData *data = loader.getType(unNormalizedUrl: url).take(); |
107 | // Run an event loop to receive the callback that release()es. |
108 | QTRY_COMPARE(data->count(), 2); |
109 | |
110 | // keep references to some of them so that they aren't trimmed. References to either the |
111 | // QQmlTypeData or its compiledData() should prevent the trimming. |
112 | if (i % 10 == 0) { |
113 | // keep ref on data, don't add ref on data->compiledData() |
114 | releaseLater.append(t: data); |
115 | } else if (i % 5 == 0) { |
116 | data->compilationUnit()->addref(); |
117 | releaseCompilationUnitLater.append(t: data->compilationUnit()); |
118 | data->release(); |
119 | } else { |
120 | data->release(); |
121 | } |
122 | } |
123 | |
124 | for (int i = 0; i < 256; ++i) { |
125 | QUrl url = testFileUrl(fileName: "trim_cache.qml" ); |
126 | url.setQuery(query: QString::number(i)); |
127 | if (i % 5 == 0) |
128 | QVERIFY(loader.isTypeLoaded(url)); |
129 | else if (i < 128) |
130 | QVERIFY(!loader.isTypeLoaded(url)); |
131 | // The cache is free to keep the others. |
132 | } |
133 | |
134 | for (auto *data : qAsConst(t&: releaseCompilationUnitLater)) |
135 | data->release(); |
136 | |
137 | for (auto *data : qAsConst(t&: releaseLater)) |
138 | data->release(); |
139 | } |
140 | |
141 | void tst_QQMLTypeLoader::trimCache2() |
142 | { |
143 | QScopedPointer<QQuickView> window(new QQuickView()); |
144 | window->setSource(testFileUrl(fileName: "trim_cache2.qml" )); |
145 | QQmlTypeLoader &loader = QQmlEnginePrivate::get(e: window->engine())->typeLoader; |
146 | // in theory if gc has already run this could be false |
147 | // QCOMPARE(loader.isTypeLoaded(testFileUrl("MyComponent2.qml")), true); |
148 | window->engine()->collectGarbage(); |
149 | QTest::qWait(ms: 1); // force event loop |
150 | window->engine()->trimComponentCache(); |
151 | QCOMPARE(loader.isTypeLoaded(testFileUrl("MyComponent2.qml" )), false); |
152 | } |
153 | |
154 | // test trimming the cache of an item that contains sub-items created via incubation |
155 | void tst_QQMLTypeLoader::trimCache3() |
156 | { |
157 | QScopedPointer<QQuickView> window(new QQuickView()); |
158 | window->setSource(testFileUrl(fileName: "trim_cache3.qml" )); |
159 | QQmlTypeLoader &loader = QQmlEnginePrivate::get(e: window->engine())->typeLoader; |
160 | QCOMPARE(loader.isTypeLoaded(testFileUrl("ComponentWithIncubator.qml" )), true); |
161 | |
162 | QQmlProperty::write(window->rootObject(), "source" , QString()); |
163 | |
164 | // handle our deleteLater and cleanup |
165 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
166 | QCoreApplication::processEvents(); |
167 | window->engine()->collectGarbage(); |
168 | |
169 | window->engine()->trimComponentCache(); |
170 | |
171 | QCOMPARE(loader.isTypeLoaded(testFileUrl("ComponentWithIncubator.qml" )), false); |
172 | } |
173 | |
174 | static void checkSingleton(const QString &dataDirectory) |
175 | { |
176 | QQmlEngine engine; |
177 | engine.addImportPath(dir: dataDirectory); |
178 | QQmlComponent component(&engine); |
179 | component.setData("import ClusterDemo 1.0\n" |
180 | "import QtQuick 2.6\n" |
181 | "import \"..\"\n" |
182 | "Item { property int t: ValueSource.something }" , |
183 | baseUrl: QUrl::fromLocalFile(localfile: dataDirectory + "/abc/Xyz.qml" )); |
184 | QCOMPARE(component.status(), QQmlComponent::Ready); |
185 | QScopedPointer<QObject> o(component.create()); |
186 | QVERIFY(o.data()); |
187 | QCOMPARE(o->property("t" ).toInt(), 10); |
188 | } |
189 | |
190 | void tst_QQMLTypeLoader::keepSingleton() |
191 | { |
192 | qmlRegisterSingletonType(url: testFileUrl(fileName: "ValueSource.qml" ), uri: "ClusterDemo" , versionMajor: 1, versionMinor: 0, qmlName: "ValueSource" ); |
193 | checkSingleton(dataDirectory: dataDirectory()); |
194 | QQmlMetaType::freeUnusedTypesAndCaches(); |
195 | checkSingleton(dataDirectory: dataDirectory()); |
196 | } |
197 | |
198 | class TestObject : public QObject |
199 | { |
200 | Q_OBJECT |
201 | public: |
202 | TestObject(QObject *parent = nullptr) : QObject(parent) {} |
203 | }; |
204 | |
205 | QML_DECLARE_TYPE(TestObject) |
206 | |
207 | static void verifyTypes(bool shouldHaveTestObject, bool shouldHaveFast) |
208 | { |
209 | bool hasTestObject = false; |
210 | bool hasFast = false; |
211 | for (const QQmlType &type : QQmlMetaType::qmlAllTypes()) { |
212 | if (type.elementName() == QLatin1String("Fast" )) |
213 | hasFast = true; |
214 | else if (type.elementName() == QLatin1String("TestObject" )) |
215 | hasTestObject = true; |
216 | } |
217 | QCOMPARE(hasTestObject, shouldHaveTestObject); |
218 | QCOMPARE(hasFast, shouldHaveFast); |
219 | } |
220 | |
221 | void tst_QQMLTypeLoader::keepRegistrations() |
222 | { |
223 | verifyTypes(shouldHaveTestObject: false, shouldHaveFast: false); |
224 | qmlRegisterType<TestObject>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "TestObject" ); |
225 | verifyTypes(shouldHaveTestObject: true, shouldHaveFast: false); |
226 | |
227 | { |
228 | QQmlEngine engine; |
229 | engine.addImportPath(dir: dataDirectory()); |
230 | QQmlComponent component(&engine); |
231 | component.setData("import Fast 1.0\nFast {}" , baseUrl: QUrl()); |
232 | QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8().constData()); |
233 | QCOMPARE(component.status(), QQmlComponent::Ready); |
234 | QScopedPointer<QObject> o(component.create()); |
235 | QVERIFY(o.data()); |
236 | verifyTypes(shouldHaveTestObject: true, shouldHaveFast: true); |
237 | } |
238 | |
239 | verifyTypes(shouldHaveTestObject: true, shouldHaveFast: false); // Fast is gone again, even though an event was still scheduled. |
240 | QQmlMetaType::freeUnusedTypesAndCaches(); |
241 | verifyTypes(shouldHaveTestObject: true, shouldHaveFast: false); // qmlRegisterType creates an undeletable type. |
242 | } |
243 | |
244 | class NetworkReply : public QNetworkReply |
245 | { |
246 | public: |
247 | NetworkReply() |
248 | { |
249 | open(mode: QIODevice::ReadOnly); |
250 | } |
251 | |
252 | void setData(const QByteArray &data) |
253 | { |
254 | if (isFinished()) |
255 | return; |
256 | m_buffer = data; |
257 | emit readyRead(); |
258 | setFinished(true); |
259 | emit finished(); |
260 | } |
261 | |
262 | void fail() |
263 | { |
264 | if (isFinished()) |
265 | return; |
266 | m_buffer.clear(); |
267 | setError(errorCode: ContentNotFoundError, errorString: "content not found" ); |
268 | emit error(ContentNotFoundError); |
269 | setFinished(true); |
270 | emit finished(); |
271 | } |
272 | |
273 | qint64 bytesAvailable() const override |
274 | { |
275 | return m_buffer.size(); |
276 | } |
277 | |
278 | qint64 readData(char *data, qint64 maxlen) override |
279 | { |
280 | if (m_buffer.length() < maxlen) |
281 | maxlen = m_buffer.length(); |
282 | std::memcpy(dest: data, src: m_buffer.data(), n: maxlen); |
283 | m_buffer.remove(index: 0, len: maxlen); |
284 | return maxlen; |
285 | } |
286 | |
287 | void abort() override |
288 | { |
289 | if (isFinished()) |
290 | return; |
291 | m_buffer.clear(); |
292 | setFinished(true); |
293 | emit finished(); |
294 | } |
295 | |
296 | private: |
297 | QByteArray m_buffer; |
298 | }; |
299 | |
300 | class NetworkAccessManager : public QNetworkAccessManager |
301 | { |
302 | Q_OBJECT |
303 | public: |
304 | |
305 | NetworkAccessManager(QObject *parent) : QNetworkAccessManager(parent) |
306 | { |
307 | } |
308 | |
309 | QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, |
310 | QIODevice *outgoingData) override |
311 | { |
312 | QUrl url = request.url(); |
313 | QString scheme = url.scheme(); |
314 | if (op != GetOperation || !scheme.endsWith(s: "+debug" )) |
315 | return QNetworkAccessManager::createRequest(op, request, outgoingData); |
316 | |
317 | scheme.chop(n: sizeof("+debug" ) - 1); |
318 | url.setScheme(scheme); |
319 | |
320 | NetworkReply *reply = new NetworkReply; |
321 | QString filename = QQmlFile::urlToLocalFileOrQrc(url); |
322 | QTimer::singleShot(interval: 10, context: reply, slot: [this, reply, filename]() { |
323 | if (filename.isEmpty()) { |
324 | reply->fail(); |
325 | } else { |
326 | QFile file(filename); |
327 | if (file.open(flags: QIODevice::ReadOnly)) { |
328 | emit loaded(filename); |
329 | reply->setData(transformQmldir(filename, content: file.readAll())); |
330 | } else |
331 | reply->fail(); |
332 | } |
333 | }); |
334 | return reply; |
335 | } |
336 | |
337 | QByteArray transformQmldir(const QString &filename, const QByteArray &content) |
338 | { |
339 | if (!filename.endsWith(s: "/qmldir" )) |
340 | return content; |
341 | |
342 | // Make qmldir plugin paths absolute, so that we don't try to load them over the network |
343 | QByteArray result; |
344 | QByteArray path = filename.toUtf8(); |
345 | path.chop(n: sizeof("qmldir" ) - 1); |
346 | for (QByteArray line : content.split(sep: '\n')) { |
347 | if (line.isEmpty()) |
348 | continue; |
349 | QList<QByteArray> segments = line.split(sep: ' '); |
350 | if (segments.startsWith(t: "plugin" )) { |
351 | if (segments.length() == 2) { |
352 | segments.append(t: path); |
353 | } else if (segments.length() == 3) { |
354 | if (!segments[2].startsWith(c: '/')) |
355 | segments[2] = path + segments[2]; |
356 | } else { |
357 | // Invalid plugin declaration. Ignore |
358 | } |
359 | result.append(a: segments.join(sep: ' ')); |
360 | } else { |
361 | result.append(a: line); |
362 | } |
363 | result.append(c: '\n'); |
364 | } |
365 | return result; |
366 | } |
367 | |
368 | signals: |
369 | void loaded(const QString &filename); |
370 | }; |
371 | |
372 | class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory |
373 | { |
374 | public: |
375 | QStringList loadedFiles; |
376 | |
377 | QNetworkAccessManager *create(QObject *parent) override |
378 | { |
379 | NetworkAccessManager *manager = new NetworkAccessManager(parent); |
380 | QObject::connect(sender: manager, signal: &NetworkAccessManager::loaded, slot: [this](const QString &filename) { |
381 | loadedFiles.append(t: filename); |
382 | }); |
383 | return manager; |
384 | } |
385 | }; |
386 | |
387 | class UrlInterceptor : public QQmlAbstractUrlInterceptor |
388 | { |
389 | public: |
390 | QUrl intercept(const QUrl &path, DataType type) override |
391 | { |
392 | Q_UNUSED(type); |
393 | if (!QQmlFile::isLocalFile(url: path)) |
394 | return path; |
395 | |
396 | QUrl result = path; |
397 | QString scheme = result.scheme(); |
398 | if (!scheme.endsWith(s: "+debug" )) |
399 | result.setScheme(scheme + "+debug" ); |
400 | return result; |
401 | } |
402 | }; |
403 | |
404 | void tst_QQMLTypeLoader::intercept() |
405 | { |
406 | qmlClearTypeRegistrations(); |
407 | |
408 | QQmlEngine engine; |
409 | engine.addImportPath(dir: dataDirectory()); |
410 | engine.addImportPath(QT_TESTCASE_BUILDDIR); |
411 | |
412 | UrlInterceptor interceptor; |
413 | NetworkAccessManagerFactory factory; |
414 | |
415 | engine.setUrlInterceptor(&interceptor); |
416 | engine.setNetworkAccessManagerFactory(&factory); |
417 | |
418 | QQmlComponent component(&engine, testFileUrl(fileName: "test_intercept.qml" )); |
419 | |
420 | QVERIFY(component.status() != QQmlComponent::Ready); |
421 | QTRY_VERIFY2(component.status() == QQmlComponent::Ready, |
422 | component.errorString().toUtf8().constData()); |
423 | |
424 | QScopedPointer<QObject> o(component.create()); |
425 | QVERIFY(o.data()); |
426 | |
427 | QTRY_COMPARE(o->property("created" ).toInt(), 2); |
428 | QTRY_COMPARE(o->property("loaded" ).toInt(), 2); |
429 | |
430 | QVERIFY(factory.loadedFiles.length() >= 6); |
431 | QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/test_intercept.qml" )); |
432 | QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/Intercept.qml" )); |
433 | QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/Fast/qmldir" )); |
434 | QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/Fast/Fast.qml" )); |
435 | QVERIFY(factory.loadedFiles.contains(dataDirectory() + "/GenericView.qml" )); |
436 | QVERIFY(factory.loadedFiles.contains(QLatin1String(QT_TESTCASE_BUILDDIR) + "/Slow/qmldir" )); |
437 | } |
438 | |
439 | void tst_QQMLTypeLoader::redirect() |
440 | { |
441 | TestHTTPServer server; |
442 | QVERIFY2(server.listen(), qPrintable(server.errorString())); |
443 | QVERIFY(server.serveDirectory(dataDirectory())); |
444 | server.addRedirect(filename: "Base.qml" , redirectName: server.urlString(documentPath: "/redirected/Redirected.qml" )); |
445 | |
446 | QQmlEngine engine; |
447 | QQmlComponent component(&engine); |
448 | component.loadUrl(url: server.urlString(documentPath: "/Load.qml" ), mode: QQmlComponent::Asynchronous); |
449 | QTRY_VERIFY2(component.isReady(), qPrintable(component.errorString())); |
450 | |
451 | QScopedPointer<QObject> object {component.create()}; |
452 | QTRY_COMPARE(object->property("xy" ).toInt(), 323232); |
453 | } |
454 | |
455 | void tst_QQMLTypeLoader::qmlSingletonWithinModule() |
456 | { |
457 | qmlClearTypeRegistrations(); |
458 | QQmlEngine engine; |
459 | qmlRegisterSingletonType(url: testFileUrl(fileName: "Singleton.qml" ), uri: "modulewithsingleton" , versionMajor: 1, versionMinor: 0, qmlName: "Singleton" ); |
460 | |
461 | QQmlComponent component(&engine, testFileUrl(fileName: "singletonuser.qml" )); |
462 | QCOMPARE(component.status(), QQmlComponent::Ready); |
463 | QScopedPointer<QObject> obj(component.create()); |
464 | QVERIFY(!obj.isNull()); |
465 | QVERIFY(obj->property("ok" ).toBool()); |
466 | } |
467 | |
468 | static void checkCleanCacheLoad(const QString &testCase) |
469 | { |
470 | #if QT_CONFIG(process) |
471 | const char *skipKey = "QT_TST_QQMLTYPELOADER_SKIP_MISMATCH" ; |
472 | if (qEnvironmentVariableIsSet(varName: skipKey)) |
473 | return; |
474 | for (int i = 0; i < 5; ++i) { |
475 | QProcess child; |
476 | child.setProgram(QCoreApplication::applicationFilePath()); |
477 | child.setArguments(QStringList(testCase)); |
478 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); |
479 | env.insert(name: QLatin1String("QT_LOGGING_RULES" ), value: QLatin1String("qt.qml.diskcache.debug=true" )); |
480 | env.insert(name: QLatin1String(skipKey), value: QLatin1String("1" )); |
481 | child.setProcessEnvironment(env); |
482 | child.start(); |
483 | QVERIFY(child.waitForFinished()); |
484 | QCOMPARE(child.exitCode(), 0); |
485 | QVERIFY(!child.readAllStandardOutput().contains("Checksum mismatch for cached version" )); |
486 | QVERIFY(!child.readAllStandardError().contains("Checksum mismatch for cached version" )); |
487 | } |
488 | #else |
489 | Q_UNUSED(testCase); |
490 | #endif |
491 | } |
492 | |
493 | void tst_QQMLTypeLoader::multiSingletonModule() |
494 | { |
495 | qmlClearTypeRegistrations(); |
496 | QQmlEngine engine; |
497 | engine.addImportPath(dir: testFile(fileName: "imports" )); |
498 | |
499 | qmlRegisterSingletonType(url: testFileUrl(fileName: "CppRegisteredSingleton1.qml" ), uri: "cppsingletonmodule" , |
500 | versionMajor: 1, versionMinor: 0, qmlName: "CppRegisteredSingleton1" ); |
501 | qmlRegisterSingletonType(url: testFileUrl(fileName: "CppRegisteredSingleton2.qml" ), uri: "cppsingletonmodule" , |
502 | versionMajor: 1, versionMinor: 0, qmlName: "CppRegisteredSingleton2" ); |
503 | |
504 | QQmlComponent component(&engine, testFileUrl(fileName: "multisingletonuser.qml" )); |
505 | QCOMPARE(component.status(), QQmlComponent::Ready); |
506 | QScopedPointer<QObject> obj(component.create()); |
507 | QVERIFY(!obj.isNull()); |
508 | QVERIFY(obj->property("ok" ).toBool()); |
509 | |
510 | checkCleanCacheLoad(testCase: QLatin1String("multiSingletonModule" )); |
511 | } |
512 | |
513 | void tst_QQMLTypeLoader::implicitComponentModule() |
514 | { |
515 | QQmlEngine engine; |
516 | QQmlComponent component(&engine, testFileUrl(fileName: "implicitcomponent.qml" )); |
517 | QCOMPARE(component.status(), QQmlComponent::Ready); |
518 | QScopedPointer<QObject> obj(component.create()); |
519 | QVERIFY(!obj.isNull()); |
520 | |
521 | checkCleanCacheLoad(testCase: QLatin1String("implicitComponentModule" )); |
522 | } |
523 | |
524 | void tst_QQMLTypeLoader::customDiskCachePath() |
525 | { |
526 | #if QT_CONFIG(process) |
527 | const char *skipKey = "QT_TST_QQMLTYPELOADER_SKIP_MISMATCH" ; |
528 | if (qEnvironmentVariableIsSet(varName: skipKey)) { |
529 | QQmlEngine engine; |
530 | QQmlComponent component(&engine, testFileUrl(fileName: "Base.qml" )); |
531 | QCOMPARE(component.status(), QQmlComponent::Ready); |
532 | QScopedPointer<QObject> obj(component.create()); |
533 | QVERIFY(!obj.isNull()); |
534 | return; |
535 | } |
536 | |
537 | QTemporaryDir dir; |
538 | QProcess child; |
539 | child.setProgram(QCoreApplication::applicationFilePath()); |
540 | child.setArguments(QStringList(QLatin1String("customDiskCachePath" ))); |
541 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); |
542 | env.insert(name: QLatin1String(skipKey), value: QLatin1String("1" )); |
543 | env.insert(name: QLatin1String("QML_DISK_CACHE_PATH" ), value: dir.path()); |
544 | child.setProcessEnvironment(env); |
545 | child.start(); |
546 | QVERIFY(child.waitForFinished()); |
547 | QCOMPARE(child.exitCode(), 0); |
548 | QDir cacheDir(dir.path()); |
549 | QVERIFY(!cacheDir.isEmpty()); |
550 | #endif |
551 | } |
552 | |
553 | void tst_QQMLTypeLoader::qrcRootPathUrl() |
554 | { |
555 | QQmlEngine engine; |
556 | QQmlComponent component(&engine, testFileUrl(fileName: "qrcRootPath.qml" )); |
557 | QCOMPARE(component.status(), QQmlComponent::Ready); |
558 | } |
559 | |
560 | void tst_QQMLTypeLoader::implicitImport() |
561 | { |
562 | QQmlEngine engine; |
563 | engine.addImportPath(dir: testFile(fileName: "imports" )); |
564 | QQmlComponent component(&engine, testFileUrl(fileName: "implicitimporttest.qml" )); |
565 | QVERIFY2(component.isReady(), qPrintable(component.errorString())); |
566 | QScopedPointer<QObject> obj(component.create()); |
567 | QVERIFY(!obj.isNull()); |
568 | } |
569 | |
570 | void tst_QQMLTypeLoader::compositeSingletonCycle() |
571 | { |
572 | TestHTTPServer server; |
573 | QVERIFY2(server.listen(), qPrintable(server.errorString())); |
574 | QVERIFY(server.serveDirectory(dataDirectory())); |
575 | |
576 | QQmlEngine engine; |
577 | QQmlComponent component(&engine); |
578 | engine.addImportPath(dir: server.baseUrl().toString()); |
579 | component.loadUrl(url: server.urlString(documentPath: "Com/Orga/Handlers/Handler.qml" ), mode: QQmlComponent::Asynchronous); |
580 | QTRY_VERIFY2(component.isReady(), qPrintable(component.errorString())); |
581 | |
582 | QScopedPointer<QObject> object {component.create()}; |
583 | QVERIFY(object); |
584 | QCOMPARE(qvariant_cast<QColor>(object->property("color" )), QColorConstants::Black); |
585 | } |
586 | |
587 | void tst_QQMLTypeLoader::declarativeCppType() |
588 | { |
589 | QQmlEngine engine; |
590 | QQmlComponent component(&engine, testFileUrl(fileName: "declarativeCppType.qml" )); |
591 | QCOMPARE(component.status(), QQmlComponent::Ready); |
592 | QScopedPointer<QObject> obj(component.create()); |
593 | QVERIFY(!obj.isNull()); |
594 | } |
595 | |
596 | void tst_QQMLTypeLoader::circularDependency() |
597 | { |
598 | QQmlEngine engine; |
599 | QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression("Cyclic dependency detected between (.*) and (.*)" )); |
600 | QQmlComponent component(&engine, testFileUrl(fileName: "CircularDependency.qml" )); |
601 | QCOMPARE(component.status(), QQmlComponent::Null); |
602 | } |
603 | |
604 | QTEST_MAIN(tst_QQMLTypeLoader) |
605 | |
606 | #include "tst_qqmltypeloader.moc" |
607 | |