1/****************************************************************************
2**
3** Copyright (C) 2016 Research In Motion
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 <QDebug>
30#include <QQmlEngine>
31#include <QQmlComponent>
32#include <QQmlContext>
33#include <qqml.h>
34#include <QMetaMethod>
35#if QT_CONFIG(process)
36#include <QProcess>
37#endif
38
39#include "../../shared/util.h"
40
41class ExportedClass : public QObject
42{
43 Q_OBJECT
44 Q_PROPERTY(int qmlObjectProp READ qmlObjectProp NOTIFY qmlObjectPropChanged)
45 Q_PROPERTY(int cppObjectProp READ cppObjectProp NOTIFY cppObjectPropChanged)
46 Q_PROPERTY(int unboundProp READ unboundProp NOTIFY unboundPropChanged)
47 Q_PROPERTY(int v8BindingProp READ v8BindingProp NOTIFY v8BindingPropChanged)
48 Q_PROPERTY(int v4BindingProp READ v4BindingProp NOTIFY v4BindingPropChanged)
49 Q_PROPERTY(int v4BindingProp2 READ v4BindingProp2 NOTIFY v4BindingProp2Changed)
50 Q_PROPERTY(int scriptBindingProp READ scriptBindingProp NOTIFY scriptBindingPropChanged)
51public:
52 int qmlObjectPropConnections = 0;
53 int cppObjectPropConnections = 0;
54 int unboundPropConnections = 0;
55 int v8BindingPropConnections = 0;
56 int v4BindingPropConnections = 0;
57 int v4BindingProp2Connections = 0;
58 int scriptBindingPropConnections = 0;
59 int boundSignalConnections = 0;
60 int unusedSignalConnections = 0;
61
62 ExportedClass() {}
63
64 ~ExportedClass()
65 {
66 QCOMPARE(qmlObjectPropConnections, 0);
67 QCOMPARE(cppObjectPropConnections, 0);
68 QCOMPARE(unboundPropConnections, 0);
69 QCOMPARE(v8BindingPropConnections, 0);
70 QCOMPARE(v4BindingPropConnections, 0);
71 QCOMPARE(v4BindingProp2Connections, 0);
72 QCOMPARE(scriptBindingPropConnections, 0);
73 QCOMPARE(boundSignalConnections, 0);
74 QCOMPARE(unusedSignalConnections, 0);
75 }
76
77 int qmlObjectProp() const { return 42; }
78 int unboundProp() const { return 42; }
79 int v8BindingProp() const { return 42; }
80 int v4BindingProp() const { return 42; }
81 int v4BindingProp2() const { return 42; }
82 int cppObjectProp() const { return 42; }
83 int scriptBindingProp() const { return 42; }
84
85 void verifyReceiverCount()
86 {
87 //Note: QTBUG-34829 means we can't call this from within disconnectNotify or it can lock
88 QCOMPARE(receivers(SIGNAL(qmlObjectPropChanged())), qmlObjectPropConnections);
89 QCOMPARE(receivers(SIGNAL(cppObjectPropChanged())), cppObjectPropConnections);
90 QCOMPARE(receivers(SIGNAL(unboundPropChanged())), unboundPropConnections);
91 QCOMPARE(receivers(SIGNAL(v8BindingPropChanged())), v8BindingPropConnections);
92 QCOMPARE(receivers(SIGNAL(v4BindingPropChanged())), v4BindingPropConnections);
93 QCOMPARE(receivers(SIGNAL(v4BindingProp2Changed())), v4BindingProp2Connections);
94 QCOMPARE(receivers(SIGNAL(scriptBindingPropChanged())), scriptBindingPropConnections);
95 QCOMPARE(receivers(SIGNAL(boundSignal())), boundSignalConnections);
96 QCOMPARE(receivers(SIGNAL(unusedSignal())), unusedSignalConnections);
97 }
98
99protected:
100 void connectNotify(const QMetaMethod &signal) override {
101 if (signal.name() == "qmlObjectPropChanged") qmlObjectPropConnections++;
102 if (signal.name() == "cppObjectPropChanged") cppObjectPropConnections++;
103 if (signal.name() == "unboundPropChanged") unboundPropConnections++;
104 if (signal.name() == "v8BindingPropChanged") v8BindingPropConnections++;
105 if (signal.name() == "v4BindingPropChanged") v4BindingPropConnections++;
106 if (signal.name() == "v4BindingProp2Changed") v4BindingProp2Connections++;
107 if (signal.name() == "scriptBindingPropChanged") scriptBindingPropConnections++;
108 if (signal.name() == "boundSignal") boundSignalConnections++;
109 if (signal.name() == "unusedSignal") unusedSignalConnections++;
110 verifyReceiverCount();
111 //qDebug() << Q_FUNC_INFO << this << signal.name();
112 }
113
114 void disconnectNotify(const QMetaMethod &signal) override {
115 if (signal.name() == "qmlObjectPropChanged") qmlObjectPropConnections--;
116 if (signal.name() == "cppObjectPropChanged") cppObjectPropConnections--;
117 if (signal.name() == "unboundPropChanged") unboundPropConnections--;
118 if (signal.name() == "v8BindingPropChanged") v8BindingPropConnections--;
119 if (signal.name() == "v4BindingPropChanged") v4BindingPropConnections--;
120 if (signal.name() == "v4BindingProp2Changed") v4BindingProp2Connections--;
121 if (signal.name() == "scriptBindingPropChanged") scriptBindingPropConnections--;
122 if (signal.name() == "boundSignal") boundSignalConnections--;
123 if (signal.name() == "unusedSignal") unusedSignalConnections--;
124 //qDebug() << Q_FUNC_INFO << this << signal.methodSignature();
125 }
126
127signals:
128 void qmlObjectPropChanged();
129 void cppObjectPropChanged();
130 void unboundPropChanged();
131 void v8BindingPropChanged();
132 void v4BindingPropChanged();
133 void v4BindingProp2Changed();
134 void scriptBindingPropChanged();
135 void boundSignal();
136 void unusedSignal();
137};
138
139class tst_qqmlnotifier : public QQmlDataTest
140{
141 Q_OBJECT
142public:
143 tst_qqmlnotifier() {}
144
145private slots:
146 void initTestCase() override;
147 void cleanupTestCase();
148 void testConnectNotify();
149
150 void removeV4Binding();
151 void removeV4Binding2();
152 void removeV8Binding();
153 void removeScriptBinding();
154 // No need to test value type proxy bindings - the user can't override disconnectNotify() anyway,
155 // as the classes are private to the QML engine
156
157 void readProperty();
158 void propertyChange();
159 void disconnectOnDestroy();
160 void lotsOfBindings();
161
162 void deleteFromHandler();
163
164private:
165 void createObjects();
166
167 QQmlEngine engine;
168 QObject *root = nullptr;
169 ExportedClass *exportedClass = nullptr;
170 ExportedClass *exportedObject = nullptr;
171};
172
173void tst_qqmlnotifier::initTestCase()
174{
175 QQmlDataTest::initTestCase();
176 qmlRegisterType<ExportedClass>(uri: "Test", versionMajor: 1, versionMinor: 0, qmlName: "ExportedClass");
177}
178
179void tst_qqmlnotifier::createObjects()
180{
181 delete root;
182 root = nullptr;
183 exportedClass = exportedObject = nullptr;
184
185 QQmlComponent component(&engine, testFileUrl(fileName: "connectnotify.qml"));
186 exportedObject = new ExportedClass();
187 exportedObject->setObjectName("exportedObject");
188 root = component.createWithInitialProperties(initialProperties: {{"exportedObject", QVariant::fromValue(value: exportedObject)}});
189 QVERIFY(root != nullptr);
190
191 exportedClass = qobject_cast<ExportedClass *>(
192 object: root->findChild<ExportedClass*>(aName: "exportedClass"));
193 QVERIFY(exportedClass != nullptr);
194 exportedClass->verifyReceiverCount();
195}
196
197void tst_qqmlnotifier::cleanupTestCase()
198{
199 delete root;
200 root = nullptr;
201 delete exportedObject;
202 exportedObject = nullptr;
203}
204
205void tst_qqmlnotifier::testConnectNotify()
206{
207 createObjects();
208
209 QCOMPARE(exportedClass->qmlObjectPropConnections, 1);
210 QCOMPARE(exportedClass->cppObjectPropConnections, 0);
211 QCOMPARE(exportedClass->unboundPropConnections, 0);
212 QCOMPARE(exportedClass->v8BindingPropConnections, 1);
213 QCOMPARE(exportedClass->v4BindingPropConnections, 1);
214 QCOMPARE(exportedClass->v4BindingProp2Connections, 2);
215 QCOMPARE(exportedClass->scriptBindingPropConnections, 1);
216 QCOMPARE(exportedClass->boundSignalConnections, 1);
217 QCOMPARE(exportedClass->unusedSignalConnections, 0);
218 exportedClass->verifyReceiverCount();
219
220 QCOMPARE(exportedObject->qmlObjectPropConnections, 0);
221 QCOMPARE(exportedObject->cppObjectPropConnections, 1);
222 QCOMPARE(exportedObject->unboundPropConnections, 0);
223 QCOMPARE(exportedObject->v8BindingPropConnections, 0);
224 QCOMPARE(exportedObject->v4BindingPropConnections, 0);
225 QCOMPARE(exportedObject->v4BindingProp2Connections, 0);
226 QCOMPARE(exportedObject->scriptBindingPropConnections, 0);
227 QCOMPARE(exportedObject->boundSignalConnections, 0);
228 QCOMPARE(exportedObject->unusedSignalConnections, 0);
229 exportedObject->verifyReceiverCount();
230}
231
232void tst_qqmlnotifier::removeV4Binding()
233{
234 createObjects();
235
236 // Removing a binding should disconnect all of its guarded properties
237 QVERIFY(QMetaObject::invokeMethod(root, "removeV4Binding"));
238 QCOMPARE(exportedClass->v4BindingPropConnections, 0);
239 exportedClass->verifyReceiverCount();
240}
241
242void tst_qqmlnotifier::removeV4Binding2()
243{
244 createObjects();
245
246 // In this case, the v4BindingProp2 property is used by two v4 bindings.
247 // Make sure that removing one binding doesn't by accident disconnect all.
248 QVERIFY(QMetaObject::invokeMethod(root, "removeV4Binding2"));
249 QCOMPARE(exportedClass->v4BindingProp2Connections, 1);
250 exportedClass->verifyReceiverCount();
251}
252
253void tst_qqmlnotifier::removeV8Binding()
254{
255 createObjects();
256
257 // Removing a binding should disconnect all of its guarded properties
258 QVERIFY(QMetaObject::invokeMethod(root, "removeV8Binding"));
259 QCOMPARE(exportedClass->v8BindingPropConnections, 0);
260 exportedClass->verifyReceiverCount();
261}
262
263void tst_qqmlnotifier::removeScriptBinding()
264{
265 createObjects();
266
267 // Removing a binding should disconnect all of its guarded properties
268 QVERIFY(QMetaObject::invokeMethod(root, "removeScriptBinding"));
269 QCOMPARE(exportedClass->scriptBindingPropConnections, 0);
270 exportedClass->verifyReceiverCount();
271}
272
273void tst_qqmlnotifier::readProperty()
274{
275 createObjects();
276
277 // Reading a property should not connect to it
278 QVERIFY(QMetaObject::invokeMethod(root, "readProperty"));
279 QCOMPARE(exportedClass->unboundPropConnections, 0);
280 exportedClass->verifyReceiverCount();
281}
282
283void tst_qqmlnotifier::propertyChange()
284{
285 createObjects();
286
287 // Changing the state will trigger the PropertyChange to overwrite a value with a binding.
288 // For this, the new binding needs to be connected, and afterwards disconnected.
289 QVERIFY(QMetaObject::invokeMethod(root, "changeState"));
290 QCOMPARE(exportedClass->unboundPropConnections, 1);
291 exportedClass->verifyReceiverCount();
292 QVERIFY(QMetaObject::invokeMethod(root, "changeState"));
293 QCOMPARE(exportedClass->unboundPropConnections, 0);
294 exportedClass->verifyReceiverCount();
295}
296
297void tst_qqmlnotifier::disconnectOnDestroy()
298{
299 createObjects();
300
301 // Deleting a QML object should remove all connections. For exportedClass, this is tested in
302 // the destructor, and for exportedObject, it is tested below.
303 delete root;
304 root = nullptr;
305 QCOMPARE(exportedObject->cppObjectPropConnections, 0);
306 exportedObject->verifyReceiverCount();
307}
308
309class TestObject : public QObject
310{
311 Q_OBJECT
312 Q_PROPERTY(int a READ a NOTIFY aChanged)
313
314public:
315 int a() const { return 0; }
316
317signals:
318 void aChanged();
319};
320
321void tst_qqmlnotifier::lotsOfBindings()
322{
323 TestObject o;
324 QQmlEngine *e = new QQmlEngine;
325
326 qmlRegisterSingletonInstance(uri: "Test", versionMajor: 1, versionMinor: 0, typeName: "Test", cppObject: &o);
327
328 QList<QQmlComponent *> components;
329 for (int i = 0; i < 20000; ++i) {
330 QQmlComponent *component = new QQmlComponent(e);
331 component->setData("import QtQuick 2.0; import Test 1.0; Item {width: Test.a; }", baseUrl: QUrl());
332 component->create(context: e->rootContext());
333 components.append(t: component);
334 }
335
336 o.aChanged();
337
338 qDeleteAll(c: components);
339 delete e;
340}
341
342void tst_qqmlnotifier::deleteFromHandler()
343{
344#ifdef Q_OS_ANDROID
345 QSKIP("Android seems to have problems with QProcess");
346#endif
347#if !QT_CONFIG(process)
348 QSKIP("Need QProcess support to test qFatal.");
349#else
350 if (qEnvironmentVariableIsSet(varName: "TST_QQMLNOTIFIER_DO_CRASH")) {
351 QQmlEngine engine;
352 QQmlComponent component(&engine, testFileUrl(fileName: "objectRenamer.qml"));
353 QPointer<QObject> mess = component.create();
354 QObject::connect(sender: mess.data(), signal: &QObject::objectNameChanged, slot: [&]() { delete mess; });
355 QTRY_VERIFY(mess.isNull()); // BANG!
356 } else {
357 QProcess process;
358 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
359 env.insert(name: "TST_QQMLNOTIFIER_DO_CRASH", value: "bang");
360 process.setProcessEnvironment(env);
361 process.setProgram(QCoreApplication::applicationFilePath());
362 process.setArguments({"deleteFromHandler"});
363 process.start();
364 QTRY_COMPARE(process.exitStatus(), QProcess::CrashExit);
365 const QByteArray output = process.readAllStandardOutput();
366 QVERIFY(output.contains("QFATAL"));
367 QVERIFY(output.contains("destroyed while one of its QML signal handlers is in progress"));
368 }
369#endif
370}
371
372QTEST_MAIN(tst_qqmlnotifier)
373
374#include "tst_qqmlnotifier.moc"
375

source code of qtdeclarative/tests/auto/qml/qqmlnotifier/tst_qqmlnotifier.cpp