// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include 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 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 jobsToExecute(qint64) override \ { \ return std::vector(); \ } \ \ 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 root = engine->rootEntity(); QVERIFY(root == e); QVERIFY(root->objectName() == "root"); root = QSharedPointer(); 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(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")); } 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(engine.aspects().at(0))); QVERIFY(qobject_cast(engine.aspects().at(1))); } }; QTEST_MAIN(tst_QAspectEngine) #include "tst_qaspectengine.moc"