2018-10-16 17:38:12 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2018 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
#include <ssh/sftpsession.h>
|
|
|
|
|
#include <ssh/sftptransfer.h>
|
2018-10-16 17:38:12 +02:00
|
|
|
#include <ssh/sshconnection.h>
|
|
|
|
|
#include <ssh/sshremoteprocessrunner.h>
|
2019-06-07 16:45:47 +02:00
|
|
|
#include <ssh/sshsettings.h>
|
2017-06-14 16:31:33 +02:00
|
|
|
#include <utils/environment.h>
|
2018-11-23 11:07:57 +01:00
|
|
|
#include <utils/temporarydirectory.h>
|
2018-10-16 17:38:12 +02:00
|
|
|
|
|
|
|
|
#include <QDateTime>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QEventLoop>
|
2020-10-30 12:11:32 +01:00
|
|
|
#include <QRandomGenerator>
|
2018-10-16 17:38:12 +02:00
|
|
|
#include <QStringList>
|
|
|
|
|
#include <QTemporaryDir>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QtTest>
|
|
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
|
|
using namespace QSsh;
|
|
|
|
|
|
|
|
|
|
static QString getHostFromEnvironment()
|
|
|
|
|
{
|
|
|
|
|
return QString::fromLocal8Bit(qgetenv("QTC_SSH_TEST_HOST"));
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
static const char *portVar() { return "QTC_SSH_TEST_PORT"; }
|
|
|
|
|
static const char *userVar() { return "QTC_SSH_TEST_USER"; }
|
|
|
|
|
static const char *keyFileVar() { return "QTC_SSH_TEST_KEYFILE"; }
|
2018-10-16 17:38:12 +02:00
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
static quint16 getPortFromEnvironment()
|
2018-10-16 17:38:12 +02:00
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
const int port = qEnvironmentVariableIntValue(portVar());
|
|
|
|
|
return port != 0 ? quint16(port) : 22;
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
static QString getUserFromEnvironment()
|
2018-10-16 17:38:12 +02:00
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
return QString::fromLocal8Bit(qgetenv(userVar()));
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
static QString getKeyFileFromEnvironment()
|
2018-10-16 17:38:12 +02:00
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
return QString::fromLocal8Bit(qgetenv(keyFileVar()));
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
static SshConnectionParameters getParameters()
|
2018-10-16 17:38:12 +02:00
|
|
|
{
|
|
|
|
|
SshConnectionParameters params;
|
2018-11-23 11:07:57 +01:00
|
|
|
params.setHost(getHostFromEnvironment());
|
|
|
|
|
params.setPort(getPortFromEnvironment());
|
|
|
|
|
params.setUserName(getUserFromEnvironment());
|
2018-10-16 17:38:12 +02:00
|
|
|
params.timeout = 10;
|
2018-11-23 11:07:57 +01:00
|
|
|
params.privateKeyFile = getKeyFileFromEnvironment();
|
|
|
|
|
params.authenticationType = !params.privateKeyFile.isEmpty()
|
|
|
|
|
? SshConnectionParameters::AuthenticationTypeSpecificKey
|
|
|
|
|
: SshConnectionParameters::AuthenticationTypeAll;
|
2018-10-16 17:38:12 +02:00
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
#define CHECK_PARAMS(params) \
|
2018-10-16 17:38:12 +02:00
|
|
|
do { \
|
|
|
|
|
if (params.host().isEmpty()) { \
|
|
|
|
|
QSKIP("No hostname provided. Set QTC_SSH_TEST_HOST."); \
|
|
|
|
|
} \
|
|
|
|
|
if (params.userName().isEmpty()) \
|
|
|
|
|
QSKIP(qPrintable(QString::fromLatin1("No user name provided. Set %1.") \
|
2019-09-10 13:13:30 +02:00
|
|
|
.arg(QString::fromUtf8(userVar())))); \
|
2018-11-23 11:07:57 +01:00
|
|
|
if (params.privateKeyFile.isEmpty()) \
|
|
|
|
|
QSKIP(qPrintable(QString::fromLatin1("No key file provided. Set %1.") \
|
2019-09-10 13:13:30 +02:00
|
|
|
.arg(QString::fromUtf8(keyFileVar())))); \
|
2018-10-16 17:38:12 +02:00
|
|
|
} while (false)
|
|
|
|
|
|
|
|
|
|
class tst_Ssh : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
private slots:
|
2018-11-23 11:07:57 +01:00
|
|
|
void initTestCase();
|
|
|
|
|
|
2018-10-16 17:38:12 +02:00
|
|
|
void errorHandling_data();
|
|
|
|
|
void errorHandling();
|
|
|
|
|
void pristineConnectionObject();
|
|
|
|
|
void remoteProcess_data();
|
|
|
|
|
void remoteProcess();
|
|
|
|
|
void remoteProcessChannels();
|
|
|
|
|
void remoteProcessInput();
|
|
|
|
|
void sftp();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool waitForConnection(SshConnection &connection);
|
|
|
|
|
};
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
void tst_Ssh::initTestCase()
|
2018-10-16 17:38:12 +02:00
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
|
|
|
|
|
+ "/qtc-ssh-autotest-XXXXXX");
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::errorHandling_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<QString>("host");
|
|
|
|
|
QTest::addColumn<quint16>("port");
|
|
|
|
|
QTest::addColumn<SshConnectionParameters::AuthenticationType>("authType");
|
|
|
|
|
QTest::addColumn<QString>("user");
|
|
|
|
|
QTest::addColumn<QString>("keyFile");
|
|
|
|
|
|
|
|
|
|
QTest::newRow("no host")
|
|
|
|
|
<< QString("hgdfxgfhgxfhxgfchxgcf") << quint16(12345)
|
2018-11-23 11:07:57 +01:00
|
|
|
<< SshConnectionParameters::AuthenticationTypeAll << QString("egal") << QString();
|
2018-10-16 17:38:12 +02:00
|
|
|
const QString theHost = getHostFromEnvironment();
|
|
|
|
|
if (theHost.isEmpty())
|
|
|
|
|
return;
|
2018-11-23 11:07:57 +01:00
|
|
|
const quint16 thePort = getPortFromEnvironment();
|
2018-10-16 17:38:12 +02:00
|
|
|
QTest::newRow("non-existing key file")
|
2018-11-23 11:07:57 +01:00
|
|
|
<< theHost << thePort << SshConnectionParameters::AuthenticationTypeSpecificKey
|
|
|
|
|
<< QString("root") << QString("somefilenamethatwedontexpecttocontainavalidkey");
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::errorHandling()
|
|
|
|
|
{
|
2019-07-15 14:18:56 +02:00
|
|
|
if (SshSettings::sshFilePath().isEmpty())
|
|
|
|
|
QSKIP("No ssh found in PATH - skipping this test.");
|
|
|
|
|
|
2018-10-16 17:38:12 +02:00
|
|
|
QFETCH(QString, host);
|
|
|
|
|
QFETCH(quint16, port);
|
|
|
|
|
QFETCH(SshConnectionParameters::AuthenticationType, authType);
|
|
|
|
|
QFETCH(QString, user);
|
|
|
|
|
QFETCH(QString, keyFile);
|
|
|
|
|
SshConnectionParameters params;
|
|
|
|
|
params.setHost(host);
|
|
|
|
|
params.setPort(port);
|
|
|
|
|
params.setUserName(user);
|
2018-11-23 11:07:57 +01:00
|
|
|
params.timeout = 3;
|
2018-10-16 17:38:12 +02:00
|
|
|
params.authenticationType = authType;
|
|
|
|
|
params.privateKeyFile = keyFile;
|
|
|
|
|
SshConnection connection(params);
|
|
|
|
|
QEventLoop loop;
|
|
|
|
|
bool disconnected = false;
|
|
|
|
|
QString dataReceived;
|
|
|
|
|
QObject::connect(&connection, &SshConnection::connected, &loop, &QEventLoop::quit);
|
2018-11-23 11:07:57 +01:00
|
|
|
QObject::connect(&connection, &SshConnection::errorOccurred, &loop, &QEventLoop::quit);
|
2018-10-16 17:38:12 +02:00
|
|
|
QObject::connect(&connection, &SshConnection::disconnected,
|
|
|
|
|
[&disconnected] { disconnected = true; });
|
|
|
|
|
QObject::connect(&connection, &SshConnection::dataAvailable,
|
|
|
|
|
[&dataReceived](const QString &data) { dataReceived = data; });
|
|
|
|
|
QTimer timer;
|
|
|
|
|
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
|
timer.setSingleShot(true);
|
2018-11-23 11:07:57 +01:00
|
|
|
timer.start((params.timeout + 15) * 1000);
|
2018-10-16 17:38:12 +02:00
|
|
|
connection.connectToHost();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
2019-06-07 16:45:47 +02:00
|
|
|
const bool expectConnected = !SshSettings::connectionSharingEnabled();
|
|
|
|
|
QCOMPARE(connection.state(), expectConnected ? SshConnection::Connected
|
|
|
|
|
: SshConnection::Unconnected);
|
|
|
|
|
QCOMPARE(connection.errorString().isEmpty(), expectConnected);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(!disconnected);
|
|
|
|
|
QVERIFY2(dataReceived.isEmpty(), qPrintable(dataReceived));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::pristineConnectionObject()
|
|
|
|
|
{
|
|
|
|
|
QSsh::SshConnection connection((SshConnectionParameters()));
|
|
|
|
|
QCOMPARE(connection.state(), SshConnection::Unconnected);
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY(!connection.createRemoteProcess(""));
|
|
|
|
|
QVERIFY(!connection.createSftpSession());
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::remoteProcess_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<QByteArray>("commandLine");
|
|
|
|
|
QTest::addColumn<bool>("useTerminal");
|
|
|
|
|
QTest::addColumn<bool>("isBlocking");
|
|
|
|
|
QTest::addColumn<bool>("successExpected");
|
|
|
|
|
QTest::addColumn<bool>("stdoutExpected");
|
|
|
|
|
QTest::addColumn<bool>("stderrExpected");
|
|
|
|
|
|
|
|
|
|
QTest::newRow("normal command")
|
|
|
|
|
<< QByteArray("ls -a /tmp") << false << false << true << true << false;
|
|
|
|
|
QTest::newRow("failing command")
|
|
|
|
|
<< QByteArray("top -n 1") << false << false << false << false << true;
|
|
|
|
|
QTest::newRow("blocking command")
|
|
|
|
|
<< QByteArray("/bin/sleep 100") << false << true << false << false << false;
|
|
|
|
|
QTest::newRow("terminal command")
|
|
|
|
|
<< QByteArray("top -n 1") << true << false << true << true << false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::remoteProcess()
|
|
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
const SshConnectionParameters params = getParameters();
|
|
|
|
|
CHECK_PARAMS(params);
|
2018-10-16 17:38:12 +02:00
|
|
|
|
|
|
|
|
QFETCH(QByteArray, commandLine);
|
|
|
|
|
QFETCH(bool, useTerminal);
|
|
|
|
|
QFETCH(bool, isBlocking);
|
|
|
|
|
QFETCH(bool, successExpected);
|
|
|
|
|
QFETCH(bool, stdoutExpected);
|
|
|
|
|
QFETCH(bool, stderrExpected);
|
|
|
|
|
|
|
|
|
|
QByteArray remoteStdout;
|
|
|
|
|
QByteArray remoteStderr;
|
|
|
|
|
SshRemoteProcessRunner runner;
|
|
|
|
|
QEventLoop loop;
|
|
|
|
|
connect(&runner, &SshRemoteProcessRunner::connectionError, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(&runner, &SshRemoteProcessRunner::processStarted, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(&runner, &SshRemoteProcessRunner::processClosed, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(&runner, &SshRemoteProcessRunner::readyReadStandardOutput,
|
|
|
|
|
[&remoteStdout, &runner] { remoteStdout += runner.readAllStandardOutput(); });
|
|
|
|
|
connect(&runner, &SshRemoteProcessRunner::readyReadStandardError,
|
|
|
|
|
[&remoteStderr, &runner] { remoteStderr += runner.readAllStandardError(); });
|
|
|
|
|
if (useTerminal)
|
2019-09-10 13:13:30 +02:00
|
|
|
runner.runInTerminal(QString::fromUtf8(commandLine), params);
|
2018-10-16 17:38:12 +02:00
|
|
|
else
|
2019-09-10 13:13:30 +02:00
|
|
|
runner.run(QString::fromUtf8(commandLine), params);
|
2018-10-16 17:38:12 +02:00
|
|
|
QTimer timer;
|
|
|
|
|
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
|
timer.setSingleShot(true);
|
|
|
|
|
timer.setInterval((params.timeout + 5) * 1000);
|
|
|
|
|
timer.start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY2(runner.lastConnectionErrorString().isEmpty(),
|
|
|
|
|
qPrintable(runner.lastConnectionErrorString()));
|
|
|
|
|
QVERIFY2(runner.processErrorString().isEmpty(), qPrintable(runner.processErrorString()));
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(runner.isProcessRunning()); // Event loop exit should have been triggered by started().
|
|
|
|
|
QVERIFY2(remoteStdout.isEmpty(), remoteStdout.constData());
|
|
|
|
|
QVERIFY2(remoteStderr.isEmpty(), remoteStderr.constData());
|
|
|
|
|
|
|
|
|
|
SshRemoteProcessRunner killer;
|
|
|
|
|
if (isBlocking)
|
2019-09-10 13:13:30 +02:00
|
|
|
killer.run("pkill -f -9 \"" + QString::fromUtf8(commandLine) + '"', params);
|
2018-10-16 17:38:12 +02:00
|
|
|
|
|
|
|
|
timer.start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
|
|
|
|
QVERIFY(!runner.isProcessRunning());
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY2(runner.lastConnectionErrorString().isEmpty(),
|
|
|
|
|
qPrintable(runner.lastConnectionErrorString()));
|
2018-10-16 17:38:12 +02:00
|
|
|
if (isBlocking) {
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY(runner.processExitStatus() == SshRemoteProcess::CrashExit
|
|
|
|
|
|| runner.processExitCode() != 0);
|
2018-10-16 17:38:12 +02:00
|
|
|
} else {
|
|
|
|
|
QCOMPARE(successExpected, runner.processExitCode() == 0);
|
|
|
|
|
}
|
|
|
|
|
QCOMPARE(stdoutExpected, !remoteStdout.isEmpty());
|
|
|
|
|
QCOMPARE(stderrExpected, !remoteStderr.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::remoteProcessChannels()
|
|
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
const SshConnectionParameters params = getParameters();
|
|
|
|
|
CHECK_PARAMS(params);
|
2018-10-16 17:38:12 +02:00
|
|
|
SshConnection connection(params);
|
|
|
|
|
QVERIFY(waitForConnection(connection));
|
|
|
|
|
|
|
|
|
|
static const QByteArray testString("ChannelTest");
|
|
|
|
|
QByteArray remoteStdout;
|
|
|
|
|
QByteArray remoteStderr;
|
|
|
|
|
QByteArray remoteData;
|
2018-11-23 11:07:57 +01:00
|
|
|
SshRemoteProcessPtr echoProcess
|
2019-09-10 13:13:30 +02:00
|
|
|
= connection.createRemoteProcess("printf " + QString::fromUtf8(testString) + " >&2");
|
2018-10-16 17:38:12 +02:00
|
|
|
echoProcess->setReadChannel(QProcess::StandardError);
|
|
|
|
|
QEventLoop loop;
|
2018-11-23 11:07:57 +01:00
|
|
|
connect(echoProcess.get(), &SshRemoteProcess::done, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(echoProcess.get(), &QIODevice::readyRead,
|
|
|
|
|
[&remoteData, p = echoProcess.get()] { remoteData += p->readAll(); });
|
|
|
|
|
connect(echoProcess.get(), &SshRemoteProcess::readyReadStandardOutput,
|
|
|
|
|
[&remoteStdout, p = echoProcess.get()] { remoteStdout += p->readAllStandardOutput(); });
|
|
|
|
|
connect(echoProcess.get(), &SshRemoteProcess::readyReadStandardError,
|
|
|
|
|
[&remoteStderr] { remoteStderr = testString; });
|
2018-10-16 17:38:12 +02:00
|
|
|
echoProcess->start();
|
|
|
|
|
QTimer timer;
|
|
|
|
|
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
|
timer.setSingleShot(true);
|
|
|
|
|
timer.setInterval((params.timeout + 5) * 1000);
|
|
|
|
|
timer.start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
|
|
|
|
QVERIFY(!echoProcess->isRunning());
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(echoProcess->exitStatus(), QProcess::NormalExit);
|
2018-10-16 17:38:12 +02:00
|
|
|
QCOMPARE(echoProcess->exitCode(), 0);
|
|
|
|
|
QVERIFY(remoteStdout.isEmpty());
|
|
|
|
|
QCOMPARE(remoteData, testString);
|
|
|
|
|
QCOMPARE(remoteData, remoteStderr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::remoteProcessInput()
|
|
|
|
|
{
|
2018-11-23 11:07:57 +01:00
|
|
|
const SshConnectionParameters params = getParameters();
|
|
|
|
|
CHECK_PARAMS(params);
|
2018-10-16 17:38:12 +02:00
|
|
|
SshConnection connection(params);
|
|
|
|
|
QVERIFY(waitForConnection(connection));
|
|
|
|
|
|
2019-06-04 15:35:08 +02:00
|
|
|
SshRemoteProcessPtr catProcess = connection.createRemoteProcess("/bin/cat");
|
2018-10-16 17:38:12 +02:00
|
|
|
QEventLoop loop;
|
2018-11-23 11:07:57 +01:00
|
|
|
connect(catProcess.get(), &SshRemoteProcess::started, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(catProcess.get(), &SshRemoteProcess::done, &loop, &QEventLoop::quit);
|
2018-10-16 17:38:12 +02:00
|
|
|
QTimer timer;
|
|
|
|
|
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
|
timer.setSingleShot(true);
|
|
|
|
|
timer.setInterval((params.timeout + 5) * 1000);
|
|
|
|
|
timer.start();
|
|
|
|
|
catProcess->start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
|
|
|
|
QVERIFY(catProcess->isRunning());
|
|
|
|
|
|
|
|
|
|
static QString testString = "x\r\n";
|
2018-11-23 11:07:57 +01:00
|
|
|
connect(catProcess.get(), &QIODevice::readyRead, &loop, &QEventLoop::quit);
|
|
|
|
|
QTextStream stream(catProcess.get());
|
2018-10-16 17:38:12 +02:00
|
|
|
stream << testString;
|
|
|
|
|
stream.flush();
|
|
|
|
|
timer.start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
|
|
|
|
QVERIFY(catProcess->isRunning());
|
|
|
|
|
|
|
|
|
|
const QString data = QString::fromUtf8(catProcess->readAll());
|
|
|
|
|
QCOMPARE(data, testString);
|
|
|
|
|
SshRemoteProcessRunner * const killer = new SshRemoteProcessRunner(this);
|
|
|
|
|
killer->run("pkill -9 cat", params);
|
|
|
|
|
timer.start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!catProcess->isRunning());
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY(catProcess->exitCode() != 0 || catProcess->exitStatus() == QProcess::CrashExit);
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_Ssh::sftp()
|
|
|
|
|
{
|
|
|
|
|
// Connect to server
|
2018-11-23 11:07:57 +01:00
|
|
|
const SshConnectionParameters params = getParameters();
|
|
|
|
|
CHECK_PARAMS(params);
|
2018-10-16 17:38:12 +02:00
|
|
|
SshConnection connection(params);
|
|
|
|
|
QVERIFY(waitForConnection(connection));
|
|
|
|
|
|
2018-11-23 11:07:57 +01:00
|
|
|
const SshConnectionInfo connInfo = connection.connectionInfo();
|
|
|
|
|
QVERIFY(connInfo.isValid());
|
|
|
|
|
QCOMPARE(connInfo.peerPort, params.port());
|
2018-10-16 17:38:12 +02:00
|
|
|
|
|
|
|
|
// Create and upload 1000 small files and one big file
|
|
|
|
|
QTemporaryDir dirForFilesToUpload;
|
|
|
|
|
QTemporaryDir dirForFilesToDownload;
|
|
|
|
|
QVERIFY2(dirForFilesToUpload.isValid(), qPrintable(dirForFilesToUpload.errorString()));
|
|
|
|
|
QVERIFY2(dirForFilesToDownload.isValid(), qPrintable(dirForFilesToDownload.errorString()));
|
|
|
|
|
static const auto getRemoteFilePath = [](const QString &localFileName) {
|
|
|
|
|
return QString("/tmp/").append(localFileName).append(".upload");
|
|
|
|
|
};
|
|
|
|
|
const auto getDownloadFilePath = [&dirForFilesToDownload](const QString &localFileName) {
|
|
|
|
|
return QString(dirForFilesToDownload.path()).append('/').append(localFileName);
|
|
|
|
|
};
|
2018-11-23 11:07:57 +01:00
|
|
|
FilesToTransfer filesToUpload;
|
2018-10-16 17:38:12 +02:00
|
|
|
std::srand(QDateTime::currentDateTime().toSecsSinceEpoch());
|
2019-06-07 16:45:47 +02:00
|
|
|
for (int i = 0; i < 100; ++i) {
|
2018-10-16 17:38:12 +02:00
|
|
|
const QString fileName = "sftptestfile" + QString::number(i + 1);
|
|
|
|
|
QFile file(dirForFilesToUpload.path() + '/' + fileName);
|
|
|
|
|
QVERIFY2(file.open(QIODevice::WriteOnly), qPrintable(file.errorString()));
|
|
|
|
|
int content[1024 / sizeof(int)];
|
|
|
|
|
for (size_t j = 0; j < sizeof content / sizeof content[0]; ++j)
|
2020-10-30 12:11:32 +01:00
|
|
|
content[j] = QRandomGenerator::global()->generate();
|
2018-10-16 17:38:12 +02:00
|
|
|
file.write(reinterpret_cast<char *>(content), sizeof content);
|
|
|
|
|
file.close();
|
|
|
|
|
QVERIFY2(file.error() == QFile::NoError, qPrintable(file.errorString()));
|
2018-11-23 11:07:57 +01:00
|
|
|
filesToUpload << FileToTransfer(file.fileName(), getRemoteFilePath(fileName));
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
const QString bigFileName("sftpbigfile");
|
|
|
|
|
QFile bigFile(dirForFilesToUpload.path() + '/' + bigFileName);
|
|
|
|
|
QVERIFY2(bigFile.open(QIODevice::WriteOnly), qPrintable(bigFile.errorString()));
|
|
|
|
|
const int bigFileSize = 100 * 1024 * 1024;
|
|
|
|
|
const int blockSize = 8192;
|
|
|
|
|
const int blockCount = bigFileSize / blockSize;
|
|
|
|
|
for (int block = 0; block < blockCount; ++block) {
|
|
|
|
|
int content[blockSize / sizeof(int)];
|
|
|
|
|
for (size_t j = 0; j < sizeof content / sizeof content[0]; ++j)
|
2020-10-30 12:11:32 +01:00
|
|
|
content[j] = QRandomGenerator::global()->generate();
|
2018-10-16 17:38:12 +02:00
|
|
|
bigFile.write(reinterpret_cast<char *>(content), sizeof content);
|
|
|
|
|
}
|
|
|
|
|
bigFile.close();
|
|
|
|
|
QVERIFY2(bigFile.error() == QFile::NoError, qPrintable(bigFile.errorString()));
|
2018-11-23 11:07:57 +01:00
|
|
|
filesToUpload << FileToTransfer(bigFile.fileName(), getRemoteFilePath(bigFileName));
|
|
|
|
|
|
|
|
|
|
const SftpTransferPtr upload = connection.createUpload(filesToUpload,
|
|
|
|
|
FileTransferErrorHandling::Abort);
|
|
|
|
|
QString jobError;
|
|
|
|
|
QEventLoop loop;
|
|
|
|
|
connect(upload.get(), &SftpTransfer::done, [&jobError, &loop](const QString &error) {
|
|
|
|
|
jobError = error;
|
|
|
|
|
loop.quit();
|
|
|
|
|
});
|
|
|
|
|
QTimer timer;
|
|
|
|
|
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
|
|
|
timer.setSingleShot(true);
|
2019-06-07 16:45:47 +02:00
|
|
|
timer.setInterval(30 * 1000);
|
2018-11-23 11:07:57 +01:00
|
|
|
timer.start();
|
|
|
|
|
upload->start();
|
2018-10-16 17:38:12 +02:00
|
|
|
loop.exec();
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
|
|
|
|
|
// Establish interactive SFTP session
|
|
|
|
|
SftpSessionPtr sftpChannel = connection.createSftpSession();
|
|
|
|
|
QList<SftpJobId> jobs;
|
|
|
|
|
bool invalidFinishedSignal = false;
|
|
|
|
|
connect(sftpChannel.get(), &SftpSession::started, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(sftpChannel.get(), &SftpSession::done, &loop, &QEventLoop::quit);
|
|
|
|
|
connect(sftpChannel.get(), &SftpSession::commandFinished,
|
|
|
|
|
[&loop, &jobs, &invalidFinishedSignal, &jobError](SftpJobId job, const QString &error) {
|
|
|
|
|
if (!jobs.removeOne(job)) {
|
|
|
|
|
invalidFinishedSignal = true;
|
|
|
|
|
loop.quit();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!error.isEmpty()) {
|
|
|
|
|
jobError = error;
|
|
|
|
|
loop.quit();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (jobs.empty())
|
|
|
|
|
loop.quit();
|
|
|
|
|
});
|
|
|
|
|
timer.start();
|
|
|
|
|
sftpChannel->start();
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
|
|
|
|
timer.stop();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
|
|
|
|
|
// Download the uploaded files to a different location
|
|
|
|
|
const QStringList allUploadedFileNames
|
|
|
|
|
= QDir(dirForFilesToUpload.path()).entryList(QDir::Files);
|
2019-06-07 16:45:47 +02:00
|
|
|
QCOMPARE(allUploadedFileNames.size(), 101);
|
2018-10-16 17:38:12 +02:00
|
|
|
for (const QString &fileName : allUploadedFileNames) {
|
|
|
|
|
const QString localFilePath = dirForFilesToUpload.path() + '/' + fileName;
|
|
|
|
|
const QString remoteFilePath = getRemoteFilePath(fileName);
|
|
|
|
|
const QString downloadFilePath = getDownloadFilePath(fileName);
|
2018-11-23 11:07:57 +01:00
|
|
|
const SftpJobId downloadJob = sftpChannel->downloadFile(remoteFilePath, downloadFilePath);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(downloadJob != SftpInvalidJob);
|
|
|
|
|
jobs << downloadJob;
|
|
|
|
|
}
|
2019-06-07 16:45:47 +02:00
|
|
|
QCOMPARE(jobs.size(), 101);
|
2018-10-16 17:38:12 +02:00
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(jobs.empty());
|
|
|
|
|
|
|
|
|
|
// Compare contents of uploaded and downloaded files
|
|
|
|
|
for (const QString &fileName : allUploadedFileNames) {
|
|
|
|
|
QFile originalFile(dirForFilesToUpload.path() + '/' + fileName);
|
|
|
|
|
QVERIFY2(originalFile.open(QIODevice::ReadOnly), qPrintable(originalFile.errorString()));
|
|
|
|
|
QFile downloadedFile(dirForFilesToDownload.path() + '/' + fileName);
|
|
|
|
|
QVERIFY2(downloadedFile.open(QIODevice::ReadOnly),
|
|
|
|
|
qPrintable(downloadedFile.errorString()));
|
|
|
|
|
QVERIFY(originalFile.fileName() != downloadedFile.fileName());
|
|
|
|
|
QCOMPARE(originalFile.size(), downloadedFile.size());
|
|
|
|
|
qint64 bytesLeft = originalFile.size();
|
|
|
|
|
while (bytesLeft > 0) {
|
|
|
|
|
const qint64 bytesToRead = qMin(bytesLeft, Q_INT64_C(1024 * 1024));
|
|
|
|
|
const QByteArray origBlock = originalFile.read(bytesToRead);
|
|
|
|
|
const QByteArray copyBlock = downloadedFile.read(bytesToRead);
|
|
|
|
|
QCOMPARE(origBlock.size(), bytesToRead);
|
|
|
|
|
QCOMPARE(origBlock, copyBlock);
|
|
|
|
|
bytesLeft -= bytesToRead;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the uploaded files on the remote system
|
2019-06-07 16:45:47 +02:00
|
|
|
timer.setInterval((params.timeout + 5) * 1000);
|
2018-10-16 17:38:12 +02:00
|
|
|
for (const QString &fileName : allUploadedFileNames) {
|
|
|
|
|
const QString remoteFilePath = getRemoteFilePath(fileName);
|
|
|
|
|
const SftpJobId removeJob = sftpChannel->removeFile(remoteFilePath);
|
|
|
|
|
QVERIFY(removeJob != SftpInvalidJob);
|
|
|
|
|
jobs << removeJob;
|
|
|
|
|
}
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(jobs.empty());
|
|
|
|
|
|
|
|
|
|
// Create a directory on the remote system
|
|
|
|
|
const QString remoteDirPath = "/tmp/sftptest-" + QDateTime::currentDateTime().toString();
|
|
|
|
|
const SftpJobId mkdirJob = sftpChannel->createDirectory(remoteDirPath);
|
|
|
|
|
QVERIFY(mkdirJob != SftpInvalidJob);
|
|
|
|
|
jobs << mkdirJob;
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(jobs.empty());
|
|
|
|
|
|
|
|
|
|
// Retrieve and check the attributes of the remote directory
|
|
|
|
|
QList<SftpFileInfo> remoteFileInfo;
|
|
|
|
|
const auto fileInfoHandler
|
|
|
|
|
= [&remoteFileInfo](SftpJobId, const QList<SftpFileInfo> &fileInfoList) {
|
|
|
|
|
remoteFileInfo << fileInfoList;
|
|
|
|
|
};
|
2018-11-23 11:07:57 +01:00
|
|
|
connect(sftpChannel.get(), &SftpSession::fileInfoAvailable, fileInfoHandler);
|
|
|
|
|
const SftpJobId statDirJob = sftpChannel->ls(remoteDirPath + "/..");
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(statDirJob != SftpInvalidJob);
|
|
|
|
|
jobs << statDirJob;
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(jobs.empty());
|
2018-11-23 11:07:57 +01:00
|
|
|
QVERIFY(!remoteFileInfo.empty());
|
|
|
|
|
SftpFileInfo remoteDirInfo;
|
|
|
|
|
for (const SftpFileInfo &fi : qAsConst(remoteFileInfo)) {
|
|
|
|
|
if (fi.name == QFileInfo(remoteDirPath).fileName()) {
|
|
|
|
|
remoteDirInfo = fi;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-16 17:38:12 +02:00
|
|
|
QCOMPARE(remoteDirInfo.type, FileTypeDirectory);
|
|
|
|
|
QCOMPARE(remoteDirInfo.name, QFileInfo(remoteDirPath).fileName());
|
|
|
|
|
|
|
|
|
|
// Retrieve and check the contents of the remote directory
|
2018-11-23 11:07:57 +01:00
|
|
|
remoteFileInfo.clear();
|
|
|
|
|
const SftpJobId lsDirJob = sftpChannel->ls(remoteDirPath);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(lsDirJob != SftpInvalidJob);
|
|
|
|
|
jobs << lsDirJob;
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(jobs.empty());
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(remoteFileInfo.size(), 0);
|
2018-10-16 17:38:12 +02:00
|
|
|
|
|
|
|
|
// Remove the remote directory.
|
|
|
|
|
const SftpJobId rmDirJob = sftpChannel->removeDirectory(remoteDirPath);
|
|
|
|
|
QVERIFY(rmDirJob != SftpInvalidJob);
|
|
|
|
|
jobs << rmDirJob;
|
|
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
2018-10-16 17:38:12 +02:00
|
|
|
QVERIFY(jobs.empty());
|
|
|
|
|
|
|
|
|
|
// Closing down
|
2018-11-23 11:07:57 +01:00
|
|
|
sftpChannel->quit();
|
|
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Closing);
|
2018-10-16 17:38:12 +02:00
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(!invalidFinishedSignal);
|
|
|
|
|
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(sftpChannel->state(), SftpSession::State::Inactive);
|
|
|
|
|
connect(&connection, &SshConnection::disconnected, &loop, &QEventLoop::quit);
|
2017-06-14 16:31:33 +02:00
|
|
|
timer.start();
|
2018-11-23 11:07:57 +01:00
|
|
|
connection.disconnectFromHost();
|
2017-06-14 16:31:33 +02:00
|
|
|
loop.exec();
|
|
|
|
|
QVERIFY(timer.isActive());
|
2018-11-23 11:07:57 +01:00
|
|
|
QCOMPARE(connection.state(), SshConnection::Unconnected);
|
|
|
|
|
QVERIFY2(connection.errorString().isEmpty(), qPrintable(connection.errorString()));
|
2017-06-14 16:31:33 +02:00
|
|
|
}
|
|
|
|
|
|
2018-10-16 17:38:12 +02:00
|
|
|
bool tst_Ssh::waitForConnection(SshConnection &connection)
|
|
|
|
|
{
|
|
|
|
|
QEventLoop loop;
|
|
|
|
|
QObject::connect(&connection, &SshConnection::connected, &loop, &QEventLoop::quit);
|
2018-11-23 11:07:57 +01:00
|
|
|
QObject::connect(&connection, &SshConnection::errorOccurred, &loop, &QEventLoop::quit);
|
2018-10-16 17:38:12 +02:00
|
|
|
connection.connectToHost();
|
|
|
|
|
loop.exec();
|
2018-11-23 11:07:57 +01:00
|
|
|
if (!connection.errorString().isEmpty())
|
2018-10-16 17:38:12 +02:00
|
|
|
qDebug() << connection.errorString();
|
2018-11-23 11:07:57 +01:00
|
|
|
return connection.state() == SshConnection::Connected && connection.errorString().isEmpty();
|
2018-10-16 17:38:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTEST_MAIN(tst_Ssh)
|
|
|
|
|
|
|
|
|
|
#include <tst_ssh.moc>
|