| /**************************************************************************** |
| ** |
| ** 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 proxyNodeUrl = QUrl(QLatin1String("tcp://127.0.0.1:12123")); |
| const QUrl remoteNodeUrl = QUrl(QLatin1String("tcp://127.0.0.1:23234")); |
| const QUrl registryUrl = QUrl(QLatin1String("local:testRegistry")); |
| const QUrl proxyHostUrl = QUrl(QLatin1String("local:fromProxy")); |
| |
| #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(); |
| void testForwardProxy(); |
| void testReverseProxy(); |
| // 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::testForwardProxy() |
| { |
| // Setup Local Registry |
| QRemoteObjectRegistryHost registry(registryUrl); |
| SET_NODE_NAME(registry); |
| |
| // Setup Local Host |
| QRemoteObjectHost host(localHostUrl, registryUrl); |
| SET_NODE_NAME(host); |
| |
| // Setup Proxy |
| QRemoteObjectRegistryHost proxyNode(proxyNodeUrl); |
| SET_NODE_NAME(proxyNode); |
| proxyNode.proxy(registryUrl, proxyHostUrl); |
| // Include the reverseProxy to make sure we don't try to send back |
| // proxied objects. |
| proxyNode.reverseProxy(); |
| |
| // Setup Source |
| EngineSimpleSource engine; |
| engine.setRpm(1234); |
| |
| // Setup Remote Node |
| QRemoteObjectHost remoteNode(remoteNodeUrl); |
| SET_NODE_NAME(remoteNode); |
| remoteNode.connectToNode(proxyNodeUrl); |
| |
| // Add source |
| host.enableRemoting(&engine); |
| |
| // Setup Replica |
| const QScopedPointer<EngineReplica> replica(remoteNode.acquire<EngineReplica>()); |
| QVERIFY(replica->waitForSource(1000)); |
| |
| // Compare Replica to Source |
| QCOMPARE(replica->rpm(), engine.rpm()); |
| } |
| |
| void ProxyTest::testReverseProxy() |
| { |
| // Setup Local Registry |
| QRemoteObjectRegistryHost registry(registryUrl); |
| SET_NODE_NAME(registry); |
| |
| // Setup Local Host |
| QRemoteObjectHost host(localHostUrl, registryUrl); |
| SET_NODE_NAME(host); |
| |
| // Setup Proxy |
| // QRemoteObjectRegistryHost proxyNode(proxyNodeUrl); |
| QRemoteObjectRegistryHost proxyNode(proxyNodeUrl); |
| SET_NODE_NAME(proxyNode); |
| proxyNode.proxy(registryUrl, proxyHostUrl); |
| proxyNode.reverseProxy(); |
| |
| // Setup Source |
| EngineSimpleSource engine; |
| engine.setRpm(1234); |
| |
| // Setup Remote Node |
| QRemoteObjectHost remoteNode(remoteNodeUrl, proxyNodeUrl); |
| SET_NODE_NAME(remoteNode); |
| |
| // Add source |
| remoteNode.enableRemoting(&engine); |
| |
| // Setup Replica |
| const QScopedPointer<EngineReplica> replica(host.acquire<EngineReplica>()); |
| QVERIFY(replica->waitForSource(1000)); |
| |
| //Compare Replica to Source |
| QCOMPARE(replica->rpm(), engine.rpm()); |
| } |
| |
| 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" |