diff options
author | Jochen Ulrich <jochenulrich@t-online.de> | 2019-07-08 00:10:04 +0200 |
---|---|---|
committer | Jochen Ulrich <jochenulrich@t-online.de> | 2019-08-01 19:35:31 +0000 |
commit | 923f98062b4297970f382da91256c21d47b01a5e (patch) | |
tree | ef825940015e78e81acc1897339913f017af1d3b /src | |
parent | 6a672fe58fe18b2f2e74c41048d4c15b8395440a (diff) |
Add command and AutotestRunner timeout
Task-number: QBS-1454
Change-Id: I6e2514d10cca0cba0a14456ecd2abfb495539ee4
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: Denis Shienkov <denis.shienkov@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/corelib/buildgraph/abstractcommandexecutor.cpp | 21 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/abstractcommandexecutor.h | 9 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/jscommandexecutor.cpp | 16 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/jscommandexecutor.h | 5 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/processcommandexecutor.cpp | 26 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/processcommandexecutor.h | 6 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/rulecommands.cpp | 14 | ||||
-rw-r--r-- | src/lib/corelib/buildgraph/rulecommands.h | 6 |
8 files changed, 78 insertions, 25 deletions
diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp index 1a1d51f11..16c3621b6 100644 --- a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -58,6 +59,14 @@ AbstractCommandExecutor::AbstractCommandExecutor(Logger logger, QObject *parent) , m_dryRun(false) , m_logger(std::move(logger)) { + m_watchdog.setSingleShot(true); + connect(&m_watchdog, &QTimer::timeout, + this, [this]() { + cancel(ErrorInfo{Tr::tr("Command cancelled because it exceeded the timeout.")}); + }); + connect(this, &AbstractCommandExecutor::finished, + &m_watchdog, &QTimer::stop); + } void AbstractCommandExecutor::start(Transformer *transformer, AbstractCommand *cmd) @@ -66,7 +75,8 @@ void AbstractCommandExecutor::start(Transformer *transformer, AbstractCommand *c m_command = cmd; doSetup(); doReportCommandDescription(transformer->product()->fullDisplayName()); - doStart(); + if (doStart()) + startTimeout(); } void AbstractCommandExecutor::doReportCommandDescription(const QString &productName) @@ -84,5 +94,14 @@ void AbstractCommandExecutor::doReportCommandDescription(const QString &productN } } +void AbstractCommandExecutor::startTimeout() +{ + if (!m_dryRun || m_command->ignoreDryRun()) { + const auto timeout = m_command->timeout(); + if (timeout > 0) + m_watchdog.start(timeout * 1000); + } +} + } // namespace Internal } // namespace qbs diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.h b/src/lib/corelib/buildgraph/abstractcommandexecutor.h index 60b2b40b2..c0f149622 100644 --- a/src/lib/corelib/buildgraph/abstractcommandexecutor.h +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -45,6 +46,7 @@ #include <tools/error.h> #include <QtCore/qobject.h> +#include <QtCore/qtimer.h> namespace qbs { class ErrorInfo; @@ -64,7 +66,7 @@ public: void setDryRunEnabled(bool enabled) { m_dryRun = enabled; } void setEchoMode(CommandEchoMode echoMode) { m_echoMode = echoMode; } - virtual void cancel() = 0; + virtual void cancel(const qbs::ErrorInfo &reason = {}) = 0; void start(Transformer *transformer, AbstractCommand *cmd); @@ -83,7 +85,9 @@ protected: private: virtual void doSetup() { }; - virtual void doStart() = 0; + virtual bool doStart() = 0; + + void startTimeout(); private: AbstractCommand *m_command; @@ -91,6 +95,7 @@ private: ScriptEngine *m_mainThreadScriptEngine; bool m_dryRun; Internal::Logger m_logger; + QTimer m_watchdog; }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.cpp b/src/lib/corelib/buildgraph/jscommandexecutor.cpp index 30970779c..5c83b2056 100644 --- a/src/lib/corelib/buildgraph/jscommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/jscommandexecutor.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -82,9 +83,11 @@ public: return m_result; } - void cancel() + void cancel(const qbs::ErrorInfo &reason) { QBS_ASSERT(m_scriptEngine, return); + m_result.success = !reason.hasError(); + m_result.errorMessage = reason.toString(); m_scriptEngine->abortEvaluation(); } @@ -226,24 +229,25 @@ void JsCommandExecutor::waitForFinished() loop.exec(); } -void JsCommandExecutor::doStart() +bool JsCommandExecutor::doStart() { - QBS_ASSERT(!m_running, return); + QBS_ASSERT(!m_running, return false); m_thread->start(); if (dryRun() && !command()->ignoreDryRun()) { QTimer::singleShot(0, this, [this] { emit finished(); }); // Don't call back on the caller. - return; + return false; } m_running = true; emit startRequested(jsCommand(), transformer()); + return true; } -void JsCommandExecutor::cancel() +void JsCommandExecutor::cancel(const qbs::ErrorInfo &reason) { if (m_running && !dryRun()) - QTimer::singleShot(0, m_objectInThread, [this] { m_objectInThread->cancel(); }); + QTimer::singleShot(0, m_objectInThread, [objectInThread = m_objectInThread, reason] { objectInThread->cancel(reason); }); } void JsCommandExecutor::onJavaScriptCommandFinished() diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.h b/src/lib/corelib/buildgraph/jscommandexecutor.h index 0170c5231..0725f0d24 100644 --- a/src/lib/corelib/buildgraph/jscommandexecutor.h +++ b/src/lib/corelib/buildgraph/jscommandexecutor.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -65,8 +66,8 @@ private: void onJavaScriptCommandFinished(); void doReportCommandDescription(const QString &productName) override; - void doStart() override; - void cancel() override; + bool doStart() override; + void cancel(const qbs::ErrorInfo &reason) override; void waitForFinished(); diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp index c34a734b4..f8a86cfe8 100644 --- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp +++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -119,9 +120,9 @@ void ProcessCommandExecutor::doSetup() m_shellInvocation = shellQuote(QDir::toNativeSeparators(m_program), m_arguments); } -void ProcessCommandExecutor::doStart() +bool ProcessCommandExecutor::doStart() { - QBS_ASSERT(m_process.state() == QProcess::NotRunning, return); + QBS_ASSERT(m_process.state() == QProcess::NotRunning, return false); const ProcessCommand * const cmd = processCommand(); @@ -131,7 +132,7 @@ void ProcessCommandExecutor::doStart() if (dryRun() && !cmd->ignoreDryRun()) { QTimer::singleShot(0, this, [this] { emit finished(); }); // Don't call back on the caller. - return; + return false; } const QString workingDir = QDir::fromNativeSeparators(cmd->workingDir()); @@ -142,7 +143,7 @@ void ProcessCommandExecutor::doStart() "is invalid.").arg(QDir::toNativeSeparators(workingDir), QDir::toNativeSeparators(m_program)), cmd->codeLocation())); - return; + return false; } } @@ -163,7 +164,7 @@ void ProcessCommandExecutor::doStart() if (!responseFile.open()) { emit finished(ErrorInfo(Tr::tr("Cannot create response file '%1'.") .arg(responseFile.fileName()))); - return; + return false; } for (int i = cmd->responseFileArgumentIndex(); i < cmd->arguments().size(); ++i) { const QString arg = cmd->arguments().at(i); @@ -172,7 +173,7 @@ void ProcessCommandExecutor::doStart() if (!f.open(QIODevice::ReadOnly)) { emit finished(ErrorInfo(Tr::tr("Cannot open command file '%1'.") .arg(QDir::toNativeSeparators(f.fileName())))); - return; + return false; } responseFile.write(f.readAll()); } else { @@ -194,13 +195,15 @@ void ProcessCommandExecutor::doStart() qCDebug(lcExec) << "Additional environment:" << additionalVariables.toStringList(); m_process.setWorkingDirectory(workingDir); m_process.start(m_program, arguments); + return true; } -void ProcessCommandExecutor::cancel() +void ProcessCommandExecutor::cancel(const qbs::ErrorInfo &reason) { // We don't want this command to be reported as failing, since we explicitly terminated it. disconnect(this, &ProcessCommandExecutor::reportProcessResult, nullptr, nullptr); + m_cancelReason = reason; m_process.cancel(); } @@ -302,10 +305,13 @@ void ProcessCommandExecutor::sendProcessOutput() const bool processError = result.error() != QProcess::UnknownError; const bool failureExit = quint32(m_process.exitCode()) > quint32(processCommand()->maxExitCode()); - result.d->success = !processError && !failureExit; + const bool cancelledWithError = m_cancelReason.hasError(); + result.d->success = !processError && !failureExit && !cancelledWithError; emit reportProcessResult(result); - if (Q_UNLIKELY(processError)) { + if (Q_UNLIKELY(cancelledWithError)) { + emit finished(m_cancelReason); + } else if (Q_UNLIKELY(processError)) { emit finished(ErrorInfo(errorString)); } else if (Q_UNLIKELY(failureExit)) { emit finished(ErrorInfo(Tr::tr("Process failed with exit code %1.") @@ -323,6 +329,8 @@ void ProcessCommandExecutor::onProcessError() QTimer::singleShot(0, this, &ProcessCommandExecutor::onProcessError); return; } + if (m_cancelReason.hasError()) + return; // Ignore. Cancel reasons will be handled by on ProcessFinished(). switch (m_process.error()) { case QProcess::FailedToStart: { removeResponseFile(); diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.h b/src/lib/corelib/buildgraph/processcommandexecutor.h index 67eb9f746..b0f955882 100644 --- a/src/lib/corelib/buildgraph/processcommandexecutor.h +++ b/src/lib/corelib/buildgraph/processcommandexecutor.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -71,8 +72,8 @@ private: void doSetup() override; void doReportCommandDescription(const QString &productName) override; - void doStart() override; - void cancel() override; + bool doStart() override; + void cancel(const qbs::ErrorInfo &reason) override; void startProcessCommand(); QString filterProcessOutput(const QByteArray &output, const QString &filterFunctionSource); @@ -91,6 +92,7 @@ private: QProcessEnvironment m_buildEnvironment; QProcessEnvironment m_commandEnvironment; QString m_responseFileName; + qbs::ErrorInfo m_cancelReason; }; } // namespace Internal diff --git a/src/lib/corelib/buildgraph/rulecommands.cpp b/src/lib/corelib/buildgraph/rulecommands.cpp index ecbc54292..31ff6be4b 100644 --- a/src/lib/corelib/buildgraph/rulecommands.cpp +++ b/src/lib/corelib/buildgraph/rulecommands.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -74,6 +75,7 @@ static QString stderrFilePathProperty() { return QStringLiteral("stderrFilePath" static QString stderrFilterFunctionProperty() { return QStringLiteral("stderrFilterFunction"); } static QString stdoutFilePathProperty() { return QStringLiteral("stdoutFilePath"); } static QString stdoutFilterFunctionProperty() { return QStringLiteral("stdoutFilterFunction"); } +static QString timeoutProperty() { return QStringLiteral("timeout"); } static QString workingDirProperty() { return QStringLiteral("workingDirectory"); } static QString invokedSourceCode(const QScriptValue codeOrFunction) @@ -87,7 +89,8 @@ AbstractCommand::AbstractCommand() m_extendedDescription(defaultExtendedDescription()), m_highlight(defaultHighLight()), m_ignoreDryRun(defaultIgnoreDryRun()), - m_silent(defaultIsSilent()) + m_silent(defaultIsSilent()), + m_timeout(defaultTimeout()) { } @@ -104,6 +107,7 @@ bool AbstractCommand::equals(const AbstractCommand *other) const && m_ignoreDryRun == other->m_ignoreDryRun && m_silent == other->m_silent && m_jobPool == other->m_jobPool + && m_timeout == other->m_timeout && m_properties == other->m_properties; } @@ -115,6 +119,9 @@ void AbstractCommand::fillFromScriptValue(const QScriptValue *scriptValue, const m_ignoreDryRun = scriptValue->property(ignoreDryRunProperty()).toBool(); m_silent = scriptValue->property(silentProperty()).toBool(); m_jobPool = scriptValue->property(StringConstants::jobPoolProperty()).toString(); + const auto timeoutScriptValue = scriptValue->property(timeoutProperty()); + if (!timeoutScriptValue.isUndefined() && !timeoutScriptValue.isNull()) + m_timeout = timeoutScriptValue.toInt32(); m_codeLocation = codeLocation; m_predefinedProperties @@ -123,7 +130,8 @@ void AbstractCommand::fillFromScriptValue(const QScriptValue *scriptValue, const << highlightProperty() << ignoreDryRunProperty() << StringConstants::jobPoolProperty() - << silentProperty(); + << silentProperty() + << timeoutProperty(); } QString AbstractCommand::fullDescription(const QString &productName) const @@ -173,6 +181,8 @@ static QScriptValue js_CommandBase(QScriptContext *context, QScriptEngine *engin engine->toScriptValue(AbstractCommand::defaultIgnoreDryRun())); cmd.setProperty(silentProperty(), engine->toScriptValue(AbstractCommand::defaultIsSilent())); + cmd.setProperty(timeoutProperty(), + engine->toScriptValue(AbstractCommand::defaultTimeout())); return cmd; } diff --git a/src/lib/corelib/buildgraph/rulecommands.h b/src/lib/corelib/buildgraph/rulecommands.h index d9d561454..d4d70d591 100644 --- a/src/lib/corelib/buildgraph/rulecommands.h +++ b/src/lib/corelib/buildgraph/rulecommands.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. @@ -70,6 +71,7 @@ public: static QString defaultHighLight() { return {}; } static bool defaultIgnoreDryRun() { return false; } static bool defaultIsSilent() { return false; } + static int defaultTimeout() { return -1; } virtual CommandType type() const = 0; virtual bool equals(const AbstractCommand *other) const; @@ -83,6 +85,7 @@ public: bool isSilent() const { return m_silent; } QString jobPool() const { return m_jobPool; } CodeLocation codeLocation() const { return m_codeLocation; } + int timeout() const { return m_timeout; } const QVariantMap &properties() const { return m_properties; } @@ -100,7 +103,7 @@ private: { pool.serializationOp<opType>(m_description, m_extendedDescription, m_highlight, m_ignoreDryRun, m_silent, m_codeLocation, m_jobPool, - m_properties); + m_timeout, m_properties); } QString m_description; @@ -110,6 +113,7 @@ private: bool m_silent; CodeLocation m_codeLocation; QString m_jobPool; + int m_timeout; QVariantMap m_properties; }; |