Files
qt-creator/src/plugins/remotelinux/linuxdevicetester.cpp
Jarek Kobus 97a66067bb TaskTree: Prepare for de-utils-ization - part 2
Move TaskTree into Tasking namespace.
Move Tasking namespace out of Utils namespace.

Change-Id: Ib4c1d7f54f1808517e54768dfa27209c33517b61
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
2023-05-12 08:09:19 +00:00

323 lines
13 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "linuxdevicetester.h"
#include "remotelinux_constants.h"
#include "remotelinuxtr.h"
#include <projectexplorer/devicesupport/deviceusedportsgatherer.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <utils/algorithm.h>
#include <utils/process.h>
#include <utils/processinterface.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
using namespace ProjectExplorer;
using namespace Tasking;
using namespace Utils;
namespace RemoteLinux {
namespace Internal {
struct TransferStorage
{
bool useGenericCopy = false;
};
class GenericLinuxDeviceTesterPrivate
{
public:
GenericLinuxDeviceTesterPrivate(GenericLinuxDeviceTester *tester) : q(tester) {}
QStringList commandsToTest() const;
TaskItem echoTask(const QString &contents) const;
TaskItem unameTask() const;
TaskItem gathererTask() const;
TaskItem transferTask(FileTransferMethod method,
const TreeStorage<TransferStorage> &storage) const;
TaskItem transferTasks() const;
TaskItem commandTask(const QString &commandName) const;
TaskItem commandTasks() const;
GenericLinuxDeviceTester *q = nullptr;
IDevice::Ptr m_device;
std::unique_ptr<TaskTree> m_taskTree;
QStringList m_extraCommands;
QList<TaskItem> m_extraTests;
};
QStringList GenericLinuxDeviceTesterPrivate::commandsToTest() const
{
static const QStringList s_commandsToTest = {"base64",
"cat",
"chmod",
"cp",
"cut",
"dd",
"df",
"echo",
"eval",
"exit",
"kill",
"ls",
"mkdir",
"mkfifo",
"mktemp",
"mv",
"printf",
"read",
"readlink",
"rm",
"sed",
"sh",
"shift",
"stat",
"tail",
"test",
"trap",
"touch",
"which"};
// other possible commands (checked for qnx):
// "awk", "grep", "netstat", "print", "pidin", "sleep", "uname"
QStringList commands = s_commandsToTest + m_extraCommands;
commands.removeDuplicates();
Utils::sort(commands);
return commands;
}
TaskItem GenericLinuxDeviceTesterPrivate::echoTask(const QString &contents) const
{
const auto setup = [this, contents](Process &process) {
emit q->progressMessage(Tr::tr("Sending echo to device..."));
process.setCommand({m_device->filePath("echo"), {contents}});
};
const auto done = [this, contents](const Process &process) {
const QString reply = Utils::chopIfEndsWith(process.cleanedStdOut(), '\n');
if (reply != contents)
emit q->errorMessage(Tr::tr("Device replied to echo with unexpected contents: \"%1\"")
.arg(reply) + '\n');
else
emit q->progressMessage(Tr::tr("Device replied to echo with expected contents.") + '\n');
};
const auto error = [this](const Process &process) {
const QString stdErrOutput = process.cleanedStdErr();
if (!stdErrOutput.isEmpty())
emit q->errorMessage(Tr::tr("echo failed: %1").arg(stdErrOutput) + '\n');
else
emit q->errorMessage(Tr::tr("echo failed.") + '\n');
};
return ProcessTask(setup, done, error);
}
TaskItem GenericLinuxDeviceTesterPrivate::unameTask() const
{
const auto setup = [this](Process &process) {
emit q->progressMessage(Tr::tr("Checking kernel version..."));
process.setCommand({m_device->filePath("uname"), {"-rsm"}});
};
const auto done = [this](const Process &process) {
emit q->progressMessage(process.cleanedStdOut());
};
const auto error = [this](const Process &process) {
const QString stdErrOutput = process.cleanedStdErr();
if (!stdErrOutput.isEmpty())
emit q->errorMessage(Tr::tr("uname failed: %1").arg(stdErrOutput) + '\n');
else
emit q->errorMessage(Tr::tr("uname failed.") + '\n');
};
return Group {
optional,
ProcessTask(setup, done, error)
};
}
TaskItem GenericLinuxDeviceTesterPrivate::gathererTask() const
{
const auto setup = [this](DeviceUsedPortsGatherer &gatherer) {
emit q->progressMessage(Tr::tr("Checking if specified ports are available..."));
gatherer.setDevice(m_device);
};
const auto done = [this](const DeviceUsedPortsGatherer &gatherer) {
if (gatherer.usedPorts().isEmpty()) {
emit q->progressMessage(Tr::tr("All specified ports are available.") + '\n');
} else {
const QString portList = transform(gatherer.usedPorts(), [](const Port &port) {
return QString::number(port.number());
}).join(", ");
emit q->errorMessage(Tr::tr("The following specified ports are currently in use: %1")
.arg(portList) + '\n');
}
};
const auto error = [this](const DeviceUsedPortsGatherer &gatherer) {
emit q->errorMessage(Tr::tr("Error gathering ports: %1").arg(gatherer.errorString()) + '\n'
+ Tr::tr("Some tools will not work out of the box.\n"));
};
return Group {
optional,
PortGatherer(setup, done, error)
};
}
TaskItem GenericLinuxDeviceTesterPrivate::transferTask(FileTransferMethod method,
const TreeStorage<TransferStorage> &storage) const
{
const auto setup = [this, method](FileTransfer &transfer) {
emit q->progressMessage(Tr::tr("Checking whether \"%1\" works...")
.arg(FileTransfer::transferMethodName(method)));
transfer.setTransferMethod(method);
transfer.setTestDevice(m_device);
};
const auto done = [this, method, storage](const FileTransfer &) {
const QString methodName = FileTransfer::transferMethodName(method);
emit q->progressMessage(Tr::tr("\"%1\" is functional.\n").arg(methodName));
if (method == FileTransferMethod::Rsync)
m_device->setExtraData(Constants::SupportsRSync, true);
else if (method == FileTransferMethod::Sftp)
m_device->setExtraData(Constants::SupportsSftp, true);
else
storage->useGenericCopy = true;
};
const auto error = [this, method, storage](const FileTransfer &transfer) {
const QString methodName = FileTransfer::transferMethodName(method);
const ProcessResultData resultData = transfer.resultData();
QString error;
if (resultData.m_error == QProcess::FailedToStart) {
error = Tr::tr("Failed to start \"%1\": %2\n").arg(methodName, resultData.m_errorString);
} else if (resultData.m_exitStatus == QProcess::CrashExit) {
error = Tr::tr("\"%1\" crashed.\n").arg(methodName);
} else if (resultData.m_exitCode != 0) {
error = Tr::tr("\"%1\" failed with exit code %2: %3\n")
.arg(methodName).arg(resultData.m_exitCode).arg(resultData.m_errorString);
}
emit q->errorMessage(error);
if (method == FileTransferMethod::Rsync)
m_device->setExtraData(Constants::SupportsRSync, false);
else if (method == FileTransferMethod::Sftp)
m_device->setExtraData(Constants::SupportsSftp, false);
const QVariant supportsRSync = m_device->extraData(Constants::SupportsRSync);
const QVariant supportsSftp = m_device->extraData(Constants::SupportsSftp);
if (supportsRSync.isValid() && !supportsRSync.toBool()
&& supportsSftp.isValid() && !supportsSftp.toBool()) {
const QString generic = FileTransfer::transferMethodName(FileTransferMethod::GenericCopy);
const QString sftp = FileTransfer::transferMethodName(FileTransferMethod::Sftp);
const QString rsync = FileTransfer::transferMethodName(FileTransferMethod::Rsync);
emit q->progressMessage(Tr::tr("\"%1\" will be used for deployment, because \"%2\" "
"and \"%3\" are not available.\n")
.arg(generic, sftp, rsync));
}
};
return FileTransferTestTask(setup, done, error);
}
TaskItem GenericLinuxDeviceTesterPrivate::transferTasks() const
{
TreeStorage<TransferStorage> storage;
return Group {
continueOnDone,
Storage(storage),
transferTask(FileTransferMethod::GenericCopy, storage),
transferTask(FileTransferMethod::Sftp, storage),
transferTask(FileTransferMethod::Rsync, storage),
OnGroupError([this] { emit q->errorMessage(Tr::tr("Deployment to this device will not "
"work out of the box.\n"));
})
};
}
TaskItem GenericLinuxDeviceTesterPrivate::commandTask(const QString &commandName) const
{
const auto setup = [this, commandName](Process &process) {
emit q->progressMessage(Tr::tr("%1...").arg(commandName));
CommandLine command{m_device->filePath("/bin/sh"), {"-c"}};
command.addArgs(QLatin1String("\"command -v %1\"").arg(commandName), CommandLine::Raw);
process.setCommand(command);
};
const auto done = [this, commandName](const Process &) {
emit q->progressMessage(Tr::tr("%1 found.").arg(commandName));
};
const auto error = [this, commandName](const Process &process) {
const QString message = process.result() == ProcessResult::StartFailed
? Tr::tr("An error occurred while checking for %1.").arg(commandName)
+ '\n' + process.errorString()
: Tr::tr("%1 not found.").arg(commandName);
emit q->errorMessage(message);
};
return ProcessTask(setup, done, error);
}
TaskItem GenericLinuxDeviceTesterPrivate::commandTasks() const
{
QList<TaskItem> tasks {continueOnError};
tasks.append(OnGroupSetup([this] {
emit q->progressMessage(Tr::tr("Checking if required commands are available..."));
}));
for (const QString &commandName : commandsToTest())
tasks.append(commandTask(commandName));
return Group {tasks};
}
} // namespace Internal
using namespace Internal;
GenericLinuxDeviceTester::GenericLinuxDeviceTester(QObject *parent)
: DeviceTester(parent), d(new GenericLinuxDeviceTesterPrivate(this))
{
}
GenericLinuxDeviceTester::~GenericLinuxDeviceTester() = default;
void GenericLinuxDeviceTester::setExtraCommandsToTest(const QStringList &extraCommands)
{
d->m_extraCommands = extraCommands;
}
void GenericLinuxDeviceTester::setExtraTests(const QList<TaskItem> &extraTests)
{
d->m_extraTests = extraTests;
}
void GenericLinuxDeviceTester::testDevice(const IDevice::Ptr &deviceConfiguration)
{
QTC_ASSERT(!d->m_taskTree, return);
d->m_device = deviceConfiguration;
auto allFinished = [this](DeviceTester::TestResult testResult) {
emit finished(testResult);
d->m_taskTree.release()->deleteLater();
};
QList<TaskItem> taskItems = {
d->echoTask("Hello"), // No quoting necessary
d->echoTask("Hello Remote World!"), // Checks quoting, too.
d->unameTask(),
d->gathererTask(),
d->transferTasks()
};
if (!d->m_extraTests.isEmpty())
taskItems << Group { d->m_extraTests };
taskItems << d->commandTasks()
<< OnGroupDone(std::bind(allFinished, TestSuccess))
<< OnGroupError(std::bind(allFinished, TestFailure));
d->m_taskTree.reset(new TaskTree(taskItems));
d->m_taskTree->start();
}
void GenericLinuxDeviceTester::stopTest()
{
QTC_ASSERT(d->m_taskTree, return);
d->m_taskTree.reset();
emit finished(TestFailure);
}
} // namespace RemoteLinux