Select appropriate version for located module components
[profile/ivi/qtdeclarative.git] / tests / auto / qml / qqmlmoduleplugin / tst_qqmlmoduleplugin.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <qtest.h>
42 #include <qdir.h>
43 #include <QtQml/qqmlengine.h>
44 #include <QtQml/qqmlcomponent.h>
45 #include <QDebug>
46
47 #include "../../shared/testhttpserver.h"
48 #include "../../shared/util.h"
49
50 #define SERVER_ADDR "http://127.0.0.1:14456"
51 #define SERVER_PORT 14456
52
53
54 class tst_qqmlmoduleplugin : public QQmlDataTest
55 {
56     Q_OBJECT
57 public:
58
59 private slots:
60     virtual void initTestCase();
61     void importsPlugin();
62     void importsPlugin2();
63     void importsPlugin21();
64     void importsMixedQmlCppPlugin();
65     void incorrectPluginCase();
66     void importPluginWithQmlFile();
67     void remoteImportWithQuotedUrl();
68     void remoteImportWithUnquotedUri();
69     void versionNotInstalled();
70     void versionNotInstalled_data();
71     void implicitQmldir();
72     void implicitQmldir_data();
73     void importsNested();
74     void importsNested_data();
75     void importLocalModule();
76     void importLocalModule_data();
77
78 private:
79     QString m_importsDirectory;
80     QString m_dataImportsDirectory;
81 };
82
83 void tst_qqmlmoduleplugin::initTestCase()
84 {
85     QQmlDataTest::initTestCase();
86     m_importsDirectory = QFINDTESTDATA(QStringLiteral("imports"));
87     QVERIFY2(QFileInfo(m_importsDirectory).isDir(),
88              qPrintable(QString::fromLatin1("Imports directory '%1' does not exist.").arg(m_importsDirectory)));
89     m_dataImportsDirectory = directory() + QStringLiteral("/imports");
90     QVERIFY2(QFileInfo(m_dataImportsDirectory).isDir(),
91              qPrintable(QString::fromLatin1("Imports directory '%1' does not exist.").arg(m_dataImportsDirectory)));
92 }
93
94 #define VERIFY_ERRORS(errorfile) \
95     if (!errorfile) { \
96         if (qgetenv("DEBUG") != "" && !component.errors().isEmpty()) \
97             qWarning() << "Unexpected Errors:" << component.errors(); \
98         QVERIFY(!component.isError()); \
99         QVERIFY(component.errors().isEmpty()); \
100     } else { \
101         QString verify_errors_file_name = testFile(errorfile); \
102         QFile file(verify_errors_file_name); \
103         QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); \
104         QByteArray data = file.readAll(); \
105         file.close(); \
106         QList<QByteArray> expected = data.split('\n'); \
107         expected.removeAll(QByteArray("")); \
108         QList<QQmlError> errors = component.errors(); \
109         QList<QByteArray> actual; \
110         for (int ii = 0; ii < errors.count(); ++ii) { \
111             const QQmlError &error = errors.at(ii); \
112             QByteArray errorStr = QByteArray::number(error.line()) + ":" +  \
113                                   QByteArray::number(error.column()) + ":" + \
114                                   error.description().toUtf8(); \
115             actual << errorStr; \
116         } \
117         if (qgetenv("DEBUG") != "" && expected != actual) { \
118             qWarning() << "Expected:" << expected << "Actual:" << actual; \
119         } \
120         if (qgetenv("QDECLARATIVELANGUAGE_UPDATEERRORS") != "" && expected != actual) {\
121             QFile file(testFile(errorfile)); \
122             QVERIFY(file.open(QIODevice::WriteOnly)); \
123             for (int ii = 0; ii < actual.count(); ++ii) { \
124                 file.write(actual.at(ii)); file.write("\n"); \
125             } \
126             file.close(); \
127         } else { \
128             QCOMPARE(expected, actual); \
129         } \
130     }
131
132 void tst_qqmlmoduleplugin::importsPlugin()
133 {
134     QQmlEngine engine;
135     engine.addImportPath(m_importsDirectory);
136     QTest::ignoreMessage(QtWarningMsg, "plugin created");
137     QTest::ignoreMessage(QtWarningMsg, "import worked");
138     QQmlComponent component(&engine, testFileUrl(QStringLiteral("works.qml")));
139     foreach (QQmlError err, component.errors())
140         qWarning() << err;
141     VERIFY_ERRORS(0);
142     QObject *object = component.create();
143     QVERIFY(object != 0);
144     QCOMPARE(object->property("value").toInt(),123);
145     delete object;
146 }
147
148 void tst_qqmlmoduleplugin::importsPlugin2()
149 {
150     QQmlEngine engine;
151     engine.addImportPath(m_importsDirectory);
152     QTest::ignoreMessage(QtWarningMsg, "plugin2 created");
153     QTest::ignoreMessage(QtWarningMsg, "import2 worked");
154     QQmlComponent component(&engine, testFileUrl(QStringLiteral("works2.qml")));
155     foreach (QQmlError err, component.errors())
156         qWarning() << err;
157     VERIFY_ERRORS(0);
158     QObject *object = component.create();
159     QVERIFY(object != 0);
160     QCOMPARE(object->property("value").toInt(),123);
161     delete object;
162 }
163
164 void tst_qqmlmoduleplugin::importsPlugin21()
165 {
166     QQmlEngine engine;
167     engine.addImportPath(m_importsDirectory);
168     QTest::ignoreMessage(QtWarningMsg, "plugin2.1 created");
169     QTest::ignoreMessage(QtWarningMsg, "import2.1 worked");
170     QQmlComponent component(&engine, testFileUrl(QStringLiteral("works21.qml")));
171     foreach (QQmlError err, component.errors())
172         qWarning() << err;
173     VERIFY_ERRORS(0);
174     QObject *object = component.create();
175     QVERIFY(object != 0);
176     QCOMPARE(object->property("value").toInt(),123);
177     delete object;
178 }
179
180 void tst_qqmlmoduleplugin::incorrectPluginCase()
181 {
182     QQmlEngine engine;
183     engine.addImportPath(m_importsDirectory);
184
185     QQmlComponent component(&engine, testFileUrl(QStringLiteral("incorrectCase.qml")));
186
187     QList<QQmlError> errors = component.errors();
188     QCOMPARE(errors.count(), 1);
189
190 #if defined(Q_OS_MAC) || defined(Q_OS_WIN32)
191 #if defined(Q_OS_MAC)
192     QString libname = "libPluGin.dylib";
193 #elif defined(Q_OS_WIN32)
194     QString libname = "PluGin.dll";
195 #endif
196     QString expectedError = QLatin1String("plugin cannot be loaded for module \"com.nokia.WrongCase\": File name case mismatch for \"") + QDir(m_importsDirectory).filePath("com/nokia/WrongCase/" + libname) + QLatin1String("\"");
197 #else
198     QString expectedError = QLatin1String("module \"com.nokia.WrongCase\" plugin \"PluGin\" not found");
199 #endif
200
201     QCOMPARE(errors.at(0).description(), expectedError);
202 }
203
204 void tst_qqmlmoduleplugin::importPluginWithQmlFile()
205 {
206     QString path = m_importsDirectory;
207
208     // QTBUG-16885: adding an import path with a lower-case "c:" causes assert failure
209     // (this only happens if the plugin includes pure QML files)
210     #ifdef Q_OS_WIN
211         QVERIFY(path.at(0).isUpper() && path.at(1) == QLatin1Char(':'));
212         path = path.at(0).toLower() + path.mid(1);
213     #endif
214
215     QQmlEngine engine;
216     engine.addImportPath(path);
217
218     QQmlComponent component(&engine, testFileUrl(QStringLiteral("pluginWithQmlFile.qml")));
219     foreach (QQmlError err, component.errors())
220         qWarning() << err;
221     VERIFY_ERRORS(0);
222     QObject *object = component.create();
223     QVERIFY(object != 0);
224     delete object;
225 }
226
227 void tst_qqmlmoduleplugin::remoteImportWithQuotedUrl()
228 {
229     TestHTTPServer server(SERVER_PORT);
230     QVERIFY(server.isValid());
231     server.serveDirectory(m_dataImportsDirectory);
232
233     QQmlEngine engine;
234     QQmlComponent component(&engine);
235     component.setData("import \"" SERVER_ADDR "/com/nokia/PureQmlModule\" \nComponentA { width: 300; ComponentB{} }", QUrl());
236
237     QTRY_COMPARE(component.status(), QQmlComponent::Ready);
238     QObject *object = component.create();
239     QCOMPARE(object->property("width").toInt(), 300);
240     QVERIFY(object != 0);
241     delete object;
242
243     foreach (QQmlError err, component.errors())
244         qWarning() << err;
245     VERIFY_ERRORS(0);
246 }
247
248 void tst_qqmlmoduleplugin::remoteImportWithUnquotedUri()
249 {
250     TestHTTPServer server(SERVER_PORT);
251     QVERIFY(server.isValid());
252     server.serveDirectory(m_dataImportsDirectory);
253
254     QQmlEngine engine;
255     engine.addImportPath(m_dataImportsDirectory);
256     QQmlComponent component(&engine);
257     component.setData("import com.nokia.PureQmlModule 1.0 \nComponentA { width: 300; ComponentB{} }", QUrl());
258
259
260     QTRY_COMPARE(component.status(), QQmlComponent::Ready);
261     QObject *object = component.create();
262     QVERIFY(object != 0);
263     QCOMPARE(object->property("width").toInt(), 300);
264     delete object;
265
266     foreach (QQmlError err, component.errors())
267         qWarning() << err;
268     VERIFY_ERRORS(0);
269 }
270
271 // QTBUG-17324
272
273 void tst_qqmlmoduleplugin::importsMixedQmlCppPlugin()
274 {
275     QQmlEngine engine;
276     engine.addImportPath(m_importsDirectory);
277
278     {
279     QQmlComponent component(&engine, testFileUrl(QStringLiteral("importsMixedQmlCppPlugin.qml")));
280
281     QObject *o = component.create();
282     QVERIFY2(o != 0, QQmlDataTest::msgComponentError(component, &engine));
283     QCOMPARE(o->property("test").toBool(), true);
284     delete o;
285     }
286
287     {
288     QQmlComponent component(&engine, testFileUrl(QStringLiteral("importsMixedQmlCppPlugin.2.qml")));
289
290     QObject *o = component.create();
291     QVERIFY2(o != 0, QQmlDataTest::msgComponentError(component, &engine));
292     QCOMPARE(o->property("test").toBool(), true);
293     QCOMPARE(o->property("test2").toBool(), true);
294     delete o;
295     }
296
297
298 }
299
300 void tst_qqmlmoduleplugin::versionNotInstalled_data()
301 {
302     QTest::addColumn<QString>("file");
303     QTest::addColumn<QString>("errorFile");
304
305     QTest::newRow("versionNotInstalled") << "versionNotInstalled.qml" << "versionNotInstalled.errors.txt";
306     QTest::newRow("versionNotInstalled") << "versionNotInstalled.2.qml" << "versionNotInstalled.2.errors.txt";
307 }
308
309 void tst_qqmlmoduleplugin::versionNotInstalled()
310 {
311     QFETCH(QString, file);
312     QFETCH(QString, errorFile);
313
314     QQmlEngine engine;
315     engine.addImportPath(m_importsDirectory);
316
317     QQmlComponent component(&engine, testFileUrl(file));
318     VERIFY_ERRORS(errorFile.toLatin1().constData());
319 }
320
321
322 // test that errors are reporting correctly for plugin loading and qmldir parsing
323 void tst_qqmlmoduleplugin::implicitQmldir_data()
324 {
325     QTest::addColumn<QString>("directory");
326     QTest::addColumn<QString>("file");
327     QTest::addColumn<QString>("errorFile");
328
329     // parsing qmldir succeeds, but plugin specified in the qmldir file doesn't exist
330     QTest::newRow("implicitQmldir") << "implicit1" << "temptest.qml" << "implicitQmldir.errors.txt";
331
332     // parsing qmldir fails due to syntax errors, etc.
333     QTest::newRow("implicitQmldir2") << "implicit2" << "temptest2.qml" << "implicitQmldir.2.errors.txt";
334 }
335 void tst_qqmlmoduleplugin::implicitQmldir()
336 {
337     QFETCH(QString, directory);
338     QFETCH(QString, file);
339     QFETCH(QString, errorFile);
340
341     QString importPath = testFile(directory);
342     QString fileName = directory + QDir::separator() + file;
343     QString errorFileName = directory + QDir::separator() + errorFile;
344     QUrl testUrl = testFileUrl(fileName);
345
346     QQmlEngine engine;
347     engine.addImportPath(importPath);
348
349     QQmlComponent component(&engine, testUrl);
350     QList<QQmlError> errors = component.errors();
351     VERIFY_ERRORS(errorFileName.toLatin1().constData());
352     QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
353     QObject *obj = component.create();
354     QVERIFY(!obj);
355     delete obj;
356 }
357
358 void tst_qqmlmoduleplugin::importsNested_data()
359 {
360     QTest::addColumn<QString>("file");
361     QTest::addColumn<QString>("errorFile");
362
363     // Note: no other test case should import the plugin used for this test, or the
364     // wrong order test will pass spuriously
365     QTest::newRow("wrongOrder") << "importsNested.1.qml" << "importsNested.1.errors.txt";
366     QTest::newRow("missingImport") << "importsNested.3.qml" << "importsNested.3.errors.txt";
367     QTest::newRow("invalidVersion") << "importsNested.4.qml" << "importsNested.4.errors.txt";
368     QTest::newRow("correctOrder") << "importsNested.2.qml" << QString();
369 }
370 void tst_qqmlmoduleplugin::importsNested()
371 {
372     QFETCH(QString, file);
373     QFETCH(QString, errorFile);
374
375     // Note: because imports are cached between test case data rows (and the plugins remain loaded),
376     // these tests should really be run in new instances of the app...
377
378     QQmlEngine engine;
379     engine.addImportPath(m_importsDirectory);
380
381     if (!errorFile.isEmpty()) {
382         QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
383     }
384
385     QQmlComponent component(&engine, testFile(file));
386     QObject *obj = component.create();
387
388     if (errorFile.isEmpty()) {
389         if (qgetenv("DEBUG") != "" && !component.errors().isEmpty())
390             qWarning() << "Unexpected Errors:" << component.errors();
391         QVERIFY(obj);
392         delete obj;
393     } else {
394         QList<QQmlError> errors = component.errors();
395         VERIFY_ERRORS(errorFile.toLatin1().constData());
396         QVERIFY(!obj);
397     }
398 }
399
400 void tst_qqmlmoduleplugin::importLocalModule()
401 {
402     QFETCH(QString, qml);
403     QFETCH(int, majorVersion);
404     QFETCH(int, minorVersion);
405
406     QQmlEngine engine;
407     QQmlComponent component(&engine);
408     component.setData(qml.toUtf8(), testFileUrl("empty.qml"));
409
410     QScopedPointer<QObject> object(component.create());
411     QVERIFY(object != 0);
412     QCOMPARE(object->property("majorVersion").value<int>(), majorVersion);
413     QCOMPARE(object->property("minorVersion").value<int>(), minorVersion);
414 }
415
416 void tst_qqmlmoduleplugin::importLocalModule_data()
417 {
418     QTest::addColumn<QString>("qml");
419     QTest::addColumn<int>("majorVersion");
420     QTest::addColumn<int>("minorVersion");
421
422     QTest::newRow("default version")
423         << "import \"localModule\"\n"
424            "TestComponent {}"
425         << 2 << 0;
426
427     QTest::newRow("specific version")
428         << "import \"localModule\" 1.1\n"
429            "TestComponent {}"
430         << 1 << 1;
431
432     QTest::newRow("lesser version")
433         << "import \"localModule\" 1.0\n"
434            "TestComponent {}"
435         << 1 << 0;
436
437     // Note: this does not match the behaviour of installed modules, which fail for this case:
438     QTest::newRow("nonexistent version")
439         << "import \"localModule\" 1.3\n"
440            "TestComponent {}"
441         << 1 << 1;
442
443     QTest::newRow("high version")
444         << "import \"localModule\" 2.0\n"
445            "TestComponent {}"
446         << 2 << 0;
447 }
448
449 QTEST_MAIN(tst_qqmlmoduleplugin)
450
451 #include "tst_qqmlmoduleplugin.moc"