/*! * \brief Unit tests for \ref Env * * \copyright Copyright (c) 2017-2019 Governikus GmbH & Co. KG, Germany */ #include "Env.h" #include "LogHandler.h" #include #include #include using namespace governikus; namespace { struct TestEmptyTmp { }; class AbstractTestPodInstance { public: virtual ~AbstractTestPodInstance() { } virtual QString dummy() = 0; }; class AbstractTestInstance { Q_GADGET public: virtual ~AbstractTestInstance() { } virtual QString dummy() = 0; }; class AbstractTestInstanceImpl : public AbstractTestInstance { Q_GADGET public: virtual QString dummy() override { return QStringLiteral("impl"); } }; class MockedAbstractTestInstance : public AbstractTestInstance { Q_GADGET public: virtual QString dummy() override { return QStringLiteral("mocked"); } }; class TestInstance { Q_GADGET protected: TestInstance() = default; virtual ~TestInstance() { } public: static TestInstance& getInstance() { static TestInstance instance; return instance; } virtual QString something() { return QStringLiteral("orig"); } }; class TestMockedInstance : public TestInstance { public: virtual QString something() override { return QStringLiteral("mocked"); } }; class TestSharedInstance : public QObject { Q_OBJECT public: virtual QString something() { return QStringLiteral("orig"); } }; class TestMockedSharedInstance : public TestSharedInstance { Q_OBJECT public: virtual QString something() override { return QStringLiteral("mocked"); } }; class TestAbstractUnmanagedInstance { Q_GADGET public: virtual ~TestAbstractUnmanagedInstance() = default; virtual QString something() = 0; }; class TestUnmanagedInstance : public TestAbstractUnmanagedInstance { Q_GADGET public: virtual QString something() override { return QStringLiteral("TestUnmanagedInstance"); } }; class TestMoveCtorAssign : public QObject { Q_OBJECT private: QString mData; public: TestMoveCtorAssign(const QString& pData) : mData(pData) { } TestMoveCtorAssign(TestMoveCtorAssign&& pCopy) { mData = std::move(pCopy.mData); } TestMoveCtorAssign& operator=(TestMoveCtorAssign&& pCopy) { mData = std::move(pCopy.mData); return *this; } QString data() { return mData; } }; class TestSingleInCtor { Q_GADGET public: TestSingleInCtor() { Env::create(); Env::getSingleton(); } static TestSingleInCtor& getInstance() { static TestSingleInCtor Instance; return Instance; } }; } // namespace Q_DECLARE_METATYPE(std::function ) namespace governikus { template<> TestAbstractUnmanagedInstance* singleton() { static TestUnmanagedInstance instance; return &instance; } template<> AbstractTestInstance* singleton() { static AbstractTestInstanceImpl instance; return &instance; } template<> AbstractTestInstance* createNewObject() { return new AbstractTestInstanceImpl; } template<> AbstractTestInstance* createNewObject(QString&& pStr) { class tmpCtor : public AbstractTestInstance { private: const QString mDummy; public: tmpCtor(const QString& pDummy) : mDummy(pDummy) { } virtual QString dummy() override { return mDummy; } }; return new tmpCtor(pStr); } template<> AbstractTestPodInstance* createNewObject() { return nullptr; } template<> AbstractTestPodInstance* createNewObject(QString&&) { return nullptr; } } // namespace governikus class test_Env : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { Env::getSingleton()->init(); } void cleanup() { Env::getSingleton()->resetBacklog(); Env::clear(); } void checkOverrideMock() { QCOMPARE(Env::getInstance().mInstancesCreator.size(), 0); QScopedPointer obj(Env::create()); QVERIFY(obj); const std::function func = [](){ return nullptr; }; Env::setCreator(func); QCOMPARE(Env::getInstance().mInstancesCreator.size(), 1); Env::setCreator(func); Env::setCreator(func); Env::setCreator(func); QCOMPARE(Env::getInstance().mInstancesCreator.size(), 1); QCOMPARE(Env::create(), nullptr); } void mockGet() { auto orig = Env::getSingleton(); QVERIFY(orig); QCOMPARE(orig->something(), QLatin1String("orig")); TestMockedInstance mock; Env::set(TestInstance::staticMetaObject, &mock); auto mocked = Env::getSingleton(); QVERIFY(mocked); QCOMPARE(mocked->something(), QLatin1String("mocked")); Env::set(TestInstance::staticMetaObject); auto orig_again = Env::getSingleton(); QVERIFY(orig_again); QCOMPARE(orig_again->something(), QLatin1String("orig")); } void mockAbstract() { auto impl = Env::getSingleton(); QVERIFY(impl); QCOMPARE(impl->dummy(), QLatin1String("impl")); auto impl2 = Env::getSingleton(); QVERIFY(impl == impl2); MockedAbstractTestInstance m; Env::set(AbstractTestInstance::staticMetaObject, &m); auto mocked = Env::getSingleton(); QVERIFY(mocked); QVERIFY(mocked != impl); QCOMPARE(mocked->dummy(), QLatin1String("mocked")); } void mockShared() { auto orig = Env::getShared(); QVERIFY(orig); QCOMPARE(orig->something(), QLatin1String("orig")); auto orig2 = Env::getShared(); QVERIFY(orig == orig2); auto mock = QSharedPointer::create(); Env::setShared(TestSharedInstance::staticMetaObject, mock); auto mocked = Env::getShared(); QVERIFY(mocked); QCOMPARE(mocked->something(), QLatin1String("mocked")); QVERIFY(mock == mocked); QVERIFY(orig != mocked); Env::setShared(TestSharedInstance::staticMetaObject); auto orig3 = Env::getShared(); QVERIFY(orig != orig3); QCOMPARE(orig3->something(), QLatin1String("orig")); } void getUnmanagedSingleton() { auto first = Env::getSingleton(); QVERIFY(first); QCOMPARE(first->something(), QLatin1String("TestUnmanagedInstance")); auto second = Env::getSingleton(); QVERIFY(second); QVERIFY(first == second); } void mockCreateNewInstance() { QScopedPointer implOrig(Env::create()); QVERIFY(implOrig); QCOMPARE(implOrig->dummy(), QLatin1String("impl")); implOrig.reset(); std::function func = [](){ class tmp : public AbstractTestInstanceImpl { virtual QString dummy() override { return QStringLiteral("lambda"); } }; return new tmp; }; Env::setCreator(func); QScopedPointer impl(Env::create()); QVERIFY(impl); QCOMPARE(impl->dummy(), QLatin1String("lambda")); QScopedPointer impl2(Env::create()); QVERIFY(impl2); QCOMPARE(impl2->dummy(), QLatin1String("lambda")); QVERIFY(impl != impl2); } void mockArgs() { QScopedPointer impl(Env::create(QString("flupp"))); QVERIFY(impl); QCOMPARE(impl->dummy(), QLatin1String("flupp")); impl.reset(); std::function funcString = [](const QString& pStr){ class tmp : public AbstractTestPodInstance { const QString mDummy; public: tmp(const QString& pDummy) : mDummy(QStringLiteral("YEAH: ") + pDummy) { } virtual QString dummy() override { return mDummy; } }; return new tmp(pStr); }; std::function funcDefault = [](){ class tmp : public AbstractTestPodInstance { public: virtual QString dummy() override { return QStringLiteral("default"); } }; return new tmp(); }; Env::setCreator(funcString); Env::setCreator(funcDefault); QScopedPointer catcher(Env::create(QString("bla"))); QVERIFY(catcher); QCOMPARE(catcher->dummy(), QLatin1String("YEAH: bla")); catcher.reset(Env::create()); QVERIFY(catcher); QCOMPARE(catcher->dummy(), QLatin1String("default")); } void mockCopy() { class TestCopyCtor { private: int mData; public: TestCopyCtor(int pData) : mData(pData) { } int data() { return mData; } }; auto obj = Env::create(666); QCOMPARE(obj.data(), 666); obj = Env::create(1904); QCOMPARE(obj.data(), 1904); std::function func = [](int){ return TestCopyCtor(1); }; Env::setCreator(func); obj = Env::create(1982); QCOMPARE(obj.data(), 1); } void mockMove() { auto obj = Env::create(QString("huhu")); QCOMPARE(obj.data(), QLatin1String("huhu")); std::function func = [](const QString&){ return TestMoveCtorAssign(QStringLiteral("mocked")); }; Env::setCreator(func); auto mock = Env::create(QString("huhu")); QCOMPARE(mock.data(), QLatin1String("mocked")); QCOMPARE(obj.data(), QLatin1String("huhu")); func = [](const QString&){ return TestMoveCtorAssign(QStringLiteral("mocked2")); }; Env::setCreator(func); mock = Env::create(QString("huhu2")); QCOMPARE(mock.data(), QLatin1String("mocked2")); QCOMPARE(obj.data(), QLatin1String("huhu")); } void mockCounter() { struct TestTmp { int mData; TestTmp(int pData = 666) : mData(pData) { } }; auto obj = Env::create(); QCOMPARE(obj.mData, 666); obj = Env::create(1904); QCOMPARE(obj.mData, 1904); const std::function func = [](){ return TestTmp(999); }; QCOMPARE(Env::getCounter(), -1); Env::setCreator(func); QCOMPARE(Env::getCounter(), 0); obj = Env::create(); QCOMPARE(obj.mData, 999); QCOMPARE(Env::getCounter(), 1); obj = Env::create(123); QCOMPARE(obj.mData, 123); QCOMPARE(Env::getCounter(), 1); const std::function func2 = [](int){ return TestTmp(777); }; QCOMPARE((Env::getCounter()), -1); Env::setCreator(func2); QCOMPARE(Env::getCounter(), 1); QCOMPARE((Env::getCounter()), 0); obj = Env::create(); QCOMPARE(obj.mData, 999); QCOMPARE(Env::getCounter(), 2); QCOMPARE((Env::getCounter()), 0); obj = Env::create(123); QCOMPARE(obj.mData, 777); QCOMPARE(Env::getCounter(), 2); QCOMPARE((Env::getCounter()), 1); Env::resetCounter(); QCOMPARE(Env::getCounter(), 0); QCOMPARE((Env::getCounter()), 0); Env::clear(); QCOMPARE(Env::getCounter(), -1); QCOMPARE((Env::getCounter()), -1); } void threadSafeCheckSetCreator() { QVERIFY(QThreadPool::globalInstance()->maxThreadCount() > 1); const std::function func = [](){ return TestEmptyTmp(); }; TestMockedInstance mock; QVector > threads; for (int i = 0; i < 100; ++i) { threads << QtConcurrent::run([func, &mock] { for (int j = 0; j < 100; ++j) { Env::setCreator(func); Env::create(); Env::getSingleton(); Env::getShared(); Env::set(TestInstance::staticMetaObject, &mock); } }); } for (auto future : qAsConst(threads)) { future.waitForFinished(); } } void threadSafeDeadlockDuringCreate_data() { QTest::addColumn >("creator"); QTest::newRow("with mock") << std::function([](){ return TestEmptyTmp(); }); QTest::newRow("without mock") << std::function(); } void threadSafeDeadlockDuringCreate() { QFETCH(std::function, creator); if (creator) { Env::setCreator(creator); } class CreateTmp { public: CreateTmp() { Env::create(); Env::getSingleton(); } }; Env::create(); if (creator) { QVERIFY(Env::getCounter() > 0); } } void checkLogForSameThreadsGadget() { QThread::currentThread()->setObjectName("Main"); QSignalSpy spy(Env::getSingleton(), &LogHandler::fireLog); Env::getSingleton(); QCOMPARE(spy.count(), 0); Env::getSingleton(); QCOMPARE(spy.count(), 0); } void checkLogForDifferentThreadsGadget() { QThread::currentThread()->setObjectName("Main"); QSignalSpy spy(Env::getSingleton(), &LogHandler::fireLog); Env::getSingleton(); QCOMPARE(spy.count(), 0); QThreadPool pool; // do not use global one, otherwise the main thread is allowed, too QtConcurrent::run(&pool, [] { Env::getSingleton(); }).waitForFinished(); QCOMPARE(spy.count(), 0); } void checkLogForSameThreadsQObject() { QThread::currentThread()->setObjectName("Main"); QSignalSpy spy(Env::getSingleton(), &LogHandler::fireLog); Env::getSingleton(); QCOMPARE(spy.count(), 0); } void checkLogForDifferentThreadsQObject() { QThread::currentThread()->setObjectName("Main"); QSignalSpy spy(Env::getSingleton(), &LogHandler::fireLog); Env::getSingleton(); QCOMPARE(spy.count(), 0); QThreadPool pool; // do not use global one, otherwise the main thead is allowed, too QtConcurrent::run(&pool, [] { Env::getSingleton(); }).waitForFinished(); QCOMPARE(spy.count(), 1); QVERIFY(spy.takeLast().at(0).toString().contains(QLatin1String("governikus::LogHandler was created in \"Main\" but is requested by \"Thread (pooled)\""))); } }; QTEST_GUILESS_MAIN(test_Env) #include "test_Env.moc"