1/****************************************************************************
2**
3** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module 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 <Qt3DCore/QAbstractAspect>
31#include <Qt3DCore/qaspectengine.h>
32#include <Qt3DCore/qentity.h>
33#include <Qt3DCore/qtransform.h>
34
35using namespace Qt3DCore;
36
37class PrintRootAspect : public QAbstractAspect
38{
39 Q_OBJECT
40public:
41 explicit PrintRootAspect(QObject *parent = 0)
42 : QAbstractAspect(parent)
43 , m_rootEntityId()
44 {
45 qDebug() << Q_FUNC_INFO;
46 }
47
48private:
49 void onRegistered() override
50 {
51 qDebug() << Q_FUNC_INFO;
52 }
53
54 void onEngineStartup() override
55 {
56 qDebug() << Q_FUNC_INFO;
57 m_rootEntityId = rootEntityId();
58 }
59
60 void onEngineShutdown() override
61 {
62 qDebug() << Q_FUNC_INFO;
63 }
64
65 QVector<QAspectJobPtr> jobsToExecute(qint64) override \
66 {
67 if (m_rootEntityId)
68 qDebug() << Q_FUNC_INFO << m_rootEntityId;
69 return QVector<QAspectJobPtr>();
70 }
71
72 QNodeId m_rootEntityId;
73};
74
75#define FAKE_ASPECT(ClassName) \
76class ClassName : public QAbstractAspect \
77{ \
78 Q_OBJECT \
79public: \
80 explicit ClassName(QObject *parent = 0) \
81 : QAbstractAspect(parent) {} \
82 \
83private: \
84 void onRegistered() override {} \
85 void onEngineStartup() override {} \
86 void onEngineShutdown() override {} \
87 \
88 QVector<QAspectJobPtr> jobsToExecute(qint64) override \
89 { \
90 return QVector<QAspectJobPtr>(); \
91 } \
92 \
93 QVariant executeCommand(const QStringList &args) override \
94 { \
95 if (args.size() >= 2 && args.first() == QLatin1String("echo")) { \
96 QStringList list = args; \
97 list.removeFirst(); \
98 return QString("%1 said '%2'").arg(metaObject()->className()).arg(list.join(QLatin1Char(' '))); \
99 } \
100 \
101 return QVariant(); \
102 } \
103};
104
105FAKE_ASPECT(FakeAspect)
106FAKE_ASPECT(FakeAspect2)
107FAKE_ASPECT(FakeAspect3)
108
109QT3D_REGISTER_ASPECT("fake", FakeAspect)
110QT3D_REGISTER_ASPECT("otherfake", FakeAspect2)
111
112
113class tst_QAspectEngine : public QObject
114{
115 Q_OBJECT
116public:
117 tst_QAspectEngine() : QObject() {}
118 ~tst_QAspectEngine() {}
119
120private Q_SLOTS:
121 // TODO: Add more QAspectEngine tests
122
123 void constructionDestruction()
124 {
125 QAspectEngine *engine = new QAspectEngine;
126 QVERIFY(engine->rootEntity() == nullptr);
127 delete engine;
128 }
129
130 void setRootEntity()
131 {
132 QAspectEngine *engine = new QAspectEngine;
133
134 QEntity *e = new QEntity;
135 e->setObjectName("root");
136 engine->setRootEntity(QEntityPtr(e));
137
138 QSharedPointer<QEntity> root = engine->rootEntity();
139 QVERIFY(root == e);
140 QVERIFY(root->objectName() == "root");
141 root = QSharedPointer<QEntity>();
142 QVERIFY(engine->rootEntity()->objectName() == "root");
143
144 delete engine;
145 }
146
147 void shouldNotCrashInNormalStartupShutdownSequence()
148 {
149#ifdef Q_OS_MACOS
150 QSKIP("Test frequently times out. See QTBUG-80660.");
151#endif
152
153 // GIVEN
154 // An initialized aspect engine...
155 QAspectEngine engine;
156 // ...and a simple aspect
157 PrintRootAspect *aspect = new PrintRootAspect;
158
159 // WHEN
160 // We register the aspect
161 engine.registerAspect(aspect);
162
163 // THEN
164 const auto registeredAspects = engine.aspects();
165 QCOMPARE(registeredAspects.size(), 1);
166 QCOMPARE(registeredAspects.first(), aspect);
167
168 // WHEN
169 QEntityPtr entity(new QEntity);
170 entity->setObjectName("RootEntity");
171 // we set a scene root entity
172 engine.setRootEntity(entity);
173
174 QEventLoop eventLoop;
175 QTimer::singleShot(msec: 1000, receiver: &eventLoop, SLOT(quit()));
176 eventLoop.exec();
177
178 // THEN
179 // we don't crash and...
180 const auto rootEntity = engine.rootEntity();
181 QCOMPARE(rootEntity, entity);
182
183 // WHEN
184 // we set an empty/null scene root...
185 engine.setRootEntity(QEntityPtr());
186 QTimer::singleShot(msec: 600, receiver: &eventLoop, SLOT(quit()));
187
188 // ...and allow events to process...
189 eventLoop.exec();
190
191 // THEN
192 // ... we don't crash.
193
194 // TODO: Add more tests to check for
195 // * re-setting a scene
196 // * deregistering aspects
197 // * destroying the aspect engine
198 }
199
200 void shouldNotCrashWhenEntityIsAddedThenImmediatelyDeleted()
201 {
202 // GIVEN
203 // An initialized aspect engine...
204 QAspectEngine engine;
205 // ...and a simple aspect
206 PrintRootAspect *aspect = new PrintRootAspect;
207
208 // WHEN
209 // We register the aspect
210 engine.registerAspect(aspect);
211
212 // THEN
213 const auto registeredAspects = engine.aspects();
214 QCOMPARE(registeredAspects.size(), 1);
215 QCOMPARE(registeredAspects.first(), aspect);
216
217 // WHEN
218 QEntityPtr entity(new QEntity);
219 entity->setObjectName("RootEntity");
220 // we set a scene root entity
221 engine.setRootEntity(entity);
222
223 QEventLoop eventLoop;
224 QTimer::singleShot(msec: 1000, receiver: &eventLoop, SLOT(quit()));
225 eventLoop.exec();
226
227 // THEN
228 // we don't crash and...
229 const auto rootEntity = engine.rootEntity();
230 QCOMPARE(rootEntity, entity);
231
232 // WHEN
233 // we create a child node and delete within the same spin of
234 // the event loop
235 Qt3DCore::QEntity *childEntity = new Qt3DCore::QEntity(entity.data());
236 delete childEntity;
237 entity = nullptr;
238 QTimer::singleShot(msec: 600, receiver: &eventLoop, SLOT(quit()));
239
240 // ...and allow events to process...
241 eventLoop.exec();
242
243 // ...and we don't crash when the childEntity is removed from the
244 // post construction init routines
245 }
246
247 void shouldNotCrashOnShutdownWhenComponentIsCreatedWithParentBeforeItsEntity()
248 {
249 // GIVEN
250 QEntity *root = new QEntity;
251 // A component parented to an entity...
252 QComponent *component = new Qt3DCore::QTransform(root);
253 // ... created *before* the entity it will be added to.
254 QEntity *entity = new QEntity(root);
255 entity->addComponent(comp: component);
256
257 // An initialized engine (so that the arbiter has been fed)
258 QAspectEngine engine;
259
260 // WHEN
261 engine.setRootEntity(QEntityPtr(root));
262
263 // THEN
264 // Nothing particular happen on exit, especially no crash
265 }
266
267 void shouldRegisterAspectsByName()
268 {
269 // GIVEN
270 QAspectEngine engine;
271
272 // THEN
273 QVERIFY(engine.aspects().isEmpty());
274
275 // WHEN
276 engine.registerAspect(name: "fake");
277
278 // THEN
279 QCOMPARE(engine.aspects().size(), 1);
280 QVERIFY(qobject_cast<FakeAspect*>(engine.aspects().first()));
281 }
282
283 void shouldListLoadedAspects()
284 {
285 // GIVEN
286 QAspectEngine engine;
287
288 // THEN
289 QCOMPARE(engine.executeCommand("list aspects").toString(),
290 QString("No loaded aspect"));
291
292 // WHEN
293 engine.registerAspect(name: "fake");
294
295 // THEN
296 QCOMPARE(engine.executeCommand("list aspects").toString(),
297 QString("fake"));
298
299 // WHEN
300 engine.registerAspect(name: "otherfake");
301
302 // THEN
303 QCOMPARE(engine.executeCommand("list aspects").toString(),
304 QString("fake\notherfake"));
305
306 // WHEN
307 engine.registerAspect(aspect: new FakeAspect3);
308
309 // THEN
310 QCOMPARE(engine.executeCommand("list aspects").toString(),
311 QString("fake\notherfake\n<unnamed>"));
312 }
313
314 void shouldDelegateCommandsToAspects()
315 {
316 // GIVEN
317 QAspectEngine engine;
318 engine.registerAspect(name: "fake");
319 engine.registerAspect(name: "otherfake");
320 engine.registerAspect(aspect: new FakeAspect3);
321
322 // WHEN
323 QVariant output = engine.executeCommand(command: "fake echo foo bar");
324
325 // THEN
326 QVERIFY(output.isValid());
327 QCOMPARE(output.toString(), QString("FakeAspect said 'foo bar'"));
328
329 // WHEN
330 output = engine.executeCommand(command: "otherfake echo fizz buzz");
331
332 // THEN
333 QVERIFY(output.isValid());
334 QCOMPARE(output.toString(), QString("FakeAspect2 said 'fizz buzz'"));
335
336 // WHEN
337 output = engine.executeCommand(command: "mehfake echo fizz buzz");
338
339 // THEN
340 QVERIFY(!output.isValid());
341
342 // WHEN
343 output = engine.executeCommand(command: "fake mooh fizz buzz");
344
345 // THEN
346 QVERIFY(!output.isValid());
347 }
348};
349
350QTEST_MAIN(tst_QAspectEngine)
351
352#include "tst_qaspectengine.moc"
353

source code of qt3d/tests/auto/core/qaspectengine/tst_qaspectengine.cpp