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#include <qtest.h>
29#include <qdir.h>
30#include <QtQml/qqmlengine.h>
31#include <QtQml/qqmlcomponent.h>
32#include <QtQml/qqmlcontext.h>
33#include <QtQml/qqmlextensionplugin.h>
34#include <QtCore/qjsondocument.h>
35#include <QtCore/qjsonarray.h>
36#include <QDebug>
37
38#if defined(Q_OS_MAC)
39// For _PC_CASE_SENSITIVE
40#include <unistd.h>
41#endif
42
43#include "../../shared/testhttpserver.h"
44#include "../../shared/util.h"
45
46// Note: this test does not use module identifier directives in the qmldir files, because
47// it would result in repeated attempts to insert types into the same namespace.
48// This occurs because type registration is process-global, while the test
49// cases should really be run in proper per-process isolation.
50
51class tst_qqmlmoduleplugin : public QQmlDataTest
52{
53 Q_OBJECT
54public:
55
56private slots:
57 virtual void initTestCase();
58 void importsPlugin();
59 void importsPlugin_data();
60 void importsMixedQmlCppPlugin();
61 void incorrectPluginCase();
62 void importPluginWithQmlFile();
63 void remoteImportWithQuotedUrl();
64 void remoteImportWithUnquotedUri();
65 void versionNotInstalled();
66 void versionNotInstalled_data();
67 void implicitQmldir();
68 void implicitQmldir_data();
69 void importsNested();
70 void importsNested_data();
71 void importLocalModule();
72 void importLocalModule_data();
73 void importStrictModule();
74 void importStrictModule_data();
75 void importProtectedModule();
76 void importVersionedModule();
77 void importsChildPlugin();
78 void importsChildPlugin2();
79 void importsChildPlugin21();
80 void parallelPluginImport();
81 void multiSingleton();
82
83private:
84 QString m_importsDirectory;
85 QString m_dataImportsDirectory;
86};
87
88class PluginThatWaits : public QQmlExtensionPlugin
89{
90public:
91 static QByteArray metaData;
92
93 static QMutex initializeEngineEntered;
94 static QWaitCondition waitingForInitializeEngineEntry;
95 static QMutex leavingInitializeEngine;
96 static QWaitCondition waitingForInitializeEngineLeave;
97
98 void registerTypes(const char *uri) override
99 {
100 qmlRegisterModule(uri, versionMajor: 1, versionMinor: 0);
101 }
102
103 void initializeEngine(QQmlEngine *, const char *) override
104 {
105 initializeEngineEntered.lock();
106 leavingInitializeEngine.lock();
107 waitingForInitializeEngineEntry.wakeOne();
108 initializeEngineEntered.unlock();
109 waitingForInitializeEngineLeave.wait(lockedMutex: &leavingInitializeEngine);
110 leavingInitializeEngine.unlock();
111 }
112};
113QByteArray PluginThatWaits::metaData;
114QMutex PluginThatWaits::initializeEngineEntered;
115QWaitCondition PluginThatWaits::waitingForInitializeEngineEntry;
116QMutex PluginThatWaits::leavingInitializeEngine;
117QWaitCondition PluginThatWaits::waitingForInitializeEngineLeave;
118
119class SecondStaticPlugin : public QQmlExtensionPlugin
120{
121public:
122 static QByteArray metaData;
123
124 void registerTypes(const char *uri) override
125 {
126 qmlRegisterModule(uri, versionMajor: 1, versionMinor: 0);
127 }
128};
129QByteArray SecondStaticPlugin::metaData;
130
131template <typename PluginType>
132void registerStaticPlugin(const char *uri)
133{
134 QStaticPlugin plugin;
135 plugin.instance = []() {
136 static PluginType plugin;
137 return static_cast<QObject*>(&plugin);
138 };
139
140 QJsonObject md;
141 md.insert(QStringLiteral("IID"), QQmlExtensionInterface_iid);
142 QJsonArray uris;
143 uris.append(value: uri);
144 md.insert(QStringLiteral("uri"), uris);
145
146 PluginType::metaData.append(QLatin1String("QTMETADATA "));
147 PluginType::metaData.append(QJsonDocument(md).toBinaryData());
148
149 plugin.rawMetaData = []() {
150 return PluginType::metaData.constData();
151 };
152 qRegisterStaticPluginFunction(staticPlugin: plugin);
153};
154
155void tst_qqmlmoduleplugin::initTestCase()
156{
157 QQmlDataTest::initTestCase();
158 m_importsDirectory = QFINDTESTDATA(QStringLiteral("imports"));
159 QVERIFY2(QFileInfo(m_importsDirectory).isDir(),
160 qPrintable(QString::fromLatin1("Imports directory '%1' does not exist.").arg(m_importsDirectory)));
161 m_dataImportsDirectory = directory() + QStringLiteral("/imports");
162 QVERIFY2(QFileInfo(m_dataImportsDirectory).isDir(),
163 qPrintable(QString::fromLatin1("Imports directory '%1' does not exist.").arg(m_dataImportsDirectory)));
164
165 registerStaticPlugin<PluginThatWaits>(uri: "moduleWithWaitingPlugin");
166 registerStaticPlugin<SecondStaticPlugin>(uri: "moduleWithStaticPlugin");
167}
168
169#define VERIFY_ERRORS(errorfile) \
170 if (!errorfile) { \
171 if (qgetenv("DEBUG") != "" && !component.errors().isEmpty()) \
172 qWarning() << "Unexpected Errors:" << component.errors(); \
173 QVERIFY(!component.isError()); \
174 QVERIFY(component.errors().isEmpty()); \
175 } else { \
176 QString verify_errors_file_name = testFile(errorfile); \
177 QFile file(verify_errors_file_name); \
178 QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); \
179 QByteArray data = file.readAll(); \
180 file.close(); \
181 QList<QByteArray> expected = data.split('\n'); \
182 expected.removeAll(QByteArray("")); \
183 QList<QQmlError> errors = component.errors(); \
184 QList<QByteArray> actual; \
185 for (int ii = 0; ii < errors.count(); ++ii) { \
186 const QQmlError &error = errors.at(ii); \
187 QByteArray errorStr = QByteArray::number(error.line()) + ':' + \
188 QByteArray::number(error.column()) + ':' + \
189 error.description().toUtf8(); \
190 actual << errorStr; \
191 } \
192 if (qgetenv("DEBUG") != "" && expected != actual) { \
193 qWarning() << "Expected:" << expected << "Actual:" << actual; \
194 } \
195 if (qgetenv("QDECLARATIVELANGUAGE_UPDATEERRORS") != "" && expected != actual) {\
196 QFile file(testFile(errorfile)); \
197 QVERIFY(file.open(QIODevice::WriteOnly)); \
198 for (int ii = 0; ii < actual.count(); ++ii) { \
199 file.write(actual.at(ii)); file.write("\n"); \
200 } \
201 file.close(); \
202 } else { \
203 QCOMPARE(expected, actual); \
204 } \
205 }
206
207void tst_qqmlmoduleplugin::importsPlugin()
208{
209 QFETCH(QString, suffix);
210 QFETCH(QString, qmlFile);
211
212 QQmlEngine engine;
213 engine.addImportPath(dir: m_importsDirectory);
214 QTest::ignoreMessage(type: QtWarningMsg, qPrintable(QString("plugin%1 created").arg(suffix)));
215 QTest::ignoreMessage(type: QtWarningMsg, qPrintable(QString("import%1 worked").arg(suffix)));
216 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
217 QQmlComponent component(&engine, testFileUrl(fileName: qmlFile));
218 foreach (QQmlError err, component.errors())
219 qWarning() << err;
220 VERIFY_ERRORS(0);
221 QObject *object = component.create();
222 QVERIFY(object != nullptr);
223 QCOMPARE(object->property("value").toInt(),123);
224 delete object;
225}
226
227void tst_qqmlmoduleplugin::importsPlugin_data()
228{
229 QTest::addColumn<QString>(name: "suffix");
230 QTest::addColumn<QString>(name: "qmlFile");
231
232 QTest::newRow(dataTag: "1.0") << "" << "works.qml";
233 QTest::newRow(dataTag: "2.0") << "2" << "works2.qml";
234 QTest::newRow(dataTag: "2.1") << "2.1" << "works21.qml";
235 QTest::newRow(dataTag: "2.2") << "2.2" << "works22.qml";
236}
237
238void tst_qqmlmoduleplugin::incorrectPluginCase()
239{
240 QQmlEngine engine;
241 engine.addImportPath(dir: m_importsDirectory);
242
243 QQmlComponent component(&engine, testFileUrl(QStringLiteral("incorrectCase.qml")));
244
245 QList<QQmlError> errors = component.errors();
246 QCOMPARE(errors.count(), 1);
247
248 QString expectedError = QLatin1String("module \"org.qtproject.WrongCase\" plugin \"PluGin\" not found");
249
250#if defined(Q_OS_MAC) || defined(Q_OS_WIN32)
251 bool caseSensitive = true;
252#if defined(Q_OS_MAC)
253 caseSensitive = pathconf(QDir::currentPath().toLatin1().constData(), _PC_CASE_SENSITIVE);
254 QString libname = "libPluGin.dylib";
255#elif defined(Q_OS_WIN32)
256 caseSensitive = false;
257 QString libname = "PluGin.dll";
258#endif
259 if (!caseSensitive) {
260 expectedError = QLatin1String("plugin cannot be loaded for module \"org.qtproject.WrongCase\": File name case mismatch for \"")
261 + QDir(m_importsDirectory).filePath("org/qtproject/WrongCase/" + libname)
262 + QLatin1Char('"');
263 }
264#endif
265
266 QCOMPARE(errors.at(0).description(), expectedError);
267}
268
269void tst_qqmlmoduleplugin::importPluginWithQmlFile()
270{
271 QString path = m_importsDirectory;
272
273 // QTBUG-16885: adding an import path with a lower-case "c:" causes assert failure
274 // (this only happens if the plugin includes pure QML files)
275 #ifdef Q_OS_WIN
276 QVERIFY(path.at(0).isUpper() && path.at(1) == QLatin1Char(':'));
277 path = path.at(0).toLower() + path.mid(1);
278 #endif
279
280 QQmlEngine engine;
281 engine.addImportPath(dir: path);
282
283 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestPluginWithQmlFile' does not contain a module identifier directive - it cannot be protected from external registrations.");
284
285 QQmlComponent component(&engine, testFileUrl(QStringLiteral("pluginWithQmlFile.qml")));
286 foreach (QQmlError err, component.errors())
287 qWarning() << err;
288 VERIFY_ERRORS(0);
289 QObject *object = component.create();
290 QVERIFY(object != nullptr);
291 delete object;
292}
293
294void tst_qqmlmoduleplugin::remoteImportWithQuotedUrl()
295{
296 ThreadedTestHTTPServer server(m_dataImportsDirectory);
297
298 QQmlEngine engine;
299 QQmlComponent component(&engine);
300 const QString qml = "import \"" + server.urlString(documentPath: "/org/qtproject/PureQmlModule") + "\" \nComponentA { width: 300; ComponentB{} }";
301 component.setData(qml.toUtf8(), baseUrl: QUrl());
302
303 QTRY_COMPARE(component.status(), QQmlComponent::Ready);
304 QObject *object = component.create();
305 QCOMPARE(object->property("width").toInt(), 300);
306 QVERIFY(object != nullptr);
307 delete object;
308
309 foreach (QQmlError err, component.errors())
310 qWarning() << err;
311 VERIFY_ERRORS(0);
312}
313
314void tst_qqmlmoduleplugin::remoteImportWithUnquotedUri()
315{
316 TestHTTPServer server;
317 QVERIFY2(server.listen(), qPrintable(server.errorString()));
318 server.serveDirectory(m_dataImportsDirectory);
319
320 QQmlEngine engine;
321 engine.addImportPath(dir: m_dataImportsDirectory);
322 QQmlComponent component(&engine);
323 component.setData("import org.qtproject.PureQmlModule 1.0 \nComponentA { width: 300; ComponentB{} }", baseUrl: QUrl());
324
325
326 QTRY_COMPARE(component.status(), QQmlComponent::Ready);
327 QObject *object = component.create();
328 QVERIFY(object != nullptr);
329 QCOMPARE(object->property("width").toInt(), 300);
330 delete object;
331
332 foreach (QQmlError err, component.errors())
333 qWarning() << err;
334 VERIFY_ERRORS(0);
335}
336
337static QByteArray msgComponentError(const QQmlComponent &c, const QQmlEngine *engine /* = 0 */)
338{
339 QString result;
340 const QList<QQmlError> errors = c.errors();
341 QTextStream str(&result);
342 str << "Component '" << c.url().toString() << "' has " << errors.size() << " errors: '";
343 for (int i = 0; i < errors.size(); ++i) {
344 if (i)
345 str << ", '";
346 str << errors.at(i).toString() << '\'';
347 }
348 if (!engine) {
349 if (QQmlContext *context = c.creationContext())
350 engine = context->engine();
351 }
352 if (engine) {
353 str << " Import paths: (" << engine->importPathList().join(QStringLiteral(", "))
354 << ") Plugin paths: (" << engine->pluginPathList().join(QStringLiteral(", "))
355 << ')';
356 }
357 return result.toLocal8Bit();
358}
359
360// QTBUG-17324
361
362void tst_qqmlmoduleplugin::importsMixedQmlCppPlugin()
363{
364 QQmlEngine engine;
365 engine.addImportPath(dir: m_importsDirectory);
366
367 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlMixedPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
368
369 {
370 QQmlComponent component(&engine, testFileUrl(QStringLiteral("importsMixedQmlCppPlugin.qml")));
371
372 QObject *o = component.create();
373 QVERIFY2(o != nullptr, msgComponentError(component, &engine));
374 QCOMPARE(o->property("test").toBool(), true);
375 delete o;
376 }
377
378 {
379 QQmlComponent component(&engine, testFileUrl(QStringLiteral("importsMixedQmlCppPlugin.2.qml")));
380
381 QObject *o = component.create();
382 QVERIFY2(o != nullptr, msgComponentError(component, &engine));
383 QCOMPARE(o->property("test").toBool(), true);
384 QCOMPARE(o->property("test2").toBool(), true);
385 delete o;
386 }
387
388
389}
390
391void tst_qqmlmoduleplugin::versionNotInstalled_data()
392{
393 QTest::addColumn<QString>(name: "file");
394 QTest::addColumn<QString>(name: "errorFile");
395
396 QTest::newRow(dataTag: "versionNotInstalled") << "versionNotInstalled.qml" << "versionNotInstalled.errors.txt";
397 QTest::newRow(dataTag: "versionNotInstalled") << "versionNotInstalled.2.qml" << "versionNotInstalled.2.errors.txt";
398}
399
400void tst_qqmlmoduleplugin::versionNotInstalled()
401{
402 QFETCH(QString, file);
403 QFETCH(QString, errorFile);
404
405 QQmlEngine engine;
406 engine.addImportPath(dir: m_importsDirectory);
407
408 static int count = 0;
409 if (++count == 1)
410 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlVersionPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
411
412 QQmlComponent component(&engine, testFileUrl(fileName: file));
413 VERIFY_ERRORS(errorFile.toLatin1().constData());
414}
415
416
417// test that errors are reporting correctly for plugin loading and qmldir parsing
418void tst_qqmlmoduleplugin::implicitQmldir_data()
419{
420 QTest::addColumn<QString>(name: "directory");
421 QTest::addColumn<QString>(name: "file");
422 QTest::addColumn<QString>(name: "errorFile");
423
424 // parsing qmldir succeeds, but plugin specified in the qmldir file doesn't exist
425 QTest::newRow(dataTag: "implicitQmldir") << "implicit1" << "temptest.qml" << "implicitQmldir.errors.txt";
426
427 // parsing qmldir fails due to syntax errors, etc.
428 QTest::newRow(dataTag: "implicitQmldir2") << "implicit2" << "temptest2.qml" << "implicitQmldir.2.errors.txt";
429}
430void tst_qqmlmoduleplugin::implicitQmldir()
431{
432 QFETCH(QString, directory);
433 QFETCH(QString, file);
434 QFETCH(QString, errorFile);
435
436 QString importPath = testFile(fileName: directory);
437 QString fileName = directory + QDir::separator() + file;
438 QString errorFileName = directory + QDir::separator() + errorFile;
439 QUrl testUrl = testFileUrl(fileName);
440
441 QQmlEngine engine;
442 engine.addImportPath(dir: importPath);
443
444 QQmlComponent component(&engine, testUrl);
445 QList<QQmlError> errors = component.errors();
446 VERIFY_ERRORS(errorFileName.toLatin1().constData());
447 QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready");
448 QObject *obj = component.create();
449 QVERIFY(!obj);
450 delete obj;
451}
452
453void tst_qqmlmoduleplugin::importsNested_data()
454{
455 QTest::addColumn<QString>(name: "file");
456 QTest::addColumn<QString>(name: "errorFile");
457
458 // Note: no other test case should import the plugin used for this test, or the
459 // wrong order test will pass spuriously
460 QTest::newRow(dataTag: "wrongOrder") << "importsNested.1.qml" << "importsNested.1.errors.txt";
461 QTest::newRow(dataTag: "missingImport") << "importsNested.3.qml" << "importsNested.3.errors.txt";
462 QTest::newRow(dataTag: "invalidVersion") << "importsNested.4.qml" << "importsNested.4.errors.txt";
463 QTest::newRow(dataTag: "correctOrder") << "importsNested.2.qml" << QString();
464}
465void tst_qqmlmoduleplugin::importsNested()
466{
467 QFETCH(QString, file);
468 QFETCH(QString, errorFile);
469
470 // Note: because imports are cached between test case data rows (and the plugins remain loaded),
471 // these tests should really be run in new instances of the app...
472
473 QQmlEngine engine;
474 engine.addImportPath(dir: m_importsDirectory);
475
476 if (!errorFile.isEmpty()) {
477 QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready");
478 }
479
480 static int count = 0;
481 if (++count == 1)
482 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlNestedPluginType' does not contain a module identifier directive - it cannot be protected from external registrations.");
483
484 QQmlComponent component(&engine, testFile(fileName: file));
485 QObject *obj = component.create();
486
487 if (errorFile.isEmpty()) {
488 if (qgetenv(varName: "DEBUG") != "" && !component.errors().isEmpty())
489 qWarning() << "Unexpected Errors:" << component.errors();
490 QVERIFY(obj);
491 delete obj;
492 } else {
493 QList<QQmlError> errors = component.errors();
494 VERIFY_ERRORS(errorFile.toLatin1().constData());
495 QVERIFY(!obj);
496 }
497}
498
499void tst_qqmlmoduleplugin::importLocalModule()
500{
501 QFETCH(QString, qml);
502 QFETCH(int, majorVersion);
503 QFETCH(int, minorVersion);
504
505 QQmlEngine engine;
506 QQmlComponent component(&engine);
507 component.setData(qml.toUtf8(), baseUrl: testFileUrl(fileName: "empty.qml"));
508
509 QScopedPointer<QObject> object(component.create());
510 QVERIFY(object != nullptr);
511 QCOMPARE(object->property("majorVersion").value<int>(), majorVersion);
512 QCOMPARE(object->property("minorVersion").value<int>(), minorVersion);
513}
514
515void tst_qqmlmoduleplugin::importLocalModule_data()
516{
517 QTest::addColumn<QString>(name: "qml");
518 QTest::addColumn<int>(name: "majorVersion");
519 QTest::addColumn<int>(name: "minorVersion");
520
521 QTest::newRow(dataTag: "default version")
522 << "import \"localModule\"\n"
523 "TestComponent {}"
524 << 2 << 0;
525
526 QTest::newRow(dataTag: "specific version")
527 << "import \"localModule\" 1.1\n"
528 "TestComponent {}"
529 << 1 << 1;
530
531 QTest::newRow(dataTag: "lesser version")
532 << "import \"localModule\" 1.0\n"
533 "TestComponent {}"
534 << 1 << 0;
535
536 // Note: this does not match the behaviour of installed modules, which fail for this case:
537 QTest::newRow(dataTag: "nonexistent version")
538 << "import \"localModule\" 1.3\n"
539 "TestComponent {}"
540 << 1 << 1;
541
542 QTest::newRow(dataTag: "high version")
543 << "import \"localModule\" 2.0\n"
544 "TestComponent {}"
545 << 2 << 0;
546}
547
548void tst_qqmlmoduleplugin::importStrictModule()
549{
550 QFETCH(QString, qml);
551 QFETCH(QString, warning);
552 QFETCH(QString, error);
553
554 if (!warning.isEmpty())
555 QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning));
556
557 QQmlEngine engine;
558 engine.addImportPath(dir: m_importsDirectory);
559
560 QUrl url(testFileUrl(fileName: "empty.qml"));
561
562 QQmlComponent component(&engine);
563 component.setData(qml.toUtf8(), baseUrl: url);
564
565 if (error.isEmpty()) {
566 QScopedPointer<QObject> object(component.create());
567 QVERIFY(object != nullptr);
568 } else {
569 QVERIFY(!component.isReady());
570 QCOMPARE(component.errors().count(), 1);
571 QCOMPARE(component.errors().first().toString(), url.toString() + error);
572 }
573}
574
575void tst_qqmlmoduleplugin::importStrictModule_data()
576{
577 QTest::addColumn<QString>(name: "qml");
578 QTest::addColumn<QString>(name: "warning");
579 QTest::addColumn<QString>(name: "error");
580
581 QTest::newRow(dataTag: "success")
582 << "import org.qtproject.StrictModule 1.0\n"
583 "MyPluginType {}"
584 << QString()
585 << QString();
586
587 QTest::newRow(dataTag: "two strict modules with different major version")
588 << "import org.qtproject.StrictModule 1.0\n"
589 "import org.qtproject.StrictModule 2.0\n"
590 "MyPluginType {}"
591 << QString()
592 << QString();
593
594 QTest::newRow(dataTag: "old namespaced strict module")
595 << "import org.qtproject.StrictModule 1.0 as Old\n"
596 "import org.qtproject.StrictModule 2.0 as New\n"
597 "Old.MyPluginType {}"
598 << QString()
599 << QString();
600
601 QTest::newRow(dataTag: "new namespaced strict modules")
602 << "import org.qtproject.StrictModule 1.0 as Old\n"
603 "import org.qtproject.StrictModule 2.0 as New\n"
604 "New.MyPluginType {}"
605 << QString()
606 << QString();
607
608 QTest::newRow(dataTag: "non-strict clash")
609 << "import org.qtproject.NonstrictModule 1.0\n"
610 "MyPluginType {}"
611 << "Module 'org.qtproject.NonstrictModule' does not contain a module identifier directive - it cannot be protected from external registrations."
612 << ":1:1: plugin cannot be loaded for module \"org.qtproject.NonstrictModule\": Cannot install element 'MyPluginType' into protected module 'org.qtproject.StrictModule' version '1'";
613
614 QTest::newRow(dataTag: "non-strict preemption")
615 << "import org.qtproject.PreemptiveModule 1.0\n"
616 "import org.qtproject.PreemptedStrictModule 1.0\n"
617 "MyPluginType {}"
618 << "Module 'org.qtproject.PreemptiveModule' does not contain a module identifier directive - it cannot be protected from external registrations."
619 << ":2:1: plugin cannot be loaded for module \"org.qtproject.PreemptedStrictModule\": Namespace 'org.qtproject.PreemptedStrictModule' has already been used for type registration";
620
621 QTest::newRow(dataTag: "invalid namespace")
622 << "import org.qtproject.InvalidNamespaceModule 1.0\n"
623 "MyPluginType {}"
624 << QString()
625 << ":1:1: plugin cannot be loaded for module \"org.qtproject.InvalidNamespaceModule\": Module namespace 'org.qtproject.AwesomeModule' does not match import URI 'org.qtproject.InvalidNamespaceModule'";
626
627 QTest::newRow(dataTag: "module directive must be first")
628 << "import org.qtproject.InvalidFirstCommandModule 1.0\n"
629 "MyPluginType {}"
630 << QString()
631 << ":1:1: module identifier directive must be the first directive in a qmldir file";
632}
633
634void tst_qqmlmoduleplugin::importProtectedModule()
635{
636 //TODO: More than basic test (test errors,test inverse works...)
637 qmlRegisterType<QObject>(uri: "org.qtproject.ProtectedModule", versionMajor: 1, versionMinor: 0, qmlName: "TestType");
638 qmlProtectModule(uri: "org.qtproject.ProtectedModule", majVersion: 1);
639
640 QQmlEngine engine;
641 engine.addImportPath(dir: m_importsDirectory);
642
643 QUrl url(testFileUrl(fileName: "empty.qml"));
644
645 QString qml = "import org.qtproject.ProtectedModule 1.0\n TestType {}\n";
646 QQmlComponent component(&engine);
647 component.setData(qml.toUtf8(), baseUrl: url);
648 //If plugin is loaded due to import, should assert
649 QScopedPointer<QObject> object(component.create());
650 //qDebug() << component.errorString();
651 QVERIFY(object != nullptr);
652}
653
654void tst_qqmlmoduleplugin::importVersionedModule()
655{
656 qmlRegisterType<QObject>(uri: "org.qtproject.VersionedModule", versionMajor: 1, versionMinor: 0, qmlName: "TestType");
657 qmlRegisterModule(uri: "org.qtproject.VersionedModule", versionMajor: 1, versionMinor: 1);
658
659 QQmlEngine engine;
660 engine.addImportPath(dir: m_importsDirectory);
661
662 QUrl url(testFileUrl(fileName: "empty.qml"));
663
664 QQmlComponent component(&engine);
665 component.setData("import org.qtproject.VersionedModule 1.0\n TestType {}\n", baseUrl: url);
666 QScopedPointer<QObject> object10(component.create());
667 QVERIFY(!object10.isNull());
668
669 component.setData("import org.qtproject.VersionedModule 1.1\n TestType {}\n", baseUrl: url);
670 QScopedPointer<QObject> object11(component.create());
671 QVERIFY(!object11.isNull());
672
673 component.setData("import org.qtproject.VersionedModule 1.2\n TestType {}\n", baseUrl: url);
674 QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready");
675 QScopedPointer<QObject> object12(component.create());
676 QVERIFY(object12.isNull());
677 QCOMPARE(component.errorString(), QString("%1:1 module \"org.qtproject.VersionedModule\" version 1.2 is not installed\n").arg(url.toString()));
678}
679
680void tst_qqmlmoduleplugin::importsChildPlugin()
681{
682 QQmlEngine engine;
683 engine.addImportPath(dir: m_importsDirectory);
684 QTest::ignoreMessage(type: QtWarningMsg, message: "child plugin created");
685 QTest::ignoreMessage(type: QtWarningMsg, message: "child import worked");
686 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlPluginType.ChildPlugin' does not contain a module identifier directive - it cannot be protected from external registrations.");
687 QQmlComponent component(&engine, testFileUrl(QStringLiteral("child.qml")));
688 foreach (QQmlError err, component.errors())
689 qWarning() << err;
690 VERIFY_ERRORS(0);
691 QObject *object = component.create();
692 QVERIFY(object != nullptr);
693 QCOMPARE(object->property("value").toInt(),123);
694 delete object;
695}
696
697void tst_qqmlmoduleplugin::importsChildPlugin2()
698{
699 QQmlEngine engine;
700 engine.addImportPath(dir: m_importsDirectory);
701 QTest::ignoreMessage(type: QtWarningMsg, message: "child plugin2 created");
702 QTest::ignoreMessage(type: QtWarningMsg, message: "child import2 worked");
703 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlPluginType.ChildPlugin' does not contain a module identifier directive - it cannot be protected from external registrations.");
704 QQmlComponent component(&engine, testFileUrl(QStringLiteral("child2.qml")));
705 foreach (QQmlError err, component.errors())
706 qWarning() << err;
707 VERIFY_ERRORS(0);
708 QObject *object = component.create();
709 QVERIFY(object != nullptr);
710 QCOMPARE(object->property("value").toInt(),123);
711 delete object;
712}
713
714void tst_qqmlmoduleplugin::importsChildPlugin21()
715{
716 QQmlEngine engine;
717 engine.addImportPath(dir: m_importsDirectory);
718 QTest::ignoreMessage(type: QtWarningMsg, message: "child plugin2.1 created");
719 QTest::ignoreMessage(type: QtWarningMsg, message: "child import2.1 worked");
720 QTest::ignoreMessage(type: QtWarningMsg, message: "Module 'org.qtproject.AutoTestQmlPluginType.ChildPlugin' does not contain a module identifier directive - it cannot be protected from external registrations.");
721 QQmlComponent component(&engine, testFileUrl(QStringLiteral("child21.qml")));
722 foreach (QQmlError err, component.errors())
723 qWarning() << err;
724 VERIFY_ERRORS(0);
725 QObject *object = component.create();
726 QVERIFY(object != nullptr);
727 QCOMPARE(object->property("value").toInt(),123);
728 delete object;
729}
730
731void tst_qqmlmoduleplugin::parallelPluginImport()
732{
733 QMutexLocker locker(&PluginThatWaits::initializeEngineEntered);
734
735 QThread worker;
736 QObject::connect(sender: &worker, signal: &QThread::started, slot: [&worker](){
737 // Engines in separate threads are tricky, but as long as we do not create a graphical
738 // object and move objects created by the engines across thread boundaries, this is safe.
739 // At the same time this allows us to place the engine's loader thread into the position
740 // where, without the fix for this bug, the global lock is acquired.
741 QQmlEngine engineInThread;
742
743 QQmlComponent component(&engineInThread);
744 component.setData("import moduleWithWaitingPlugin 1.0\nimport QtQml 2.0\nQtObject {}",
745 baseUrl: QUrl());
746
747 QScopedPointer<QObject> obj(component.create());
748 QVERIFY(!obj.isNull());
749
750 worker.quit();
751 });
752 worker.start();
753
754 PluginThatWaits::waitingForInitializeEngineEntry.wait(lockedMutex: &PluginThatWaits::initializeEngineEntered);
755
756 {
757 // After acquiring this lock, the engine in the other thread as well as its type loader
758 // thread are blocked. However they should not hold the global plugin lock
759 // qmlEnginePluginsWithRegisteredTypes()->mutex in qqmllimports.cpp, allowing for the load
760 // of a component in a different engine with its own plugin to proceed.
761 QMutexLocker continuationLock(&PluginThatWaits::leavingInitializeEngine);
762
763 QQmlEngine secondEngine;
764 QQmlComponent secondComponent(&secondEngine);
765 secondComponent.setData("import moduleWithStaticPlugin 1.0\nimport QtQml 2.0\nQtObject {}",
766 baseUrl: QUrl());
767 QScopedPointer<QObject> o(secondComponent.create());
768 QVERIFY(!o.isNull());
769
770 PluginThatWaits::waitingForInitializeEngineLeave.wakeOne();
771 }
772
773 worker.wait();
774}
775
776void tst_qqmlmoduleplugin::multiSingleton()
777{
778 QQmlEngine engine;
779 QObject obj;
780 qmlRegisterSingletonInstance(uri: "Test", versionMajor: 1, versionMinor: 0, typeName: "Tracker", cppObject: &obj);
781 engine.addImportPath(dir: m_importsDirectory);
782 QQmlComponent component(&engine, testFileUrl(fileName: "multiSingleton.qml"));
783 QObject *object = component.create();
784 QVERIFY(object != nullptr);
785 QCOMPARE(obj.objectName(), QLatin1String("first"));
786 delete object;
787}
788
789
790QTEST_MAIN(tst_qqmlmoduleplugin)
791
792#include "tst_qqmlmoduleplugin.moc"
793

source code of qtdeclarative/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp