From 4b4e591ff6722eb381bb7defdac7b4b4586b8e5a Mon Sep 17 00:00:00 2001 From: Christian Kandeler <christian.kandeler@nokia.com> Date: Thu, 28 Oct 2010 15:03:03 +0200 Subject: [PATCH] SSH: Add test for remote process execution. Task-number: QTCREATORBUG-2706 --- .../ssh/remoteprocess/argumentscollector.cpp | 129 ++++++++++++ .../ssh/remoteprocess/argumentscollector.h | 31 +++ tests/manual/ssh/remoteprocess/main.cpp | 26 +++ .../ssh/remoteprocess/remoteprocess.pro | 6 + .../ssh/remoteprocess/remoteprocesstest.cpp | 184 ++++++++++++++++++ .../ssh/remoteprocess/remoteprocesstest.h | 37 ++++ tests/manual/ssh/ssh.pro | 2 +- 7 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 tests/manual/ssh/remoteprocess/argumentscollector.cpp create mode 100644 tests/manual/ssh/remoteprocess/argumentscollector.h create mode 100644 tests/manual/ssh/remoteprocess/main.cpp create mode 100644 tests/manual/ssh/remoteprocess/remoteprocess.pro create mode 100644 tests/manual/ssh/remoteprocess/remoteprocesstest.cpp create mode 100644 tests/manual/ssh/remoteprocess/remoteprocesstest.h diff --git a/tests/manual/ssh/remoteprocess/argumentscollector.cpp b/tests/manual/ssh/remoteprocess/argumentscollector.cpp new file mode 100644 index 00000000000..87b765b4723 --- /dev/null +++ b/tests/manual/ssh/remoteprocess/argumentscollector.cpp @@ -0,0 +1,129 @@ +#include "argumentscollector.h" + +#include <iostream> + +using namespace std; +using namespace Core; + +ArgumentsCollector::ArgumentsCollector(const QStringList &args) + : m_arguments(args) +{ +} + +Core::SshConnectionParameters ArgumentsCollector::collect(bool &success) const +{ + SshConnectionParameters parameters(Core::SshConnectionParameters::NoProxy); + try { + bool authTypeGiven = false; + bool portGiven = false; + bool timeoutGiven = false; + bool proxySettingGiven = false; + int pos; + int port; + for (pos = 1; pos < m_arguments.count() - 1; ++pos) { + if (checkAndSetStringArg(pos, parameters.host, "-h") + || checkAndSetStringArg(pos, parameters.uname, "-u")) + continue; + if (checkAndSetIntArg(pos, port, portGiven, "-p") + || checkAndSetIntArg(pos, parameters.timeout, timeoutGiven, "-t")) + continue; + if (checkAndSetStringArg(pos, parameters.pwd, "-pwd")) { + if (!parameters.privateKeyFile.isEmpty()) + throw ArgumentErrorException(QLatin1String("-pwd and -k are mutually exclusive.")); + parameters.authType + = SshConnectionParameters::AuthByPwd; + authTypeGiven = true; + continue; + } + if (checkAndSetStringArg(pos, parameters.privateKeyFile, "-k")) { + if (!parameters.pwd.isEmpty()) + throw ArgumentErrorException(QLatin1String("-pwd and -k are mutually exclusive.")); + parameters.authType + = SshConnectionParameters::AuthByKey; + authTypeGiven = true; + continue; + } + if (!checkForNoProxy(pos, parameters.proxyType, proxySettingGiven)) + throw ArgumentErrorException(QLatin1String("unknown option ") + m_arguments.at(pos)); + } + + Q_ASSERT(pos <= m_arguments.count()); + if (pos == m_arguments.count() - 1) { + if (!checkForNoProxy(pos, parameters.proxyType, proxySettingGiven)) + throw ArgumentErrorException(QLatin1String("unknown option ") + m_arguments.at(pos)); + } + + if (!authTypeGiven) + throw ArgumentErrorException(QLatin1String("No authentication argument given.")); + if (parameters.host.isEmpty()) + throw ArgumentErrorException(QLatin1String("No host given.")); + if (parameters.uname.isEmpty()) + throw ArgumentErrorException(QLatin1String("No user name given.")); + + parameters.port = portGiven ? port : 22; + if (!timeoutGiven) + parameters.timeout = 30; + success = true; + } catch (ArgumentErrorException &ex) { + cerr << "Error: " << qPrintable(ex.error) << endl; + printUsage(); + success = false; + } + return parameters; +} + +void ArgumentsCollector::printUsage() const +{ + cerr << "Usage: " << qPrintable(m_arguments.first()) + << " -h <host> -u <user> " + << "-pwd <password> | -k <private key file> [ -p <port> ] " + << "[ -t <timeout> ] [ -no-proxy ]" << endl; +} + +bool ArgumentsCollector::checkAndSetStringArg(int &pos, QString &arg, const char *opt) const +{ + if (m_arguments.at(pos) == QLatin1String(opt)) { + if (!arg.isEmpty()) { + throw ArgumentErrorException(QLatin1String("option ") + opt + + QLatin1String(" was given twice.")); + } + arg = m_arguments.at(++pos); + if (arg.isEmpty() && QLatin1String(opt) != QLatin1String("-pwd")) + throw ArgumentErrorException(QLatin1String("empty argument not allowed here.")); + return true; + } + return false; +} + +bool ArgumentsCollector::checkAndSetIntArg(int &pos, int &val, + bool &alreadyGiven, const char *opt) const +{ + if (m_arguments.at(pos) == QLatin1String(opt)) { + if (alreadyGiven) { + throw ArgumentErrorException(QLatin1String("option ") + opt + + QLatin1String(" was given twice.")); + } + bool isNumber; + val = m_arguments.at(++pos).toInt(&isNumber); + if (!isNumber) { + throw ArgumentErrorException(QLatin1String("option ") + opt + + QLatin1String(" needs integer argument")); + } + alreadyGiven = true; + return true; + } + return false; +} + +bool ArgumentsCollector::checkForNoProxy(int &pos, + SshConnectionParameters::ProxyType &type, bool &alreadyGiven) const +{ + if (m_arguments.at(pos) == QLatin1String("-no-proxy")) { + if (alreadyGiven) + throw ArgumentErrorException(QLatin1String("proxy setting given twice.")); + type = SshConnectionParameters::NoProxy; + alreadyGiven = true; + return true; + } + return false; +} diff --git a/tests/manual/ssh/remoteprocess/argumentscollector.h b/tests/manual/ssh/remoteprocess/argumentscollector.h new file mode 100644 index 00000000000..f7ba749a4bb --- /dev/null +++ b/tests/manual/ssh/remoteprocess/argumentscollector.h @@ -0,0 +1,31 @@ +#ifndef ARGUMENTSCOLLECTOR_H +#define ARGUMENTSCOLLECTOR_H + +#include <coreplugin/ssh/sshconnection.h> + +#include <QtCore/QStringList> + +class ArgumentsCollector +{ +public: + ArgumentsCollector(const QStringList &args); + Core::SshConnectionParameters collect(bool &success) const; +private: + struct ArgumentErrorException + { + ArgumentErrorException(const QString &error) : error(error) {} + const QString error; + }; + + void printUsage() const; + bool checkAndSetStringArg(int &pos, QString &arg, const char *opt) const; + bool checkAndSetIntArg(int &pos, int &val, bool &alreadyGiven, + const char *opt) const; + bool checkForNoProxy(int &pos, + Core::SshConnectionParameters::ProxyType &type, + bool &alreadyGiven) const; + + const QStringList m_arguments; +}; + +#endif // ARGUMENTSCOLLECTOR_H diff --git a/tests/manual/ssh/remoteprocess/main.cpp b/tests/manual/ssh/remoteprocess/main.cpp new file mode 100644 index 00000000000..e2c4afcd2d9 --- /dev/null +++ b/tests/manual/ssh/remoteprocess/main.cpp @@ -0,0 +1,26 @@ +#include "argumentscollector.h" +#include "remoteprocesstest.h" + +#include <coreplugin/ssh/sshconnection.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QObject> +#include <QtCore/QStringList> + +#include <cstdlib> +#include <iostream> + +using namespace Core; + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + bool parseSuccess; + const Core::SshConnectionParameters ¶meters + = ArgumentsCollector(app.arguments()).collect(parseSuccess); + if (!parseSuccess) + return EXIT_FAILURE; + RemoteProcessTest remoteProcessTest(parameters); + remoteProcessTest.run(); + return app.exec(); +} diff --git a/tests/manual/ssh/remoteprocess/remoteprocess.pro b/tests/manual/ssh/remoteprocess/remoteprocess.pro new file mode 100644 index 00000000000..96c272268a3 --- /dev/null +++ b/tests/manual/ssh/remoteprocess/remoteprocess.pro @@ -0,0 +1,6 @@ +include(../ssh.pri) + +TARGET=remoteprocess +SOURCES=main.cpp remoteprocesstest.cpp argumentscollector.cpp +HEADERS=remoteprocesstest.h argumentscollector.h + diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp b/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp new file mode 100644 index 00000000000..bb61b1cb58e --- /dev/null +++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.cpp @@ -0,0 +1,184 @@ +#include "remoteprocesstest.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QTimer> + +#include <iostream> + +using namespace Core; + +RemoteProcessTest::RemoteProcessTest(const SshConnectionParameters ¶ms) + : m_timeoutTimer(new QTimer(this)), + m_remoteRunner(SshRemoteProcessRunner::create(params)), + m_state(Inactive) +{ + m_timeoutTimer->setInterval(5000); + connect(m_timeoutTimer, SIGNAL(timeout()), SLOT(handleTimeout())); +} + +RemoteProcessTest::~RemoteProcessTest() { } + +void RemoteProcessTest::run() +{ + connect(m_remoteRunner.data(), SIGNAL(connectionError(Core::SshError)), + SLOT(handleConnectionError())); + connect(m_remoteRunner.data(), SIGNAL(processStarted()), + SLOT(handleProcessStarted())); + connect(m_remoteRunner.data(), SIGNAL(processOutputAvailable(QByteArray)), + SLOT(handleProcessStdout(QByteArray))); + connect(m_remoteRunner.data(), + SIGNAL(processErrorOutputAvailable(QByteArray)), + SLOT(handleProcessStderr(QByteArray))); + connect(m_remoteRunner.data(), SIGNAL(processClosed(int)), + SLOT(handleProcessClosed(int))); + + std::cout << "Testing successful remote process..." << std::endl; + m_state = TestingSuccess; + m_started = false; + m_timeoutTimer->start(); + m_remoteRunner->run("ls -a /tmp"); +} + +void RemoteProcessTest::handleConnectionError() +{ + std::cerr << "Error: Connection failure (" + << qPrintable(m_remoteRunner->connection()->errorString()) << ")." + << std::endl; + qApp->quit(); +} + +void RemoteProcessTest::handleProcessStarted() +{ + if (m_started) { + std::cerr << "Error: Received started() signal again." << std::endl; + qApp->quit(); + } else { + m_started = true; + if (m_state == TestingCrash) { + Core::SshRemoteProcess::Ptr killer + = m_remoteRunner->connection()->createRemoteProcess("pkill -9 sleep"); + killer->start(); + } + } +} + +void RemoteProcessTest::handleProcessStdout(const QByteArray &output) +{ + if (!m_started) { + std::cerr << "Error: Remote output from non-started process." + << std::endl; + qApp->quit(); + } else if (m_state != TestingSuccess) { + std::cerr << "Error: Got remote standard output in state " << m_state + << "." << std::endl; + qApp->quit(); + } else { + m_remoteStdout += output; + } +} + +void RemoteProcessTest::handleProcessStderr(const QByteArray &output) +{ + if (!m_started) { + std::cerr << "Error: Remote error output from non-started process." + << std::endl; + qApp->quit(); + } else if (m_state == TestingSuccess) { + std::cerr << "Error: Unexpected remote standard error output." + << std::endl; + qApp->quit(); + } else { + m_remoteStderr += output; + } +} + +void RemoteProcessTest::handleProcessClosed(int exitStatus) +{ + switch (exitStatus) { + case SshRemoteProcess::ExitedNormally: + if (!m_started) { + std::cerr << "Error: Process exited without starting." << std::endl; + qApp->quit(); + return; + } + switch (m_state) { + case TestingSuccess: { + const int exitCode = m_remoteRunner->process()->exitCode(); + if (exitCode != 0) { + std::cerr << "Error: exit code is " << exitCode + << ", expected zero." << std::endl; + qApp->quit(); + return; + } + if (m_remoteStdout.isEmpty()) { + std::cerr << "Error: Command did not produce output." + << std::endl; + qApp->quit(); + return; + } + + std::cout << "Ok. Testing unsuccessful remote process..." + << std::endl; + m_state = TestingFailure; + m_started = false; + m_timeoutTimer->start(); + m_remoteRunner->run("ls /wedontexepectsuchafiletoexist"); + break; + } + case TestingFailure: { + const int exitCode = m_remoteRunner->process()->exitCode(); + if (exitCode == 0) { + std::cerr << "Error: exit code is zero, expected non-zero." + << std::endl; + qApp->quit(); + return; + } + if (m_remoteStderr.isEmpty()) { + std::cerr << "Error: Command did not produce error output." + << std::endl; + qApp->quit(); + return; + } + + std::cout << "Ok. Testing crashing remote process..." + << std::endl; + m_state = TestingCrash; + m_started = false; + m_timeoutTimer->start(); + m_remoteRunner->run("sleep 100"); + break; + } + case TestingCrash: + std::cerr << "Error: Successful exit from process that was " + "supposed to crash." << std::endl; + qApp->quit(); + return; + case Inactive: + Q_ASSERT(false); + } + break; + case SshRemoteProcess::FailedToStart: + if (m_started) { + std::cerr << "Error: Got 'failed to start' signal for process " + "that has not started yet." << std::endl; + } else { + std::cerr << "Error: Process failed to start." << std::endl; + } + qApp->quit(); + break; + case SshRemoteProcess::KilledBySignal: + if (m_state != TestingCrash) { + std::cerr << "Error: Unexpected crash." << std::endl; + qApp->quit(); + return; + } + std::cout << "Ok. All tests succeeded." << std::endl; + qApp->quit(); + } +} + +void RemoteProcessTest::handleTimeout() +{ + std::cerr << "Error: Timeout waiting for progress." << std::endl; + qApp->quit(); +} diff --git a/tests/manual/ssh/remoteprocess/remoteprocesstest.h b/tests/manual/ssh/remoteprocess/remoteprocesstest.h new file mode 100644 index 00000000000..a9a2219c7e5 --- /dev/null +++ b/tests/manual/ssh/remoteprocess/remoteprocesstest.h @@ -0,0 +1,37 @@ +#ifndef SFTPTEST_H +#define SFTPTEST_H + +#include <coreplugin/ssh/sshremoteprocessrunner.h> + +QT_FORWARD_DECLARE_CLASS(QTimer); +#include <QtCore/QObject> + +class RemoteProcessTest : public QObject +{ + Q_OBJECT +public: + RemoteProcessTest(const Core::SshConnectionParameters ¶ms); + ~RemoteProcessTest(); + void run(); + +private slots: + void handleConnectionError(); + void handleProcessStarted(); + void handleProcessStdout(const QByteArray &output); + void handleProcessStderr(const QByteArray &output); + void handleProcessClosed(int exitStatus); + void handleTimeout(); + +private: + enum State { Inactive, TestingSuccess, TestingFailure, TestingCrash }; + + QTimer * const m_timeoutTimer; + const Core::SshRemoteProcessRunner::Ptr m_remoteRunner; + QByteArray m_remoteStdout; + QByteArray m_remoteStderr; + State m_state; + bool m_started; +}; + + +#endif // SFTPTEST_H diff --git a/tests/manual/ssh/ssh.pro b/tests/manual/ssh/ssh.pro index 432f8f192b5..a722b3d543e 100644 --- a/tests/manual/ssh/ssh.pro +++ b/tests/manual/ssh/ssh.pro @@ -5,4 +5,4 @@ #------------------------------------------------- TEMPLATE = subdirs -SUBDIRS = errorhandling sftp +SUBDIRS = errorhandling sftp remoteprocess -- GitLab