Revision control
++ // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtTest/QTest>
++ #include <QTimer>
++
++ #include <Qt3DCore/QAbstractAspect>
++ #include <Qt3DCore/qaspectengine.h>
++ #include <Qt3DCore/qentity.h>
++ #include <Qt3DCore/qtransform.h>
++
++ using namespace Qt3DCore;
++
++ class PrintRootAspect : public QAbstractAspect
++ {
++ Q_OBJECT
++ public:
++ explicit PrintRootAspect(QObject *parent = 0)
++ : QAbstractAspect(parent)
++ , m_rootEntityId()
++ {
++ qDebug() << Q_FUNC_INFO;
++ }
++
++ private:
++ void onRegistered() override
++ {
++ qDebug() << Q_FUNC_INFO;
++ }
++
++ void onEngineStartup() override
++ {
++ qDebug() << Q_FUNC_INFO;
++ m_rootEntityId = rootEntityId();
++ }
++
++ void onEngineShutdown() override
++ {
++ qDebug() << Q_FUNC_INFO;
++ }
++
++ std::vector<QAspectJobPtr> jobsToExecute(qint64) override \
++ {
++ if (m_rootEntityId)
++ qDebug() << Q_FUNC_INFO << m_rootEntityId;
++ return {};
++ }
++
++ QNodeId m_rootEntityId;
++ };
++
++ #define FAKE_ASPECT(ClassName, dependAspects) \
++ class ClassName : public QAbstractAspect \
++ { \
++ Q_OBJECT \
++ public: \
++ explicit ClassName(QObject *parent = nullptr) \
++ : QAbstractAspect(parent) {} \
++ \
++ private: \
++ void onRegistered() override {} \
++ void onEngineStartup() override {} \
++ void onEngineShutdown() override {} \
++ \
++ std::vector<QAspectJobPtr> jobsToExecute(qint64) override \
++ { \
++ return std::vector<QAspectJobPtr>(); \
++ } \
++ \
++ QVariant executeCommand(const QStringList &args) override \
++ { \
++ if (args.size() >= 2 && args.first() == QLatin1String("echo")) { \
++ QStringList list = args; \
++ list.removeFirst(); \
++ return QString("%1 said '%2'").arg(metaObject()->className()).arg(list.join(QLatin1Char(' '))); \
++ } \
++ \
++ return QVariant(); \
++ } \
++ QStringList dependencies() const override { return dependAspects; } \
++ };
++
++ FAKE_ASPECT(FakeAspect, {})
++ FAKE_ASPECT(FakeAspect2, {})
++ FAKE_ASPECT(FakeAspect3, {})
++ FAKE_ASPECT(FakeAspectDependent, {QLatin1String("fake")})
++
++ QT3D_REGISTER_ASPECT("fake", FakeAspect)
++ QT3D_REGISTER_ASPECT("otherfake", FakeAspect2)
++ QT3D_REGISTER_ASPECT("dependfake", FakeAspectDependent)
++
++
++ class tst_QAspectEngine : public QObject
++ {
++ Q_OBJECT
++ public:
++ tst_QAspectEngine() : QObject() {}
++ ~tst_QAspectEngine() {}
++
++ private Q_SLOTS:
++ // TODO: Add more QAspectEngine tests
++
++ void constructionDestruction()
++ {
++ QAspectEngine *engine = new QAspectEngine;
++ QVERIFY(engine->rootEntity() == nullptr);
++ delete engine;
++ }
++
++ void setRootEntity()
++ {
++ QAspectEngine *engine = new QAspectEngine;
++
++ QEntity *e = new QEntity;
++ e->setObjectName("root");
++ engine->setRootEntity(QEntityPtr(e));
++
++ QSharedPointer<QEntity> root = engine->rootEntity();
++ QVERIFY(root == e);
++ QVERIFY(root->objectName() == "root");
++ root = QSharedPointer<QEntity>();
++ QVERIFY(engine->rootEntity()->objectName() == "root");
++
++ delete engine;
++ }
++
++ void shouldNotCrashInNormalStartupShutdownSequence()
++ {
++ #ifdef Q_OS_MACOS
++ QSKIP("Test frequently times out. See QTBUG-80660.");
++ #endif
++
++ // GIVEN
++ // An initialized aspect engine...
++ QAspectEngine engine;
++ // ...and a simple aspect
++ PrintRootAspect *aspect = new PrintRootAspect;
++
++ // WHEN
++ // We register the aspect
++ engine.registerAspect(aspect);
++
++ // THEN
++ const auto registeredAspects = engine.aspects();
++ QCOMPARE(registeredAspects.size(), 1);
++ QCOMPARE(registeredAspects.first(), aspect);
++
++ // WHEN
++ QEntityPtr entity(new QEntity);
++ entity->setObjectName("RootEntity");
++ // we set a scene root entity
++ engine.setRootEntity(entity);
++
++ QEventLoop eventLoop;
++ QTimer::singleShot(1000, &eventLoop, SLOT(quit()));
++ eventLoop.exec();
++
++ // THEN
++ // we don't crash and...
++ const auto rootEntity = engine.rootEntity();
++ QCOMPARE(rootEntity, entity);
++
++ // WHEN
++ // we set an empty/null scene root...
++ engine.setRootEntity(QEntityPtr());
++ QTimer::singleShot(600, &eventLoop, SLOT(quit()));
++
++ // ...and allow events to process...
++ eventLoop.exec();
++
++ // THEN
++ // ... we don't crash.
++
++ // TODO: Add more tests to check for
++ // * re-setting a scene
++ // * deregistering aspects
++ // * destroying the aspect engine
++ }
++
++ void shouldNotCrashWhenEntityIsAddedThenImmediatelyDeleted()
++ {
++ // GIVEN
++ // An initialized aspect engine...
++ QAspectEngine engine;
++ // ...and a simple aspect
++ PrintRootAspect *aspect = new PrintRootAspect;
++
++ // WHEN
++ // We register the aspect
++ engine.registerAspect(aspect);
++
++ // THEN
++ const auto registeredAspects = engine.aspects();
++ QCOMPARE(registeredAspects.size(), 1);
++ QCOMPARE(registeredAspects.first(), aspect);
++
++ // WHEN
++ QEntityPtr entity(new QEntity);
++ entity->setObjectName("RootEntity");
++ // we set a scene root entity
++ engine.setRootEntity(entity);
++
++ QEventLoop eventLoop;
++ QTimer::singleShot(1000, &eventLoop, SLOT(quit()));
++ eventLoop.exec();
++
++ // THEN
++ // we don't crash and...
++ const auto rootEntity = engine.rootEntity();
++ QCOMPARE(rootEntity, entity);
++
++ // WHEN
++ // we create a child node and delete within the same spin of
++ // the event loop
++ Qt3DCore::QEntity *childEntity = new Qt3DCore::QEntity(entity.data());
++ delete childEntity;
++ entity = nullptr;
++ QTimer::singleShot(600, &eventLoop, SLOT(quit()));
++
++ // ...and allow events to process...
++ eventLoop.exec();
++
++ // ...and we don't crash when the childEntity is removed from the
++ // post construction init routines
++ }
++
++ void shouldNotCrashOnShutdownWhenComponentIsCreatedWithParentBeforeItsEntity()
++ {
++ // GIVEN
++ QEntity *root = new QEntity;
++ // A component parented to an entity...
++ QComponent *component = new Qt3DCore::QTransform(root);
++ // ... created *before* the entity it will be added to.
++ QEntity *entity = new QEntity(root);
++ entity->addComponent(component);
++
++ // An initialized engine (so that the arbiter has been fed)
++ QAspectEngine engine;
++
++ // WHEN
++ engine.setRootEntity(QEntityPtr(root));
++
++ // THEN
++ // Nothing particular happen on exit, especially no crash
++ }
++
++ void shouldRegisterAspectsByName()
++ {
++ // GIVEN
++ QAspectEngine engine;
++
++ // THEN
++ QVERIFY(engine.aspects().isEmpty());
++
++ // WHEN
++ engine.registerAspect("fake");
++
++ // THEN
++ QCOMPARE(engine.aspects().size(), 1);
++ QVERIFY(qobject_cast<FakeAspect*>(engine.aspects().first()));
++ }
++
++ void shouldListLoadedAspects()
++ {
++ // GIVEN
++ QAspectEngine engine;
++
++ // THEN
++ QCOMPARE(engine.executeCommand("list aspects").toString(),
++ QString("No loaded aspect"));
++
++ // WHEN
++ engine.registerAspect("fake");
++
++ // THEN
++ QCOMPARE(engine.executeCommand("list aspects").toString(),
++ QString("fake"));
++
++ // WHEN
++ engine.registerAspect("otherfake");
++
++ // THEN
++ QCOMPARE(engine.executeCommand("list aspects").toString(),
++ QString("fake\notherfake"));
++
++ // WHEN
++ engine.registerAspect(new FakeAspect3);
++
++ // THEN
++ QCOMPARE(engine.executeCommand("list aspects").toString(),
++ QString("fake\notherfake\n<unnamed>"));
++ }
++
++ void shouldDelegateCommandsToAspects()
++ {
++ // GIVEN
++ QAspectEngine engine;
++ engine.registerAspect("fake");
++ engine.registerAspect("otherfake");
++ engine.registerAspect(new FakeAspect3);
++
++ // WHEN
++ QVariant output = engine.executeCommand("fake echo foo bar");
++
++ // THEN
++ QVERIFY(output.isValid());
++ QCOMPARE(output.toString(), QString("FakeAspect said 'foo bar'"));
++
++ // WHEN
++ output = engine.executeCommand("otherfake echo fizz buzz");
++
++ // THEN
++ QVERIFY(output.isValid());
++ QCOMPARE(output.toString(), QString("FakeAspect2 said 'fizz buzz'"));
++
++ // WHEN
++ output = engine.executeCommand("mehfake echo fizz buzz");
++
++ // THEN
++ QVERIFY(!output.isValid());
++
++ // WHEN
++ output = engine.executeCommand("fake mooh fizz buzz");
++
++ // THEN
++ QVERIFY(!output.isValid());
++ }
++
++ void shouldRegisterDependentAspects()
++ {
++ // GIVEN
++ QAspectEngine engine;
++
++ // THEN
++ QVERIFY(engine.aspects().isEmpty());
++
++ // WHEN
++ engine.registerAspect("dependfake");
++
++ // THEN
++ QCOMPARE(engine.aspects().size(), 2);
++ QVERIFY(qobject_cast<FakeAspect*>(engine.aspects().at(0)));
++ QVERIFY(qobject_cast<FakeAspectDependent*>(engine.aspects().at(1)));
++ }
++ };
++
++ QTEST_MAIN(tst_QAspectEngine)
++
++ #include "tst_qaspectengine.moc"