summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorKari Oikarinen <kari.oikarinen@qt.io>2016-09-22 16:23:20 +0300
committerKari Oikarinen <kari.oikarinen@qt.io>2016-09-23 08:57:54 +0000
commitc8cda60d49adb562405794ab5ac496c2fd01e1e3 (patch)
tree4219948d4307f0866e93ff526802f946d5bf7470 /tests
parenta132c64b5d590516869a252a4a6fc823787c4b61 (diff)
Rename test/ folder to tests/ to comply with CI
Also change the install targets to be under QT_INSTALL_BINS and QT_INSTALL_LIBS. Task-number: QTBUG-56066 Change-Id: Icaf309ebb6e62bbf35df09fd6a72795f64f6f041 Reviewed-by: Samuli Piippo <samuli.piippo@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/servicetest.cpp508
-rw-r--r--tests/servicetest.pro55
-rw-r--r--tests/streamtest.cpp515
-rw-r--r--tests/streamtest.pro34
-rw-r--r--tests/tests.pro7
-rw-r--r--tests/tst_qdbmessage.cpp148
-rw-r--r--tests/tst_qdbmessage.pro19
-rw-r--r--tests/tst_stream.cpp143
-rw-r--r--tests/tst_stream.pro44
9 files changed, 1473 insertions, 0 deletions
diff --git a/tests/servicetest.cpp b/tests/servicetest.cpp
new file mode 100644
index 0000000..c87d429
--- /dev/null
+++ b/tests/servicetest.cpp
@@ -0,0 +1,508 @@
+/******************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Debug Bridge.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+#include "../client/connection.h"
+#include "../client/filepullservice.h"
+#include "../client/filepushservice.h"
+#include "../client/processservice.h"
+#include "../client/echoservice.h"
+#include "../utils/make_unique.h"
+#include "usb/usbconnection.h"
+#include "protocol/qdbtransport.h"
+#include "protocol/services.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qtimer.h>
+#include <QtTest/QtTest>
+
+const int testTimeout = 500; // in milliseconds
+
+// Helper to initialize Connection in testcases
+struct ConnectionContext
+{
+ ConnectionContext()
+ : connection{new QdbTransport{new UsbConnection{}}}
+ {
+ QVERIFY(connection.initialize());
+
+ connection.connect();
+ }
+ Connection connection;
+};
+
+class ServiceTest : public QObject
+{
+ Q_OBJECT
+private slots:
+ void initTestCase();
+ void echo();
+ void processOutput();
+ void processMultipleOutput();
+ void processErrorCode();
+ void processNonExistent();
+ void processCrash();
+ void processInput();
+ void processMultipleInput();
+ void filePush();
+ void filePushNonexistent();
+ void filePull();
+ void filePullNonexistent();
+ void filePullToUnopenable();
+};
+
+const QString pushPullFileName = "qdbtestfile1";
+static QByteArray pushPullFileContents = "abcd\nefgh\n";
+const QString nonexistentFileName{"qdbtestfile2"};
+
+void ServiceTest::initTestCase()
+{
+ qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
+ qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
+}
+
+void ServiceTest::echo()
+{
+ ConnectionContext ctx;
+
+ EchoService echo{&ctx.connection};
+ connect(&echo, &EchoService::initialized, [&]() {
+ echo.send("ABCD");
+ });
+ QSignalSpy spy{&echo, &EchoService::echo};
+
+ echo.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy[0][0].toByteArray(), QByteArray{"ABCD"});
+}
+
+void ServiceTest::processOutput()
+{
+ ConnectionContext ctx;
+
+ QByteArray output;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("echo", {"ABCD"});
+ });
+ connect(&processService, &ProcessService::readyRead, [&]() {
+ output.append(processService.read());
+ });
+ QSignalSpy spy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ auto exitCode = spy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>();
+ auto finalOutput = spy[0][2].toByteArray();
+ output.append(finalOutput);
+ QCOMPARE(exitCode, 0);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+ QCOMPARE(output, QByteArray{"ABCD\n"});
+}
+
+void ServiceTest::processMultipleOutput()
+{
+ ConnectionContext ctx;
+
+ QByteArray output;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("sh", {"-c", "echo abcd && sleep 1 && echo defg"});
+ });
+ connect(&processService, &ProcessService::readyRead, [&]() {
+ output.append(processService.read());
+ });
+ QSignalSpy readyReadSpy{&processService, &ProcessService::readyRead};
+ QSignalSpy executedSpy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ executedSpy.wait(1000 + testTimeout);
+ QCOMPARE(executedSpy.count(), 1);
+ // In principle there could be only one (or more than two) readyRead, but in
+ // practice the above command seems split into two outputs and otherwise
+ // this test is not fulfilling its purpose.
+ QCOMPARE(readyReadSpy.count(), 2);
+ auto exitCode = executedSpy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = executedSpy[0][1].value<QProcess::ExitStatus>();
+ auto finalOutput = executedSpy[0][2].toByteArray();
+ output.append(finalOutput);
+ QCOMPARE(exitCode, 0);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+ QCOMPARE(output, QByteArray{"abcd\ndefg\n"});
+}
+
+void ServiceTest::processErrorCode()
+{
+ ConnectionContext ctx;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("test", {"-z", "ABCD"});
+ });
+ QSignalSpy spy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ auto exitCode = spy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>();
+ auto output = spy[0][2].toString();
+ QCOMPARE(exitCode, 1);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+ QCOMPARE(output, QString{""});
+}
+
+void ServiceTest::processNonExistent()
+{
+ ConnectionContext ctx;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executed, [](int, QProcess::ExitStatus, QString) {
+ QFAIL("Command was unexpectedly run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("lsfdajlvaie", {});
+ });
+ QSignalSpy spy{&processService, &ProcessService::executionError};
+
+ processService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ auto error = spy[0][0].value<QProcess::ProcessError>();
+ QCOMPARE(error, QProcess::FailedToStart);
+}
+
+void ServiceTest::processCrash()
+{
+ ConnectionContext ctx;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &Service::initialized, [&]() {
+ // Crash the process by having it send SIGSEGV to itself
+ processService.execute("sh", {"-c", "kill -SEGV $$"});
+ });
+ QSignalSpy errorSpy{&processService, &ProcessService::executionError};
+ QSignalSpy executedSpy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ errorSpy.wait(testTimeout);
+ QCOMPARE(errorSpy.count(), 1);
+ auto error = errorSpy[0][0].value<QProcess::ProcessError>();
+ QCOMPARE(error, QProcess::Crashed);
+
+ executedSpy.wait(testTimeout);
+ QCOMPARE(executedSpy.count(), 1);
+ auto exitCode = executedSpy[0][0].toInt();
+ auto exitStatus = executedSpy[0][1].value<QProcess::ExitStatus>();
+ auto output = executedSpy[0][2].toString();
+ QCOMPARE(exitCode, 11); // 11 for segfault
+ QCOMPARE(exitStatus, QProcess::CrashExit);
+ QCOMPARE(output, QString{""});
+}
+
+void ServiceTest::processInput()
+{
+ ConnectionContext ctx;
+
+ QByteArray output;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("sh", {"-c", "read input; echo $input"});
+ });
+ connect(&processService, &ProcessService::readyRead, [&]() {
+ output.append(processService.read());
+ });
+ connect(&processService, &ProcessService::started, [&]() {
+ processService.write("abcd\n");
+ });
+ QSignalSpy spy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ auto exitCode = spy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>();
+ auto finalOutput = spy[0][2].toByteArray();
+ output.append(finalOutput);
+ QCOMPARE(exitCode, 0);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+ QCOMPARE(output, QByteArray{"abcd\n"});
+}
+
+void ServiceTest::processMultipleInput()
+{
+ ConnectionContext ctx;
+
+ QByteArray output;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("sh", {"-c", "for i in {1..2}; do read input; echo $input; done"});
+ });
+ connect(&processService, &ProcessService::readyRead, [&]() {
+ output.append(processService.read());
+ });
+ connect(&processService, &ProcessService::started, [&]() {
+ processService.write("abcd\n");
+ processService.write("efgh\n");
+ });
+ QSignalSpy spy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ auto exitCode = spy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = spy[0][1].value<QProcess::ExitStatus>();
+ auto finalOutput = spy[0][2].toByteArray();
+ output.append(finalOutput);
+ QCOMPARE(exitCode, 0);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+ QCOMPARE(output, QByteArray{"abcd\nefgh\n"});
+}
+
+void ServiceTest::filePush()
+{
+ ConnectionContext ctx;
+
+ // Write source file
+ QFile source{pushPullFileName};
+ QVERIFY(source.open(QIODevice::WriteOnly));
+ source.write(pushPullFileContents);
+ source.close();
+
+ // Push source file to device (it's cleaned up in filePullToUnopenable())
+ FilePushService filePushService{&ctx.connection};
+ connect(&filePushService, &FilePushService::error, [](QString error) {
+ qCritical() << error;
+ QFAIL("Error while pushing file.");
+ });
+ connect(&filePushService, &Service::initialized, [&]() {
+ filePushService.push(pushPullFileName, pushPullFileName);
+ });
+ QSignalSpy pushSpy{&filePushService, &FilePushService::pushed};
+
+ filePushService.initialize();
+
+ pushSpy.wait(testTimeout);
+ QCOMPARE(pushSpy.count(), 1);
+
+ // Remove source file
+ source.remove();
+
+ // Check contents on device
+ QByteArray output;
+
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("cat", {pushPullFileName});
+ });
+ connect(&processService, &ProcessService::readyRead, [&]() {
+ output.append(processService.read());
+ });
+ QSignalSpy processSpy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ processSpy.wait(testTimeout);
+ QCOMPARE(processSpy.count(), 1);
+ auto exitCode = processSpy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = processSpy[0][1].value<QProcess::ExitStatus>();
+ auto finalOutput = processSpy[0][2].toByteArray();
+ output.append(finalOutput);
+ QCOMPARE(exitCode, 0);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+ QCOMPARE(output, pushPullFileContents);
+}
+
+void ServiceTest::filePushNonexistent()
+{
+ QVERIFY(!QFile::exists(nonexistentFileName));
+
+ ConnectionContext ctx;
+
+ FilePushService filePushService{&ctx.connection};
+ connect(&filePushService, &FilePushService::pushed, []() {
+ QFAIL("Unexpectedly succeeded pushing nonexistent file.");
+ });
+ connect(&filePushService, &Service::initialized, [&]() {
+ filePushService.push(nonexistentFileName, nonexistentFileName);
+ });
+ QSignalSpy spy{&filePushService, &FilePushService::error};
+
+ filePushService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ QRegularExpression regexp{"^Could not open.+host$"};
+ auto errorMessage = spy[0][0].toString();
+ QVERIFY(regexp.match(errorMessage).hasMatch());
+}
+
+// This test relies on the file pushed in filePush()
+void ServiceTest::filePull()
+{
+ ConnectionContext ctx;
+
+ // Pull source file from device
+ FilePullService filePullService{&ctx.connection};
+ connect(&filePullService, &FilePullService::error, [](QString error) {
+ qCritical() << error;
+ QFAIL("Error while pulling file");
+ });
+ connect(&filePullService, &Service::initialized, [&]() {
+ filePullService.pull(pushPullFileName, pushPullFileName);
+ });
+ QSignalSpy pullSpy{&filePullService, &FilePullService::pulled};
+
+ filePullService.initialize();
+
+ pullSpy.wait(testTimeout);
+ QCOMPARE(pullSpy.count(), 1);
+
+ // Check contents
+ QFile sink{pushPullFileName};
+ QVERIFY(sink.open(QIODevice::ReadOnly));
+ auto contents = sink.readAll();
+ QCOMPARE(contents, pushPullFileContents);
+
+ sink.close();
+ sink.remove();
+}
+
+void ServiceTest::filePullNonexistent()
+{
+ const QString nonexistentFileName{"qdbtestfile2"};
+
+ ConnectionContext ctx;
+
+ // Pull source file from device
+ FilePullService filePullService{&ctx.connection};
+ connect(&filePullService, &FilePullService::pulled, []() {
+ QFAIL("Unexpectedly succeeded pulling nonexistent file");
+ });
+ connect(&filePullService, &Service::initialized, [&]() {
+ filePullService.pull(nonexistentFileName, nonexistentFileName);
+ });
+ QSignalSpy spy{&filePullService, &FilePullService::error};
+
+ filePullService.initialize();
+
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+ QRegularExpression regexp{"^Could not open.+device$"};
+ auto errorMessage = spy[0][0].toString();
+ QVERIFY(regexp.match(errorMessage).hasMatch());
+}
+
+// This test relies on the file pushed in filePush() and removes it in the end
+void ServiceTest::filePullToUnopenable()
+{
+ const QString fileName{"qdbtestfile2"};
+
+ QFile blocker{fileName};
+ blocker.open(QIODevice::WriteOnly);
+ blocker.setPermissions(QFileDevice::ReadUser);
+ blocker.close();
+
+ ConnectionContext ctx;
+
+ // Pull source file from device
+ FilePullService filePullService{&ctx.connection};
+ connect(&filePullService, &FilePullService::pulled, []() {
+ QFAIL("Unexpectedly succeeded pulling into file that can't be written to");
+ });
+ connect(&filePullService, &Service::initialized, [&]() {
+ filePullService.pull(pushPullFileName, fileName);
+ });
+ QSignalSpy spy{&filePullService, &FilePullService::error};
+
+ filePullService.initialize();
+
+ spy.wait(testTimeout);
+
+ blocker.remove();
+
+ QCOMPARE(spy.count(), 1);
+ QRegularExpression regexp{"^Could not open.+host$"};
+ auto errorMessage = spy[0][0].toString();
+ QVERIFY(regexp.match(errorMessage).hasMatch());
+
+ // Remove file from device
+ ProcessService processService{&ctx.connection};
+ connect(&processService, &ProcessService::executionError, [](QProcess::ProcessError error) {
+ qDebug() << "Command not run, error:" << error;
+ QFAIL("Command was not run successfully");
+ });
+ connect(&processService, &Service::initialized, [&]() {
+ processService.execute("rm", {pushPullFileName});
+ });
+ QSignalSpy processSpy{&processService, &ProcessService::executed};
+
+ processService.initialize();
+
+ processSpy.wait(testTimeout);
+ QCOMPARE(processSpy.count(), 1);
+ auto exitCode = processSpy[0][0].toInt();
+ QProcess::ExitStatus exitStatus = processSpy[0][1].value<QProcess::ExitStatus>();
+ QCOMPARE(exitCode, 0);
+ QCOMPARE(exitStatus, QProcess::NormalExit);
+}
+
+QTEST_GUILESS_MAIN(ServiceTest)
+#include "servicetest.moc"
diff --git a/tests/servicetest.pro b/tests/servicetest.pro
new file mode 100644
index 0000000..a5914f8
--- /dev/null
+++ b/tests/servicetest.pro
@@ -0,0 +1,55 @@
+QT -= gui
+QT += testlib
+
+win32: CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+HEADERS += \
+ ../client/connection.h \
+ ../client/filepullservice.h \
+ ../client/filepushservice.h \
+ ../client/processservice.h \
+ ../client/echoservice.h \
+ ../client/service.h
+
+SOURCES += \
+ servicetest.cpp \
+ ../client/connection.cpp \
+ ../client/filepullservice.cpp \
+ ../client/filepushservice.cpp \
+ ../client/processservice.cpp \
+ ../client/echoservice.cpp \
+ ../client/service.cpp
+
+INCLUDEPATH += $$PWD/../libqdb
+
+unix {
+ LIBS = -L$$OUT_PWD/../libqdb -lqdb
+ QMAKE_RPATHDIR += ../libqdb
+}
+
+win32 {
+HEADERS += \
+ ../libqdb/protocol/protocol.h \
+ ../libqdb/protocol/qdbmessage.h \
+ ../libqdb/protocol/qdbtransport.h \
+ ../libqdb/stream.h \
+ ../libqdb/abstractconnection.h \
+ ../libqdb/streampacket.h
+
+SOURCES += \
+ ../libqdb/protocol/qdbmessage.cpp \
+ ../libqdb/protocol/qdbtransport.cpp \
+ ../libqdb/stream.cpp \
+ ../libqdb/abstractconnection.cpp \
+ ../libqdb/streampacket.cpp
+
+CONFIG(debug, debug|release) {
+ LIBQDBDIR = $$OUT_PWD/../libqdb/debug
+} else {
+ LIBQDBDIR = $$OUT_PWD/../libqdb/release
+}
+LIBS = -L$$LIBQDBDIR -lqdb
+}
diff --git a/tests/streamtest.cpp b/tests/streamtest.cpp
new file mode 100644
index 0000000..df0c814
--- /dev/null
+++ b/tests/streamtest.cpp
@@ -0,0 +1,515 @@
+/******************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Debug Bridge.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+#include "../utils/make_unique.h"
+#include "usb/usbconnection.h"
+#include "protocol/protocol.h"
+#include "protocol/qdbmessage.h"
+#include "protocol/qdbtransport.h"
+#include "protocol/services.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qtimer.h>
+#include <QtTest/QtTest>
+
+const int testTimeout = 500; // in milliseconds
+
+class TestCase : public QObject
+{
+ Q_OBJECT
+public:
+ TestCase()
+ : m_transport{nullptr}, m_versionBuffer{}, m_phase{0}
+ {
+ QDataStream dataStream{&m_versionBuffer, QIODevice::WriteOnly};
+ dataStream << qdbProtocolVersion;
+ }
+public slots:
+ void run()
+ {
+ m_transport = make_unique<QdbTransport>(new UsbConnection{});
+ if (m_transport->open()) {
+ qDebug() << "opened transport";
+ connect(m_transport.get(), &QdbTransport::messageAvailable, this, &TestCase::testPhases);
+ testPhases();
+ } else {
+ qDebug() << "failed to open transport";
+ }
+ }
+
+ virtual void testPhases() = 0;
+signals:
+ void passed();
+protected:
+ std::unique_ptr<QdbTransport> m_transport;
+ QByteArray m_versionBuffer;
+ int m_phase;
+};
+
+class OpenWriteCloseEchoTest : public TestCase
+{
+ Q_OBJECT
+public slots:
+ void testPhases() override
+ {
+ switch (m_phase) {
+ case 0: {
+ QdbMessage cnxn{QdbMessage::Connect, 0, 0, m_versionBuffer};
+ QVERIFY(m_transport->send(cnxn));
+ break;
+ }
+ case 1: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+ QCOMPARE(response.data(), m_versionBuffer);
+
+ QdbMessage open{QdbMessage::Open, m_hostId, 0, tagBuffer(EchoTag)};
+ QVERIFY(m_transport->send(open));
+ break;
+ }
+ case 2: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QVERIFY(response.deviceStream() != 0);
+ QCOMPARE(response.data(), QByteArray{});
+
+ m_deviceId = response.deviceStream();
+
+ QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId, QByteArray{"\x00\x00\x00\x04""ABCD", 8}};
+ QVERIFY(m_transport->send(write));
+ break;
+ }
+ case 3: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QCOMPARE(response.deviceStream(), m_deviceId);
+ QCOMPARE(response.data(), QByteArray{});
+ break;
+ }
+ case 4: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Write);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QCOMPARE(response.deviceStream(), m_deviceId);
+ QByteArray data{"\x00\x00\x00\x04""ABCD", 8};
+ QCOMPARE(response.data(), data);
+
+ QdbMessage ok{QdbMessage::Ok, m_hostId, m_deviceId};
+ QVERIFY(m_transport->send(ok));
+
+ QdbMessage close{QdbMessage::Close, m_hostId, m_deviceId};
+ QVERIFY(m_transport->send(close));
+
+ emit passed();
+ break;
+ }
+ }
+ ++m_phase;
+ }
+
+private:
+ const StreamId m_hostId = 1;
+ StreamId m_deviceId = 0;
+};
+
+class DoubleConnectTest : public TestCase
+{
+ Q_OBJECT
+public slots:
+ void testPhases() override
+ {
+ switch (m_phase) {
+ case 0: {
+ QdbMessage connect{QdbMessage::Connect, 0, 0, m_versionBuffer};
+ QVERIFY(m_transport->send(connect));
+ break;
+ }
+ case 1: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+ QCOMPARE(response.hostStream(), 0u);
+ QCOMPARE(response.deviceStream(), 0u);
+ QCOMPARE(response.data(), m_versionBuffer);
+
+ QdbMessage connect{QdbMessage::Connect, 0, 0, m_versionBuffer};
+ QVERIFY(m_transport->send(connect));
+ break;
+ }
+ case 2: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+ QCOMPARE(response.hostStream(), 0u);
+ QCOMPARE(response.deviceStream(), 0u);
+ QCOMPARE(response.data(), m_versionBuffer);
+
+ emit passed();
+ break;
+ }
+ }
+ ++m_phase;
+ }
+};
+
+class ConnectWithUnsupportedVersionTest : public TestCase
+{
+ Q_OBJECT
+public slots:
+ void testPhases() override
+ {
+ switch (m_phase) {
+ case 0: {
+ QByteArray unsupported{};
+ QDataStream dataStream{&unsupported, QIODevice::WriteOnly};
+ dataStream << (qdbProtocolVersion + 1);
+ QdbMessage connect{QdbMessage::Connect, 0, 0, unsupported};
+ QVERIFY(m_transport->send(connect));
+ break;
+ }
+ case 1: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+ QCOMPARE(response.hostStream(), 0u);
+ QCOMPARE(response.deviceStream(), 0u);
+ QCOMPARE(response.data(), m_versionBuffer);
+
+ emit passed();
+ break;
+ }
+ }
+ ++m_phase;
+ }
+};
+
+// Closing a stream twice caused a segmentation fault at one point. This is
+// guarding against that regression.
+class DoubleCloseTest : public TestCase
+{
+ Q_OBJECT
+public slots:
+ void testPhases() override
+ {
+ switch (m_phase) {
+ case 0: {
+ QdbMessage cnxn{QdbMessage::Connect, 0, 0};
+ QVERIFY(m_transport->send(cnxn));
+ break;
+ }
+ case 1: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+
+ QdbMessage open{QdbMessage::Open, m_hostId, 0, tagBuffer(EchoTag)};
+ QVERIFY(m_transport->send(open));
+ break;
+ }
+ case 2: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QVERIFY(response.deviceStream() != 0);
+ QCOMPARE(response.data(), QByteArray{});
+
+ m_deviceId = response.deviceStream();
+
+ QdbMessage close{QdbMessage::Close, m_hostId, m_deviceId};
+ QVERIFY(m_transport->send(close));
+ QVERIFY(m_transport->send(close));
+
+ // do a write to check whether qdbd is still alive
+
+ QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId, QByteArray{"\x00\x00\x00\x04""ABCD", 8}};
+ QVERIFY(m_transport->send(write));
+ break;
+ }
+ case 3: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Close);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QCOMPARE(response.deviceStream(), m_deviceId);
+ QCOMPARE(response.data(), QByteArray{});
+
+ emit passed();
+ break;
+ }
+ }
+ ++m_phase;
+ }
+
+private:
+ const StreamId m_hostId = 1;
+ StreamId m_deviceId = 0;
+};
+
+class WriteToNonExistentStreamTest : public TestCase
+{
+ Q_OBJECT
+public slots:
+ void testPhases() override
+ {
+ switch (m_phase) {
+ case 0: {
+ QdbMessage connect{QdbMessage::Connect, 0, 0, m_versionBuffer};
+ QVERIFY(m_transport->send(connect));
+ break;
+ }
+ case 1: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+ QCOMPARE(response.hostStream(), 0u);
+ QCOMPARE(response.deviceStream(), 0u);
+ QCOMPARE(response.data(), m_versionBuffer);
+
+ // Try to directly write to an unopened stream
+ QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId};
+ QVERIFY(m_transport->send(write));
+ break;
+ }
+ case 2: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Close);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QCOMPARE(response.deviceStream(), m_deviceId);
+ QCOMPARE(response.data(), QByteArray{});
+
+ // Open and close a stream and then try to write to it
+ QdbMessage open{QdbMessage::Open, m_hostId, 0, tagBuffer(EchoTag)};
+ QVERIFY(m_transport->send(open));
+ break;
+ }
+ case 3: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId);
+ QVERIFY(response.deviceStream() != 0);
+ QCOMPARE(response.data(), QByteArray{});
+
+ m_deviceId2 = response.deviceStream();
+
+ QdbMessage close{QdbMessage::Close, m_hostId, m_deviceId2};
+ QVERIFY(m_transport->send(close));
+
+ QdbMessage write{QdbMessage::Write, m_hostId, m_deviceId2, QByteArray{"\x00\x00\x00\x04""ABCD", 8}};
+ QVERIFY(m_transport->send(write));
+ break;
+ }
+ case 4: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Close);
+ QCOMPARE(response.hostStream(), m_hostId);
+ // Device stream ID does not matter, since device already closed it
+ QCOMPARE(response.data(), QByteArray{});
+ emit passed();
+ break;
+ }
+ }
+ ++m_phase;
+ }
+private:
+ const StreamId m_hostId = 435;
+ const StreamId m_deviceId = 7542;
+ StreamId m_deviceId2 = 0;
+};
+
+class TwoEchoStreamsTest : public TestCase
+{
+ Q_OBJECT
+public slots:
+ void testPhases() override
+ {
+ switch (m_phase) {
+ case 0: {
+ QdbMessage cnxn{QdbMessage::Connect, 0, 0, m_versionBuffer};
+ QVERIFY(m_transport->send(cnxn));
+ break;
+ }
+ case 1: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Connect);
+
+ QdbMessage open{QdbMessage::Open, m_hostId1, 0, tagBuffer(EchoTag)};
+ QVERIFY(m_transport->send(open));
+ break;
+ }
+ case 2: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId1);
+ QVERIFY(response.deviceStream() != 0);
+ QCOMPARE(response.data(), QByteArray{});
+
+ m_deviceId1 = response.deviceStream();
+
+ qDebug() << "writing ABCD";
+ QdbMessage write{QdbMessage::Write, m_hostId1, m_deviceId1, QByteArray{"\x00\x00\x00\x04""ABCD", 8}};
+ QVERIFY(m_transport->send(write));
+ qDebug() << "wrote";
+ break;
+ }
+ case 3: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId1);
+ QCOMPARE(response.deviceStream(), m_deviceId1);
+ QCOMPARE(response.data(), QByteArray{});
+ break;
+ }
+ case 4: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Write);
+ QCOMPARE(response.hostStream(), m_hostId1);
+ QCOMPARE(response.deviceStream(), m_deviceId1);
+ QByteArray data{"\x00\x00\x00\x04""ABCD", 8};
+ QCOMPARE(response.data(), data);
+
+ QdbMessage ok{QdbMessage::Ok, m_hostId1, m_deviceId1};
+ QVERIFY(m_transport->send(ok));
+
+ QdbMessage open{QdbMessage::Open, m_hostId2, 0, tagBuffer(EchoTag)};
+ QVERIFY(m_transport->send(open));
+ break;
+ }
+ case 5: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId2);
+ QVERIFY(response.deviceStream() != 0);
+ QCOMPARE(response.data(), QByteArray{});
+
+ m_deviceId2 = response.deviceStream();
+
+ QdbMessage write{QdbMessage::Write, m_hostId2, m_deviceId2,
+ QByteArray{"\x00\x00\x00\x05\x00\x01\x02\x03\x04", 9}};
+ QVERIFY(m_transport->send(write));
+ break;
+ }
+ case 6: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId2);
+ QCOMPARE(response.deviceStream(), m_deviceId2);
+ QCOMPARE(response.data(), QByteArray{});
+ break;
+ }
+ case 7: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Write);
+ QCOMPARE(response.hostStream(), m_hostId2);
+ QCOMPARE(response.deviceStream(), m_deviceId2);
+ auto data = QByteArray{"\x00\x00\x00\x05\x00\x01\x02\x03\x04", 9};
+ QCOMPARE(response.data(), data);
+
+ QdbMessage ok{QdbMessage::Ok, m_hostId2, m_deviceId2};
+ QVERIFY(m_transport->send(ok));
+
+ QdbMessage close{QdbMessage::Close, m_hostId2, m_deviceId2};
+ QVERIFY(m_transport->send(close));
+
+ QdbMessage write{QdbMessage::Write, m_hostId1, m_deviceId1, QByteArray{"\x00\x00\x00\x03QDB", 7}};
+ QVERIFY(m_transport->send(write));
+ break;
+ }
+ case 8: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Ok);
+ QCOMPARE(response.hostStream(), m_hostId1);
+ QCOMPARE(response.deviceStream(), m_deviceId1);
+ QCOMPARE(response.data(), QByteArray{});
+ break;
+ }
+ case 9: {
+ QdbMessage response = m_transport->receive();
+ QCOMPARE(response.command(), QdbMessage::Write);
+ QCOMPARE(response.hostStream(), m_hostId1);
+ QCOMPARE(response.deviceStream(), m_deviceId1);
+ QByteArray data{"\x00\x00\x00\x03QDB", 7};
+ QCOMPARE(response.data(), data);
+
+ QdbMessage ok{QdbMessage::Ok, m_hostId1, m_deviceId1};
+ QVERIFY(m_transport->send(ok));
+
+ QdbMessage close{QdbMessage::Close, m_hostId1, m_deviceId1};
+ QVERIFY(m_transport->send(close));
+
+ emit passed();
+ break;
+ }
+ }
+ ++m_phase;
+ }
+private:
+ const StreamId m_hostId1 = 1;
+ StreamId m_deviceId1 = 0;
+ const StreamId m_hostId2 = 2;
+ StreamId m_deviceId2 = 0;
+};
+
+void testCase(TestCase *test)
+{
+ QSignalSpy spy{test, &TestCase::passed};
+ QTimer::singleShot(0, test, &TestCase::run);
+ spy.wait(testTimeout);
+ QCOMPARE(spy.count(), 1);
+}
+
+class StreamTest : public QObject
+{
+ Q_OBJECT
+private slots:
+ void openWriteCloseEcho()
+ {
+ OpenWriteCloseEchoTest test;
+ testCase(&test);
+ }
+
+ void doubleConnect()
+ {
+ DoubleConnectTest test;
+ testCase(&test);
+ }
+
+ void doubleClose()
+ {
+ DoubleCloseTest test;
+ testCase(&test);
+ }
+
+ void connectWithUnsupportedVersion()
+ {
+ ConnectWithUnsupportedVersionTest test;
+ testCase(&test);
+ }
+
+ void writeToNonexistentStream()
+ {
+ WriteToNonExistentStreamTest test;
+ testCase(&test);
+ }
+
+ void twoEchoStreamsTest()
+ {
+ TwoEchoStreamsTest test;
+ testCase(&test);
+ }
+};
+
+QTEST_GUILESS_MAIN(StreamTest)
+#include "streamtest.moc"
diff --git a/tests/streamtest.pro b/tests/streamtest.pro
new file mode 100644
index 0000000..6ff8223
--- /dev/null
+++ b/tests/streamtest.pro
@@ -0,0 +1,34 @@
+QT -= gui
+QT += testlib
+
+win32: CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += streamtest.cpp
+
+INCLUDEPATH += $$PWD/../libqdb
+
+unix {
+LIBS = -L$$OUT_PWD/../libqdb -lqdb
+QMAKE_RPATHDIR += ../libqdb
+}
+
+win32 {
+HEADERS += \
+ ../libqdb/protocol/qdbmessage.h \
+ ../libqdb/protocol/qdbtransport.h
+
+SOURCES += \
+ ../libqdb/protocol/qdbmessage.cpp \
+ ../libqdb/protocol/qdbtransport.cpp
+
+CONFIG(debug, debug|release) {
+ LIBQDBDIR = $$OUT_PWD/../libqdb/debug
+} else {
+ LIBQDBDIR = $$OUT_PWD/../libqdb/release
+}
+LIBS = -L$$LIBQDBDIR -lqdb
+}
+
diff --git a/tests/tests.pro b/tests/tests.pro
new file mode 100644
index 0000000..28b6fad
--- /dev/null
+++ b/tests/tests.pro
@@ -0,0 +1,7 @@
+TEMPLATE = subdirs
+
+SUBDIRS = \
+ tst_qdbmessage.pro \
+ tst_stream.pro \
+ servicetest.pro \
+ streamtest.pro
diff --git a/tests/tst_qdbmessage.cpp b/tests/tst_qdbmessage.cpp
new file mode 100644
index 0000000..121d65d
--- /dev/null
+++ b/tests/tst_qdbmessage.cpp
@@ -0,0 +1,148 @@
+/******************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Debug Bridge.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+#include <QtTest/QtTest>
+
+#include "../libqdb/protocol/protocol.h"
+#include "../libqdb/protocol/qdbmessage.h"
+
+class tst_QdbMessage : public QObject
+{
+ Q_OBJECT
+private slots:
+ void construction_data();
+ void construction();
+ void setters();
+ void roundtrip_data();
+ void roundtrip();
+ void gettingSize_data();
+ void gettingSize();
+};
+
+void testData()
+{
+ QTest::addColumn<QdbMessage::CommandType>("command");
+ QTest::addColumn<StreamId>("hostStream");
+ QTest::addColumn<StreamId>("deviceStream");
+ QTest::addColumn<QByteArray>("data");
+ QTest::addColumn<int>("dataSize");
+
+ QTest::newRow("connect") << QdbMessage::Connect << 0u << 0u << QByteArray() << 0;
+ QTest::newRow("open") << QdbMessage::Open << 1u << 0u << QByteArray("\x05\x04\x03\x02\x01") << 5;
+ QTest::newRow("write") << QdbMessage::Write << 255u << 254u << QByteArray("\x01\x02\x03") << 3;
+ QTest::newRow("close") << QdbMessage::Write << 0u << 1u << QByteArray("1234") << 4;
+ QTest::newRow("ok") << QdbMessage::Ok << 3u << 5u << QByteArray("\x0A\x0B\x0C\x0D") << 4;
+}
+
+void tst_QdbMessage::construction_data()
+{
+ testData();
+}
+
+void tst_QdbMessage::construction()
+{
+ QFETCH(QdbMessage::CommandType, command);
+ QFETCH(StreamId, hostStream);
+ QFETCH(StreamId, deviceStream);
+
+ QdbMessage message{command, hostStream, deviceStream};
+ QCOMPARE(message.command(), command);
+ QCOMPARE(message.hostStream(), hostStream);
+ QCOMPARE(message.deviceStream(), deviceStream);
+ QCOMPARE(message.data().isEmpty(), true);
+
+ QFETCH(QByteArray, data);
+
+ message = QdbMessage{command, hostStream, deviceStream, data};
+ QCOMPARE(message.command(), command);
+ QCOMPARE(message.hostStream(), hostStream);
+ QCOMPARE(message.deviceStream(), deviceStream);
+ QCOMPARE(message.data(), data);
+}
+
+void tst_QdbMessage::setters()
+{
+ QdbMessage message{QdbMessage::Open, 0, 0};
+
+ message.setHostStream(255u);
+ QCOMPARE(message.hostStream(), 255u);
+
+ message.setDeviceStream((12u));
+ QCOMPARE(message.deviceStream(), 12u);
+
+ message.setData("ABCD", 4);
+ QCOMPARE(message.data(), QByteArray("ABCD", 4));
+}
+
+void tst_QdbMessage::roundtrip_data()
+{
+ testData();
+}
+
+void tst_QdbMessage::roundtrip()
+{
+ QFETCH(QdbMessage::CommandType, command);
+ QFETCH(StreamId, hostStream);
+ QFETCH(StreamId, deviceStream);
+ QFETCH(QByteArray, data);
+
+ QByteArray buf{qdbMessageSize, '\0'};
+ QDataStream writeStream{&buf, QIODevice::WriteOnly};
+
+ QdbMessage message{command, hostStream, deviceStream, data};
+ writeStream << message;
+
+ QDataStream readStream{buf};
+ QdbMessage readMessage;
+ readStream >> readMessage;
+
+ QCOMPARE(readMessage.command(), message.command());
+ QCOMPARE(readMessage.hostStream(), message.hostStream());
+ QCOMPARE(readMessage.deviceStream(), message.deviceStream());
+ QCOMPARE(readMessage.data(), message.data());
+}
+
+void tst_QdbMessage::gettingSize_data()
+{
+ testData();
+}
+
+void tst_QdbMessage::gettingSize()
+{
+ QFETCH(QdbMessage::CommandType, command);
+ QFETCH(StreamId, hostStream);
+ QFETCH(StreamId, deviceStream);
+ QFETCH(QByteArray, data);
+
+ QByteArray buf{qdbMessageSize, '\0'};
+ QDataStream writeStream{&buf, QIODevice::WriteOnly};
+
+ QdbMessage message{command, hostStream, deviceStream, data};
+ writeStream << message;
+
+ QFETCH(int, dataSize);
+
+ int size = QdbMessage::GetDataSize(buf);
+
+ QCOMPARE(size, dataSize);
+}
+
+QTEST_APPLESS_MAIN(tst_QdbMessage)
+#include "tst_qdbmessage.moc"
diff --git a/tests/tst_qdbmessage.pro b/tests/tst_qdbmessage.pro
new file mode 100644
index 0000000..180f616
--- /dev/null
+++ b/tests/tst_qdbmessage.pro
@@ -0,0 +1,19 @@
+QT -= gui
+QT += testlib
+
+CONFIG += c++11
+CONFIG += testcase
+
+TARGET = tst_qdbmessage
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+HEADERS += \
+ ../libqdb/protocol/protocol.h \
+ ../libqdb/protocol/qdbmessage.h
+
+SOURCES += \
+ tst_qdbmessage.cpp \
+ ../libqdb/protocol/qdbmessage.cpp
diff --git a/tests/tst_stream.cpp b/tests/tst_stream.cpp
new file mode 100644
index 0000000..8bdace1
--- /dev/null
+++ b/tests/tst_stream.cpp
@@ -0,0 +1,143 @@
+/******************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Debug Bridge.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+#include <QtTest/QtTest>
+
+#include "abstractconnection.h"
+#include "stream.h"
+
+class ConnectionStub : public AbstractConnection
+{
+public:
+ ConnectionStub() : AbstractConnection{nullptr} {}
+
+ bool initialize() override
+ {
+ return true;
+ }
+
+ void enqueueMessage(const QdbMessage &message) override
+ {
+ enqueued.append(message);
+ }
+
+ void handleMessage() override
+ {
+
+ }
+
+ void reset()
+ {
+ enqueued.clear();
+ }
+
+ QList<QdbMessage> enqueued;
+};
+
+class tst_Stream : public QObject
+{
+ Q_OBJECT
+public:
+ tst_Stream()
+ : m_connection{},
+ m_stream{&m_connection, 13, 26},
+ m_hostId{13},
+ m_deviceId{26}
+ { }
+
+private slots:
+ void singleWrite();
+ void doubleWrite();
+ void singleMessagePacket();
+ void splitPacket();
+ void closedIsEmitted();
+
+private:
+ ConnectionStub m_connection;
+ Stream m_stream;
+ StreamId m_hostId;
+ StreamId m_deviceId;
+};
+
+void tst_Stream::singleWrite()
+{
+ m_connection.reset();
+
+ StreamPacket packet{QByteArray{"ABCD"}};
+ m_stream.write(packet);
+
+ QCOMPARE(m_connection.enqueued.size(), 1);
+ QByteArray expected{"\x00\x00\x00\x04""ABCD", 8};
+ QCOMPARE(m_connection.enqueued[0].data(), expected);
+}
+
+void tst_Stream::doubleWrite()
+{
+ m_connection.reset();
+
+ StreamPacket packet{QByteArray{"ABCD"}};
+ m_stream.write(packet);
+ StreamPacket packet2{QByteArray{"QDB"}};
+ m_stream.write(packet2);
+
+ QCOMPARE(m_connection.enqueued.size(), 2);
+ QByteArray expected{"\x00\x00\x00\x04""ABCD", 8};
+ QCOMPARE(m_connection.enqueued[0].data(), expected);
+ expected = QByteArray{"\x00\x00\x00\x03QDB", 7};
+ QCOMPARE(m_connection.enqueued[1].data(), expected);
+}
+
+void tst_Stream::singleMessagePacket()
+{
+ QSignalSpy spy{&m_stream, &Stream::packetAvailable};
+ m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId,
+ QByteArray{"\x00\x00\x00\x02OK", 6}});
+
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(static_cast<int>(spy[0][0].type()), QMetaType::type("StreamPacket"));
+ auto packet = spy[0][0].value<StreamPacket>();
+ QCOMPARE(packet.buffer(), QByteArray{"OK"});
+}
+
+void tst_Stream::splitPacket()
+{
+ QSignalSpy spy{&m_stream, &Stream::packetAvailable};
+ m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId,
+ QByteArray{"\x00\x00\x00\x05""AB", 6}});
+ m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId,
+ QByteArray{"C"}});
+ m_stream.receiveMessage(QdbMessage{QdbMessage::Write, m_hostId, m_deviceId,
+ QByteArray{"DE"}});
+
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(static_cast<int>(spy[0][0].type()), QMetaType::type("StreamPacket"));
+ auto packet = spy[0][0].value<StreamPacket>();
+ QCOMPARE(packet.buffer(), QByteArray{"ABCDE"});
+}
+
+void tst_Stream::closedIsEmitted()
+{
+ QSignalSpy spy{&m_stream, &Stream::closed};
+ m_stream.close();
+ QCOMPARE(spy.count(), 1);
+}
+
+QTEST_APPLESS_MAIN(tst_Stream)
+#include "tst_stream.moc"
diff --git a/tests/tst_stream.pro b/tests/tst_stream.pro
new file mode 100644
index 0000000..fb690f5
--- /dev/null
+++ b/tests/tst_stream.pro
@@ -0,0 +1,44 @@
+QT -= gui
+QT += testlib
+
+CONFIG += c++11
+CONFIG += testcase
+
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+SOURCES += \
+ tst_stream.cpp \
+
+INCLUDEPATH += $$PWD/../libqdb
+
+unix {
+LIBS = -L$$OUT_PWD/../libqdb -lqdb
+QMAKE_RPATHDIR += ../libqdb
+}
+
+win32 {
+HEADERS += \
+ ../libqdb/protocol/qdbmessage.h \
+ ../libqdb/protocol/qdbtransport.h \
+ ../libqdb/stream.h \
+ ../libqdb/abstractconnection.h \
+ ../libqdb/streampacket.h
+
+SOURCES += \
+ ../libqdb/protocol/qdbmessage.cpp \
+ ../libqdb/protocol/qdbtransport.cpp \
+ ../libqdb/stream.cpp \
+ ../libqdb/abstractconnection.cpp \
+ ../libqdb/streampacket.cpp
+
+CONFIG(debug, debug|release) {
+ LIBQDBDIR = $$OUT_PWD/../libqdb/debug
+} else {
+ LIBQDBDIR = $$OUT_PWD/../libqdb/release
+}
+LIBS = -L$$LIBQDBDIR -lqdb
+}
+