From 57c0979012c43f329e61547c638d7935a1444439 Mon Sep 17 00:00:00 2001
From: Christian Kandeler <christian.kandeler@nokia.com>
Date: Tue, 29 Nov 2011 10:18:10 +0100
Subject: [PATCH] SSH: Support different read channels in SshRemoteProcess.

This is part of the effort to support more QProcess concepts.

Change-Id: Idb888e733570a58d3810f371409b657b30bbd929
Reviewed-by: hjk <qthjk@ovi.com>
---
 src/libs/utils/ssh/sshremoteprocess.cpp       | 47 ++++++++---
 src/libs/utils/ssh/sshremoteprocess.h         |  4 +
 src/libs/utils/ssh/sshremoteprocess_p.h       |  6 ++
 .../ssh/remoteprocess/remoteprocesstest.cpp   | 80 ++++++++++++++++---
 .../ssh/remoteprocess/remoteprocesstest.h     |  7 +-
 5 files changed, 121 insertions(+), 23 deletions(-)

diff --git a/src/libs/utils/ssh/sshremoteprocess.cpp b/src/libs/utils/ssh/sshremoteprocess.cpp
index 52068ba8694..875325a1587 100644
--- a/src/libs/utils/ssh/sshremoteprocess.cpp
+++ b/src/libs/utils/ssh/sshremoteprocess.cpp
@@ -96,28 +96,35 @@ SshRemoteProcess::~SshRemoteProcess()
 
 bool SshRemoteProcess::atEnd() const
 {
-    return QIODevice::atEnd() && d->m_stdout.isEmpty();
+    return QIODevice::atEnd() && d->data().isEmpty();
 }
 
 qint64 SshRemoteProcess::bytesAvailable() const
 {
-    return QIODevice::bytesAvailable() + d->m_stdout.count();
+    return QIODevice::bytesAvailable() + d->data().count();
 }
 
 bool SshRemoteProcess::canReadLine() const
 {
-    return QIODevice::canReadLine() || d->m_stdout.contains('\n'); // TODO: Not cross-platform?
+    return QIODevice::canReadLine() || d->data().contains('\n');
 }
 
 QByteArray SshRemoteProcess::readAllStandardOutput()
 {
-    return readAll();
+    return readAllFromChannel(QProcess::StandardOutput);
 }
 
 QByteArray SshRemoteProcess::readAllStandardError()
 {
-    const QByteArray data = d->m_stderr;
-    d->m_stderr.clear();
+    return readAllFromChannel(QProcess::StandardError);
+}
+
+QByteArray SshRemoteProcess::readAllFromChannel(QProcess::ProcessChannel channel)
+{
+    const QProcess::ProcessChannel currentReadChannel = readChannel();
+    setReadChannel(channel);
+    const QByteArray &data = readAll();
+    setReadChannel(currentReadChannel);
     return data;
 }
 
@@ -129,9 +136,9 @@ void SshRemoteProcess::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);
+    const qint64 bytesRead = qMin(qint64(d->data().count()), maxlen);
+    memcpy(data, d->data().constData(), bytesRead);
+    d->data().remove(0, bytesRead);
     return bytesRead;
 }
 
@@ -144,13 +151,23 @@ qint64 SshRemoteProcess::writeData(const char *data, qint64 len)
     return 0;
 }
 
+QProcess::ProcessChannel SshRemoteProcess::readChannel() const
+{
+    return d->m_readChannel;
+}
+
+void SshRemoteProcess::setReadChannel(QProcess::ProcessChannel channel)
+{
+    d->m_readChannel = channel;
+}
+
 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(readyRead()), 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);
@@ -228,6 +245,7 @@ void SshRemoteProcessPrivate::init()
     m_procState = NotYetStarted;
     m_wasRunning = false;
     m_exitCode = 0;
+    m_readChannel = QProcess::StandardOutput;
 }
 
 void SshRemoteProcessPrivate::setProcState(ProcessState newState)
@@ -244,6 +262,11 @@ void SshRemoteProcessPrivate::setProcState(ProcessState newState)
     }
 }
 
