From 151810d6fc377685ceca829a7f73c2ad00290f68 Mon Sep 17 00:00:00 2001 From: Mat Sutcliffe Date: Fri, 31 Jul 2020 00:25:16 +0100 Subject: [PATCH] Issue #15 Shared state unittest --- cppcheck.supp | 1 + src/blackmisc/sharedstate/datalinkdbus.cpp | 7 +- src/blackmisc/sharedstate/datalinkdbus.h | 6 + src/blackmisc/sharedstate/dbus/hub.h | 2 +- src/blackmisc/sharedstate/dbus/hubimpl.cpp | 6 +- src/blackmisc/sharedstate/dbus/hubimpl.h | 3 +- src/blackmisc/sharedstate/dbus/hubproxy.cpp | 6 +- src/blackmisc/sharedstate/dbus/hubproxy.h | 2 +- tests/blackmisc/blackmisc.pro | 2 + .../sharedstatetestserver/server.cpp | 52 +++++ .../sharedstatetestserver.pro | 27 +++ .../testsharedstate/testsharedstate.cpp | 213 ++++++++++++++++++ .../testsharedstate/testsharedstate.h | 117 ++++++++++ .../testsharedstate/testsharedstate.pro | 28 +++ 14 files changed, 461 insertions(+), 11 deletions(-) create mode 100644 tests/blackmisc/testsharedstate/sharedstatetestserver/server.cpp create mode 100644 tests/blackmisc/testsharedstate/sharedstatetestserver/sharedstatetestserver.pro create mode 100644 tests/blackmisc/testsharedstate/testsharedstate.cpp create mode 100644 tests/blackmisc/testsharedstate/testsharedstate.h create mode 100644 tests/blackmisc/testsharedstate/testsharedstate.pro diff --git a/cppcheck.supp b/cppcheck.supp index 54e4dec21..735246a4b 100644 --- a/cppcheck.supp +++ b/cppcheck.supp @@ -42,6 +42,7 @@ returnDanglingLifetime:src/blackmisc/iterator.h unreadVariable:tests/blackcore/testreaders/testreaders.cpp unknownMacro:src/blackmisc/aviation/altitude.h unknownMacro:src/blackmisc/geo/elevationplane.h +unknownMacro:tests/blackmisc/testsharedstate/testsharedstate.h ctuArrayIndex:src/xswiftbus/libxplanemp/src/XPMPPlaneRenderer.cpp // False positive memory leaks due to Qt parent/child relationship diff --git a/src/blackmisc/sharedstate/datalinkdbus.cpp b/src/blackmisc/sharedstate/datalinkdbus.cpp index cf9c8db55..841146e41 100644 --- a/src/blackmisc/sharedstate/datalinkdbus.cpp +++ b/src/blackmisc/sharedstate/datalinkdbus.cpp @@ -35,6 +35,11 @@ namespace BlackMisc setConnectionStatus(false); } + void CDataLinkDBus::overrideIdentifier(const CIdentifier &id) + { + m_identifier = id; + } + void CDataLinkDBus::initializeLocal(CDBusServer *server) { Q_ASSERT_X(!m_hub, Q_FUNC_INFO, "Already initialized"); @@ -62,7 +67,7 @@ namespace BlackMisc if (m_duplex) { return; } QFuture ready; - std::tie(m_duplex, ready) = m_hub->getDuplex(); + std::tie(m_duplex, ready) = m_hub->getDuplex(m_identifier); connect(m_duplex.get(), &IDuplex::eventPosted, this, &CDataLinkDBus::handlePeerEvent); connect(m_duplex.get(), &IDuplex::peerSubscriptionsReceived, this, &CDataLinkDBus::setPeerSubscriptions); connect(m_duplex.get(), &IDuplex::requestReceived, this, &CDataLinkDBus::handlePeerRequest); diff --git a/src/blackmisc/sharedstate/datalinkdbus.h b/src/blackmisc/sharedstate/datalinkdbus.h index c9874213b..cd54c93a7 100644 --- a/src/blackmisc/sharedstate/datalinkdbus.h +++ b/src/blackmisc/sharedstate/datalinkdbus.h @@ -14,6 +14,7 @@ #include "blackmisc/sharedstate/datalink.h" #include "blackmisc/blackmiscexport.h" #include "blackmisc/variantlist.h" +#include "blackmisc/identifier.h" #include class QDBusConnection; @@ -46,6 +47,9 @@ namespace BlackMisc //! Destructor. virtual ~CDataLinkDBus() override; + //! Override identifier for testing purposes. + void overrideIdentifier(const CIdentifier &); + //! Initialize on server side. void initializeLocal(CDBusServer *server = nullptr); @@ -83,6 +87,8 @@ namespace BlackMisc QTimer m_watchTimer; DBus::IHub *m_hub = nullptr; QSharedPointer m_duplex; + CIdentifier m_identifier = CIdentifier::anonymous(); + QMap m_channels; mutable QMutex m_channelsMutex { QMutex::Recursive }; }; diff --git a/src/blackmisc/sharedstate/dbus/hub.h b/src/blackmisc/sharedstate/dbus/hub.h index 8d084436e..12dde6eaa 100644 --- a/src/blackmisc/sharedstate/dbus/hub.h +++ b/src/blackmisc/sharedstate/dbus/hub.h @@ -53,7 +53,7 @@ namespace BlackMisc virtual bool isConnected() const = 0; //! Get a duplex object for the calling process. - virtual std::pair, QFuture> getDuplex() = 0; + virtual std::pair, QFuture> getDuplex(const CIdentifier &) = 0; public slots: //! Create a duplex object for the identified process. diff --git a/src/blackmisc/sharedstate/dbus/hubimpl.cpp b/src/blackmisc/sharedstate/dbus/hubimpl.cpp index 2ba95b2c0..c2f98e4cd 100644 --- a/src/blackmisc/sharedstate/dbus/hubimpl.cpp +++ b/src/blackmisc/sharedstate/dbus/hubimpl.cpp @@ -24,10 +24,10 @@ namespace BlackMisc if (server) { server->addObject(BLACKMISC_HUB_PATH, this); } } - std::pair, QFuture> CHub::getDuplex() + std::pair, QFuture> CHub::getDuplex(const CIdentifier &identifier) { - auto future = openDuplexAsync(CIdentifier::anonymous()); - return std::make_pair(m_clients.value(CIdentifier::anonymous()), future); + auto future = openDuplexAsync(identifier); + return std::make_pair(m_clients.value(identifier), future); } bool CHub::openDuplex(const BlackMisc::CIdentifier &client) diff --git a/src/blackmisc/sharedstate/dbus/hubimpl.h b/src/blackmisc/sharedstate/dbus/hubimpl.h index 1357895d5..f599286e5 100644 --- a/src/blackmisc/sharedstate/dbus/hubimpl.h +++ b/src/blackmisc/sharedstate/dbus/hubimpl.h @@ -48,8 +48,7 @@ namespace BlackMisc //! \name Interface implementations //! @{ virtual bool isConnected() const override { return true; } - virtual std::pair, QFuture> getDuplex() override; - //! @} + virtual std::pair, QFuture> getDuplex(const CIdentifier &) override; public slots: //! \name Interface implementations diff --git a/src/blackmisc/sharedstate/dbus/hubproxy.cpp b/src/blackmisc/sharedstate/dbus/hubproxy.cpp index 0c42f56ad..cd9a921de 100644 --- a/src/blackmisc/sharedstate/dbus/hubproxy.cpp +++ b/src/blackmisc/sharedstate/dbus/hubproxy.cpp @@ -31,11 +31,11 @@ namespace BlackMisc return m_interface->isValid(); } - std::pair, QFuture> CHubProxy::getDuplex() + std::pair, QFuture> CHubProxy::getDuplex(const CIdentifier &identifier) { auto duplex = QSharedPointer::create(m_interface->connection(), m_service, this); - connect(duplex.get(), &QObject::destroyed, this, [ = ] { closeDuplex(CIdentifier::anonymous()); }); - return std::make_pair(duplex, openDuplexAsync(CIdentifier::anonymous())); + connect(duplex.get(), &QObject::destroyed, this, [ = ] { closeDuplex(identifier); }); + return std::make_pair(duplex, openDuplexAsync(identifier)); } bool CHubProxy::openDuplex(const CIdentifier& client) diff --git a/src/blackmisc/sharedstate/dbus/hubproxy.h b/src/blackmisc/sharedstate/dbus/hubproxy.h index ebbd4105e..717476182 100644 --- a/src/blackmisc/sharedstate/dbus/hubproxy.h +++ b/src/blackmisc/sharedstate/dbus/hubproxy.h @@ -41,7 +41,7 @@ namespace BlackMisc //! \name Interface implementations //! @{ virtual bool isConnected() const override; - virtual std::pair, QFuture> getDuplex() override; + virtual std::pair, QFuture> getDuplex(const CIdentifier &) override; //! @} public slots: diff --git a/tests/blackmisc/blackmisc.pro b/tests/blackmisc/blackmisc.pro index 133043da7..53de83909 100644 --- a/tests/blackmisc/blackmisc.pro +++ b/tests/blackmisc/blackmisc.pro @@ -16,6 +16,8 @@ SUBDIRS += \ testlibrarypath \ testprocess \ testpropertyindex \ + testsharedstate \ + testsharedstate/sharedstatetestserver \ testslot \ teststatusmessage \ teststringutils \ diff --git a/tests/blackmisc/testsharedstate/sharedstatetestserver/server.cpp b/tests/blackmisc/testsharedstate/sharedstatetestserver/server.cpp new file mode 100644 index 000000000..ceea6f9e8 --- /dev/null +++ b/tests/blackmisc/testsharedstate/sharedstatetestserver/server.cpp @@ -0,0 +1,52 @@ +/* Copyright (C) 2020 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \cond PRIVATE_TESTS + +/*! + * \file + * \ingroup testblackmisc + */ + +#include "../testsharedstate.h" +#include "blackmisc/sharedstate/datalinkdbus.h" +#include "blackmisc/registermetadata.h" +#include "blackmisc/dbusserver.h" +#include +#include + +using namespace BlackMisc; +using namespace BlackMisc::SharedState; +using namespace BlackMiscTest; + +//! \private +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + BlackMisc::registerMetadata(); + CTestFilter::registerMetadata(); + + CDBusServer server(CDBusServer::sessionBusAddress()); + CDataLinkDBus dataLink; + dataLink.initializeLocal(&server); + + CTestScalarMutator scalarMutator(nullptr); + CTestScalarJournal scalarJournal(nullptr); + CTestListMutator listMutator(nullptr); + CTestListJournal listJournal(nullptr); + scalarMutator.initialize(&dataLink); + scalarJournal.initialize(&dataLink); + listMutator.initialize(&dataLink); + listJournal.initialize(&dataLink); + + scalarMutator.setValue(42); + for (int e = 1; e <= 6; ++e) { listMutator.addElement(e); } + return app.exec(); +} + +//! \endcond \ No newline at end of file diff --git a/tests/blackmisc/testsharedstate/sharedstatetestserver/sharedstatetestserver.pro b/tests/blackmisc/testsharedstate/sharedstatetestserver/sharedstatetestserver.pro new file mode 100644 index 000000000..008b9fa1a --- /dev/null +++ b/tests/blackmisc/testsharedstate/sharedstatetestserver/sharedstatetestserver.pro @@ -0,0 +1,27 @@ +load(common_pre) + +QT += core dbus network + +TARGET = sharedstatetestserver +CONFIG -= app_bundle +CONFIG += console +CONFIG += blackconfig +CONFIG += blackmisc + +TEMPLATE = app + +DEPENDPATH += \ + . \ + $$SourceRoot/src \ + $$SourceRoot/tests \ + +INCLUDEPATH += \ + $$SourceRoot/src \ + $$SourceRoot/tests \ + +SOURCES += server.cpp +HEADERS += ../testsharedstate.h + +DESTDIR = $$DestRoot/bin + +load(common_post) diff --git a/tests/blackmisc/testsharedstate/testsharedstate.cpp b/tests/blackmisc/testsharedstate/testsharedstate.cpp new file mode 100644 index 000000000..2dc3154f7 --- /dev/null +++ b/tests/blackmisc/testsharedstate/testsharedstate.cpp @@ -0,0 +1,213 @@ +/* Copyright (C) 2020 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \cond PRIVATE_TESTS + +/*! + * \file + * \ingroup testblackmisc + */ + +#include "testsharedstate.h" +#include "blackmisc/sharedstate/datalinklocal.h" +#include "blackmisc/sharedstate/datalinkdbus.h" +#include "blackmisc/registermetadata.h" +#include "blackmisc/dbusserver.h" +#include "test.h" +#include +#include +#include + +using namespace QTest; +using namespace BlackMisc; +using namespace BlackMisc::SharedState; + +namespace BlackMiscTest +{ + //! DBus implementation classes tests + class CTestSharedState : public QObject + { + Q_OBJECT + + private slots: + //! Init test case environment + void initTestCase(); + + //! Test promise/future pair + void testPromise(); + + //! Test scalar value shared over local datalink + void localScalar(); + + //! Test list value shared over local datalink + void localList(); + + //! Test scalar value shared over dbus datalink + void dbusScalar(); + + //! Test list value shared over dbus datalink + void dbusList(); + }; + + void CTestSharedState::initTestCase() + { + BlackMisc::registerMetadata(); + CTestFilter::registerMetadata(); + } + + void CTestSharedState::testPromise() + { + int result = 0; + CPromise promise; + doAfter(promise.future(), nullptr, [ & ](QFuture future) { result = future.result(); }); + promise.setResult(42); + bool ok = qWaitFor([ & ] { return result == 42; }); + QVERIFY2(ok, "future contains expected result"); + } + + void CTestSharedState::localScalar() + { + CDataLinkLocal dataLink; + CTestScalarMutator mutator(this); + CTestScalarJournal journal(this); + CTestScalarObserver observer(this); + mutator.initialize(&dataLink); + journal.initialize(&dataLink); + observer.initialize(&dataLink); + + mutator.setValue(42); + bool ok = qWaitFor([ & ] { return observer.value() == 42; }); + QVERIFY2(ok, "expected value received"); + qWait(0); + QVERIFY2(observer.value() == 42, "still has expected value"); + + CTestScalarObserver observer2(this); + observer2.initialize(&dataLink); + + ok = qWaitFor([ & ] { return observer2.value() == 42; }); + QVERIFY2(ok, "new observer got existing value"); + qWait(0); + QVERIFY2(observer2.value() == 42, "still has correct value"); + } + + void CTestSharedState::localList() + { + CDataLinkLocal dataLink; + CTestListMutator mutator(this); + CTestListJournal journal(this); + CTestListObserver observer(this); + mutator.initialize(&dataLink); + journal.initialize(&dataLink); + observer.initialize(&dataLink); + + observer.setFilter({}); + mutator.addElement(1); + bool ok = qWaitFor([ & ] { return observer.allValues() == QList { 1 }; }); + QVERIFY2(ok, "expected value received"); + qWait(0); + QVERIFY2(observer.allValues() == QList { 1 }, "still has expected value"); + + for (int e = 2; e <= 6; ++e) { mutator.addElement(e); } + ok = qWaitFor([ & ] { return observer.allValues() == QList { 1, 2, 3, 4, 5, 6 }; }); + QVERIFY2(ok, "expected value received"); + + observer.allValues() = {}; + observer.setFilter({ 1 }); + ok = qWaitFor([ & ] { return observer.allValues() == QList { 1, 3, 5 }; }); + QVERIFY2(ok, "expected value received"); + } + + //! RAII wrapper + class Server + { + public: + //! ctor + Server() + { + m_process.start("sharedstatetestserver", QStringList()); + m_process.waitForStarted(); + } + //! dtor + ~Server() + { + m_process.kill(); + m_process.waitForFinished(); + } + private: + QProcess m_process; + }; + + void CTestSharedState::dbusScalar() + { + QDBusConnection connection = QDBusConnection::sessionBus(); + if (!connection.isConnected()) { QSKIP("No session bus"); } + Server s; + + CDataLinkDBus dataLink; + dataLink.initializeRemote(connection, SWIFT_SERVICENAME); + bool ok = qWaitFor([ & ] { return dataLink.watcher()->isConnected(); }); + QVERIFY2(ok, "Connection failed"); + + CTestScalarObserver observer(this); + CTestScalarMutator mutator(this); + observer.initialize(&dataLink); + mutator.initialize(&dataLink); + + ok = qWaitFor([ & ] { return observer.value() == 42; }); + QVERIFY2(ok, "expected value received"); + qWait(1000); + QVERIFY2(observer.value() == 42, "still has expected value"); + + mutator.setValue(69); + ok = qWaitFor([ & ] { return observer.value() == 69; }); + QVERIFY2(ok, "expected value received"); + qWait(1000); + QVERIFY2(observer.value() == 69, "still has expected value"); + } + + void CTestSharedState::dbusList() + { + QDBusConnection connection = QDBusConnection::sessionBus(); + if (!connection.isConnected()) { QSKIP("No session bus"); } + Server s; + + CDataLinkDBus dataLink; + dataLink.initializeRemote(connection, SWIFT_SERVICENAME); + bool ok = qWaitFor([ & ] { return dataLink.watcher()->isConnected(); }); + QVERIFY2(ok, "Connection failed"); + + CTestListObserver observer(this); + CTestListMutator mutator(this); + observer.initialize(&dataLink); + mutator.initialize(&dataLink); + + observer.setFilter({}); + ok = qWaitFor([ & ] { return observer.allValues() == QList { 1, 2, 3, 4, 5, 6 }; }); + QVERIFY2(ok, "expected value received"); + qWait(1000); + QVERIFY2(observer.allValues() == QList({ 1, 2, 3, 4, 5, 6 }), "still has expected value"); + + observer.allValues() = {}; + observer.setFilter({ 1 }); + ok = qWaitFor([ & ] { return observer.allValues() == QList { 1, 3, 5 }; }); + QVERIFY2(ok, "expected value received"); + + mutator.addElement(7); + ok = qWaitFor([ & ] { return observer.allValues() == QList { 1, 3, 5, 7 }; }); + QVERIFY2(ok, "expected value received"); + qWait(1000); + QVERIFY2(observer.allValues() == QList({ 1, 3, 5, 7 }), "still has expected value"); + } +} + +//! main +BLACKTEST_MAIN(BlackMiscTest::CTestSharedState); + +#include "testsharedstate.moc" + +//! \endcond diff --git a/tests/blackmisc/testsharedstate/testsharedstate.h b/tests/blackmisc/testsharedstate/testsharedstate.h new file mode 100644 index 000000000..11d7b5353 --- /dev/null +++ b/tests/blackmisc/testsharedstate/testsharedstate.h @@ -0,0 +1,117 @@ +/* Copyright (C) 2020 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \cond PRIVATE_TESTS + +/*! + * \file + * \ingroup testblackmisc + */ + +#include "blackmisc/sharedstate/scalarmutator.h" +#include "blackmisc/sharedstate/scalarjournal.h" +#include "blackmisc/sharedstate/scalarobserver.h" +#include "blackmisc/sharedstate/listmutator.h" +#include "blackmisc/sharedstate/listjournal.h" +#include "blackmisc/sharedstate/listobserver.h" +#include "blackmisc/sharedstate/datalink.h" +#include + +namespace BlackMiscTest +{ + //! \private List filter for testing + class CTestFilter : public BlackMisc::CValueObject + { + public: + bool matches(const BlackMisc::CVariant &value) const + { + return value.canConvert() && (value.to() & m_mask); + } + QString convertToQString(bool = false) const { return QString::number(m_mask); } + CTestFilter(unsigned mask = ~0u) : m_mask(mask) {} + private: + unsigned m_mask = ~0u; + BLACK_METACLASS(CTestFilter, BLACK_METAMEMBER(mask)); + }; +} + +//! \cond +Q_DECLARE_METATYPE(BlackMiscTest::CTestFilter); +//! \endcond + +namespace BlackMiscTest +{ + //! Scalar mutator subclass + class CTestScalarMutator : public BlackMisc::SharedState::CScalarMutator + { + Q_OBJECT + BLACK_SHARED_STATE_CHANNEL("test_scalar_channel") + public: + //! Ctor + CTestScalarMutator(QObject *parent) : CScalarMutator(parent) {} + }; + + //! Scalar journal subclass + class CTestScalarJournal : public BlackMisc::SharedState::CScalarJournal + { + Q_OBJECT + BLACK_SHARED_STATE_CHANNEL("test_scalar_channel") + public: + //! Ctor + CTestScalarJournal(QObject *parent) : CScalarJournal(parent) {} + }; + + //! Scalar observer subclass + class CTestScalarObserver : public BlackMisc::SharedState::CScalarObserver + { + Q_OBJECT + BLACK_SHARED_STATE_CHANNEL("test_scalar_channel") + public: + //! Ctor + CTestScalarObserver(QObject *parent) : CScalarObserver(parent) {} + virtual void onValueChanged(const int &) override {} + }; + + //! List mutator subclass + class CTestListMutator : public BlackMisc::SharedState::CListMutator> + { + Q_OBJECT + BLACK_SHARED_STATE_CHANNEL("test_list_channel") + public: + //! Ctor + CTestListMutator(QObject *parent) : CListMutator(parent) {} + }; + + //! List journal subclass + class CTestListJournal : public BlackMisc::SharedState::CListJournal> + { + Q_OBJECT + BLACK_SHARED_STATE_CHANNEL("test_list_channel") + public: + //! Ctor + CTestListJournal(QObject *parent) : CListJournal(parent) {} + }; + + //! List observer subclass + class CTestListObserver : public BlackMisc::SharedState::CListObserver, CTestFilter> + { + Q_OBJECT + BLACK_SHARED_STATE_CHANNEL("test_list_channel") + public: + //! Ctor + CTestListObserver(QObject *parent) : CListObserver(parent) {} + + //! \name Interface implementation + //! @{ + virtual void onElementAdded(const int &) override {} + virtual void onElementsReplaced(const QList &) override {} + //! @} + }; +} + +//! \endcond diff --git a/tests/blackmisc/testsharedstate/testsharedstate.pro b/tests/blackmisc/testsharedstate/testsharedstate.pro new file mode 100644 index 000000000..847ec8565 --- /dev/null +++ b/tests/blackmisc/testsharedstate/testsharedstate.pro @@ -0,0 +1,28 @@ +load(common_pre) + +QT += core dbus testlib network + +TARGET = testsharedstate +CONFIG -= app_bundle +CONFIG += blackconfig +CONFIG += blackmisc +CONFIG += testcase +CONFIG += no_testcase_installs + +TEMPLATE = app + +DEPENDPATH += \ + . \ + $$SourceRoot/src \ + $$SourceRoot/tests \ + +INCLUDEPATH += \ + $$SourceRoot/src \ + $$SourceRoot/tests \ + +SOURCES += testsharedstate.cpp +HEADERS += testsharedstate.h + +DESTDIR = $$DestRoot/bin + +load(common_post)