blob: 79e1adf8ef4bd4c9637a81c3fe794451cffc355e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Ford Motor Company
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtRemoteObjects module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "rep_engine_merged.h"
#include "rep_subclass_merged.h"
#include "../shared/model_utilities.h"
#include <QtTest/QtTest>
#include <QRemoteObjectReplica>
#include <QRemoteObjectNode>
const QUrl localHostUrl = QUrl(QLatin1String("local:testHost"));
const QUrl tcpHostUrl = QUrl(QLatin1String("tcp://127.0.0.1:9989"));
const QUrl registryUrl = QUrl(QLatin1String("local:testRegistry"));
#define SET_NODE_NAME(obj) (obj).setName(QLatin1String(#obj))
class ProxyTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void cleanup()
{
// wait for delivery of RemoveObject events to the source
QTest::qWait(200);
}
void testProxy_data();
void testProxy();
// The following should fail to compile, verifying the SourceAPI templates work
// for subclasses
/*
void testSubclass()
{
QRemoteObjectHost host(localHostUrl);
struct invalidchild {
MyPOD myPOD() { return MyPOD(12, 13.f, QStringLiteral("Yay!")); }
};
struct badparent {
invalidchild *subClass() { return new invalidchild; }
} parent;
host.enableRemoting<ParentClassSourceAPI>(&parent);
}
*/
void testTopLevelModel();
};
void ProxyTest::testProxy_data()
{
QTest::addColumn<bool>("sourceApi");
QTest::addColumn<bool>("useProxy");
QTest::addColumn<bool>("dynamic");
QTest::newRow("dynamicApi, no proxy") << false << false << false;
QTest::newRow("sourceApi, no proxy") << true << false << false;
QTest::newRow("dynamicApi, with proxy") << false << true << false;
QTest::newRow("sourceApi, with proxy") << true << true << false;
QTest::newRow("dynamicApi, no proxy, dynamicRep") << false << false << true;
QTest::newRow("sourceApi, no proxy, dynamicRep") << true << false << true;
QTest::newRow("dynamicApi, with proxy, dynamicRep") << false << true << true;
QTest::newRow("sourceApi, with proxy, dynamicRep") << true << true << true;
}
void ProxyTest::testProxy()
{
QFETCH(bool, sourceApi);
QFETCH(bool, useProxy);
QFETCH(bool, dynamic);
//Setup Local Registry
QRemoteObjectRegistryHost registry(registryUrl);
SET_NODE_NAME(registry);
//Setup Local Host
QRemoteObjectHost host(localHostUrl);
SET_NODE_NAME(host);
host.setRegistryUrl(registryUrl);
EngineSimpleSource engine;
engine.setRpm(1234);
engine.setType(EngineSimpleSource::Gas);
if (sourceApi)
host.enableRemoting<EngineSourceAPI>(&engine);
else
host.enableRemoting(&engine);
QRemoteObjectHost proxyNode;
SET_NODE_NAME(proxyNode);
if (useProxy) {
proxyNode.setHostUrl(tcpHostUrl);
proxyNode.proxy(registryUrl);
}
//Setup Local Replica
QRemoteObjectNode client;
SET_NODE_NAME(client);
if (useProxy)
client.connectToNode(tcpHostUrl);
else
client.setRegistryUrl(registryUrl);
QScopedPointer<QRemoteObjectReplica> replica;
if (!dynamic) {
//QLoggingCategory::setFilterRules("qt.remoteobjects*=true");
replica.reset(client.acquire<EngineReplica>());
QVERIFY(replica->waitForSource(1000));
EngineReplica *rep = qobject_cast<EngineReplica *>(replica.data());
//Compare Replica to Source
QCOMPARE(rep->rpm(), engine.rpm());
QCOMPARE((EngineReplica::EngineType)rep->type(), EngineReplica::Gas);
//Change Replica and make sure change propagates to source
QSignalSpy sourceSpy(&engine, SIGNAL(rpmChanged(int)));
QSignalSpy replicaSpy(rep, SIGNAL(rpmChanged(int)));
rep->pushRpm(42);
sourceSpy.wait();
QCOMPARE(sourceSpy.count(), 1);
QCOMPARE(engine.rpm(), 42);
// ... and the change makes it back to the replica
replicaSpy.wait();
QCOMPARE(replicaSpy.count(), 1);
QCOMPARE(rep->rpm(), 42);
} else {
replica.reset(client.acquireDynamic(QStringLiteral("Engine")));
QVERIFY(replica->waitForSource(1000));
//Compare Replica to Source
const QMetaObject *metaObject = replica->metaObject();
const int rpmIndex = metaObject->indexOfProperty("rpm");
Q_ASSERT(rpmIndex != -1);
const QMetaProperty rpmMeta = metaObject->property(rpmIndex);
QCOMPARE(rpmMeta.read(replica.data()).value<int>(), engine.rpm());
const int typeIndex = metaObject->indexOfProperty("type");
Q_ASSERT(typeIndex != -1);
const QMetaProperty typeMeta = metaObject->property(typeIndex);
QCOMPARE(typeMeta.read(replica.data()).value<EngineReplica::EngineType>(), EngineReplica::Gas);
//Change Replica and make sure change propagates to source
QSignalSpy sourceSpy(&engine, SIGNAL(rpmChanged(int)));
QSignalSpy replicaSpy(replica.data(), QByteArray(QByteArrayLiteral("2")+rpmMeta.notifySignal().methodSignature().constData()));
const int rpmPushIndex = metaObject->indexOfMethod("pushRpm(int)");
Q_ASSERT(rpmPushIndex != -1);
QMetaMethod pushMethod = metaObject->method(rpmPushIndex);
Q_ASSERT(pushMethod.isValid());
QVERIFY(pushMethod.invoke(replica.data(), Q_ARG(int, 42)));
sourceSpy.wait();
QCOMPARE(sourceSpy.count(), 1);
QCOMPARE(engine.rpm(), 42);
// ... and the change makes it back to the replica
replicaSpy.wait();
QCOMPARE(replicaSpy.count(), 1);
QCOMPARE(rpmMeta.read(replica.data()).value<int>(), engine.rpm());
}
// Make sure disabling the Source cascades the state change
bool res = host.disableRemoting(&engine);
Q_ASSERT(res);
QSignalSpy stateSpy(replica.data(), SIGNAL(stateChanged(QRemoteObjectReplica::State,QRemoteObjectReplica::State)));
stateSpy.wait();
QCOMPARE(stateSpy.count(), 1);
QCOMPARE(replica->state(), QRemoteObjectReplica::Suspect);
// Now test subclass Source
ParentClassSimpleSource parent;
SubClassSimpleSource subclass;
const MyPOD initialValue(42, 3.14, QStringLiteral("SubClass"));
subclass.setMyPOD(initialValue);
QStringListModel model;
model.setStringList(QStringList() << "Track1" << "Track2" << "Track3");
parent.setSubClass(&subclass);
parent.setTracks(&model);
QCOMPARE(subclass.myPOD(), initialValue);
if (sourceApi)
host.enableRemoting<ParentClassSourceAPI>(&parent);
else
host.enableRemoting(&parent);
if (!dynamic) {
replica.reset(client.acquire<ParentClassReplica>());
ParentClassReplica *rep = qobject_cast<ParentClassReplica *>(replica.data());
QSignalSpy tracksSpy(rep->tracks(), &QAbstractItemModelReplica::initialized);
QVERIFY(replica->waitForSource(1000));
//QLoggingCategory::setFilterRules("qt.remoteobjects*=false");
//Compare Replica to Source
QVERIFY(rep->subClass() != nullptr);
QCOMPARE(rep->subClass()->myPOD(), parent.subClass()->myPOD());
QVERIFY(rep->tracks() != nullptr);
QVERIFY(tracksSpy.count() || tracksSpy.wait());
// Rep file only uses display role, but proxy doesn't forward that yet
if (!useProxy)
QCOMPARE(rep->tracks()->availableRoles(), QVector<int>{Qt::DisplayRole});
else {
const auto &availableRolesVec = rep->tracks()->availableRoles();
QSet<int> availableRoles;
for (int r : availableRolesVec)
availableRoles.insert(r);
const auto &rolesHash = model.roleNames();
QSet<int> roles;
for (auto it = rolesHash.cbegin(), end = rolesHash.cend(); it != end; ++it)
roles.insert(it.key());
QCOMPARE(availableRoles, roles);
}
QSignalSpy dataSpy(rep->tracks(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QVector<QModelIndex> pending;
QTest::qWait(100);
QCOMPARE(rep->tracks()->rowCount(), model.rowCount());
for (int i = 0; i < rep->tracks()->rowCount(); i++)
{
// We haven't received any data yet
const auto index = rep->tracks()->index(i, 0);
QCOMPARE(rep->tracks()->data(index), QVariant());
pending.append(index);
}
if (useProxy) { // A first batch of updates will be the empty proxy values
WaitForDataChanged w(pending, &dataSpy);
QVERIFY(w.wait());
}
WaitForDataChanged w(pending, &dataSpy);
QVERIFY(w.wait());
for (int i = 0; i < rep->tracks()->rowCount(); i++)
{
QCOMPARE(rep->tracks()->data(rep->tracks()->index(i, 0)), model.data(model.index(i), Qt::DisplayRole));
}
//Change SubClass and make sure change propagates
SubClassSimpleSource updatedSubclass;
const MyPOD updatedValue(-1, 123.456, QStringLiteral("Updated"));
updatedSubclass.setMyPOD(updatedValue);
QSignalSpy replicaSpy(rep, SIGNAL(subClassChanged(SubClassReplica*)));
parent.setSubClass(&updatedSubclass);
replicaSpy.wait();
QCOMPARE(replicaSpy.count(), 1);
QCOMPARE(rep->subClass()->myPOD(), parent.subClass()->myPOD());
QCOMPARE(rep->subClass()->myPOD(), updatedValue);
} else {
replica.reset(client.acquireDynamic(QStringLiteral("ParentClass")));
QVERIFY(replica->waitForSource(1000));
const QMetaObject *metaObject = replica->metaObject();
// Verify subClass pointer
const int subclassIndex = metaObject->indexOfProperty("subClass");
QVERIFY(subclassIndex != -1);
const QMetaProperty subclassMeta = metaObject->property(subclassIndex);
QObject *subclassQObjectPtr = subclassMeta.read(replica.data()).value<QObject *>();
QVERIFY(subclassQObjectPtr != nullptr);
QRemoteObjectDynamicReplica *subclassReplica = qobject_cast<QRemoteObjectDynamicReplica *>(subclassQObjectPtr);
QVERIFY(subclassReplica != nullptr);
// Verify tracks pointer
const int tracksIndex = metaObject->indexOfProperty("tracks");
QVERIFY(tracksIndex != -1);
const QMetaProperty tracksMeta = metaObject->property(tracksIndex);
QObject *tracksQObjectPtr = tracksMeta.read(replica.data()).value<QObject *>();
QVERIFY(tracksQObjectPtr != nullptr);
QAbstractItemModelReplica *tracksReplica = qobject_cast<QAbstractItemModelReplica *>(tracksQObjectPtr);
QVERIFY(tracksReplica != nullptr);
// Verify subClass data
const int podIndex = subclassReplica->metaObject()->indexOfProperty("myPOD");
QVERIFY(podIndex != -1);
const QMetaProperty podMeta = subclassReplica->metaObject()->property(podIndex);
MyPOD pod = podMeta.read(subclassReplica).value<MyPOD>();
QCOMPARE(pod, parent.subClass()->myPOD());
// Verify tracks data
// Rep file only uses display role, but proxy doesn't forward that yet
if (!useProxy)
QCOMPARE(tracksReplica->availableRoles(), QVector<int>{Qt::DisplayRole});
else {
const auto &availableRolesVec = tracksReplica->availableRoles();
QSet<int> availableRoles;
for (int r : availableRolesVec)
availableRoles.insert(r);
const auto &rolesHash = model.roleNames();
QSet<int> roles;
for (auto it = rolesHash.cbegin(), end = rolesHash.cend(); it != end; ++it)
roles.insert(it.key());
QCOMPARE(availableRoles, roles);
}
QTRY_COMPARE(tracksReplica->isInitialized(), true);
QSignalSpy dataSpy(tracksReplica, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QVector<QModelIndex> pending;
QTest::qWait(100);
QCOMPARE(tracksReplica->rowCount(), model.rowCount());
for (int i = 0; i < tracksReplica->rowCount(); i++)
{
// We haven't received any data yet
const auto index = tracksReplica->index(i, 0);
QCOMPARE(tracksReplica->data(index), QVariant());
pending.append(index);
}
if (useProxy) { // A first batch of updates will be the empty proxy values
WaitForDataChanged w(pending, &dataSpy);
QVERIFY(w.wait());
}
WaitForDataChanged w(pending, &dataSpy);
QVERIFY(w.wait());
for (int i = 0; i < tracksReplica->rowCount(); i++)
{
QCOMPARE(tracksReplica->data(tracksReplica->index(i, 0)), model.data(model.index(i), Qt::DisplayRole));
}
//Change SubClass and make sure change propagates
SubClassSimpleSource updatedSubclass;
const MyPOD updatedValue(-1, 123.456, QStringLiteral("Updated"));
updatedSubclass.setMyPOD(updatedValue);
QSignalSpy replicaSpy(replica.data(), QByteArray(QByteArrayLiteral("2")+subclassMeta.notifySignal().methodSignature().constData()));
parent.setSubClass(&updatedSubclass);
replicaSpy.wait();
QCOMPARE(replicaSpy.count(), 1);
subclassQObjectPtr = subclassMeta.read(replica.data()).value<QObject *>();
QVERIFY(subclassQObjectPtr != nullptr);
subclassReplica = qobject_cast<QRemoteObjectDynamicReplica *>(subclassQObjectPtr);
QVERIFY(subclassReplica != nullptr);
pod = podMeta.read(subclassReplica).value<MyPOD>();
QCOMPARE(pod, parent.subClass()->myPOD());
}
replica.reset();
}
void ProxyTest::testTopLevelModel()
{
QRemoteObjectRegistryHost registry(registryUrl);
//Setup Local Host
QRemoteObjectHost host(localHostUrl);
SET_NODE_NAME(host);
host.setRegistryUrl(registryUrl);
QStringListModel model;
model.setStringList(QStringList() << "Track1" << "Track2" << "Track3");
host.enableRemoting(&model, "trackList", QVector<int>() << Qt::DisplayRole);
QRemoteObjectHost proxyNode;
SET_NODE_NAME(proxyNode);
proxyNode.setHostUrl(tcpHostUrl);
proxyNode.proxy(registryUrl);
//Setup Local Replica
QRemoteObjectNode client;
SET_NODE_NAME(client);
client.connectToNode(tcpHostUrl);
QAbstractItemModelReplica *replica = client.acquireModel("trackList");
QSignalSpy tracksSpy(replica, &QAbstractItemModelReplica::initialized);
QVERIFY(tracksSpy.wait());
QTest::qWait(100);
QCOMPARE(replica->rowCount(), model.rowCount());
}
QTEST_MAIN(ProxyTest)
#include "tst_proxy.moc"