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 | |
35 | using namespace Qt3DCore; |
36 | |
37 | class PrintRootAspect : public QAbstractAspect |
38 | { |
39 | Q_OBJECT |
40 | public: |
41 | explicit PrintRootAspect(QObject *parent = 0) |
42 | : QAbstractAspect(parent) |
43 | , m_rootEntityId() |
44 | { |
45 | qDebug() << Q_FUNC_INFO; |
46 | } |
47 | |
48 | private: |
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) \ |
76 | class ClassName : public QAbstractAspect \ |
77 | { \ |
78 | Q_OBJECT \ |
79 | public: \ |
80 | explicit ClassName(QObject *parent = 0) \ |
81 | : QAbstractAspect(parent) {} \ |
82 | \ |
83 | private: \ |
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 | |
105 | FAKE_ASPECT(FakeAspect) |
106 | FAKE_ASPECT(FakeAspect2) |
107 | FAKE_ASPECT(FakeAspect3) |
108 | |
109 | QT3D_REGISTER_ASPECT("fake" , FakeAspect) |
110 | QT3D_REGISTER_ASPECT("otherfake" , FakeAspect2) |
111 | |
112 | |
113 | class tst_QAspectEngine : public QObject |
114 | { |
115 | Q_OBJECT |
116 | public: |
117 | tst_QAspectEngine() : QObject() {} |
118 | ~tst_QAspectEngine() {} |
119 | |
120 | private 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 | |
350 | QTEST_MAIN(tst_QAspectEngine) |
351 | |
352 | #include "tst_qaspectengine.moc" |
353 | |