Issue #15 Shared state unittest

This commit is contained in:
Mat Sutcliffe
2020-07-31 00:25:16 +01:00
parent 46caf4640b
commit 151810d6fc
14 changed files with 461 additions and 11 deletions

View File

@@ -42,6 +42,7 @@ returnDanglingLifetime:src/blackmisc/iterator.h
unreadVariable:tests/blackcore/testreaders/testreaders.cpp unreadVariable:tests/blackcore/testreaders/testreaders.cpp
unknownMacro:src/blackmisc/aviation/altitude.h unknownMacro:src/blackmisc/aviation/altitude.h
unknownMacro:src/blackmisc/geo/elevationplane.h unknownMacro:src/blackmisc/geo/elevationplane.h
unknownMacro:tests/blackmisc/testsharedstate/testsharedstate.h
ctuArrayIndex:src/xswiftbus/libxplanemp/src/XPMPPlaneRenderer.cpp ctuArrayIndex:src/xswiftbus/libxplanemp/src/XPMPPlaneRenderer.cpp
// False positive memory leaks due to Qt parent/child relationship // False positive memory leaks due to Qt parent/child relationship

View File

@@ -35,6 +35,11 @@ namespace BlackMisc
setConnectionStatus(false); setConnectionStatus(false);
} }
void CDataLinkDBus::overrideIdentifier(const CIdentifier &id)
{
m_identifier = id;
}
void CDataLinkDBus::initializeLocal(CDBusServer *server) void CDataLinkDBus::initializeLocal(CDBusServer *server)
{ {
Q_ASSERT_X(!m_hub, Q_FUNC_INFO, "Already initialized"); Q_ASSERT_X(!m_hub, Q_FUNC_INFO, "Already initialized");
@@ -62,7 +67,7 @@ namespace BlackMisc
if (m_duplex) { return; } if (m_duplex) { return; }
QFuture<void> ready; QFuture<void> 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::eventPosted, this, &CDataLinkDBus::handlePeerEvent);
connect(m_duplex.get(), &IDuplex::peerSubscriptionsReceived, this, &CDataLinkDBus::setPeerSubscriptions); connect(m_duplex.get(), &IDuplex::peerSubscriptionsReceived, this, &CDataLinkDBus::setPeerSubscriptions);
connect(m_duplex.get(), &IDuplex::requestReceived, this, &CDataLinkDBus::handlePeerRequest); connect(m_duplex.get(), &IDuplex::requestReceived, this, &CDataLinkDBus::handlePeerRequest);

View File

@@ -14,6 +14,7 @@
#include "blackmisc/sharedstate/datalink.h" #include "blackmisc/sharedstate/datalink.h"
#include "blackmisc/blackmiscexport.h" #include "blackmisc/blackmiscexport.h"
#include "blackmisc/variantlist.h" #include "blackmisc/variantlist.h"
#include "blackmisc/identifier.h"
#include <QSharedPointer> #include <QSharedPointer>
class QDBusConnection; class QDBusConnection;
@@ -46,6 +47,9 @@ namespace BlackMisc
//! Destructor. //! Destructor.
virtual ~CDataLinkDBus() override; virtual ~CDataLinkDBus() override;
//! Override identifier for testing purposes.
void overrideIdentifier(const CIdentifier &);
//! Initialize on server side. //! Initialize on server side.
void initializeLocal(CDBusServer *server = nullptr); void initializeLocal(CDBusServer *server = nullptr);
@@ -83,6 +87,8 @@ namespace BlackMisc
QTimer m_watchTimer; QTimer m_watchTimer;
DBus::IHub *m_hub = nullptr; DBus::IHub *m_hub = nullptr;
QSharedPointer<DBus::IDuplex> m_duplex; QSharedPointer<DBus::IDuplex> m_duplex;
CIdentifier m_identifier = CIdentifier::anonymous();
QMap<QString, Channel> m_channels; QMap<QString, Channel> m_channels;
mutable QMutex m_channelsMutex { QMutex::Recursive }; mutable QMutex m_channelsMutex { QMutex::Recursive };
}; };

View File

@@ -53,7 +53,7 @@ namespace BlackMisc
virtual bool isConnected() const = 0; virtual bool isConnected() const = 0;
//! Get a duplex object for the calling process. //! Get a duplex object for the calling process.
virtual std::pair<QSharedPointer<IDuplex>, QFuture<void>> getDuplex() = 0; virtual std::pair<QSharedPointer<IDuplex>, QFuture<void>> getDuplex(const CIdentifier &) = 0;
public slots: public slots:
//! Create a duplex object for the identified process. //! Create a duplex object for the identified process.

View File

@@ -24,10 +24,10 @@ namespace BlackMisc
if (server) { server->addObject(BLACKMISC_HUB_PATH, this); } if (server) { server->addObject(BLACKMISC_HUB_PATH, this); }
} }
std::pair<QSharedPointer<IDuplex>, QFuture<void>> CHub::getDuplex() std::pair<QSharedPointer<IDuplex>, QFuture<void>> CHub::getDuplex(const CIdentifier &identifier)
{ {
auto future = openDuplexAsync(CIdentifier::anonymous()); auto future = openDuplexAsync(identifier);
return std::make_pair(m_clients.value(CIdentifier::anonymous()), future); return std::make_pair(m_clients.value(identifier), future);
} }
bool CHub::openDuplex(const BlackMisc::CIdentifier &client) bool CHub::openDuplex(const BlackMisc::CIdentifier &client)

View File

@@ -48,8 +48,7 @@ namespace BlackMisc
//! \name Interface implementations //! \name Interface implementations
//! @{ //! @{
virtual bool isConnected() const override { return true; } virtual bool isConnected() const override { return true; }
virtual std::pair<QSharedPointer<IDuplex>, QFuture<void>> getDuplex() override; virtual std::pair<QSharedPointer<IDuplex>, QFuture<void>> getDuplex(const CIdentifier &) override;
//! @}
public slots: public slots:
//! \name Interface implementations //! \name Interface implementations

View File

@@ -31,11 +31,11 @@ namespace BlackMisc
return m_interface->isValid(); return m_interface->isValid();
} }
std::pair<QSharedPointer<IDuplex>, QFuture<void>> CHubProxy::getDuplex() std::pair<QSharedPointer<IDuplex>, QFuture<void>> CHubProxy::getDuplex(const CIdentifier &identifier)
{ {
auto duplex = QSharedPointer<CDuplexProxy>::create(m_interface->connection(), m_service, this); auto duplex = QSharedPointer<CDuplexProxy>::create(m_interface->connection(), m_service, this);
connect(duplex.get(), &QObject::destroyed, this, [ = ] { closeDuplex(CIdentifier::anonymous()); }); connect(duplex.get(), &QObject::destroyed, this, [ = ] { closeDuplex(identifier); });
return std::make_pair(duplex, openDuplexAsync(CIdentifier::anonymous())); return std::make_pair(duplex, openDuplexAsync(identifier));
} }
bool CHubProxy::openDuplex(const CIdentifier& client) bool CHubProxy::openDuplex(const CIdentifier& client)

View File

@@ -41,7 +41,7 @@ namespace BlackMisc
//! \name Interface implementations //! \name Interface implementations
//! @{ //! @{
virtual bool isConnected() const override; virtual bool isConnected() const override;
virtual std::pair<QSharedPointer<IDuplex>, QFuture<void>> getDuplex() override; virtual std::pair<QSharedPointer<IDuplex>, QFuture<void>> getDuplex(const CIdentifier &) override;
//! @} //! @}
public slots: public slots:

View File

@@ -16,6 +16,8 @@ SUBDIRS += \
testlibrarypath \ testlibrarypath \
testprocess \ testprocess \
testpropertyindex \ testpropertyindex \
testsharedstate \
testsharedstate/sharedstatetestserver \
testslot \ testslot \
teststatusmessage \ teststatusmessage \
teststringutils \ teststringutils \

View File

@@ -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 <QCoreApplication>
#include <QDBusConnection>
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

View File

@@ -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)

View File

@@ -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 <QTest>
#include <QProcess>
#include <QDBusConnection>
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<int> promise;
doAfter(promise.future(), nullptr, [ & ](QFuture<int> 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<int> { 1 }; });
QVERIFY2(ok, "expected value received");
qWait(0);
QVERIFY2(observer.allValues() == QList<int> { 1 }, "still has expected value");
for (int e = 2; e <= 6; ++e) { mutator.addElement(e); }
ok = qWaitFor([ & ] { return observer.allValues() == QList<int> { 1, 2, 3, 4, 5, 6 }; });
QVERIFY2(ok, "expected value received");
observer.allValues() = {};
observer.setFilter({ 1 });
ok = qWaitFor([ & ] { return observer.allValues() == QList<int> { 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<int> { 1, 2, 3, 4, 5, 6 }; });
QVERIFY2(ok, "expected value received");
qWait(1000);
QVERIFY2(observer.allValues() == QList<int>({ 1, 2, 3, 4, 5, 6 }), "still has expected value");
observer.allValues() = {};
observer.setFilter({ 1 });
ok = qWaitFor([ & ] { return observer.allValues() == QList<int> { 1, 3, 5 }; });
QVERIFY2(ok, "expected value received");
mutator.addElement(7);
ok = qWaitFor([ & ] { return observer.allValues() == QList<int> { 1, 3, 5, 7 }; });
QVERIFY2(ok, "expected value received");
qWait(1000);
QVERIFY2(observer.allValues() == QList<int>({ 1, 3, 5, 7 }), "still has expected value");
}
}
//! main
BLACKTEST_MAIN(BlackMiscTest::CTestSharedState);
#include "testsharedstate.moc"
//! \endcond

View File

@@ -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 <QMetaType>
namespace BlackMiscTest
{
//! \private List filter for testing
class CTestFilter : public BlackMisc::CValueObject<CTestFilter>
{
public:
bool matches(const BlackMisc::CVariant &value) const
{
return value.canConvert<unsigned>() && (value.to<unsigned>() & 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<int>
{
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<int>
{
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<int>
{
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<QList<int>>
{
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<QList<int>>
{
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<QList<int>, 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<int> &) override {}
//! @}
};
}
//! \endcond

View File

@@ -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)