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 <QtCore/qdebug.h>
30#include <QtCore/qtimer.h>
31#include <QtCore/qdir.h>
32#include <QtCore/qfileinfo.h>
33#include <QtCore/qregularexpression.h>
34#include <QtQml/qjsengine.h>
35
36#include <QtQml/qqmlcomponent.h>
37#include <QtQml/qqmlengine.h>
38
39#include <private/qquickworkerscript_p.h>
40#include <private/qqmlengine_p.h>
41#include "../../shared/util.h"
42
43class tst_QQuickWorkerScript : public QQmlDataTest
44{
45 Q_OBJECT
46public:
47 tst_QQuickWorkerScript() {}
48private slots:
49 void source();
50 void ready();
51 void messaging();
52 void messaging_data();
53 void messaging_sendQObjectList();
54 void messaging_sendJsObject();
55 void messaging_sendExternalObject();
56 void script_with_pragma();
57 void script_included();
58 void scriptError_onLoad();
59 void scriptError_onCall();
60 void script_function();
61 void script_var();
62 void stressDispose();
63 void xmlHttpRequest();
64
65private:
66 void waitForEchoMessage(QQuickWorkerScript *worker) {
67 QEventLoop loop;
68 QVERIFY(connect(worker, SIGNAL(done()), &loop, SLOT(quit())));
69 QTimer timer;
70 timer.setSingleShot(true);
71 connect(sender: &timer, SIGNAL(timeout()), receiver: &loop, SLOT(quit()));
72 timer.start(msec: 10000);
73 loop.exec();
74 QVERIFY(timer.isActive());
75 }
76
77 QQmlEngine m_engine;
78};
79
80void tst_QQuickWorkerScript::source()
81{
82 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker.qml"));
83 QScopedPointer<QQuickWorkerScript>worker(qobject_cast<QQuickWorkerScript*>(object: component.create()));
84 QVERIFY(worker != nullptr);
85 const QMetaObject *mo = worker->metaObject();
86
87 QVariant value(100);
88 QVERIFY(QMetaObject::invokeMethod(worker.data(), "testSend", Q_ARG(QVariant, value)));
89 waitForEchoMessage(worker: worker.data());
90 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), value);
91
92 QUrl source = testFileUrl(fileName: "script_fixed_return.js");
93 worker->setSource(source);
94 QCOMPARE(worker->source(), source);
95 QVERIFY(QMetaObject::invokeMethod(worker.data(), "testSend", Q_ARG(QVariant, value)));
96 waitForEchoMessage(worker: worker.data());
97 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), QVariant::fromValue(QString("Hello_World")));
98
99 source = testFileUrl(fileName: "script_module.mjs");
100 worker->setSource(source);
101 QCOMPARE(worker->source(), source);
102 QVERIFY(QMetaObject::invokeMethod(worker.data(), "testSend", Q_ARG(QVariant, value)));
103 waitForEchoMessage(worker: worker.data());
104 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), QVariant::fromValue(QString("Hello from the module")));
105
106 qApp->processEvents();
107}
108
109void tst_QQuickWorkerScript::ready()
110{
111 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker.qml"));
112 QScopedPointer<QQuickWorkerScript>worker(qobject_cast<QQuickWorkerScript*>(object: component.create()));
113 QVERIFY(worker != nullptr);
114
115 const QMetaObject *mo = worker->metaObject();
116
117 QTRY_VERIFY(worker->ready());
118
119 QVariant readyChangedCalled = mo->property(index: mo->indexOfProperty(name: "readyChangedCalled")).read(obj: worker.data()).value<QVariant>();
120
121 QVERIFY(!readyChangedCalled.isNull());
122 QVERIFY(readyChangedCalled.toBool());
123}
124
125void tst_QQuickWorkerScript::messaging()
126{
127 QFETCH(QVariant, value);
128
129 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker.qml"));
130 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
131 QVERIFY(worker != nullptr);
132
133 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, value)));
134 waitForEchoMessage(worker);
135
136 const QMetaObject *mo = worker->metaObject();
137 QVariant response = mo->property(index: mo->indexOfProperty(name: "response")).read(obj: worker).value<QVariant>();
138 if (response.userType() == qMetaTypeId<QJSValue>())
139 response = response.value<QJSValue>().toVariant();
140
141 if (value.type() == QMetaType::QRegExp && response.type() == QMetaType::QRegularExpression) {
142 // toVariant() doesn't know if we want QRegExp or QRegularExpression. It always creates
143 // a QRegularExpression from a JavaScript regular expression.
144 const QRegularExpression responseRegExp = response.toRegularExpression();
145 const QRegExp valueRegExp = value.toRegExp();
146 QCOMPARE(responseRegExp.pattern(), valueRegExp.pattern());
147 QCOMPARE(bool(responseRegExp.patternOptions() & QRegularExpression::CaseInsensitiveOption),
148 bool(valueRegExp.caseSensitivity() == Qt::CaseInsensitive));
149 } else {
150 QCOMPARE(response, value);
151 }
152
153 qApp->processEvents();
154 delete worker;
155}
156
157void tst_QQuickWorkerScript::messaging_data()
158{
159 QTest::addColumn<QVariant>(name: "value");
160
161 QTest::newRow(dataTag: "invalid") << QVariant();
162 QTest::newRow(dataTag: "bool") << QVariant::fromValue(value: true);
163 QTest::newRow(dataTag: "int") << QVariant::fromValue(value: 1001);
164 QTest::newRow(dataTag: "real") << QVariant::fromValue(value: 10334.375);
165 QTest::newRow(dataTag: "string") << QVariant::fromValue(value: QString("More cheeeese, Gromit!"));
166 QTest::newRow(dataTag: "variant list") << QVariant::fromValue(value: (QVariantList() << "a" << "b" << "c"));
167 QTest::newRow(dataTag: "date time") << QVariant::fromValue(value: QDateTime::currentDateTime());
168 QTest::newRow(dataTag: "regexp") << QVariant::fromValue(value: QRegExp("^\\d\\d?$", Qt::CaseInsensitive,
169 QRegExp::RegExp2));
170 QTest::newRow(dataTag: "regularexpression") << QVariant::fromValue(value: QRegularExpression(
171 "^\\d\\d?$", QRegularExpression::CaseInsensitiveOption));
172 QTest::newRow(dataTag: "url") << QVariant::fromValue(value: QUrl("http://example.com/foo/bar"));
173}
174
175void tst_QQuickWorkerScript::messaging_sendQObjectList()
176{
177 // Not allowed to send QObjects other than QQmlListModelWorkerAgent
178 // instances. If objects are sent in a list, they will be sent as 'undefined'
179 // js values.
180
181 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker.qml"));
182 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
183 QVERIFY(worker != nullptr);
184
185 QVariantList objects;
186 for (int i=0; i<3; i++)
187 objects << QVariant::fromValue(value: new QObject(this));
188
189 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, QVariant::fromValue(objects))));
190 waitForEchoMessage(worker);
191
192 const QMetaObject *mo = worker->metaObject();
193 QVariantList result = mo->property(index: mo->indexOfProperty(name: "response")).read(obj: worker).value<QVariantList>();
194 QCOMPARE(result, (QVariantList() << QVariant() << QVariant() << QVariant()));
195
196 qApp->processEvents();
197 delete worker;
198}
199
200void tst_QQuickWorkerScript::messaging_sendJsObject()
201{
202 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker.qml"));
203 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
204 QVERIFY(worker != nullptr);
205
206 // Properties are in alphabetical order to enable string-based comparison after
207 // QVariant roundtrip, since the properties will be stored in a QVariantMap.
208 QString jsObject = "{'haste': 1125, 'name': 'zyz', 'spell power': 3101}";
209
210 QVariantMap map;
211 map.insert(key: "haste", value: 1125);
212 map.insert(key: "name", value: "zyz");
213 map.insert(key: "spell power", value: 3101);
214
215 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, QVariant::fromValue(map))));
216 waitForEchoMessage(worker);
217
218 QVariant result = QVariant::fromValue(value: false);
219 QVERIFY(QMetaObject::invokeMethod(worker, "compareLiteralResponse", Qt::DirectConnection,
220 Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, jsObject)));
221 QVERIFY(result.toBool());
222
223 qApp->processEvents();
224 delete worker;
225}
226
227void tst_QQuickWorkerScript::messaging_sendExternalObject()
228{
229 QQmlComponent component(&m_engine, testFileUrl(fileName: "externalObjectWorker.qml"));
230 QObject *obj = component.create();
231 QVERIFY(obj);
232 QMetaObject::invokeMethod(obj, member: "testExternalObject");
233 QTest::qWait(ms: 100); // shouldn't crash.
234 delete obj;
235}
236
237void tst_QQuickWorkerScript::script_with_pragma()
238{
239 QVariant value(100);
240
241 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker_pragma.qml"));
242 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
243 QVERIFY(worker != nullptr);
244
245 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, value)));
246 waitForEchoMessage(worker);
247
248 const QMetaObject *mo = worker->metaObject();
249 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker).value<QVariant>(), value);
250
251 qApp->processEvents();
252 delete worker;
253}
254
255void tst_QQuickWorkerScript::script_included()
256{
257 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker_include.qml"));
258 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
259 QVERIFY(worker != nullptr);
260
261 QString value("Hello");
262
263 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, value)));
264 waitForEchoMessage(worker);
265
266 const QMetaObject *mo = worker->metaObject();
267 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker).toString(), value + " World");
268
269 qApp->processEvents();
270 delete worker;
271}
272
273static QString qquickworkerscript_lastWarning;
274static void qquickworkerscript_warningsHandler(QtMsgType type, const QMessageLogContext &, const QString &msg)
275{
276 if (type == QtWarningMsg)
277 qquickworkerscript_lastWarning = msg;
278}
279
280void tst_QQuickWorkerScript::scriptError_onLoad()
281{
282 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker_error_onLoad.qml"));
283
284 QtMessageHandler previousMsgHandler = qInstallMessageHandler(qquickworkerscript_warningsHandler);
285 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
286 QVERIFY(worker != nullptr);
287
288 QTRY_COMPARE(qquickworkerscript_lastWarning,
289 testFileUrl("script_error_onLoad.js").toString() + QLatin1String(":3:10: SyntaxError: Expected token `,'"));
290
291 qInstallMessageHandler(previousMsgHandler);
292 qApp->processEvents();
293 delete worker;
294}
295
296void tst_QQuickWorkerScript::scriptError_onCall()
297{
298 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker_error_onCall.qml"));
299 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
300 QVERIFY(worker != nullptr);
301
302 QtMessageHandler previousMsgHandler = qInstallMessageHandler(qquickworkerscript_warningsHandler);
303 QVariant value;
304 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, value)));
305
306 QTRY_COMPARE(qquickworkerscript_lastWarning,
307 testFileUrl("script_error_onCall.js").toString() + QLatin1String(":4: ReferenceError: getData is not defined"));
308
309 qInstallMessageHandler(previousMsgHandler);
310 qApp->processEvents();
311 delete worker;
312}
313
314void tst_QQuickWorkerScript::script_function()
315{
316 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker_function.qml"));
317 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
318 QVERIFY(worker != nullptr);
319
320 QString value("Hello");
321
322 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, value)));
323 waitForEchoMessage(worker);
324
325 const QMetaObject *mo = worker->metaObject();
326 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker).toString(), value + " World");
327
328 qApp->processEvents();
329 delete worker;
330}
331
332void tst_QQuickWorkerScript::script_var()
333{
334 QQmlComponent component(&m_engine, testFileUrl(fileName: "worker_var.qml"));
335 QQuickWorkerScript *worker = qobject_cast<QQuickWorkerScript*>(object: component.create());
336 QVERIFY(worker != nullptr);
337
338 QString value("Hello");
339
340 QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, value)));
341 waitForEchoMessage(worker);
342
343 const QMetaObject *mo = worker->metaObject();
344 QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker).toString(), value + " World");
345
346 qApp->processEvents();
347 delete worker;
348}
349
350// Rapidly create and destroy worker scripts to test resources are being disposed
351// in the correct isolate
352void tst_QQuickWorkerScript::stressDispose()
353{
354 for (int ii = 0; ii < 100; ++ii) {
355 QQmlEngine engine;
356 QQmlComponent component(&engine, testFileUrl(fileName: "stressDispose.qml"));
357 QObject *o = component.create();
358 QVERIFY(o);
359 delete o;
360 }
361}
362
363void tst_QQuickWorkerScript::xmlHttpRequest()
364{
365 QQmlComponent component(&m_engine, testFileUrl(fileName: "xmlHttpRequest.qml"));
366 QScopedPointer<QObject> root{component.create()}; // should not crash
367 QVERIFY(root);
368}
369
370QTEST_MAIN(tst_QQuickWorkerScript)
371
372#include "tst_qquickworkerscript.moc"
373

source code of qtdeclarative/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp