From 4c76e40617d22d3578bb95973b54b0e266591e7c Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler@nokia.com>
Date: Tue, 15 Nov 2011 17:13:02 +0100
Subject: [PATCH] SSH: Derive SshRemoteProcess from QIODevice.

Now it looks even more like QProcess. Things like process channels are
still missing.

Change-Id: I3f30cd00ed4a054d02e83add9a6f4162b48f8345
Reviewed-by: Christian Kandeler <christian.kandeler@nokia.com>
---
 src/libs/utils/ssh/sftpchannel.cpp            |   4 +-
 src/libs/utils/ssh/sftpchannel_p.h            |   2 +-
 src/libs/utils/ssh/sshchannel.cpp             |   7 +-
 src/libs/utils/ssh/sshchannel_p.h             |   6 +-
 src/libs/utils/ssh/sshremoteprocess.cpp       | 103 ++++++++++--------
 src/libs/utils/ssh/sshremoteprocess.h         |  20 +++-
 src/libs/utils/ssh/sshremoteprocess_p.h       |   7 +-
 src/plugins/debugger/gdb/remotegdbprocess.cpp |   2 +-
 src/plugins/debugger/lldb/lldbenginehost.cpp  |   4 +-
 src/plugins/madde/maddedevicetester.cpp       |   2 +-
 .../madde/maemopublisherfremantlefree.cpp     |   6 +-
 src/plugins/madde/maemoremotemounter.cpp      |   4 +-
 src/plugins/remotelinux/linuxdevicetester.cpp |   2 +-
 .../remotelinuxcustomcommanddeployservice.cpp |   2 +-
 .../ssh/remoteprocess/remoteprocesstest.cpp   |  74 +++++++++++--
 .../ssh/remoteprocess/remoteprocesstest.h     |  15 ++-
 tests/manual/ssh/shell/shell.cpp              |   2 +-
 17 files changed, 171 insertions(+), 91 deletions(-)

diff --git a/src/libs/utils/ssh/sftpchannel.cpp b/src/libs/utils/ssh/sftpchannel.cpp
index c17ce21d193..79e1f3f2ef6 100644
--- a/src/libs/utils/ssh/sftpchannel.cpp
+++ b/src/libs/utils/ssh/sftpchannel.cpp
@@ -821,13 +821,13 @@ void SftpChannelPrivate::handleOpenSuccessInternal()
     m_sftpState = SubsystemRequested;
 }
 
-void SftpChannelPrivate::handleOpenFailureInternal()
+void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
 {
     if (channelState() != SessionRequested) {
         throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
             "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
     }
-    emit initializationFailed(tr("Server could not start session."));
+    emit initializationFailed(tr("Server could not start session: %1").arg(reason));
 }
 
 void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
diff --git a/src/libs/utils/ssh/sftpchannel_p.h b/src/libs/utils/ssh/sftpchannel_p.h
index 241cbb77a7b..e2efd89afd7 100644
--- a/src/libs/utils/ssh/sftpchannel_p.h
+++ b/src/libs/utils/ssh/sftpchannel_p.h
@@ -74,7 +74,7 @@ private:
     SftpJobId createJob(const AbstractSftpOperation::Ptr &job);
 
     virtual void handleOpenSuccessInternal();
-    virtual void handleOpenFailureInternal();
+    virtual void handleOpenFailureInternal(const QString &reason);
     virtual void handleChannelDataInternal(const QByteArray &data);
     virtual void handleChannelExtendedDataInternal(quint32 type,
         const QByteArray &data);
diff --git a/src/libs/utils/ssh/sshchannel.cpp b/src/libs/utils/ssh/sshchannel.cpp
index b6965da5b1a..4fa68dc89e3 100644
--- a/src/libs/utils/ssh/sshchannel.cpp
+++ b/src/libs/utils/ssh/sshchannel.cpp
@@ -84,7 +84,7 @@ void AbstractSshChannel::requestSessionStart()
         setChannelState(SessionRequested);
         m_timeoutTimer->start(ReplyTimeout);
     }  catch (Botan::Exception &e) {
-        m_errorString = QString::fromAscii(e.what());
+        qDebug("Botan error: %s", e.what());
         closeChannel();
     }
 }
@@ -95,7 +95,7 @@ void AbstractSshChannel::sendData(const QByteArray &data)
         m_sendBuffer += data;
         flushSendBuffer();
     }  catch (Botan::Exception &e) {
-        m_errorString = QString::fromAscii(e.what());
+        qDebug("Botan error: %s", e.what());
         closeChannel();
     }
 }
@@ -163,8 +163,7 @@ void AbstractSshChannel::handleOpenFailure(const QString &reason)
 #ifdef CREATOR_SSH_DEBUG
    qDebug("Channel open request failed for channel %u", m_localChannel);
 #endif
-   m_errorString = reason;
-   handleOpenFailureInternal();
+   handleOpenFailureInternal(reason);
 }
 
 void AbstractSshChannel::handleChannelEof()
diff --git a/src/libs/utils/ssh/sshchannel_p.h b/src/libs/utils/ssh/sshchannel_p.h
index 201b77b9703..c6c81f5abf3 100644
--- a/src/libs/utils/ssh/sshchannel_p.h
+++ b/src/libs/utils/ssh/sshchannel_p.h
@@ -58,9 +58,6 @@ public:
     ChannelState channelState() const { return m_state; }
     void setChannelState(ChannelState state);
 
-    void setError(const QString &error) { m_errorString = error; }
-    QString errorString() const { return m_errorString; }
-
     quint32 localChannelId() const { return m_localChannel; }
     quint32 remoteChannel() const { return m_remoteChannel; }
 
@@ -101,7 +98,7 @@ protected:
 
 private:
     virtual void handleOpenSuccessInternal() = 0;
-    virtual void handleOpenFailureInternal() = 0;
+    virtual void handleOpenFailureInternal(const QString &reason) = 0;
     virtual void handleChannelDataInternal(const QByteArray &data) = 0;
     virtual void handleChannelExtendedDataInternal(quint32 type,
         const QByteArray &data) = 0;
@@ -119,7 +116,6 @@ private:
     quint32 m_remoteMaxPacketSize;
     ChannelState m_state;
     QByteArray m_sendBuffer;
-    QString m_errorString;
 };
 
 } // namespace Internal
diff --git a/src/libs/utils/ssh/sshremoteprocess.cpp b/src/libs/utils/ssh/sshremoteprocess.cpp
index 90ab74735ff..52068ba8694 100644
--- a/src/libs/utils/ssh/sshremoteprocess.cpp
+++ b/src/libs/utils/ssh/sshremoteprocess.cpp
@@ -42,6 +42,8 @@
 
 #include <QtCore/QTimer>
 
+#include <cstring>
+
 /*!
     \class Utils::SshRemoteProcess
 
@@ -49,17 +51,10 @@
 
     Objects are created via SshConnection::createRemoteProcess.
     The process is started via the start() member function.
-    A closeChannel() function is provided, but rarely useful, because
-
-    \list
-    \i  a) when the process ends, the channel is closed automatically, and
-    \i  b) closing a channel will not necessarily kill the remote process.
-    \endlist
-
-    Therefore, the only sensible use case for calling closeChannel() is to
-    get rid of an SshRemoteProces object before the process is actually started.
     If the process needs a pseudo terminal, you can request one
     via requestTerminal() before calling start().
+    Note that this class does not support QIODevice's waitFor*() functions, i.e. it has
+    no synchronous mode.
  */
 
 namespace Utils {
@@ -99,12 +94,63 @@ SshRemoteProcess::~SshRemoteProcess()
     delete d;
 }
 
+bool SshRemoteProcess::atEnd() const
+{
+    return QIODevice::atEnd() && d->m_stdout.isEmpty();
+}
+
+qint64 SshRemoteProcess::bytesAvailable() const
+{
+    return QIODevice::bytesAvailable() + d->m_stdout.count();
+}
+
+bool SshRemoteProcess::canReadLine() const
+{
+    return QIODevice::canReadLine() || d->m_stdout.contains('\n'); // TODO: Not cross-platform?
+}
+
+QByteArray SshRemoteProcess::readAllStandardOutput()
+{
+    return readAll();
+}
+
+QByteArray SshRemoteProcess::readAllStandardError()
+{
+    const QByteArray data = d->m_stderr;
+    d->m_stderr.clear();
+    return data;
+}
+
+void SshRemoteProcess::close()
+{
+    d->closeChannel();
+    QIODevice::close();
+}
+
+qint64 SshRemoteProcess::readData(char *data, qint64 maxlen)
+{
+    const qint64 bytesRead = qMin(qint64(d->m_stdout.count()), maxlen);
+    memcpy(data, d->m_stdout.constData(), bytesRead);
+    d->m_stdout.remove(0, bytesRead);
+    return bytesRead;
+}
+
+qint64 SshRemoteProcess::writeData(const char *data, qint64 len)
+{
+    if (isRunning()) {
+        d->sendData(QByteArray(data, len));
+        return len;
+    }
+    return 0;
+}
+
 void SshRemoteProcess::init()
 {
     connect(d, SIGNAL(started()), this, SIGNAL(started()),
         Qt::QueuedConnection);
     connect(d, SIGNAL(readyReadStandardOutput()), this, SIGNAL(readyReadStandardOutput()),
         Qt::QueuedConnection);
+    connect(d, SIGNAL(readyReadStandardOutput()), this, SIGNAL(readyRead()), Qt::QueuedConnection);
     connect(d, SIGNAL(readyReadStandardError()), this,
         SIGNAL(readyReadStandardError()), Qt::QueuedConnection);
     connect(d, SIGNAL(closed(int)), this, SIGNAL(closed(int)), Qt::QueuedConnection);
@@ -129,6 +175,7 @@ void SshRemoteProcess::start()
 #ifdef CREATOR_SSH_DEBUG
         qDebug("process start requested, channel id = %u", d->localChannelId());
 #endif
+        QIODevice::open(QIODevice::ReadWrite);
         d->requestSessionStart();
     }
 }
@@ -140,36 +187,19 @@ void SshRemoteProcess::sendSignal(const QByteArray &signal)
             d->m_sendFacility.sendChannelSignalPacket(d->remoteChannel(),
                 signal);
     }  catch (Botan::Exception &e) {
-        d->setError(QString::fromAscii(e.what()));
+        setErrorString(QString::fromAscii(e.what()));
         d->closeChannel();
     }
 }
 
-void SshRemoteProcess::closeChannel()
-{
-    d->closeChannel();
-}
-
-void SshRemoteProcess::sendInput(const QByteArray &data)
-{
-    if (isRunning())
-        d->sendData(data);
-}
-
 bool SshRemoteProcess::isRunning() const
 {
     return d->m_procState == Internal::SshRemoteProcessPrivate::Running;
 }
 
-QString SshRemoteProcess::errorString() const { return d->errorString(); }
-
 int SshRemoteProcess::exitCode() const { return d->m_exitCode; }
-
 QByteArray SshRemoteProcess::exitSignal() const { return d->m_signal; }
 
-QByteArray SshRemoteProcess::readAllStandardOutput() { return d->readAllStandardOutput(); }
-QByteArray SshRemoteProcess::readAllStandardError() { return d->readAllStandardError(); }
-
 namespace Internal {
 
 SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
@@ -214,20 +244,6 @@ void SshRemoteProcessPrivate::setProcState(ProcessState newState)
     }
 }
 
-QByteArray SshRemoteProcessPrivate::readAllStandardOutput()
-{
-    const QByteArray data = m_stdout;
-    m_stdout.clear();
-    return data;
-}
-
-QByteArray SshRemoteProcessPrivate::readAllStandardError()
-{
-    const QByteArray data = m_stderr;
-    m_stderr.clear();
-    return data;
-}
-
 void SshRemoteProcessPrivate::closeHook()
 {
     if (m_wasRunning) {
@@ -256,9 +272,10 @@ void SshRemoteProcessPrivate::handleOpenSuccessInternal()
    m_timeoutTimer->start(ReplyTimeout);
 }
 
-void SshRemoteProcessPrivate::handleOpenFailureInternal()
+void SshRemoteProcessPrivate::handleOpenFailureInternal(const QString &reason)
 {
    setProcState(StartFailed);
+   m_proc->setErrorString(reason);
 }
 
 void SshRemoteProcessPrivate::handleChannelSuccess()
@@ -313,9 +330,9 @@ void SshRemoteProcessPrivate::handleExitSignal(const SshChannelExitSignal &signa
 #ifdef CREATOR_SSH_DEBUG
     qDebug("Exit due to signal %s", signal.signal.data());
 #endif
-    setError(signal.error);
     m_signal = signal.signal;
     m_procState = Exited;
+    m_proc->setErrorString(tr("Process killed by signal"));
 }
 
 } // namespace Internal
diff --git a/src/libs/utils/ssh/sshremoteprocess.h b/src/libs/utils/ssh/sshremoteprocess.h
index 59f61385c20..fcd3f0edd54 100644
--- a/src/libs/utils/ssh/sshremoteprocess.h
+++ b/src/libs/utils/ssh/sshremoteprocess.h
@@ -35,7 +35,7 @@
 
 #include <utils/utils_global.h>
 
-#include <QtCore/QObject>
+#include <QtCore/QProcess>
 #include <QtCore/QSharedPointer>
 
 QT_BEGIN_NAMESPACE
@@ -50,7 +50,8 @@ class SshRemoteProcessPrivate;
 class SshSendFacility;
 } // namespace Internal
 
-class QTCREATOR_UTILS_EXPORT SshRemoteProcess : public QObject
+// TODO: ProcessChannel
+class QTCREATOR_UTILS_EXPORT SshRemoteProcess : public QIODevice
 {
     Q_OBJECT
 
@@ -77,6 +78,13 @@ public:
 
     ~SshRemoteProcess();
 
+    // QIODevice stuff
+    bool atEnd() const;
+    qint64 bytesAvailable() const;
+    bool canReadLine() const;
+    void close();
+    bool isSequential() const { return true; }
+
     /*
      * Note that this is of limited value in practice, because servers are
      * usually configured to ignore such requests for security reasons.
@@ -85,10 +93,8 @@ public:
 
     void requestTerminal(const SshPseudoTerminal &terminal);
     void start();
-    void closeChannel();
 
     bool isRunning() const;
-    QString errorString() const;
     int exitCode() const;
     QByteArray exitSignal() const;
 
@@ -99,8 +105,6 @@ public:
     void sendSignal(const QByteArray &signal);
     void kill() { sendSignal(KillSignal); }
 
-    void sendInput(const QByteArray &data); // Should usually have a trailing newline.
-
 signals:
     void started();
 
@@ -118,6 +122,10 @@ private:
         Internal::SshSendFacility &sendFacility);
     SshRemoteProcess(quint32 channelId, Internal::SshSendFacility &sendFacility);
 
+    // QIODevice stuff
+    qint64 readData(char *data, qint64 maxlen);
+    qint64 writeData(const char *data, qint64 len);
+
     void init();
 
     Internal::SshRemoteProcessPrivate *d;
diff --git a/src/libs/utils/ssh/sshremoteprocess_p.h b/src/libs/utils/ssh/sshremoteprocess_p.h
index da47927f3a0..32564ef9cbf 100644
--- a/src/libs/utils/ssh/sshremoteprocess_p.h
+++ b/src/libs/utils/ssh/sshremoteprocess_p.h
@@ -52,7 +52,7 @@ class SshRemoteProcessPrivate : public AbstractSshChannel
     friend class Utils::SshRemoteProcess;
 public:
     enum ProcessState {
-        NotYetStarted, ExecRequested, StartFailed,Running, Exited
+        NotYetStarted, ExecRequested, StartFailed, Running, Exited
     };
 
     virtual void handleChannelSuccess();
@@ -60,9 +60,6 @@ public:
 
     virtual void closeHook();
 
-    QByteArray readAllStandardOutput();
-    QByteArray readAllStandardError();
-
 signals:
     void started();
     void readyReadStandardOutput();
@@ -76,7 +73,7 @@ private:
         SshRemoteProcess *proc);
 
     virtual void handleOpenSuccessInternal();
-    virtual void handleOpenFailureInternal();
+    virtual void handleOpenFailureInternal(const QString &reason);
     virtual void handleChannelDataInternal(const QByteArray &data);
     virtual void handleChannelExtendedDataInternal(quint32 type,
         const QByteArray &data);
diff --git a/src/plugins/debugger/gdb/remotegdbprocess.cpp b/src/plugins/debugger/gdb/remotegdbprocess.cpp
index 80a1dd58373..e9bb37aefe0 100644
--- a/src/plugins/debugger/gdb/remotegdbprocess.cpp
+++ b/src/plugins/debugger/gdb/remotegdbprocess.cpp
@@ -341,7 +341,7 @@ void RemoteGdbProcess::sendInput(const QByteArray &data)
         if (!isdigit(data.at(pos)))
             break;
     m_lastSeqNr = data.left(pos);
-    m_gdbProc->sendInput(data);
+    m_gdbProc->write(data);
 }
 
 void RemoteGdbProcess::handleAppOutput()
diff --git a/src/plugins/debugger/lldb/lldbenginehost.cpp b/src/plugins/debugger/lldb/lldbenginehost.cpp
index 70189a51393..d5115bbd6ad 100644
--- a/src/plugins/debugger/lldb/lldbenginehost.cpp
+++ b/src/plugins/debugger/lldb/lldbenginehost.cpp
@@ -95,7 +95,7 @@ qint64 SshIODevice::writeData (const char * data, qint64 maxSize)
         startupbuffer += QByteArray::fromRawData(data, maxSize);
         return maxSize;
     }
-    proc->sendInput(QByteArray::fromRawData(data, maxSize));
+    proc->write(data, maxSize);
     return maxSize;
 }
 qint64 SshIODevice::readData (char * data, qint64 maxSize)
@@ -128,7 +128,7 @@ qint64 SshIODevice::readData (char * data, qint64 maxSize)
 void SshIODevice::processStarted()
 {
     proc = runner->process();
-    proc->sendInput(startupbuffer);
+    proc->write(startupbuffer);
 }
 
 void SshIODevice::outputAvailable(const QByteArray &output)
diff --git a/src/plugins/madde/maddedevicetester.cpp b/src/plugins/madde/maddedevicetester.cpp
index a275971f4e7..80090f07d69 100644
--- a/src/plugins/madde/maddedevicetester.cpp
+++ b/src/plugins/madde/maddedevicetester.cpp
@@ -89,7 +89,7 @@ void MaddeDeviceTester::stopTest()
     case QtTest:
     case MadDeveloperTest:
     case QmlToolingTest:
-        m_processRunner->process()->closeChannel();
+        m_processRunner->process()->close();
         break;
     }
 
diff --git a/src/plugins/madde/maemopublisherfremantlefree.cpp b/src/plugins/madde/maemopublisherfremantlefree.cpp
index aa85f8138bf..e0cf2f70432 100644
--- a/src/plugins/madde/maemopublisherfremantlefree.cpp
+++ b/src/plugins/madde/maemopublisherfremantlefree.cpp
@@ -447,7 +447,7 @@ void MaemoPublisherFremantleFree::prepareToSendFile()
     emit progressReport(tr("Uploading file %1 ...")
         .arg(QDir::toNativeSeparators(nextFilePath)));
     QFileInfo info(nextFilePath);
-    m_uploader->process()->sendInput("C0644 " + QByteArray::number(info.size())
+    m_uploader->process()->write("C0644 " + QByteArray::number(info.size())
         + ' ' + info.fileName().toUtf8() + '\n');
 }
 
@@ -473,13 +473,13 @@ void MaemoPublisherFremantleFree::sendFile()
                 tr("Upload failed."));
             return;
         }
-        m_uploader->process()->sendInput(data);
+        m_uploader->process()->write(data);
         bytesToSend -= data.size();
         QCoreApplication::processEvents();
         if (m_state == Inactive)
             return;
     }
-    m_uploader->process()->sendInput(QByteArray(1, '\0'));
+    m_uploader->process()->write(QByteArray(1, '\0'));
 }
 
 void MaemoPublisherFremantleFree::handleScpStdOut(const QByteArray &output)
diff --git a/src/plugins/madde/maemoremotemounter.cpp b/src/plugins/madde/maemoremotemounter.cpp
index a151feaf3d4..6144036243c 100644
--- a/src/plugins/madde/maemoremotemounter.cpp
+++ b/src/plugins/madde/maemoremotemounter.cpp
@@ -380,11 +380,11 @@ void MaemoRemoteMounter::setState(State newState)
         m_utfsServerTimer->stop();
         if (m_mountProcess) {
             disconnect(m_mountProcess.data(), 0, this, 0);
-            m_mountProcess->closeChannel();
+            m_mountProcess->close();
         }
         if (m_unmountProcess) {
             disconnect(m_unmountProcess.data(), 0, this, 0);
-            m_unmountProcess->closeChannel();
+            m_unmountProcess->close();
         }
     }
     m_state = newState;
diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp
index 8c9e1cd2b9f..cd9c4651748 100644
--- a/src/plugins/remotelinux/linuxdevicetester.cpp
+++ b/src/plugins/remotelinux/linuxdevicetester.cpp
@@ -106,7 +106,7 @@ void GenericLinuxDeviceTester::stopTest()
         d->portsGatherer.stop();
         break;
     case RunningUname:
-        d->process->closeChannel();
+        d->process->close();
         break;
     case Inactive:
         break;
diff --git a/src/plugins/remotelinux/remotelinuxcustomcommanddeployservice.cpp b/src/plugins/remotelinux/remotelinuxcustomcommanddeployservice.cpp
index 9f393b89247..d489d550300 100644
--- a/src/plugins/remotelinux/remotelinuxcustomcommanddeployservice.cpp
+++ b/src/plugins/remotelinux/remotelinuxcustomcommanddeployservice.cpp
@@ -115,7 +115,7 @@ void RemoteLinuxCustomCommandDeployService::stopDeployment()
     QTC_ASSERT(d->state == Running, return);
 
     disconnect(d->runner, 0, this, 0);
-    d->runner->process()->closeChannel();
+    d->runner->process()->close();
     d->state = Inactive;
     handleDeploymentDone();
 }
diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp b/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp
index d4a52340c75..dd8cfafdef6 100644
--- a/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp
+++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp
@@ -35,6 +35,7 @@
 #include <utils/ssh/sshpseudoterminal.h>
 
 #include <QtCore/QCoreApplication>
+#include <QtCore/QTextStream>
 #include <QtCore/QTimer>
 
 #include <iostream>
@@ -75,9 +76,10 @@ void RemoteProcessTest::run()
 
 void RemoteProcessTest::handleConnectionError()
 {
-    std::cerr << "Error: Connection failure ("
-        << qPrintable(m_remoteRunner->lastConnectionErrorString()) << ")."
-        << std::endl;
+    const QString error = m_state == TestingIoDevice
+        ? m_sshConnection->errorString() : m_remoteRunner->lastConnectionErrorString();
+
+    std::cerr << "Error: Connection failure (" << qPrintable(error) << ")." << std::endl;
     qApp->quit();
 }
 
@@ -92,6 +94,11 @@ void RemoteProcessTest::handleProcessStarted()
             Utils::SshRemoteProcessRunner * const killer
                 = new Utils::SshRemoteProcessRunner(this);
             killer->run("pkill -9 sleep", m_sshParams);
+        } else if (m_state == TestingIoDevice) {
+            connect(m_catProcess.data(), SIGNAL(readyRead()), SLOT(handleReadyRead()));
+            m_textStream = new QTextStream(m_catProcess.data());
+            *m_textStream << testString();
+            m_textStream->flush();
         }
     }
 }
@@ -198,10 +205,16 @@ void RemoteProcessTest::handleProcessClosed(int exitStatus)
                 qApp->quit();
                 return;
             }
-            std::cout << "Ok.\nAll tests succeeded." << std::endl;
-            qApp->quit();
+            std::cout << "Ok.\nTesting I/O device functionality... " << std::flush;
+            m_state = TestingIoDevice;
+            m_sshConnection = Utils::SshConnection::create(m_sshParams);
+            connect(m_sshConnection.data(), SIGNAL(connected()), SLOT(handleConnected()));
+            connect(m_sshConnection.data(), SIGNAL(error(Utils::SshError)),
+                SLOT(handleConnectionError()));
+            m_sshConnection->connectToHost();
             break;
         }
+        case TestingIoDevice:
         case Inactive:
             Q_ASSERT(false);
         }
@@ -216,16 +229,23 @@ void RemoteProcessTest::handleProcessClosed(int exitStatus)
         qApp->quit();
         break;
     case SshRemoteProcess::KilledBySignal:
-        if (m_state != TestingCrash) {
+        switch (m_state) {
+        case TestingCrash:
+            std::cout << "Ok.\nTesting remote process with terminal... " << std::flush;
+            m_state = TestingTerminal;
+            m_started = false;
+            m_timeoutTimer->start();
+            m_remoteRunner->runInTerminal("top -n 1", SshPseudoTerminal(), m_sshParams);
+            break;
+        case TestingIoDevice:
+            std::cout << "Ok.\nAll tests succeeded." << std::endl;
+            qApp->quit();
+            break;
+        default:
             std::cerr << "Error: Unexpected crash." << std::endl;
             qApp->quit();
             return;
         }
-        std::cout << "Ok.\nTesting remote process with terminal... " << std::flush;
-        m_state = TestingTerminal;
-        m_started = false;
-        m_timeoutTimer->start();
-        m_remoteRunner->runInTerminal("top -n 1", SshPseudoTerminal(), m_sshParams);
     }
 }
 
@@ -234,3 +254,35 @@ void RemoteProcessTest::handleTimeout()
     std::cerr << "Error: Timeout waiting for progress." << std::endl;
     qApp->quit();
 }
+
+void RemoteProcessTest::handleConnected()
+{
+    Q_ASSERT(m_state == TestingIoDevice);
+
+    m_catProcess = m_sshConnection->createRemoteProcess(QString::fromLocal8Bit("cat").toUtf8());
+    connect(m_catProcess.data(), SIGNAL(started()), SLOT(handleProcessStarted()));
+    connect(m_catProcess.data(), SIGNAL(closed(int)), SLOT(handleProcessClosed(int)));
+    m_started = false;
+    m_timeoutTimer->start();
+    m_catProcess->start();
+}
+
+QString RemoteProcessTest::testString() const
+{
+    return QLatin1String("x");
+}
+
+void RemoteProcessTest::handleReadyRead()
+{
+    Q_ASSERT(m_state == TestingIoDevice);
+
+    const QString &data = QString::fromUtf8(m_catProcess->readAll());
+    if (data != testString()) {
+        std::cerr << "Testing of QIODevice functionality failed: Expected '"
+            << qPrintable(testString()) << "', got '" << qPrintable(data) << "'." << std::endl;
+        qApp->exit(1);
+    }
+
+    Utils::SshRemoteProcessRunner * const killer = new Utils::SshRemoteProcessRunner(this);
+    killer->run("pkill -9 cat", m_sshParams);
+}
diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.h b/tests/manual/ssh/remoteprocess/remoteprocesstest.h
index 46a20dd5b67..bc1a3819990 100644
--- a/tests/manual/ssh/remoteprocess/remoteprocesstest.h
+++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.h
@@ -35,9 +35,11 @@
 
 #include <utils/ssh/sshremoteprocessrunner.h>
 
-QT_FORWARD_DECLARE_CLASS(QTimer);
 #include <QtCore/QObject>
 
+QT_FORWARD_DECLARE_CLASS(QTextStream)
+QT_FORWARD_DECLARE_CLASS(QTimer)
+
 class RemoteProcessTest : public QObject
 {
     Q_OBJECT
@@ -53,13 +55,22 @@ private slots:
     void handleProcessStderr(const QByteArray &output);
     void handleProcessClosed(int exitStatus);
     void handleTimeout();
+    void handleReadyRead();
+    void handleConnected();
 
 private:
-    enum State { Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal };
+    enum State {
+        Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal, TestingIoDevice
+    };
+
+    QString testString() const;
 
     const Utils::SshConnectionParameters m_sshParams;
     QTimer * const m_timeoutTimer;
+    QTextStream *m_textStream;
     Utils::SshRemoteProcessRunner * const m_remoteRunner;
+    Utils::SshRemoteProcess::Ptr m_catProcess;
+    Utils::SshConnection::Ptr m_sshConnection;
     QByteArray m_remoteStdout;
     QByteArray m_remoteStderr;
     State m_state;
diff --git a/tests/manual/ssh/shell/shell.cpp b/tests/manual/ssh/shell/shell.cpp
index 96b59c96208..e363f9c52a3 100644
--- a/tests/manual/ssh/shell/shell.cpp
+++ b/tests/manual/ssh/shell/shell.cpp
@@ -115,5 +115,5 @@ void Shell::handleChannelClosed(int exitStatus)
 
 void Shell::handleStdin()
 {
-    m_shell->sendInput(m_stdin->readLine());
+    m_shell->write(m_stdin->readLine());
 }
-- 
GitLab