+QByteArray &SshRemoteProcessPrivate::data()
+{
+    return m_readChannel == QProcess::StandardOutput ? m_stdout : m_stderr;
+}
+
 void SshRemoteProcessPrivate::closeHook()
 {
     if (m_wasRunning) {
@@ -303,6 +326,8 @@ void SshRemoteProcessPrivate::handleChannelDataInternal(const QByteArray &data)
 {
     m_stdout += data;
     emit readyReadStandardOutput();
+    if (m_readChannel == QProcess::StandardOutput)
+        emit readyRead();
 }
 
 void SshRemoteProcessPrivate::handleChannelExtendedDataInternal(quint32 type,
@@ -313,6 +338,8 @@ void SshRemoteProcessPrivate::handleChannelExtendedDataInternal(quint32 type,
     } else {
         m_stderr += data;
         emit readyReadStandardError();
+        if (m_readChannel == QProcess::StandardError)
+            emit readyRead();
     }
 }
 
diff --git a/src/libs/utils/ssh/sshremoteprocess.h b/src/libs/utils/ssh/sshremoteprocess.h
index fcd3f0edd54..d696228b01d 100644
--- a/src/libs/utils/ssh/sshremoteprocess.h
+++ b/src/libs/utils/ssh/sshremoteprocess.h
@@ -85,6 +85,9 @@ public:
     void close();
     bool isSequential() const { return true; }
 
+    QProcess::ProcessChannel readChannel() const;
+    void setReadChannel(QProcess::ProcessChannel channel);
+
     /*
      * Note that this is of limited value in practice, because servers are
      * usually configured to ignore such requests for security reasons.
@@ -127,6 +130,7 @@ private:
     qint64 writeData(const char *data, qint64 len);
 
     void init();
+    QByteArray readAllFromChannel(QProcess::ProcessChannel channel);
 
     Internal::SshRemoteProcessPrivate *d;
 };
diff --git a/src/libs/utils/ssh/sshremoteprocess_p.h b/src/libs/utils/ssh/sshremoteprocess_p.h
index 32564ef9cbf..300050d5ebd 100644
--- a/src/libs/utils/ssh/sshremoteprocess_p.h
+++ b/src/libs/utils/ssh/sshremoteprocess_p.h
@@ -39,6 +39,7 @@
 
 #include <QtCore/QList>
 #include <QtCore/QPair>
+#include <QtCore/QProcess>
 
 namespace Utils {
 class SshRemoteProcess;
@@ -60,8 +61,11 @@ public:
 
     virtual void closeHook();
 
+    QByteArray &data();
+
 signals:
     void started();
+    void readyRead();
     void readyReadStandardOutput();
     void readyReadStandardError();
     void closed(int exitStatus);
@@ -83,6 +87,8 @@ private:
     void init();
     void setProcState(ProcessState newState);
 
+    QProcess::ProcessChannel m_readChannel;
+
     ProcessState m_procState;
     bool m_wasRunning;
     QByteArray m_signal;
diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp b/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp
index dd8cfafdef6..7f5fcd63cda 100644
--- a/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp
+++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp
@@ -42,6 +42,8 @@
 
 using namespace Utils;
 
+const QByteArray StderrOutput("ChannelTest");
+
 RemoteProcessTest::RemoteProcessTest(const SshConnectionParameters &params)
     : m_sshParams(params),
       m_timeoutTimer(new QTimer(this)),
@@ -76,7 +78,7 @@ void RemoteProcessTest::run()
 
 void RemoteProcessTest::handleConnectionError()
 {
-    const QString error = m_state == TestingIoDevice
+    const QString error = m_state == TestingIoDevice || m_state == TestingProcessChannels
         ? m_sshConnection->errorString() : m_remoteRunner->lastConnectionErrorString();
 
     std::cerr << "Error: Connection failure (" << qPrintable(error) << ")." << std::endl;
@@ -212,9 +214,29 @@ void RemoteProcessTest::handleProcessClosed(int exitStatus)
             connect(m_sshConnection.data(), SIGNAL(error(Utils::SshError)),
                 SLOT(handleConnectionError()));
             m_sshConnection->connectToHost();
+            m_timeoutTimer->start();
             break;
         }
         case TestingIoDevice:
+            std::cerr << "Error: Successful exit from process that was supposed to crash."
+                << std::endl;
+            qApp->exit(EXIT_FAILURE);
+            break;
+        case TestingProcessChannels:
+            if (m_remoteStderr.isEmpty()) {
+                std::cerr << "Error: Did not receive readyReadStderr()." << std::endl;
+                qApp->exit(EXIT_FAILURE);
+                return;
+            }
+            if (m_remoteData != StderrOutput) {
+                std::cerr << "Error: Expected output '" << StderrOutput.data() << "', received '"
+                    << m_remoteData.data() << "'." << std::endl;
+                qApp->exit(EXIT_FAILURE);
+                return;
+            }
+            std::cout << "Ok.\nAll tests succeeded." << std::endl;
+            qApp->quit();
+            break;
         case Inactive:
             Q_ASSERT(false);
         }
@@ -238,8 +260,19 @@ void RemoteProcessTest::handleProcessClosed(int exitStatus)
             m_remoteRunner->runInTerminal("top -n 1", SshPseudoTerminal(), m_sshParams);
             break;
         case TestingIoDevice:
-            std::cout << "Ok.\nAll tests succeeded." << std::endl;
-            qApp->quit();
+            std::cout << "Ok\nTesting process channels... " << std::flush;
+            m_state = TestingProcessChannels;
+            m_started = false;
+            m_remoteStderr.clear();
+            m_echoProcess = m_sshConnection->createRemoteProcess("printf " + StderrOutput + " >&2");
+            m_echoProcess->setReadChannel(QProcess::StandardError);
+            connect(m_echoProcess.data(), SIGNAL(started()), SLOT(handleProcessStarted()));
+            connect(m_echoProcess.data(), SIGNAL(closed(int)), SLOT(handleProcessClosed(int)));
+            connect(m_echoProcess.data(), SIGNAL(readyRead()), SLOT(handleReadyRead()));
+            connect(m_echoProcess.data(), SIGNAL(readyReadStandardError()),
+                SLOT(handleReadyReadStderr()));
+            m_echoProcess->start();
+            m_timeoutTimer->start();
             break;
         default:
             std::cerr << "Error: Unexpected crash." << std::endl;
@@ -274,15 +307,38 @@ QString RemoteProcessTest::testString() const
 
 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);
+    switch (m_state) {
+    case 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);
+        break;
     }
+    case TestingProcessChannels:
+        m_remoteData += m_echoProcess->readAll();
+        break;
+    default:
+        qFatal("%s: Unexpected state %d.", Q_FUNC_INFO, m_state);
+    }
+
+}
+
+void RemoteProcessTest::handleReadyReadStdout()
+{
+    Q_ASSERT(m_state == TestingProcessChannels);
+
+    std::cerr << "Error: Received unexpected stdout data." << std::endl;
+    qApp->exit(EXIT_FAILURE);
+}
+
+void RemoteProcessTest::handleReadyReadStderr()
+{
+    Q_ASSERT(m_state == TestingProcessChannels);
 
-    Utils::SshRemoteProcessRunner * const killer = new Utils::SshRemoteProcessRunner(this);
-    killer->run("pkill -9 cat", m_sshParams);
+    m_remoteStderr = "dummy";
 }
diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.h b/tests/manual/ssh/remoteprocess/remoteprocesstest.h
index bc1a3819990..9a90f6a4b9a 100644
--- a/tests/manual/ssh/remoteprocess/remoteprocesstest.h
+++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.h
@@ -56,11 +56,14 @@ private slots:
     void handleProcessClosed(int exitStatus);
     void handleTimeout();
     void handleReadyRead();
+    void handleReadyReadStdout();
+    void handleReadyReadStderr();
     void handleConnected();
 
 private:
     enum State {
-        Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal, TestingIoDevice
+        Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal, TestingIoDevice,
+        TestingProcessChannels
     };
 
     QString testString() const;
@@ -70,9 +73,11 @@ private:
     QTextStream *m_textStream;
     Utils::SshRemoteProcessRunner * const m_remoteRunner;
     Utils::SshRemoteProcess::Ptr m_catProcess;
+    Utils::SshRemoteProcess::Ptr m_echoProcess;
     Utils::SshConnection::Ptr m_sshConnection;
     QByteArray m_remoteStdout;
     QByteArray m_remoteStderr;
+    QByteArray m_remoteData;
     State m_state;
     bool m_started;
 };
-- 
GitLab