summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc137
-rw-r--r--src/remoteobjects/doc/src/remoteobjects-index.qdoc1
-rw-r--r--src/remoteobjects/qconnectionfactories.cpp38
-rw-r--r--src/remoteobjects/qconnectionfactories_p.h22
-rw-r--r--src/remoteobjects/qremoteobjectnode.cpp148
-rw-r--r--src/remoteobjects/qremoteobjectnode.h14
-rw-r--r--src/remoteobjects/qremoteobjectnode_p.h5
-rw-r--r--src/remoteobjects/qremoteobjectreplica.cpp17
-rw-r--r--src/remoteobjects/qremoteobjectreplica_p.h6
-rw-r--r--src/remoteobjects/qremoteobjectsource.cpp8
-rw-r--r--src/remoteobjects/qremoteobjectsource_p.h8
-rw-r--r--src/remoteobjects/qremoteobjectsourceio.cpp51
-rw-r--r--src/remoteobjects/qremoteobjectsourceio_p.h7
-rw-r--r--src/remoteobjects/qtremoteobjectglobal.h1
-rw-r--r--tests/auto/auto.pro2
-rw-r--r--tests/auto/integration/tst_integration.cpp59
-rw-r--r--tests/auto/integration_external/MyInterface.rep18
-rw-r--r--tests/auto/integration_external/client/client.pro16
-rw-r--r--tests/auto/integration_external/client/main.cpp202
-rw-r--r--tests/auto/integration_external/external/external.pro9
-rw-r--r--tests/auto/integration_external/external/tst_integration_external.cpp110
-rw-r--r--tests/auto/integration_external/integration_external.pro2
-rw-r--r--tests/auto/integration_external/server/main.cpp121
-rw-r--r--tests/auto/integration_external/server/mytestserver.cpp74
-rw-r--r--tests/auto/integration_external/server/mytestserver.h59
-rw-r--r--tests/auto/integration_external/server/server.pro19
26 files changed, 1089 insertions, 65 deletions
diff --git a/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc
new file mode 100644
index 0000000..fdc8e4f
--- /dev/null
+++ b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Ford Motor Company
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+\page qtremoteobjects-external-schemas.html
+\title Qt Remote Objects - External QIODevices
+\brief Describes how Qt Remote Objects supports custom QIODevice channels.
+
+\section1 External QIODevices
+
+Qt Remote Objects supports several communications channels out-of-the-box, such
+as the \l QTcpServer and \l QTcpSocket pair. Given the desired \l QUrl for tcp,
+or the desired name (for the \l QLocalServer and \l QLocalSocket pair), the
+code needed to listen and connect are boilerplate and handled internally by Qt.
+Qt Remote Objects supports other types of \l QIODevice as well, and the \l
+QRemoteObjectNode classes provide additional methods to support cases where
+custom code is needed.
+
+A contrived example with TCP/IP is shown below. A more realistic example would
+use an SSL connection, which would require configuration of certificates, etc.
+
+\code
+ // Create the server and listen outside of QtRO
+ QTcpServer tcpServer;
+ tcpServer.listen(QHostAddress(127.0.0.1), 65213);
+
+ // Create the host node. We don't need a hostUrl unless we want to take
+ // advantage of external schemas (see next example).
+ QRemoteObjectHost srcNode();
+
+ // Make sure any connections are handed to QtRO
+ QObject::connect(&tcpServer, &QTcpServer::newConnection, &srcNode,
+ [&srcNode, &tcpServer]() {
+ srcNode.addHostSideConnection(tcpServer.nextPendingConnection());
+ });
+\endcode
+
+The Replica side code needs to manually connect to the Host
+\code
+ QRemoteObjectNode repNode();
+ QTcpSocket *socket = new QTcpSocket(&repNode);
+ connect(socket, &QTcpSocket::connected, &repNode,
+ [socket, &repNode]() {
+ repNode.addClientSideConnection(socket);
+ });
+ socket->connectToHost(QHostAddress(127.0.0.1), 65213);
+ };
+\endcode
+
+\section1 External Schemas
+
+It is possible to create each side of the QIODevice and call \l
+{QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice)} and \l
+{QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice)} as shown
+above. This is fully supported, but requires the client know how to establish
+the connection or have a way to discover that information. This is exactly the
+problem the registry was designed to solve.
+
+Qt Remote Objects also allows "External Schemas" to be used with the registry,
+which helps with connection setup. On the \l QRemoteObjectHostNode side, the
+user must set the hostUrl with the desired schema.
+
+\code
+ // Use standard tcp url for the registry
+ const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212"));
+ // Use "exttcp" for the "external" interface
+ const QUrl extUrl = QUrl(QStringLiteral("exttcp://127.0.0.1:65213"));
+
+ // Create the server and listen outside of QtRO
+ QTcpServer tcpServer;
+ tcpServer.listen(QHostAddress(extUrl.host()), extUrl.port());
+
+ // We need a registry for everyone to connect to
+ QRemoteObjectRegistryHost registry(registryUrl);
+
+ // Finally, we create our host node and register "exttcp" as our schema.
+ // We need the AllowExternalRegistration parameter to prevent the node from
+ // setting a hostUrlInvalid error.
+ QRemoteObjectHost srcNode(extUrl, registryUrl, QRemoteObjectHost::AllowExternalRegistration);
+ // From now on, when we call enableRemoting() from this node, the registry
+ // will be updated to show the Source object at extUrl.
+\endcode
+
+On the \l Replica side, the \l QRemoteObjectNode needs to register a callback
+to be used when the external schema is detected. The callback must be a \l
+RemoteObjectSchemaHandler.
+
+\code
+ // Use standard tcp url for the registry
+ const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212"));
+
+ // This time create the node connected to the registry
+ QRemoteObjectNode repNode(registryUrl);
+
+ // Create the RemoteObjectSchemaHandler callback
+ QRemoteObjectNode::RemoteObjectSchemaHandler setupTcp = [&repNode](QUrl url) {
+ QTcpSocket *socket = new QTcpSocket(&repNode);
+ connect(socket, &QTcpSocket::connected,
+ [socket, &repNode]() {
+ repNode.addClientSideConnection(socket);
+ });
+ connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error),
+ [socket](QAbstractSocket::SocketError error) {
+ delete socket;
+ });
+ socket->connectToHost(url.host(), url.port());
+ };
+
+ // Once we call registerExternalSchema, the above method will be called
+ // whenever the registry sees an object we are interested in on "exttcp"
+ repNode.registerExternalSchema(QStringLiteral("exttcp"), setupTcp);
+\endcode
+*/
diff --git a/src/remoteobjects/doc/src/remoteobjects-index.qdoc b/src/remoteobjects/doc/src/remoteobjects-index.qdoc
index d483271..7b008bf 100644
--- a/src/remoteobjects/doc/src/remoteobjects-index.qdoc
+++ b/src/remoteobjects/doc/src/remoteobjects-index.qdoc
@@ -96,6 +96,7 @@ See \l{Qt Licensing} for further details.
\li \l {Source Objects}{Qt Remote Objects Source Objects}
\li \l {Replica Objects}{Qt Remote Objects Replica Objects}
\li \l {Qt Remote Objects Registry}
+ \li \l {Qt Remote Objects - External QIODevices}
\li \l {Qt Remote Objects Compiler}
\li \l {Remote Object Interaction}
\li \l {Using Qt Remote Objects}
diff --git a/src/remoteobjects/qconnectionfactories.cpp b/src/remoteobjects/qconnectionfactories.cpp
index 317d2fa..0ca2fa8 100644
--- a/src/remoteobjects/qconnectionfactories.cpp
+++ b/src/remoteobjects/qconnectionfactories.cpp
@@ -79,13 +79,14 @@ inline bool fromDataStream(QDataStream &in, QRemoteObjectPacketTypeEnum &type, Q
case Ping: type = Ping; break;
case Pong: type = Pong; break;
default:
- qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << type;
+ qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << _type;
}
if (type == Invalid)
return false;
if (type == ObjectList)
return true;
in >> name;
+ qCDebug(QT_REMOTEOBJECT_IO) << "Packet received of type" << type << "for object" << name;
return true;
}
@@ -225,6 +226,41 @@ ServerIoDevice *QConnectionAbstractServer::nextPendingConnection()
return iodevice;
}
+ExternalIoDevice::ExternalIoDevice(QIODevice *device, QObject *parent)
+ : IoDeviceBase(parent)
+ , m_device(device)
+{
+ initializeDataStream();
+ connect(m_device, &QIODevice::aboutToClose, this, [this]() { this->m_isClosing = true; });
+ connect(m_device, &QIODevice::readyRead, this, &ExternalIoDevice::readyRead);
+ auto meta = device->metaObject();
+ if (-1 == meta->indexOfSignal(SIGNAL(disconnected())))
+ connect(m_device, SIGNAL(disconnected()), this, SIGNAL(disconnected()));
+}
+
+QIODevice *ExternalIoDevice::connection() const
+{
+ return m_device;
+}
+
+bool ExternalIoDevice::isOpen() const
+{
+ if (!m_device)
+ return false;
+ return m_device->isOpen() && IoDeviceBase::isOpen();
+}
+
+void ExternalIoDevice::doClose()
+{
+ if (isOpen())
+ m_device->close();
+}
+
+QString ExternalIoDevice::deviceType() const
+{
+ return QStringLiteral("ExternalIoDevice");
+}
+
/*!
\class QtROServerFactory
\inmodule QtRemoteObjects
diff --git a/src/remoteobjects/qconnectionfactories_p.h b/src/remoteobjects/qconnectionfactories_p.h
index 56283be..84bc917 100644
--- a/src/remoteobjects/qconnectionfactories_p.h
+++ b/src/remoteobjects/qconnectionfactories_p.h
@@ -53,6 +53,8 @@
#include <QAbstractSocket>
#include <QDataStream>
+#include <QIODevice>
+#include <QPointer>
#include <QtRemoteObjects/qtremoteobjectglobal.h>
@@ -91,6 +93,7 @@ public:
Q_SIGNALS:
void readyRead();
+ void disconnected();
protected:
virtual QString deviceType() const = 0;
@@ -111,9 +114,6 @@ class Q_REMOTEOBJECTS_EXPORT ServerIoDevice : public IoDeviceBase
public:
explicit ServerIoDevice(QObject *parent = nullptr);
-Q_SIGNALS:
- void disconnected();
-
protected:
QString deviceType() const override;
};
@@ -156,7 +156,6 @@ public:
QUrl url() const;
Q_SIGNALS:
- void disconnected();
void shouldReconnect(ClientIoDevice*);
protected:
@@ -169,6 +168,21 @@ private:
QUrl m_url;
};
+class ExternalIoDevice : public IoDeviceBase
+{
+ Q_OBJECT
+
+public:
+ explicit ExternalIoDevice(QIODevice *device, QObject *parent=nullptr);
+ QIODevice *connection() const override;
+ bool isOpen() const override;
+
+protected:
+ void doClose() override;
+ QString deviceType() const override;
+ QPointer<QIODevice> m_device;
+};
+
class QtROServerFactory
{
public:
diff --git a/src/remoteobjects/qremoteobjectnode.cpp b/src/remoteobjects/qremoteobjectnode.cpp
index 5173c45..2dd7086 100644
--- a/src/remoteobjects/qremoteobjectnode.cpp
+++ b/src/remoteobjects/qremoteobjectnode.cpp
@@ -163,6 +163,44 @@ void QRemoteObjectNode::setHeartbeatInterval(int interval)
}
/*!
+ \since 5.12
+ \typedef QRemoteObjectNode::RemoteObjectSchemaHandler
+
+ Typedef for a std::function method that can take a QUrl input and is
+ responsible for creating the communications channel between this node and
+ the node hosting the desired \l Source. As some types of QIODevices (e.g.,
+ QSslSocket) require additional steps before the device is ready for use,
+ the method is responsible for calling \l addClientSideConnection once the
+ connection is fully established.
+*/
+
+/*!
+ \since 5.12
+ \brief Provide a custom method to handle externally provided schemas
+
+ This method is tied to the \l Registry and \l {External Schemas}. By
+ registering a std::function handler for an external schema, the registered
+ method will be called when the registry is notified of a \l Source you've
+ acquired being available. Without this registration, QtRO would only be
+ able to handle the "built-in" schemas.
+
+ The provided method, \a handler, will be called when the registry sees a \l
+ Source object on a new (not yet connected) Node with a {QUrl::schema()} of
+ \a schema. The \a handler, of type \l
+ QRemoteObjectNode::RemoteObjectSchemaHandler will get the \l QUrl of the
+ Node providing the \l Source as an input parameter, and is responsible for
+ establishing the communications channel (a \l QIODevice of some sort) and
+ calling \l addClientSideConnection with it.
+
+ \sa RemoteObjectSchemaHandler
+*/
+void QRemoteObjectNode::registerExternalSchema(const QString &schema, QRemoteObjectNode::RemoteObjectSchemaHandler handler)
+{
+ Q_D(QRemoteObjectNode);
+ d->schemaHandlers.insert(schema, handler);
+}
+
+/*!
\since 5.11
\brief Forward Remote Objects from another network
@@ -642,21 +680,26 @@ bool QRemoteObjectNodePrivate::initConnection(const QUrl &address)
requestedUrls.insert(address);
+ if (schemaHandlers.contains(address.scheme())) {
+ schemaHandlers[address.scheme()](address);
+ return true;
+ }
+
ClientIoDevice *connection = QtROClientFactory::instance()->create(address, q);
if (!connection) {
qROPrivWarning() << "Could not create ClientIoDevice for client. Invalid url/scheme provided?" << address;
return false;
}
-
qROPrivDebug() << "Opening connection to" << address.toString();
qROPrivDebug() << "Replica Connection isValid" << connection->isOpen();
QObject::connect(connection, &ClientIoDevice::shouldReconnect, q, [this, connection]() {
onShouldReconnect(connection);
});
- QObject::connect(connection, &ClientIoDevice::readyRead, q, [this, connection]() {
+ QObject::connect(connection, &IoDeviceBase::readyRead, q, [this, connection]() {
onClientRead(connection);
});
connection->connectToServer();
+
return true;
}
@@ -791,7 +834,7 @@ void QRemoteObjectNodePrivate::handleReplicaConnection(const QString &name)
}
}
-void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, ClientIoDevice *connection)
+void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection)
{
if (!checkSignatures(rep->m_objectSignature, sourceSignature)) {
qROPrivWarning() << "Signature mismatch for" << rep->m_metaObject->className() << (rep->m_objectName.isEmpty() ? QLatin1String("(unnamed)") : rep->m_objectName);
@@ -820,7 +863,7 @@ QReplicaImplementationInterface *QRemoteObjectHostBasePrivate::handleNewAcquire(
void QRemoteObjectNodePrivate::onClientRead(QObject *obj)
{
using namespace QRemoteObjectPackets;
- ClientIoDevice *connection = qobject_cast<ClientIoDevice*>(obj);
+ IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(obj);
QRemoteObjectPacketTypeEnum packetType;
Q_ASSERT(connection);
@@ -913,7 +956,7 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj)
qROPrivDebug() << "RemoveObject-->" << rxName << this;
connectedSources.remove(rxName);
connection->removeSource(rxName);
- if (replicas.contains(rxName)) { //We have a replica waiting on this remoteObject
+ if (replicas.contains(rxName)) { //We have a replica using the removed source
QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(replicas.value(rxName).toStrongRef());
if (rep && !rep->connectionToSource.isNull()) {
rep->connectionToSource.clear();
@@ -1105,6 +1148,26 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj)
*/
/*!
+ \enum QRemoteObjectHostBase::AllowedSchemas
+
+ This enum is used to specify whether a Node will accept a url with an
+ unrecognized schema for the hostUrl. By default only urls with known
+ schemas are accepted, but using \c AllowExternalRegistration will enable
+ the \l Registry to pass your external (to QtRO) url to client Nodes.
+
+ \value BuiltInSchemasOnly Only allow the hostUrl to be set to a QtRO
+ supported schema. This is the default value, and causes a Node error to be
+ set if an unrecognized schema is provided.
+ \value AllowExternalRegistration The provided schema is registered as an
+ \l {External Schemas} {External Schema}
+
+ \sa QRemoteObjectHost(const QUrl &address, const QUrl &registryAddress =
+ QUrl(), AllowedSchemas allowedSchemas=BuiltInSchemasOnly, QObject *parent =
+ nullptr), setHostUrl(const QUrl &hostAddress, AllowedSchemas
+ allowedSchemas=BuiltInSchemasOnly)
+*/
+
+/*!
\fn ObjectType *QRemoteObjectNode::acquire(const QString &name)
Returns a pointer to a Replica of type ObjectType (which is a template
@@ -1217,15 +1280,20 @@ QRemoteObjectHost::QRemoteObjectHost(QObject *parent)
Constructs a new QRemoteObjectHost Node (i.e., a Node that supports
exposing \l Source objects on the QtRO network) with address \a address. If
set, \a registryAddress will be used to connect to the \l
- QRemoteObjectRegistry at the provided address.
+ QRemoteObjectRegistry at the provided address. The \a allowedSchemas
+ parameter is only needed (and should be set to \l
+ {QRemoteObjectHostBase::AllowExternalRegistration}
+ {AllowExternalRegistration}) if the schema of the url should be used as an
+ \l {External Schemas} {External Schema} by the registry.
\sa setHostUrl(), setRegistryUrl()
*/
-QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl &registryAddress, QObject *parent)
+QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl &registryAddress,
+ AllowedSchemas allowedSchemas, QObject *parent)
: QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent)
{
if (!address.isEmpty()) {
- if (!setHostUrl(address))
+ if (!setHostUrl(address, allowedSchemas))
return;
}
@@ -1325,7 +1393,7 @@ QUrl QRemoteObjectHostBase::hostUrl() const
\internal The HostBase version of this method is protected so the method
isn't exposed on RegistryHost nodes.
*/
-bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress)
+bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas)
{
Q_D(QRemoteObjectHostBase);
if (d->remoteObjectIo) {
@@ -1333,13 +1401,11 @@ bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress)
return false;
}
- d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this);
- if (d->remoteObjectIo->m_server.isNull()) { //Invalid url/scheme
+ if (allowedSchemas == AllowedSchemas::BuiltInSchemasOnly && !QtROServerFactory::instance()->isValid(hostAddress)) {
d->setLastError(HostUrlInvalid);
- delete d->remoteObjectIo;
- d->remoteObjectIo = 0;
return false;
}
+ d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this);
//If we've given a name to the node, set it on the sourceIo as well
if (!objectName().isEmpty())
@@ -1368,10 +1434,15 @@ QUrl QRemoteObjectHost::hostUrl() const
Sets the \a hostAddress for a host QRemoteObjectNode.
Returns \c true if the Host address is set, otherwise \c false.
+
+ The \a allowedSchemas parameter is only needed (and should be set to \l
+ {QRemoteObjectHostBase::AllowExternalRegistration}
+ {AllowExternalRegistration}) if the schema of the url should be used as an
+ \l {External Schemas} {External Schema} by the registry.
*/
-bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress)
+bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas)
{
- return QRemoteObjectHostBase::setHostUrl(hostAddress);
+ return QRemoteObjectHostBase::setHostUrl(hostAddress, allowedSchemas);
}
/*!
@@ -1568,6 +1639,30 @@ bool QRemoteObjectNode::connectToNode(const QUrl &address)
}
/*!
+ \since 5.12
+
+ In order to \l QRemoteObjectNode::acquire() \l Replica objects over \l
+ {External QIODevices}, Qt Remote Objects needs access to the communications
+ channel (a \l QIODEvice) between the respective nodes. It is the
+ addClientSideConnection() call that enables this, taking the \a ioDevice as
+ input. Any acquire() call made without calling addClientSideConnection will
+ still work, but the Node will not be able to initialize the \l Replica
+ without being provided the connection to the Host node.
+
+ \sa {QRemoteObjectHostBase::addHostSideConnection}
+*/
+void QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice)
+{
+ Q_D(QRemoteObjectNode);
+ ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this);
+ connect(device, &IoDeviceBase::readyRead, this, [d, device]() {
+ d->onClientRead(device);
+ });
+ if (device->bytesAvailable())
+ d->onClientRead(device);
+}
+
+/*!
\fn void QRemoteObjectNode::remoteObjectAdded(const QRemoteObjectSourceLocation &loc)
This signal is emitted whenever a new \l {Source} object is added to
@@ -1784,6 +1879,29 @@ bool QRemoteObjectHostBase::disableRemoting(QObject *remoteObject)
}
/*!
+ \since 5.12
+
+ In order to \l QRemoteObjectHost::enableRemoting() \l Source objects over
+ \l {External QIODevices}, Qt Remote Objects needs access to the
+ communications channel (a \l QIODEvice) between the respective nodes. It is
+ the addHostSideConnection() call that enables this on the \l Source side,
+ taking the \a ioDevice as input. Any enableRemoting() call will still work
+ without calling addHostSideConnection, but the Node will not be able to
+ share the \l Source objects without being provided the connection to the
+ Replica node.
+
+ \sa addClientSideConnection
+*/
+void QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice)
+{
+ Q_D(QRemoteObjectHostBase);
+ if (!d->remoteObjectIo)
+ d->remoteObjectIo = new QRemoteObjectSourceIo(this);
+ ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this);
+ return d->remoteObjectIo->newConnection(device);
+}
+
+/*!
Returns a pointer to a Replica which is specifically derived from \l
QAbstractItemModel. The \a name provided must match the name used with the
matching \l {QRemoteObjectHostBase::enableRemoting} {enableRemoting} that put
diff --git a/src/remoteobjects/qremoteobjectnode.h b/src/remoteobjects/qremoteobjectnode.h
index a8c4d8f..35d0d9d 100644
--- a/src/remoteobjects/qremoteobjectnode.h
+++ b/src/remoteobjects/qremoteobjectnode.h
@@ -105,6 +105,7 @@ public:
~QRemoteObjectNode() override;
Q_INVOKABLE bool connectToNode(const QUrl &address);
+ void addClientSideConnection(QIODevice *ioDevice);
virtual void setName(const QString &name);
template < class ObjectType >
ObjectType *acquire(const QString &name = QString())
@@ -140,6 +141,9 @@ public:
int heartbeatInterval() const;
void setHeartbeatInterval(int interval);
+ typedef std::function<void (QUrl)> RemoteObjectSchemaHandler;
+ void registerExternalSchema(const QString &schema, RemoteObjectSchemaHandler handler);
+
Q_SIGNALS:
void remoteObjectAdded(const QRemoteObjectSourceLocation &);
void remoteObjectRemoved(const QRemoteObjectSourceLocation &);
@@ -165,6 +169,8 @@ class Q_REMOTEOBJECTS_EXPORT QRemoteObjectHostBase : public QRemoteObjectNode
{
Q_OBJECT
public:
+ enum AllowedSchemas { BuiltInSchemasOnly, AllowExternalRegistration };
+ Q_ENUM(AllowedSchemas)
~QRemoteObjectHostBase() override;
void setName(const QString &name) override;
@@ -177,6 +183,7 @@ public:
bool enableRemoting(QObject *object, const QString &name = QString());
bool enableRemoting(QAbstractItemModel *model, const QString &name, const QVector<int> roles, QItemSelectionModel *selectionModel = nullptr);
bool disableRemoting(QObject *remoteObject);
+ void addHostSideConnection(QIODevice *ioDevice);
typedef std::function<bool(const QString &, const QString &)> RemoteObjectNameFilter;
bool proxy(const QUrl &registryUrl, const QUrl &hostUrl={},
@@ -185,7 +192,7 @@ public:
protected:
virtual QUrl hostUrl() const;
- virtual bool setHostUrl(const QUrl &hostAddress);
+ virtual bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly);
QRemoteObjectHostBase(QRemoteObjectHostBasePrivate &, QObject *);
private:
@@ -198,11 +205,12 @@ class Q_REMOTEOBJECTS_EXPORT QRemoteObjectHost : public QRemoteObjectHostBase
Q_OBJECT
public:
QRemoteObjectHost(QObject *parent = nullptr);
- QRemoteObjectHost(const QUrl &address, const QUrl &registryAddress = QUrl(), QObject *parent = nullptr);
+ QRemoteObjectHost(const QUrl &address, const QUrl &registryAddress = QUrl(),
+ AllowedSchemas allowedSchemas=BuiltInSchemasOnly, QObject *parent = nullptr);
QRemoteObjectHost(const QUrl &address, QObject *parent);
~QRemoteObjectHost() override;
QUrl hostUrl() const override;
- bool setHostUrl(const QUrl &hostAddress) override;
+ bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly) override;
protected:
QRemoteObjectHost(QRemoteObjectHostPrivate &, QObject *);
diff --git a/src/remoteobjects/qremoteobjectnode_p.h b/src/remoteobjects/qremoteobjectnode_p.h
index efcf936..1a8ebb6 100644
--- a/src/remoteobjects/qremoteobjectnode_p.h
+++ b/src/remoteobjects/qremoteobjectnode_p.h
@@ -154,7 +154,7 @@ public:
virtual QReplicaImplementationInterface *handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name);
void handleReplicaConnection(const QString &name);
- void handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, ClientIoDevice *connection);
+ void handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection);
void initialize();
private:
bool checkSignatures(const QByteArray &a, const QByteArray &b);
@@ -162,7 +162,7 @@ private:
public:
struct SourceInfo
{
- ClientIoDevice* device;
+ IoDeviceBase* device;
QString typeName;
QByteArray objectSignature;
};
@@ -171,6 +171,7 @@ public:
QUrl registryAddress;
QHash<QString, QWeakPointer<QReplicaImplementationInterface> > replicas;
QMap<QString, SourceInfo> connectedSources;
+ QMap<QString, QRemoteObjectNode::RemoteObjectSchemaHandler> schemaHandlers;
QSet<ClientIoDevice*> pendingReconnect;
QSet<QUrl> requestedUrls;
QRemoteObjectRegistry *registry;
diff --git a/src/remoteobjects/qremoteobjectreplica.cpp b/src/remoteobjects/qremoteobjectreplica.cpp
index 6cfec3c..dc2f926 100644
--- a/src/remoteobjects/qremoteobjectreplica.cpp
+++ b/src/remoteobjects/qremoteobjectreplica.cpp
@@ -104,16 +104,23 @@ QConnectedReplicaImplementation::QConnectedReplicaImplementation(const QString &
m_heartbeatTimer.start();
});
connect(&m_heartbeatTimer, &QTimer::timeout, this, [this] {
+ // TODO: Revisit if a baseclass method can be used to avoid specialized cast
+ // conditional logic.
+ auto clientIo = qobject_cast<ClientIoDevice *>(connectionToSource);
if (m_pendingCalls.contains(0)) {
// The source didn't respond in time, disconnect the connection
- if (connectionToSource)
- connectionToSource->disconnectFromServer();
+ if (clientIo)
+ clientIo->disconnectFromServer();
+ else if (connectionToSource)
+ connectionToSource->close();
} else {
serializePingPacket(m_packet, m_objectName);
if (sendCommandWithReply(0).d->serialId == -1) {
m_heartbeatTimer.stop();
- if (connectionToSource)
- connectionToSource->disconnectFromServer();
+ if (clientIo)
+ clientIo->disconnectFromServer();
+ else if (connectionToSource)
+ connectionToSource->close();
}
}
});
@@ -449,7 +456,7 @@ void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop)
m_propertyStorage[i] = prop;
}
-void QConnectedReplicaImplementation::setConnection(ClientIoDevice *conn)
+void QConnectedReplicaImplementation::setConnection(IoDeviceBase *conn)
{
if (connectionToSource.isNull()) {
connectionToSource = conn;
diff --git a/src/remoteobjects/qremoteobjectreplica_p.h b/src/remoteobjects/qremoteobjectreplica_p.h
index 6b64b67..471964c 100644
--- a/src/remoteobjects/qremoteobjectreplica_p.h
+++ b/src/remoteobjects/qremoteobjectreplica_p.h
@@ -67,7 +67,7 @@ QT_BEGIN_NAMESPACE
class QRemoteObjectReplica;
class QRemoteObjectSource;
-class ClientIoDevice;
+class IoDeviceBase;
class QReplicaImplementationInterface
{
@@ -165,7 +165,7 @@ public:
QRemoteObjectPendingCall sendCommandWithReply(int serialId);
bool waitForFinished(const QRemoteObjectPendingCall &call, int timeout) override;
void notifyAboutReply(int ackedSerialId, const QVariant &value) override;
- void setConnection(ClientIoDevice *conn);
+ void setConnection(IoDeviceBase *conn);
void setDisconnected();
void _q_send(QMetaObject::Call call, int index, const QVariantList &args) override;
@@ -176,7 +176,7 @@ public:
QVector<QRemoteObjectReplica *> m_parentsNeedingConnect;
QVariantList m_propertyStorage;
QVector<int> m_childIndices;
- QPointer<ClientIoDevice> connectionToSource;
+ QPointer<IoDeviceBase> connectionToSource;
// pending call data
int m_curSerialId = 1; // 0 is reserved for heartbeat signals
diff --git a/src/remoteobjects/qremoteobjectsource.cpp b/src/remoteobjects/qremoteobjectsource.cpp
index 5c9f7f9..36bee20 100644
--- a/src/remoteobjects/qremoteobjectsource.cpp
+++ b/src/remoteobjects/qremoteobjectsource.cpp
@@ -228,7 +228,7 @@ QRemoteObjectRootSource::~QRemoteObjectRootSource()
delete it;
}
d->m_sourceIo->unregisterSource(this);
- Q_FOREACH (ServerIoDevice *io, d->m_listeners) {
+ Q_FOREACH (IoDeviceBase *io, d->m_listeners) {
removeListener(io, true);
}
delete d;
@@ -323,11 +323,11 @@ void QRemoteObjectSourceBase::handleMetaCall(int index, QMetaObject::Call call,
serializeInvokePacket(d->m_packet, name(), call, index, *marshalArgs(index, a), -1, propertyIndex);
d->m_packet.baseAddress = 0;
- Q_FOREACH (ServerIoDevice *io, d->m_listeners)
+ Q_FOREACH (IoDeviceBase *io, d->m_listeners)
io->write(d->m_packet.array, d->m_packet.size);
}
-void QRemoteObjectRootSource::addListener(ServerIoDevice *io, bool dynamic)
+void QRemoteObjectRootSource::addListener(IoDeviceBase *io, bool dynamic)
{
d->m_listeners.append(io);
d->isDynamic = dynamic;
@@ -347,7 +347,7 @@ void QRemoteObjectRootSource::addListener(ServerIoDevice *io, bool dynamic)
d->isDynamic = false;
}
-int QRemoteObjectRootSource::removeListener(ServerIoDevice *io, bool shouldSendRemove)
+int QRemoteObjectRootSource::removeListener(IoDeviceBase *io, bool shouldSendRemove)
{
d->m_listeners.removeAll(io);
if (shouldSendRemove)
diff --git a/src/remoteobjects/qremoteobjectsource_p.h b/src/remoteobjects/qremoteobjectsource_p.h
index 86c1c5e..2961e5c 100644
--- a/src/remoteobjects/qremoteobjectsource_p.h
+++ b/src/remoteobjects/qremoteobjectsource_p.h
@@ -62,7 +62,7 @@
QT_BEGIN_NAMESPACE
class QRemoteObjectSourceIo;
-class ServerIoDevice;
+class IoDeviceBase;
class QRemoteObjectSourceBase : public QObject
{
@@ -87,7 +87,7 @@ public:
struct Private {
Private(QRemoteObjectSourceIo *io) : m_sourceIo(io), isDynamic(false) {}
QRemoteObjectSourceIo *m_sourceIo;
- QVector<ServerIoDevice*> m_listeners;
+ QVector<IoDeviceBase*> m_listeners;
QRemoteObjectPackets::DataStreamPacket m_packet;
// Types needed during recursively sending a root to a new listener
@@ -122,8 +122,8 @@ public:
bool isRoot() const override { return true; }
QString name() const override { return m_name; }
- void addListener(ServerIoDevice *io, bool dynamic = false);
- int removeListener(ServerIoDevice *io, bool shouldSendRemove = false);
+ void addListener(IoDeviceBase *io, bool dynamic = false);
+ int removeListener(IoDeviceBase *io, bool shouldSendRemove = false);
QString m_name;
};
diff --git a/src/remoteobjects/qremoteobjectsourceio.cpp b/src/remoteobjects/qremoteobjectsourceio.cpp
index 2ba2043..d864ed5 100644
--- a/src/remoteobjects/qremoteobjectsourceio.cpp
+++ b/src/remoteobjects/qremoteobjectsourceio.cpp
@@ -52,22 +52,31 @@ using namespace QtRemoteObjects;
QRemoteObjectSourceIo::QRemoteObjectSourceIo(const QUrl &address, QObject *parent)
: QObject(parent)
- , m_server(QtROServerFactory::instance()->create(address, this))
+ , m_server(QtROServerFactory::instance()->isValid(address) ?
+ QtROServerFactory::instance()->create(address, this) : nullptr)
{
if (m_server && m_server->listen(address)) {
qRODebug(this) << "QRemoteObjectSourceIo is Listening" << address;
} else {
- qROWarning(this) << "Listen failed for URL:" << address;
- if (m_server)
+ if (m_server) {
+ qROWarning(this) << "Listen failed for URL:" << address;
qROWarning(this) << m_server->serverError();
- else
- qROWarning(this) << "Most likely an unrecognized scheme was used.";
+ } else {
+ m_address = address;
+ qRODebug(this) << "Using" << address << "as external url.";
+ }
return;
}
connect(m_server.data(), &QConnectionAbstractServer::newConnection, this, &QRemoteObjectSourceIo::handleConnection);
}
+QRemoteObjectSourceIo::QRemoteObjectSourceIo(QObject *parent)
+ : QObject(parent)
+ , m_server(nullptr)
+{
+}
+
QRemoteObjectSourceIo::~QRemoteObjectSourceIo()
{
qDeleteAll(m_sourceRoots.values());
@@ -93,7 +102,7 @@ bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const SourceApiMap *
new QRemoteObjectRootSource(object, api, adapter, this);
QRemoteObjectPackets::serializeObjectListPacket(m_packet, {QRemoteObjectPackets::ObjectInfo{api->name(), api->typeName(), api->objectSignature()}});
- foreach (ServerIoDevice *conn, m_connections)
+ for (auto conn : m_connections)
conn->write(m_packet.array, m_packet.size);
if (const int count = m_connections.size())
qRODebug(this) << "Wrote new QObjectListPacket for" << api->name() << "to" << count << "connections";
@@ -120,8 +129,10 @@ void QRemoteObjectSourceIo::registerSource(QRemoteObjectSourceBase *source)
qRODebug(this) << "Registering" << name;
m_sourceRoots[name] = root;
m_objectToSourceMap[source->m_object] = root;
- const auto &type = source->m_api->typeName();
- emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress())));
+ if (serverAddress().isValid()) {
+ const auto &type = source->m_api->typeName();
+ emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress())));
+ }
}
}
@@ -134,13 +145,14 @@ void QRemoteObjectSourceIo::unregisterSource(QRemoteObjectSourceBase *source)
const auto type = source->m_api->typeName();
m_objectToSourceMap.remove(source->m_object);
m_sourceRoots.remove(name);
- emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress())));
+ if (serverAddress().isValid())
+ emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress())));
}
}
void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn)
{
- ServerIoDevice *connection = qobject_cast<ServerIoDevice*>(conn);
+ IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn);
m_connections.remove(connection);
qRODebug(this) << "OnServerDisconnect";
@@ -158,7 +170,7 @@ void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn)
void QRemoteObjectSourceIo::onServerRead(QObject *conn)
{
// Assert the invariant here conn is of type QIODevice
- ServerIoDevice *connection = qobject_cast<ServerIoDevice*>(conn);
+ IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn);
QRemoteObjectPacketTypeEnum packetType;
do {
@@ -258,13 +270,18 @@ void QRemoteObjectSourceIo::handleConnection()
qRODebug(this) << "handleConnection" << m_connections;
ServerIoDevice *conn = m_server->nextPendingConnection();
+ newConnection(conn);
+}
+
+void QRemoteObjectSourceIo::newConnection(IoDeviceBase *conn)
+{
m_connections.insert(conn);
- connect(conn, &ServerIoDevice::disconnected, this, [this, conn]() {
- onServerDisconnect(conn);
- });
- connect(conn, &ServerIoDevice::readyRead, this, [this, conn]() {
+ connect(conn, &IoDeviceBase::readyRead, this, [this, conn]() {
onServerRead(conn);
});
+ connect(conn, &IoDeviceBase::disconnected, this, [this, conn]() {
+ onServerDisconnect(conn);
+ });
serializeHandshakePacket(m_packet);
conn->write(m_packet.array, m_packet.size);
@@ -280,7 +297,9 @@ void QRemoteObjectSourceIo::handleConnection()
QUrl QRemoteObjectSourceIo::serverAddress() const
{
- return m_server->address();
+ if (m_server)
+ return m_server->address();
+ return m_address;
}
QT_END_NAMESPACE
diff --git a/src/remoteobjects/qremoteobjectsourceio_p.h b/src/remoteobjects/qremoteobjectsourceio_p.h
index c9adde7..4cc045c 100644
--- a/src/remoteobjects/qremoteobjectsourceio_p.h
+++ b/src/remoteobjects/qremoteobjectsourceio_p.h
@@ -70,12 +70,14 @@ class QRemoteObjectSourceIo : public QObject
Q_OBJECT
public:
explicit QRemoteObjectSourceIo(const QUrl &address, QObject *parent = nullptr);
+ explicit QRemoteObjectSourceIo(QObject *parent = nullptr);
~QRemoteObjectSourceIo() override;
bool enableRemoting(QObject *object, const QMetaObject *meta, const QString &name,
const QString &typeName);
bool enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter = nullptr);
bool disableRemoting(QObject *object);
+ void newConnection(IoDeviceBase *conn);
QUrl serverAddress() const;
@@ -94,15 +96,16 @@ public:
void unregisterSource(QRemoteObjectSourceBase *source);
QHash<QIODevice*, quint32> m_readSize;
- QSet<ServerIoDevice*> m_connections;
+ QSet<IoDeviceBase*> m_connections;
QHash<QObject *, QRemoteObjectRootSource*> m_objectToSourceMap;
QMap<QString, QRemoteObjectSourceBase*> m_sourceObjects;
QMap<QString, QRemoteObjectRootSource*> m_sourceRoots;
- QHash<ServerIoDevice*, QUrl> m_registryMapping;
+ QHash<IoDeviceBase*, QUrl> m_registryMapping;
QScopedPointer<QConnectionAbstractServer> m_server;
QRemoteObjectPackets::DataStreamPacket m_packet;
QString m_rxName;
QVariantList m_rxArgs;
+ QUrl m_address;
};
QT_END_NAMESPACE
diff --git a/src/remoteobjects/qtremoteobjectglobal.h b/src/remoteobjects/qtremoteobjectglobal.h
index 7e6dfb9..eb23b44 100644
--- a/src/remoteobjects/qtremoteobjectglobal.h
+++ b/src/remoteobjects/qtremoteobjectglobal.h
@@ -173,6 +173,7 @@ enum QRemoteObjectPacketTypeEnum
Ping,
Pong
};
+Q_ENUM_NS(QRemoteObjectPacketTypeEnum)
enum InitialAction {
FetchRootSize,
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index c5f7abc..1627198 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -22,4 +22,4 @@ SUBDIRS += \
sub_localsockettestserver
qtHaveModule(qml): SUBDIRS += qml
-qtConfig(process): SUBDIRS += integration_multiprocess
+qtConfig(process): SUBDIRS += integration_multiprocess integration_external
diff --git a/tests/auto/integration/tst_integration.cpp b/tests/auto/integration/tst_integration.cpp
index d89e537..1eb7604 100644
--- a/tests/auto/integration/tst_integration.cpp
+++ b/tests/auto/integration/tst_integration.cpp
@@ -32,11 +32,12 @@
#include <QMetaType>
#include <QProcess>
#include <QFileInfo>
+#include <QTcpServer>
+#include <QTcpSocket>
#include <QRemoteObjectReplica>
#include <QRemoteObjectNode>
#include <QRemoteObjectSettingsStore>
-
#include "engine.h"
#include "speedometer.h"
#include "rep_engine_replica.h"
@@ -136,6 +137,21 @@ class tst_Integration: public QObject
{
Q_OBJECT
+ void setupTcp()
+ {
+ if (!tcpServer) {
+ tcpServer = new QTcpServer;
+ tcpServer->listen(QHostAddress::Any, 65511);
+ socketClient = new QTcpSocket;
+ socketClient->connectToHost(QHostAddress::LocalHost, tcpServer->serverPort());
+ QVERIFY(socketClient->waitForConnected(5000));
+
+ QVERIFY(tcpServer->waitForNewConnection(5000));
+ QVERIFY(tcpServer->hasPendingConnections());
+ socketServer = tcpServer->nextPendingConnection();
+ }
+ }
+
void setupHost(bool useRegistry=false)
{
QFETCH_GLOBAL(QUrl, hostUrl);
@@ -146,6 +162,9 @@ class tst_Integration: public QObject
host->setHostUrl(hostUrl);
if (useRegistry)
host->setRegistryUrl(registryUrl);
+ } else {
+ setupTcp();
+ host->addHostSideConnection(socketServer);
}
}
@@ -153,16 +172,19 @@ class tst_Integration: public QObject
{
QFETCH_GLOBAL(QUrl, hostUrl);
QFETCH_GLOBAL(QUrl, registryUrl);
+ client = new QRemoteObjectNode;
+ Q_SET_OBJECT_NAME(*client);
if (!hostUrl.isEmpty())
{
if (useRegistry)
- client = new QRemoteObjectNode(registryUrl);
+ client->setRegistryUrl(registryUrl);
else {
- client = new QRemoteObjectNode;
client->connectToNode(hostUrl);
}
+ } else {
+ setupTcp();
+ client->addClientSideConnection(socketClient);
}
- Q_SET_OBJECT_NAME(*client);
}
void setupRegistry()
@@ -179,16 +201,20 @@ private:
QRemoteObjectHost *host;
QRemoteObjectNode *client;
QRemoteObjectRegistryHost *registry;
+ QTcpServer *tcpServer;
+ QPointer<QTcpSocket> socketClient, socketServer;
private slots:
void initTestCase_data()
{
QTest::addColumn<QUrl>("hostUrl");
QTest::addColumn<QUrl>("registryUrl");
+
#ifndef SKIP_LOCAL
QTest::newRow("local") << QUrl(QLatin1String("local:replica_local_integration")) << QUrl(QLatin1String("local:registry_local_integration"));
#endif
QTest::newRow("tcp") << QUrl(QLatin1String("tcp://127.0.0.1:65511")) << QUrl(QLatin1String("tcp://127.0.0.1:65512"));
+ QTest::newRow("external") << QUrl() << QUrl();
#ifdef __QNXNTO__
QTest::newRow("qnx") << QUrl(QLatin1String("qnx:replica")) << QUrl(QLatin1String("qnx:registry"));
#endif
@@ -208,6 +234,9 @@ private slots:
registry = nullptr;
host = nullptr;
client = nullptr;
+ tcpServer = nullptr;
+ socketClient = nullptr;
+ socketServer = nullptr;
}
void cleanup()
@@ -215,6 +244,13 @@ private slots:
delete registry;
delete host;
delete client;
+ delete tcpServer;
+ if (socketClient) {
+ socketClient->deleteLater();
+ }
+ if (socketServer) {
+ socketServer->deleteLater();
+ }
// wait for delivery of RemoveObject events to the source
QTest::qWait(200);
}
@@ -424,6 +460,9 @@ private slots:
void registryAddedTest()
{
+ QFETCH_GLOBAL(QUrl, registryUrl);
+ if (registryUrl.isEmpty())
+ QSKIP("Skipping registry tests for external QIODevice types.");
setupRegistry();
setupHost(true);
@@ -489,6 +528,9 @@ private slots:
void registryTest()
{
+ QFETCH_GLOBAL(QUrl, registryUrl);
+ if (registryUrl.isEmpty())
+ QSKIP("Skipping registry tests for external QIODevice types.");
setupRegistry();
TcpDataCenterSimpleSource source1;
source1.setData1(5);
@@ -558,7 +600,9 @@ private slots:
void noRegistryTest()
{
- setupHost(true);
+ QFETCH_GLOBAL(QUrl, registryUrl);
+ if (registryUrl.isEmpty())
+ QSKIP("Skipping registry tests for external QIODevice types."); setupHost(true);
const bool res = host->waitForRegistry(3000);
QVERIFY(!res);
QCOMPARE(host->registry()->isInitialized(), false);
@@ -571,6 +615,8 @@ private slots:
{
QFETCH_GLOBAL(QUrl, hostUrl);
QFETCH_GLOBAL(QUrl, registryUrl);
+ if (registryUrl.isEmpty())
+ QSKIP("Skipping registry tests for external QIODevice types.");
setupClient(true);
// create a replica before the registry host started
@@ -1219,10 +1265,13 @@ private slots:
void SchemeTest()
{
QFETCH_GLOBAL(QUrl, hostUrl);
+ QFETCH_GLOBAL(QUrl, registryUrl);
QRemoteObjectHost valid(hostUrl);
QVERIFY(valid.lastError() == QRemoteObjectNode::NoError);
QRemoteObjectHost invalid(QUrl(QLatin1String("invalid:invalid")));
QVERIFY(invalid.lastError() == QRemoteObjectNode::HostUrlInvalid);
+ QRemoteObjectHost validExternal(QUrl(QLatin1String("invalid:invalid")), registryUrl, QRemoteObjectHost::AllowExternalRegistration);
+ QVERIFY(validExternal.lastError() == QRemoteObjectNode::NoError);
QRemoteObjectNode invalidRegistry(QUrl(QLatin1String("invalid:invalid")));
QVERIFY(invalidRegistry.lastError() == QRemoteObjectNode::RegistryNotAcquired);
}
diff --git a/tests/auto/integration_external/MyInterface.rep b/tests/auto/integration_external/MyInterface.rep
new file mode 100644
index 0000000..dfb8752
--- /dev/null
+++ b/tests/auto/integration_external/MyInterface.rep
@@ -0,0 +1,18 @@
+#include <QtCore>
+
+class MyInterface
+{
+ ENUM Enum1 { First, Second, Third }
+ PROP(Enum1 enum1 = First READWRITE)
+
+ PROP(bool started = false)
+
+ SLOT(bool start())
+ SLOT(bool stop())
+ SLOT(bool quit())
+ SLOT(bool next())
+ SLOT(void testEnumParamsInSlots(Enum1 enumSlotParam, bool slotParam2, int))
+
+ SIGNAL(advance())
+ SIGNAL(testEnumParamsInSignals(Enum1 enumSignalParam, bool signalParam2, QString))
+};
diff --git a/tests/auto/integration_external/client/client.pro b/tests/auto/integration_external/client/client.pro
new file mode 100644
index 0000000..4c42f5c
--- /dev/null
+++ b/tests/auto/integration_external/client/client.pro
@@ -0,0 +1,16 @@
+TEMPLATE = app
+QT += remoteobjects core testlib
+QT -= gui
+
+TARGET = client
+DESTDIR = ./
+CONFIG += c++11
+CONFIG -= app_bundle
+
+REPC_REPLICA = ../MyInterface.rep
+
+SOURCES += main.cpp \
+
+HEADERS += \
+
+INCLUDEPATH += $$PWD
diff --git a/tests/auto/integration_external/client/main.cpp b/tests/auto/integration_external/client/main.cpp
new file mode 100644
index 0000000..05f7f59
--- /dev/null
+++ b/tests/auto/integration_external/client/main.cpp
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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_MyInterface_replica.h"
+
+#include <QCoreApplication>
+#include <QtRemoteObjects/qremoteobjectnode.h>
+#include <QtTest/QtTest>
+
+const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212"));
+
+class tst_Client_Process : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ m_repNode.setRegistryUrl(registryUrl);
+ QRemoteObjectNode::RemoteObjectSchemaHandler setupTcp = [this](QUrl url) {
+ QTcpSocket *socket = new QTcpSocket(&this->m_repNode);
+ connect(socket, &QTcpSocket::connected,
+ [socket, this]() {
+ this->m_repNode.addClientSideConnection(socket);
+ });
+ connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error),
+ [socket](QAbstractSocket::SocketError error) {
+ qDebug() << "SocketError" << error;
+ delete socket;
+ });
+ socket->connectToHost(url.host(), url.port());
+ };
+ m_repNode.registerExternalSchema(QStringLiteral("exttcp"), setupTcp);
+ QVERIFY(m_repNode.waitForRegistry(3000));
+ m_rep.reset(m_repNode.acquire<MyInterfaceReplica>());
+ }
+
+ void testRun()
+ {
+
+ QVERIFY(m_rep->waitForSource());
+ auto reply = m_rep->start();
+ QVERIFY(reply.waitForFinished());
+
+ // BEGIN: Testing
+ QSignalSpy advanceSpy(m_rep.data(), SIGNAL(advance()));
+
+ QSignalSpy spy(m_rep.data(), SIGNAL(enum1Changed(MyInterfaceReplica::Enum1)));
+ QVERIFY(advanceSpy.wait());
+
+ QCOMPARE(spy.count(), 2);
+ // END: Testing
+
+ reply = m_rep->stop();
+ QVERIFY(reply.waitForFinished());
+ }
+
+ void testEnumDetails()
+ {
+ QHash<QByteArray, int> kvs = {{"First", 0}, {"Second", 1}, {"Third", 2}};
+ QScopedPointer<QRemoteObjectDynamicReplica> rep(m_repNode.acquireDynamic("MyInterface"));
+ QVERIFY(rep->waitForSource());
+
+ auto mo = rep->metaObject();
+ int enumIdx = mo->indexOfEnumerator("Enum1");
+ QVERIFY(enumIdx != -1);
+ auto enumerator = mo->enumerator(enumIdx);
+ QCOMPARE(enumerator.name(), "Enum1");
+ QCOMPARE(enumerator.keyCount(), 3);
+ for (int i = 0; i < 3; ++i) {
+ auto key = enumerator.key(i);
+ auto val = enumerator.value(i);
+ auto it = kvs.find(key);
+ QVERIFY(it != kvs.end());
+ QCOMPARE(*it, val);
+ kvs.erase(it);
+ }
+
+ int propIdx = mo->indexOfProperty("enum1");
+ QVERIFY(propIdx != -1);
+ auto property = mo->property(propIdx);
+ property.write(rep.data(), 1);
+ QTRY_COMPARE(property.read(rep.data()).toInt(), 1);
+ }
+
+ void testMethodSignalParamDetails()
+ {
+ QScopedPointer<QRemoteObjectDynamicReplica> rep(m_repNode.acquireDynamic("MyInterface"));
+ QVERIFY(rep->waitForSource());
+
+ auto mo = rep->metaObject();
+ int signalIdx = mo->indexOfSignal("testEnumParamsInSignals(MyInterfaceReplica::Enum1,bool,QString)");
+ QVERIFY(signalIdx != -1);
+ auto simm = mo->method(signalIdx);
+ {
+ QCOMPARE(simm.parameterCount(), 3);
+ auto paramNames = simm.parameterNames();
+ QCOMPARE(paramNames.size(), 3);
+ QCOMPARE(paramNames.at(0), QByteArrayLiteral("enumSignalParam"));
+ QCOMPARE(paramNames.at(1), QByteArrayLiteral("signalParam2"));
+ QCOMPARE(paramNames.at(2), QByteArrayLiteral("__repc_variable_1"));
+ QCOMPARE(simm.parameterType(0), QMetaType::type("MyInterfaceReplica::Enum1"));
+ QCOMPARE(simm.parameterType(1), int(QMetaType::Bool));
+ QCOMPARE(simm.parameterType(2), int(QMetaType::QString));
+ }
+
+ int slotIdx = mo->indexOfSlot("testEnumParamsInSlots(Enum1,bool,int)");
+ QVERIFY(slotIdx != -1);
+ auto slmm = mo->method(slotIdx);
+ {
+ QCOMPARE(slmm .parameterCount(), 3);
+ auto paramNames = slmm .parameterNames();
+ QCOMPARE(paramNames.size(), 3);
+ QCOMPARE(paramNames.at(0), QByteArrayLiteral("enumSlotParam"));
+ QCOMPARE(paramNames.at(1), QByteArrayLiteral("slotParam2"));
+ QCOMPARE(paramNames.at(2), QByteArrayLiteral("__repc_variable_1"));
+ }
+
+ int enumVal = 0;
+ mo->invokeMethod(rep.data(), "testEnumParamsInSlots",
+ QGenericArgument("Enum1", &enumVal),
+ Q_ARG(bool, true), Q_ARG(int, 1234));
+
+ int enumIdx = mo->indexOfProperty("enum1");
+ QVERIFY(enumIdx != -1);
+ QTRY_COMPARE(mo->property(enumIdx).read(rep.data()).toInt(), 0);
+
+ int startedIdx = mo->indexOfProperty("started");
+ QVERIFY(startedIdx != -1);
+ QTRY_COMPARE(mo->property(startedIdx).read(rep.data()).toBool(), true);
+ }
+
+ void testMethodSignal()
+ {
+ QScopedPointer<MyInterfaceReplica> rep(new MyInterfaceReplica());
+ rep->setNode(&m_repNode);
+ QVERIFY(rep->waitForSource());
+
+ rep->testEnumParamsInSlots(MyInterfaceReplica::Second, false, 74);
+
+ connect(rep.data(), &MyInterfaceReplica::testEnumParamsInSignals,
+ [](MyInterfaceReplica::Enum1 enumSignalParam) { QCOMPARE(enumSignalParam, MyInterfaceReplica::Second); });
+
+ QTRY_COMPARE(rep->enum1(), MyInterfaceReplica::Second);
+ QTRY_COMPARE(rep->started(), false);
+ }
+
+ void testDisconnect()
+ {
+ auto reply = m_rep->next();
+ QSignalSpy stateSpy(m_rep.data(), &MyInterfaceReplica::stateChanged);
+ QVERIFY(reply.waitForFinished());
+
+ QVERIFY(stateSpy.wait());
+ QVERIFY(m_rep->state() == QRemoteObjectReplica::Suspect);
+
+ stateSpy.clear();
+ QVERIFY(stateSpy.wait());
+ QVERIFY(m_rep->state() == QRemoteObjectReplica::Valid);
+ // Make sure we updated to the correct enum1 value
+ QVERIFY(m_rep->enum1() == MyInterfaceReplica::First);
+ }
+
+ void cleanupTestCase()
+ {
+ auto reply = m_rep->quit();
+ QVERIFY(reply.waitForFinished());
+ }
+
+private:
+ QRemoteObjectNode m_repNode;
+ QScopedPointer<MyInterfaceReplica> m_rep;
+};
+
+QTEST_MAIN(tst_Client_Process)
+
+#include "main.moc"
diff --git a/tests/auto/integration_external/external/external.pro b/tests/auto/integration_external/external/external.pro
new file mode 100644
index 0000000..5fcf5be
--- /dev/null
+++ b/tests/auto/integration_external/external/external.pro
@@ -0,0 +1,9 @@
+CONFIG += testcase c++11
+CONFIG -= app_bundle
+TARGET = tst_integration_external
+DESTDIR = ./
+QT += testlib remoteobjects
+QT -= gui
+
+SOURCES += \
+ tst_integration_external.cpp
diff --git a/tests/auto/integration_external/external/tst_integration_external.cpp b/tests/auto/integration_external/external/tst_integration_external.cpp
new file mode 100644
index 0000000..8994db8
--- /dev/null
+++ b/tests/auto/integration_external/external/tst_integration_external.cpp
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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 <QtTest/QtTest>
+#include <QMetaType>
+#include <QProcess>
+#include <QStandardPaths>
+
+namespace {
+
+QString findExecutable(const QString &executableName, const QStringList &paths)
+{
+ const auto path = QStandardPaths::findExecutable(executableName, paths);
+ if (!path.isEmpty()) {
+ return path;
+ }
+
+ qWarning() << "Could not find executable:" << executableName << "in any of" << paths;
+ return QString();
+}
+
+}
+
+class tst_Integration_External: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase()
+ {
+ QLoggingCategory::setFilterRules("qt.remoteobjects.warning=false");
+ }
+
+ void cleanup()
+ {
+ // wait for delivery of RemoveObject events to the source
+ QTest::qWait(200);
+ }
+
+ void testRun_data()
+ {
+ QTest::addColumn<bool>("templated");
+ QTest::newRow("non-templated enableRemoting") << false;
+ QTest::newRow("templated enableRemoting") << true;
+ }
+
+ void testRun()
+ {
+ QFETCH(bool, templated);
+
+ qDebug() << "Starting server process";
+ QProcess serverProc;
+ serverProc.setProcessChannelMode(QProcess::ForwardedChannels);
+ if (templated) {
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert("TEMPLATED_REMOTING", "true");
+ serverProc.setProcessEnvironment(env);
+ }
+ serverProc.start(findExecutable("server", {
+ QCoreApplication::applicationDirPath() + "/../server/"
+ }));
+ QVERIFY(serverProc.waitForStarted());
+
+ // wait for server start
+ QTest::qWait(200);
+
+ qDebug() << "Starting client process";
+ QProcess clientProc;
+ clientProc.setProcessChannelMode(QProcess::ForwardedChannels);
+ clientProc.start(findExecutable("client", {
+ QCoreApplication::applicationDirPath() + "/../client/"
+ }));
+ QVERIFY(clientProc.waitForStarted());
+
+ QVERIFY(clientProc.waitForFinished());
+ QVERIFY(serverProc.waitForFinished());
+
+ QCOMPARE(serverProc.exitCode(), 0);
+ QCOMPARE(clientProc.exitCode(), 0);
+ }
+};
+
+QTEST_MAIN(tst_Integration_External)
+
+#include "tst_integration_external.moc"
diff --git a/tests/auto/integration_external/integration_external.pro b/tests/auto/integration_external/integration_external.pro
new file mode 100644
index 0000000..6dead73
--- /dev/null
+++ b/tests/auto/integration_external/integration_external.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS = client server external
diff --git a/tests/auto/integration_external/server/main.cpp b/tests/auto/integration_external/server/main.cpp
new file mode 100644
index 0000000..e97408b
--- /dev/null
+++ b/tests/auto/integration_external/server/main.cpp
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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 "mytestserver.h"
+
+#include <QCoreApplication>
+#include <QTcpServer>
+#include <QtTest/QtTest>
+
+const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212"));
+const QUrl extUrl = QUrl(QStringLiteral("exttcp://127.0.0.1:65213"));
+const QUrl extUrl2 = QUrl(QStringLiteral("exttcp://127.0.0.1:65214"));
+
+class tst_Server_Process : public QObject
+{
+ Q_OBJECT
+
+ struct Device
+ {
+ Device(QUrl url) : srcNode(url, registryUrl, QRemoteObjectHost::AllowExternalRegistration)
+ {
+ tcpServer.listen(QHostAddress(url.host()), url.port());
+ QVERIFY(srcNode.waitForRegistry(3000));
+ QObject::connect(&tcpServer, &QTcpServer::newConnection, [this]() {
+ auto conn = this->tcpServer.nextPendingConnection();
+ this->srcNode.addHostSideConnection(conn);
+ });
+ }
+ QTcpServer tcpServer;
+ QRemoteObjectHost srcNode;
+ };
+
+private Q_SLOTS:
+ void testRun()
+ {
+ QRemoteObjectRegistryHost registry(registryUrl);
+
+ Device dev1(extUrl);
+ MyTestServer myTestServer;
+ bool templated = qEnvironmentVariableIsSet("TEMPLATED_REMOTING");
+ if (templated)
+ QVERIFY(dev1.srcNode.enableRemoting<MyInterfaceSourceAPI>(&myTestServer));
+ else
+ QVERIFY(dev1.srcNode.enableRemoting(&myTestServer));
+
+ qDebug() << "Waiting for incoming connections";
+
+ QSignalSpy waitForStartedSpy(&myTestServer, SIGNAL(startedChanged(bool)));
+ QVERIFY(waitForStartedSpy.isValid());
+ QVERIFY(waitForStartedSpy.wait());
+ QCOMPARE(waitForStartedSpy.value(0).value(0).toBool(), true);
+
+ // wait for delivery of events
+ QTest::qWait(200);
+
+ qDebug() << "Client connected";
+
+ // BEGIN: Testing
+
+ // make sure continuous changes to enums don't mess up the protocol
+ myTestServer.setEnum1(MyTestServer::Second);
+ myTestServer.setEnum1(MyTestServer::Third);
+
+ emit myTestServer.advance();
+
+ waitForStartedSpy.clear();
+ QVERIFY(waitForStartedSpy.wait());
+ QCOMPARE(waitForStartedSpy.value(0).value(0).toBool(), false);
+
+ bool next = false;
+ connect(&myTestServer, &MyTestServer::nextStep, [&next]{ next = true; });
+ QTRY_VERIFY_WITH_TIMEOUT(next, 5000);
+ dev1.srcNode.disableRemoting(&myTestServer);
+
+ // Change a value while replica is suspect
+ myTestServer.setEnum1(MyTestServer::First);
+
+ // Share the object on a different "device", make sure registry updates and connects
+ Device dev2(extUrl2);
+ dev2.srcNode.enableRemoting(&myTestServer);
+
+ // wait for quit
+ bool quit = false;
+ connect(&myTestServer, &MyTestServer::quitApp, [&quit]{quit = true;});
+ QTRY_VERIFY_WITH_TIMEOUT(quit, 5000);
+
+ // wait for delivery of events
+ QTest::qWait(200);
+
+ qDebug() << "Done. Shutting down.";
+ }
+};
+
+QTEST_MAIN(tst_Server_Process)
+
+#include "main.moc"
diff --git a/tests/auto/integration_external/server/mytestserver.cpp b/tests/auto/integration_external/server/mytestserver.cpp
new file mode 100644
index 0000000..44421f4
--- /dev/null
+++ b/tests/auto/integration_external/server/mytestserver.cpp
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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 <qdebug.h>
+
+#include "mytestserver.h"
+#include "rep_MyInterface_source.h"
+
+MyTestServer::MyTestServer(QObject *parent)
+ : MyInterfaceSimpleSource(parent)
+{
+ qDebug() << "Server started";
+}
+
+MyTestServer::~MyTestServer()
+{
+ qDebug() << "Server stopped";
+}
+
+bool MyTestServer::start()
+{
+ setStarted(true);
+ return true;
+}
+
+bool MyTestServer::stop()
+{
+ setStarted(false);
+ return true;
+}
+
+bool MyTestServer::quit()
+{
+ emit quitApp();
+ return true;
+}
+
+bool MyTestServer::next()
+{
+ emit nextStep();
+ return true;
+}
+
+void MyTestServer::testEnumParamsInSlots(Enum1 enumSlotParam, bool slotParam2, int number)
+{
+ setEnum1(enumSlotParam);
+ setStarted(slotParam2);
+ emit testEnumParamsInSignals(enum1(), started(), QString::number(number));
+}
diff --git a/tests/auto/integration_external/server/mytestserver.h b/tests/auto/integration_external/server/mytestserver.h
new file mode 100644
index 0000000..c0edda0
--- /dev/null
+++ b/tests/auto/integration_external/server/mytestserver.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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$
+**
+****************************************************************************/
+
+#ifndef MYTESTSERVER_H
+#define MYTESTSERVER_H
+
+#include <QTimer>
+
+#include <QtRemoteObjects/qremoteobjectnode.h>
+#include <QtRemoteObjects/qremoteobjectsource.h>
+
+#include "rep_MyInterface_source.h"
+
+class MyTestServer : public MyInterfaceSimpleSource
+{
+ Q_OBJECT
+
+public:
+ MyTestServer(QObject *parent = nullptr);
+ ~MyTestServer() override;
+
+public Q_SLOTS:
+ bool start() override;
+ bool stop() override;
+ bool quit() override;
+ bool next() override;
+ void testEnumParamsInSlots(Enum1 enumSlotParam, bool slotParam2, int __repc_variable_1) override;
+
+Q_SIGNALS:
+ void quitApp();
+ void nextStep();
+};
+
+#endif // MYTESTSERVER_H
diff --git a/tests/auto/integration_external/server/server.pro b/tests/auto/integration_external/server/server.pro
new file mode 100644
index 0000000..fea0c03
--- /dev/null
+++ b/tests/auto/integration_external/server/server.pro
@@ -0,0 +1,19 @@
+TEMPLATE = app
+QT += remoteobjects core testlib
+QT -= gui
+
+TARGET = server
+DESTDIR = ./
+CONFIG += c++11
+CONFIG -= app_bundle
+
+REPC_SOURCE = $$PWD/../MyInterface.rep
+
+SOURCES += main.cpp \
+ mytestserver.cpp
+
+HEADERS += \
+ mytestserver.h
+ $$OUT_PWD/rep_MyInterface_source.h
+
+INCLUDEPATH += $$PWD