aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/reference/commands.qdoc9
-rw-r--r--share/qbs/imports/qbs/base/AutotestRunner.qbs6
-rw-r--r--share/qbs/modules/autotest/autotest.qbs1
-rw-r--r--src/lib/corelib/buildgraph/abstractcommandexecutor.cpp21
-rw-r--r--src/lib/corelib/buildgraph/abstractcommandexecutor.h9
-rw-r--r--src/lib/corelib/buildgraph/jscommandexecutor.cpp16
-rw-r--r--src/lib/corelib/buildgraph/jscommandexecutor.h5
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.cpp26
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.h6
-rw-r--r--src/lib/corelib/buildgraph/rulecommands.cpp14
-rw-r--r--src/lib/corelib/buildgraph/rulecommands.h6
-rw-r--r--tests/auto/api/testdata/timeout-js/timeout.qbs20
-rw-r--r--tests/auto/api/testdata/timeout-process/main.cpp37
-rw-r--r--tests/auto/api/testdata/timeout-process/timeout.qbs24
-rw-r--r--tests/auto/api/tst_api.cpp64
-rw-r--r--tests/auto/api/tst_api.h3
-rw-r--r--tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs9
-rw-r--r--tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp37
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp29
-rw-r--r--tests/auto/blackbox/tst_blackbox.h3
20 files changed, 320 insertions, 25 deletions
diff --git a/doc/reference/commands.qdoc b/doc/reference/commands.qdoc
index eb93e8f7a..d34c20cb7 100644
--- a/doc/reference/commands.qdoc
+++ b/doc/reference/commands.qdoc
@@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2017 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.
@@ -116,6 +117,14 @@
\li A flag that controls whether the \c description is printed. Set it to \c true for commands that
users need not know about. \note If this property is \c false, then \c description must
not be empty.
+ \row
+ \li \c timeout
+ \li int
+ \li -1
+ \li Time limit for the command execution in seconds. If the command does not finish within
+ the timeout, it is cancelled. In case of a \c Command, the process is requested to
+ terminate. If it does not terminate within three seconds, it is killed. A value below
+ or equal to 0 means no timeout.
\endtable
\section2 Command Properties
diff --git a/share/qbs/imports/qbs/base/AutotestRunner.qbs b/share/qbs/imports/qbs/base/AutotestRunner.qbs
index ab9ba15f7..b9adee88d 100644
--- a/share/qbs/imports/qbs/base/AutotestRunner.qbs
+++ b/share/qbs/imports/qbs/base/AutotestRunner.qbs
@@ -42,6 +42,7 @@ Product {
property stringList wrapper: []
property string workingDir
property stringList auxiliaryInputs
+ property int timeout: -1
Depends {
productTypes: "autotest"
@@ -74,6 +75,7 @@ Product {
: FileInfo.path(commandFilePath);
var arguments = product.arguments;
var allowFailure = false;
+ var timeout = product.timeout;
if (input.autotest) {
// FIXME: We'd like to let the user override with an empty list, but
// qbscore turns undefined lists into empty ones at the moment.
@@ -83,6 +85,9 @@ Product {
if (input.autotest.workingDir)
workingDir = input.autotest.workingDir;
allowFailure = input.autotest.allowFailure;
+
+ if (input.autotest.timeout !== undefined)
+ timeout = input.autotest.timeout;
}
var fullCommandLine = product.wrapper
.concat([commandFilePath])
@@ -91,6 +96,7 @@ Product {
cmd.description = "Running test " + input.fileName;
cmd.environment = product.environment;
cmd.workingDirectory = workingDir;
+ cmd.timeout = timeout;
if (allowFailure)
cmd.maxExitCode = 32767;
return cmd;
diff --git a/share/qbs/modules/autotest/autotest.qbs b/share/qbs/modules/autotest/autotest.qbs
index ba280169e..c8a1c5180 100644
--- a/share/qbs/modules/autotest/autotest.qbs
+++ b/share/qbs/modules/autotest/autotest.qbs
@@ -2,4 +2,5 @@ Module {
property stringList arguments
property bool allowFailure: false
property string workingDir
+ property int timeout
}
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;
};
diff --git a/tests/auto/api/testdata/timeout-js/timeout.qbs b/tests/auto/api/testdata/timeout-js/timeout.qbs
new file mode 100644
index 000000000..26aa4ce87
--- /dev/null
+++ b/tests/auto/api/testdata/timeout-js/timeout.qbs
@@ -0,0 +1,20 @@
+Product {
+ type: "product-under-test"
+ Rule {
+ multiplex: true
+ Artifact {
+ filePath: "output.txt"
+ fileTags: "product-under-test"
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "Running infinite loop";
+ cmd.sourceCode = function() {
+ while (true)
+ ;
+ }
+ cmd.timeout = 3;
+ return cmd;
+ }
+ }
+}
diff --git a/tests/auto/api/testdata/timeout-process/main.cpp b/tests/auto/api/testdata/timeout-process/main.cpp
new file mode 100644
index 000000000..f9b9336ba
--- /dev/null
+++ b/tests/auto/api/testdata/timeout-process/main.cpp
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <chrono>
+#include <thread>
+
+int main()
+{
+ std::this_thread::sleep_for(std::chrono::seconds(700));
+ return 0;
+}
+
diff --git a/tests/auto/api/testdata/timeout-process/timeout.qbs b/tests/auto/api/testdata/timeout-process/timeout.qbs
new file mode 100644
index 000000000..9f1491ff4
--- /dev/null
+++ b/tests/auto/api/testdata/timeout-process/timeout.qbs
@@ -0,0 +1,24 @@
+Project {
+ CppApplication {
+ type: "application"
+ consoleApplication: true // suppress bundle generation
+ files: "main.cpp"
+ name: "infinite-loop"
+ }
+
+ Product {
+ type: "product-under-test"
+ name: "caller"
+ Depends { name: "infinite-loop" }
+ Rule {
+ inputsFromDependencies: "application"
+ outputFileTags: "product-under-test"
+ prepare: {
+ var cmd = new Command(inputs["application"][0].filePath);
+ cmd.description = "Calling application that runs forever";
+ cmd.timeout = 3;
+ return cmd;
+ }
+ }
+ }
+}
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index aac0f3e7e..b47d3b5c8 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.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.
@@ -2829,6 +2830,69 @@ void TestApi::targetArtifactStatus()
QCOMPARE(product.targetArtifacts().size(), enableTagging ? 2 : 1);
}
+void TestApi::timeout()
+{
+ QFETCH(QString, projectDirName);
+ QFETCH(qint64, expectedMaxRunTime);
+ const auto setupParams = defaultSetupParameters(projectDirName + "/timeout.qbs");
+ std::unique_ptr<qbs::SetupProjectJob> setupJob{
+ qbs::Project().setupProject(setupParams, m_logSink, nullptr)};
+ waitForFinished(setupJob.get());
+ QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString()));
+ auto project = setupJob->project();
+ const auto products = project.projectData().products();
+ QList<qbs::ProductData> helperProducts;
+ qbs::ProductData productUnderTest;
+ for (const auto &product : products) {
+ if (!product.type().contains(QLatin1String("product-under-test")))
+ helperProducts.append(product);
+ else
+ productUnderTest = product;
+ }
+ const std::unique_ptr<qbs::BuildJob> buildHelpersJob{
+ project.buildSomeProducts(helperProducts, qbs::BuildOptions())};
+ QVERIFY(waitForFinished(buildHelpersJob.get(), testTimeoutInMsecs()));
+ if (buildHelpersJob->error().hasError()) {
+ qDebug().noquote() << buildHelpersJob->error().toString();
+ QFAIL("Could not build helper products");
+ }
+
+ QElapsedTimer timer;
+ timer.start();
+ const std::unique_ptr<qbs::BuildJob> buildJob(project.buildOneProduct(productUnderTest,
+ qbs::BuildOptions()));
+ const auto testAbortTimeout = 20 * 1000ll;
+ /* We add an additional buffer to the expectedMaxRunTime because Qbs can take some
+ * time (>1 second) to finish after the command has been cancelled.
+ */
+ expectedMaxRunTime += 4 * 1000ll;
+ Q_ASSERT_X(testAbortTimeout > expectedMaxRunTime,
+ Q_FUNC_INFO,
+ "testAbortTimeout must be larger than the expectedMaxRunTime. Else the "
+ "test might be cancelled to early although the code is working correctly.");
+ QTimer::singleShot(testAbortTimeout, buildJob.get(), &qbs::AbstractJob::cancel);
+ QVERIFY(waitForFinished(buildJob.get(), testTimeoutInMsecs()));
+ const auto actualRunTime = timer.elapsed();
+ QVERIFY(buildJob->error().hasError());
+ const auto errorString = buildJob->error().toString();
+ QVERIFY(errorString.contains("cancel"));
+ QVERIFY(errorString.contains("timeout"));
+ if (actualRunTime > expectedMaxRunTime)
+ qDebug() << actualRunTime << "vs." << expectedMaxRunTime;
+ QVERIFY(actualRunTime < expectedMaxRunTime);
+}
+
+void TestApi::timeout_data()
+{
+ QTest::addColumn<QString>("projectDirName");
+ QTest::addColumn<qint64>("expectedMaxRunTime");
+ QTest::newRow("JS Command") << QString("timeout-js") << 3 * 1000ll;
+ /* The timeout is 3 seconds. Qbs will try to terminate the process for 3 seconds and then kill
+ * it.
+ */
+ QTest::newRow("Process Command") << QString("timeout-process") << (3 + 3) * 1000ll;
+}
+
void TestApi::toolInModule()
{
QVariantMap overrides({std::make_pair("qbs.installRoot", m_workingDataDir
diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h
index c04e9feb0..ce1678133 100644
--- a/tests/auto/api/tst_api.h
+++ b/tests/auto/api/tst_api.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.
@@ -142,6 +143,8 @@ private slots:
void subProjects();
void targetArtifactStatus_data();
void targetArtifactStatus();
+ void timeout();
+ void timeout_data();
void toolInModule();
void trackAddQObjectHeader();
void trackRemoveQObjectHeader();
diff --git a/tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs b/tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs
new file mode 100644
index 000000000..2cc20c3fd
--- /dev/null
+++ b/tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs
@@ -0,0 +1,9 @@
+Project {
+ CppApplication {
+ name: "testApp"
+ type: ["application", "autotest"]
+ Depends { name: "autotest" }
+ files: "test-main.cpp"
+ }
+ AutotestRunner {}
+}
diff --git a/tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp b/tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp
new file mode 100644
index 000000000..4bb1ca27c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Jochen Ulrich <jochenulrich@t-online.de>
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <chrono>
+#include <thread>
+
+int main()
+{
+ std::this_thread::sleep_for(std::chrono::seconds(5));
+ return 0;
+}
+
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index f87889155..edc2b51c8 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.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.
@@ -5624,6 +5625,34 @@ void TestBlackbox::autotestWithDependencies()
&& m_qbsStdout.contains("i am the helper"), m_qbsStdout.constData());
}
+void TestBlackbox::autotestTimeout()
+{
+ QFETCH(QStringList, resolveParams);
+ QFETCH(bool, expectFailure);
+ QDir::setCurrent(testDataDir + "/autotest-timeout");
+ QbsRunParameters resolveParameters("resolve", resolveParams);
+ QCOMPARE(runQbs(resolveParameters), 0);
+ QbsRunParameters buildParameters(QStringList({"-p", "autotest-runner"}));
+ buildParameters.expectFailure = expectFailure;
+ if (expectFailure) {
+ QVERIFY(runQbs(buildParameters) != 0);
+ QVERIFY(m_qbsStderr.contains("cancelled") && m_qbsStderr.contains("timeout"));
+ }
+ else
+ QVERIFY(runQbs(buildParameters) == 0);
+}
+
+void TestBlackbox::autotestTimeout_data()
+{
+ QTest::addColumn<QStringList>("resolveParams");
+ QTest::addColumn<bool>("expectFailure");
+ QTest::newRow("no timeout") << QStringList() << false;
+ QTest::newRow("timeout on test") << QStringList({"products.testApp.autotest.timeout:2"})
+ << true;
+ QTest::newRow("timeout on runner") << QStringList({"products.autotest-runner.timeout:2"})
+ << true;
+}
+
void TestBlackbox::autotests_data()
{
QTest::addColumn<QString>("evilPropertySpec");
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index 0a14c418c..4c1268912 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.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.
@@ -48,6 +49,8 @@ private slots:
void artifactScanning();
void assembly();
void autotestWithDependencies();
+ void autotestTimeout();
+ void autotestTimeout_data();
void autotests_data();
void autotests();
void auxiliaryInputsFromDependencies();