TestRunner: Reuse TaskTree

Get rid of QFutureInterface argument from
ITestConfiguration::createOutputReader() and from
TestOutputReader c'tor.

The fine-grained progress reporting was broken anyway:

1. The assumption was that testCaseCount was meant to be
   the total number of test functions executed. It didn't
   include the initTestCase() and cleanupTestCase(),
   while those were reported on runtime apparently
   (and exceeding the max progress by 2).
2. In case of tst_qtcprocess, when the whole test was run,
   the testCaseCount reported 41, while the real
   number of functions was 26 (+2 = 28 for init/cleanup).
3. While the max progress was set to testCaseCount initially,
   the corresponding FutureProgress rendered the progress
   always in 0-100 range, what didn't match the reality.

Instead, rely on TaskTree progress, which resolution
is per test as a whole. So, when executing a series
of tests this should scale fine. In addition, the
progress advances fluently according to the expected
run time - with 10 seconds hardcoded.

The original code locations, where progress was bumped,
are left with a TODO comment for any possible future tweaks.
Like in case of result reporting, fine-grained progress
reporting may be implemented by providing additional signal,
so there is no need for QFutureInterface inside
TestOutputReader.

Change-Id: Idc11d55e3a49dac8d1788948b9a82f68199203c6
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Jarek Kobus
2023-01-17 00:45:50 +01:00
parent 6908130c83
commit 4f70aa7052
28 changed files with 219 additions and 351 deletions

View File

@@ -11,18 +11,16 @@
#include "../testsettings.h" #include "../testsettings.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/stringutils.h>
using namespace Utils; using namespace Utils;
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
TestOutputReader *BoostTestConfiguration::createOutputReader( TestOutputReader *BoostTestConfiguration::createOutputReader(QtcProcess *app) const
const QFutureInterface<TestResult> &fi, QtcProcess *app) const
{ {
auto settings = static_cast<BoostTestSettings *>(framework()->testSettings()); auto settings = static_cast<BoostTestSettings *>(framework()->testSettings());
return new BoostTestOutputReader(fi, app, buildDirectory(), projectFile(), return new BoostTestOutputReader(app, buildDirectory(), projectFile(),
LogLevel(settings->logLevel.value()), LogLevel(settings->logLevel.value()),
ReportLevel(settings->reportLevel.value())); ReportLevel(settings->reportLevel.value()));
} }

View File

@@ -13,8 +13,7 @@ class BoostTestConfiguration : public DebuggableTestConfiguration
public: public:
explicit BoostTestConfiguration(ITestFramework *framework) explicit BoostTestConfiguration(ITestFramework *framework)
: DebuggableTestConfiguration(framework) {} : DebuggableTestConfiguration(framework) {}
TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
}; };

View File

@@ -5,6 +5,7 @@
#include "boosttestsettings.h" #include "boosttestsettings.h"
#include "boosttestresult.h" #include "boosttestresult.h"
#include "../autotesttr.h" #include "../autotesttr.h"
#include "../testtreeitem.h" #include "../testtreeitem.h"
@@ -21,12 +22,11 @@ namespace Internal {
static Q_LOGGING_CATEGORY(orLog, "qtc.autotest.boost.outputreader", QtWarningMsg) static Q_LOGGING_CATEGORY(orLog, "qtc.autotest.boost.outputreader", QtWarningMsg)
BoostTestOutputReader::BoostTestOutputReader(const QFutureInterface<TestResult> &futureInterface, BoostTestOutputReader::BoostTestOutputReader(QtcProcess *testApplication,
QtcProcess *testApplication,
const FilePath &buildDirectory, const FilePath &buildDirectory,
const FilePath &projectFile, const FilePath &projectFile,
LogLevel log, ReportLevel report) LogLevel log, ReportLevel report)
: TestOutputReader(futureInterface, testApplication, buildDirectory) : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile) , m_projectFile(projectFile)
, m_logLevel(log) , m_logLevel(log)
, m_reportLevel(report) , m_reportLevel(report)

View File

@@ -15,8 +15,7 @@ class BoostTestOutputReader : public TestOutputReader
{ {
Q_OBJECT Q_OBJECT
public: public:
BoostTestOutputReader(const QFutureInterface<TestResult> &futureInterface, BoostTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile, LogLevel log, ReportLevel report); const Utils::FilePath &projectFile, LogLevel log, ReportLevel report);
protected: protected:
void processOutputLine(const QByteArray &outputLine) override; void processOutputLine(const QByteArray &outputLine) override;

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "catchconfiguration.h" #include "catchconfiguration.h"
#include "catchoutputreader.h" #include "catchoutputreader.h"
#include "catchtestsettings.h" #include "catchtestsettings.h"
@@ -9,17 +10,14 @@
#include "../itestframework.h" #include "../itestframework.h"
#include "../testsettings.h" #include "../testsettings.h"
#include <utils/stringutils.h>
using namespace Utils; using namespace Utils;
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
TestOutputReader *CatchConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *CatchConfiguration::createOutputReader(QtcProcess *app) const
QtcProcess *app) const
{ {
return new CatchOutputReader(fi, app, buildDirectory(), projectFile()); return new CatchOutputReader(app, buildDirectory(), projectFile());
} }
static QStringList filterInterfering(const QStringList &provided, QStringList *omitted) static QStringList filterInterfering(const QStringList &provided, QStringList *omitted)

View File

@@ -12,8 +12,7 @@ class CatchConfiguration : public DebuggableTestConfiguration
{ {
public: public:
CatchConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {} CatchConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {}
TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
}; };

View File

@@ -2,13 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "catchoutputreader.h" #include "catchoutputreader.h"
#include "catchresult.h" #include "catchresult.h"
#include "../autotesttr.h" #include "../autotesttr.h"
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
using namespace Utils; using namespace Utils;
namespace Autotest { namespace Autotest {
@@ -31,11 +29,10 @@ namespace CatchXml {
const char TestCaseResultElement[] = "OverallResult"; const char TestCaseResultElement[] = "OverallResult";
} }
CatchOutputReader::CatchOutputReader(const QFutureInterface<TestResult> &futureInterface, CatchOutputReader::CatchOutputReader(QtcProcess *testApplication,
QtcProcess *testApplication,
const FilePath &buildDirectory, const FilePath &buildDirectory,
const FilePath &projectFile) const FilePath &projectFile)
: TestOutputReader (futureInterface, testApplication, buildDirectory) : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile) , m_projectFile(projectFile)
{ {
} }

View File

@@ -14,8 +14,7 @@ namespace Internal {
class CatchOutputReader : public TestOutputReader class CatchOutputReader : public TestOutputReader
{ {
public: public:
CatchOutputReader(const QFutureInterface<TestResult> &futureInterface, CatchOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile); const Utils::FilePath &projectFile);
protected: protected:

View File

@@ -13,10 +13,9 @@ CTestConfiguration::CTestConfiguration(ITestBase *testBase)
setDisplayName("CTest"); setDisplayName("CTest");
} }
TestOutputReader *CTestConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *CTestConfiguration::createOutputReader(Utils::QtcProcess *app) const
Utils::QtcProcess *app) const
{ {
return new CTestOutputReader(fi, app, workingDirectory()); return new CTestOutputReader(app, workingDirectory());
} }
} // namespace Internal } // namespace Internal

View File

@@ -13,8 +13,7 @@ class CTestConfiguration final : public Autotest::TestToolConfiguration
public: public:
explicit CTestConfiguration(ITestBase *testBase); explicit CTestConfiguration(ITestBase *testBase);
TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *createOutputReader(Utils::QtcProcess *app) const final;
Utils::QtcProcess *app) const final;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -5,13 +5,11 @@
#include "../autotesttr.h" #include "../autotesttr.h"
#include "../testframeworkmanager.h" #include "../testframeworkmanager.h"
#include "../testresult.h"
#include "../testtreeitem.h" #include "../testtreeitem.h"
#include <cmakeprojectmanager/cmakeprojectconstants.h> #include <cmakeprojectmanager/cmakeprojectconstants.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/treemodel.h>
#include <QRegularExpression> #include <QRegularExpression>
@@ -52,10 +50,9 @@ public:
{} {}
}; };
CTestOutputReader::CTestOutputReader(const QFutureInterface<TestResult> &futureInterface, CTestOutputReader::CTestOutputReader(QtcProcess *testApplication,
QtcProcess *testApplication,
const FilePath &buildDirectory) const FilePath &buildDirectory)
: TestOutputReader(futureInterface, testApplication, buildDirectory) : TestOutputReader(testApplication, buildDirectory)
{ {
} }

View File

@@ -13,8 +13,7 @@ namespace Internal {
class CTestOutputReader final : public Autotest::TestOutputReader class CTestOutputReader final : public Autotest::TestOutputReader
{ {
public: public:
CTestOutputReader(const QFutureInterface<TestResult> &futureInterface, CTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
protected: protected:
void processOutputLine(const QByteArray &outputLineWithNewLine) final; void processOutputLine(const QByteArray &outputLineWithNewLine) final;

View File

@@ -5,22 +5,21 @@
#include "gtestoutputreader.h" #include "gtestoutputreader.h"
#include "gtestsettings.h" #include "gtestsettings.h"
#include "../autotestplugin.h" #include "../autotestplugin.h"
#include "../itestframework.h" #include "../itestframework.h"
#include "../testsettings.h" #include "../testsettings.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/stringutils.h>
using namespace Utils; using namespace Utils;
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
TestOutputReader *GTestConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *GTestConfiguration::createOutputReader(QtcProcess *app) const
QtcProcess *app) const
{ {
return new GTestOutputReader(fi, app, buildDirectory(), projectFile()); return new GTestOutputReader(app, buildDirectory(), projectFile());
} }
QStringList filterInterfering(const QStringList &provided, QStringList *omitted) QStringList filterInterfering(const QStringList &provided, QStringList *omitted)

View File

@@ -14,8 +14,7 @@ public:
explicit GTestConfiguration(ITestFramework *framework) explicit GTestConfiguration(ITestFramework *framework)
: DebuggableTestConfiguration(framework) {} : DebuggableTestConfiguration(framework) {}
TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
}; };

View File

@@ -17,11 +17,10 @@ using namespace Utils;
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
GTestOutputReader::GTestOutputReader(const QFutureInterface<TestResult> &futureInterface, GTestOutputReader::GTestOutputReader(QtcProcess *testApplication,
QtcProcess *testApplication,
const FilePath &buildDirectory, const FilePath &buildDirectory,
const FilePath &projectFile) const FilePath &projectFile)
: TestOutputReader(futureInterface, testApplication, buildDirectory) : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile) , m_projectFile(projectFile)
{ {
if (testApplication) { if (testApplication) {
@@ -121,7 +120,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
testResult.setResult(ResultType::MessageInternal); testResult.setResult(ResultType::MessageInternal);
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2))); testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
reportResult(testResult); reportResult(testResult);
m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); // TODO: bump progress?
} else if (ExactMatch match = testSetFail.match(line)) { } else if (ExactMatch match = testSetFail.match(line)) {
m_testSetStarted = false; m_testSetStarted = false;
TestResult testResult = createDefaultResult(); TestResult testResult = createDefaultResult();
@@ -132,7 +131,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
testResult.setResult(ResultType::MessageInternal); testResult.setResult(ResultType::MessageInternal);
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2))); testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
reportResult(testResult); reportResult(testResult);
m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); // TODO: bump progress?
} else if (ExactMatch match = testSetSkipped.match(line)) { } else if (ExactMatch match = testSetSkipped.match(line)) {
if (!m_testSetStarted) // ignore SKIPPED at summary if (!m_testSetStarted) // ignore SKIPPED at summary
return; return;

View File

@@ -11,8 +11,7 @@ namespace Internal {
class GTestOutputReader : public TestOutputReader class GTestOutputReader : public TestOutputReader
{ {
public: public:
GTestOutputReader(const QFutureInterface<TestResult> &futureInterface, GTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile); const Utils::FilePath &projectFile);
protected: protected:
void processOutputLine(const QByteArray &outputLine) override; void processOutputLine(const QByteArray &outputLine) override;

View File

@@ -2,16 +2,16 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qttestconfiguration.h" #include "qttestconfiguration.h"
#include "qttestconstants.h"
#include "qttestoutputreader.h" #include "qttestoutputreader.h"
#include "qttestsettings.h" #include "qttestsettings.h"
#include "qttest_utils.h" #include "qttest_utils.h"
#include "../autotestplugin.h" #include "../autotestplugin.h"
#include "../itestframework.h" #include "../itestframework.h"
#include "../testsettings.h" #include "../testsettings.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/stringutils.h>
using namespace Utils; using namespace Utils;
@@ -28,14 +28,13 @@ static QStringList quoteIfNeeded(const QStringList &testCases, bool debugMode)
}); });
} }
TestOutputReader *QtTestConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *QtTestConfiguration::createOutputReader(QtcProcess *app) const
QtcProcess *app) const
{ {
auto qtSettings = static_cast<QtTestSettings *>(framework()->testSettings()); auto qtSettings = static_cast<QtTestSettings *>(framework()->testSettings());
const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value() const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value()
? QtTestOutputReader::XML ? QtTestOutputReader::XML
: QtTestOutputReader::PlainText; : QtTestOutputReader::PlainText;
return new QtTestOutputReader(fi, app, buildDirectory(), projectFile(), mode, TestType::QtTest); return new QtTestOutputReader(app, buildDirectory(), projectFile(), mode, TestType::QtTest);
} }
QStringList QtTestConfiguration::argumentsForTestRunner(QStringList *omitted) const QStringList QtTestConfiguration::argumentsForTestRunner(QStringList *omitted) const

View File

@@ -13,8 +13,7 @@ class QtTestConfiguration : public DebuggableTestConfiguration
public: public:
explicit QtTestConfiguration(ITestFramework *framework) explicit QtTestConfiguration(ITestFramework *framework)
: DebuggableTestConfiguration(framework) {} : DebuggableTestConfiguration(framework) {}
TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
}; };

View File

@@ -12,8 +12,6 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <cctype>
using namespace Utils; using namespace Utils;
namespace Autotest { namespace Autotest {
@@ -99,18 +97,15 @@ static QString constructBenchmarkInformation(const QString &metric, double value
else if (metric == "CPUCycles") // -perf else if (metric == "CPUCycles") // -perf
metricsText = "CPU cycles"; metricsText = "CPU cycles";
return Tr::tr("%1 %2 per iteration (total: %3, iterations: %4)") return Tr::tr("%1 %2 per iteration (total: %3, iterations: %4)")
.arg(formatResult(value)) .arg(formatResult(value), metricsText, formatResult(value * double(iterations)))
.arg(metricsText)
.arg(formatResult(value * double(iterations)))
.arg(iterations); .arg(iterations);
} }
QtTestOutputReader::QtTestOutputReader(const QFutureInterface<TestResult> &futureInterface, QtTestOutputReader::QtTestOutputReader(QtcProcess *testApplication,
QtcProcess *testApplication,
const FilePath &buildDirectory, const FilePath &buildDirectory,
const FilePath &projectFile, const FilePath &projectFile,
OutputMode mode, TestType type) OutputMode mode, TestType type)
: TestOutputReader(futureInterface, testApplication, buildDirectory) : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile) , m_projectFile(projectFile)
, m_mode(mode) , m_mode(mode)
, m_testType(type) , m_testType(type)
@@ -177,8 +172,6 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine)
m_xmlReader.addData("\n"); m_xmlReader.addData("\n");
m_xmlReader.addData(QString::fromUtf8(outputLine)); m_xmlReader.addData(QString::fromUtf8(outputLine));
while (!m_xmlReader.atEnd()) { while (!m_xmlReader.atEnd()) {
if (m_futureInterface.isCanceled())
return;
QXmlStreamReader::TokenType token = m_xmlReader.readNext(); QXmlStreamReader::TokenType token = m_xmlReader.readNext();
switch (token) { switch (token) {
case QXmlStreamReader::StartDocument: case QXmlStreamReader::StartDocument:
@@ -277,7 +270,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine)
const QStringView currentTag = m_xmlReader.name(); const QStringView currentTag = m_xmlReader.name();
if (currentTag == QStringLiteral("TestFunction")) { if (currentTag == QStringLiteral("TestFunction")) {
sendFinishMessage(true); sendFinishMessage(true);
m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); // TODO: bump progress?
m_dataTag.clear(); m_dataTag.clear();
m_formerTestCase = m_testCase; m_formerTestCase = m_testCase;
m_testCase.clear(); m_testCase.clear();
@@ -347,9 +340,6 @@ void QtTestOutputReader::processPlainTextOutput(const QByteArray &outputLine)
static const QRegularExpression locationUnix(QT_TEST_FAIL_UNIX_REGEXP); static const QRegularExpression locationUnix(QT_TEST_FAIL_UNIX_REGEXP);
static const QRegularExpression locationWin(QT_TEST_FAIL_WIN_REGEXP); static const QRegularExpression locationWin(QT_TEST_FAIL_WIN_REGEXP);
if (m_futureInterface.isCanceled())
return;
const QString line = QString::fromUtf8(outputLine); const QString line = QString::fromUtf8(outputLine);
QRegularExpressionMatch match; QRegularExpressionMatch match;

View File

@@ -3,9 +3,10 @@
#pragma once #pragma once
#include "qttestconstants.h"
#include "../testoutputreader.h" #include "../testoutputreader.h"
#include "qttestconstants.h"
#include <QXmlStreamReader> #include <QXmlStreamReader>
namespace Autotest { namespace Autotest {
@@ -22,8 +23,7 @@ public:
PlainText PlainText
}; };
QtTestOutputReader(const QFutureInterface<TestResult> &futureInterface, QtTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile, OutputMode mode, TestType type); const Utils::FilePath &projectFile, OutputMode mode, TestType type);
protected: protected:
void processOutputLine(const QByteArray &outputLine) override; void processOutputLine(const QByteArray &outputLine) override;

View File

@@ -2,16 +2,14 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "quicktestconfiguration.h" #include "quicktestconfiguration.h"
#include "../qtest/qttestconstants.h"
#include "../autotestplugin.h"
#include "../itestframework.h"
#include "../qtest/qttestoutputreader.h" #include "../qtest/qttestoutputreader.h"
#include "../qtest/qttestsettings.h" #include "../qtest/qttestsettings.h"
#include "../qtest/qttest_utils.h" #include "../qtest/qttest_utils.h"
#include "../autotestplugin.h"
#include "../itestframework.h"
#include "../testsettings.h" #include "../testsettings.h"
#include <utils/stringutils.h>
using namespace Utils; using namespace Utils;
namespace Autotest { namespace Autotest {
@@ -23,15 +21,13 @@ QuickTestConfiguration::QuickTestConfiguration(ITestFramework *framework)
setMixedDebugging(true); setMixedDebugging(true);
} }
TestOutputReader *QuickTestConfiguration::createOutputReader( TestOutputReader *QuickTestConfiguration::createOutputReader(QtcProcess *app) const
const QFutureInterface<TestResult> &fi, QtcProcess *app) const
{ {
auto qtSettings = static_cast<QtTestSettings *>(framework()->testSettings()); auto qtSettings = static_cast<QtTestSettings *>(framework()->testSettings());
const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value() const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value()
? QtTestOutputReader::XML ? QtTestOutputReader::XML
: QtTestOutputReader::PlainText; : QtTestOutputReader::PlainText;
return new QtTestOutputReader(fi, app, buildDirectory(), projectFile(), return new QtTestOutputReader(app, buildDirectory(), projectFile(), mode, TestType::QuickTest);
mode, TestType::QuickTest);
} }
QStringList QuickTestConfiguration::argumentsForTestRunner(QStringList *omitted) const QStringList QuickTestConfiguration::argumentsForTestRunner(QStringList *omitted) const

View File

@@ -12,8 +12,7 @@ class QuickTestConfiguration : public DebuggableTestConfiguration
{ {
public: public:
explicit QuickTestConfiguration(ITestFramework *framework); explicit QuickTestConfiguration(ITestFramework *framework);
TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
}; };

View File

@@ -4,22 +4,19 @@
#include "testconfiguration.h" #include "testconfiguration.h"
#include "itestframework.h" #include "itestframework.h"
#include "testoutputreader.h"
#include "testrunconfiguration.h" #include "testrunconfiguration.h"
#include <cppeditor/cppmodelmanager.h>
#include <cppeditor/projectinfo.h>
#include <projectexplorer/buildconfiguration.h> #include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h> #include <projectexplorer/buildsystem.h>
#include <projectexplorer/buildtargetinfo.h> #include <projectexplorer/buildtargetinfo.h>
#include <projectexplorer/deploymentdata.h> #include <projectexplorer/deploymentdata.h>
#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/kitinformation.h> #include <projectexplorer/kitinformation.h>
#include <projectexplorer/runconfiguration.h> #include <projectexplorer/runconfiguration.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <utils/algorithm.h>
#include <QLoggingCategory> #include <QLoggingCategory>
static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testconfiguration", QtWarningMsg) static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testconfiguration", QtWarningMsg)
@@ -29,7 +26,6 @@ using namespace Utils;
namespace Autotest { namespace Autotest {
ITestConfiguration::ITestConfiguration(ITestBase *testBase) ITestConfiguration::ITestConfiguration(ITestBase *testBase)
: m_testBase(testBase) : m_testBase(testBase)
{ {
@@ -94,7 +90,7 @@ static FilePath ensureExeEnding(const FilePath &file)
return file.withExecutableSuffix(); return file.withExecutableSuffix();
} }
void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguration *rc, void TestConfiguration::completeTestInformation(RunConfiguration *rc,
TestRunMode runMode) TestRunMode runMode)
{ {
QTC_ASSERT(rc, return); QTC_ASSERT(rc, return);

View File

@@ -9,7 +9,6 @@
#include <projectexplorer/runcontrol.h> #include <projectexplorer/runcontrol.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <QFutureInterface>
#include <QPointer> #include <QPointer>
#include <QStringList> #include <QStringList>
@@ -40,8 +39,7 @@ public:
Utils::FilePath executableFilePath() const; Utils::FilePath executableFilePath() const;
virtual Utils::FilePath testExecutable() const { return executableFilePath(); }; virtual Utils::FilePath testExecutable() const { return executableFilePath(); };
virtual TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi, virtual TestOutputReader *createOutputReader(Utils::QtcProcess *app) const = 0;
Utils::QtcProcess *app) const = 0;
virtual Utils::Environment filteredEnvironment(const Utils::Environment &original) const; virtual Utils::Environment filteredEnvironment(const Utils::Environment &original) const;
ITestBase *testBase() const { return m_testBase; } ITestBase *testBase() const { return m_testBase; }

View File

@@ -4,17 +4,12 @@
#include "testoutputreader.h" #include "testoutputreader.h"
#include "autotesttr.h" #include "autotesttr.h"
#include "testresult.h"
#include "testresultspane.h"
#include "testtreeitem.h" #include "testtreeitem.h"
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <QDebug> #include <QRegularExpression>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
using namespace Utils; using namespace Utils;
@@ -26,11 +21,8 @@ FilePath TestOutputReader::constructSourceFilePath(const FilePath &path, const Q
return filePath.isReadableFile() ? filePath : FilePath(); return filePath.isReadableFile() ? filePath : FilePath();
} }
TestOutputReader::TestOutputReader(const QFutureInterface<TestResult> &futureInterface, TestOutputReader::TestOutputReader(QtcProcess *testApplication, const FilePath &buildDirectory)
QtcProcess *testApplication, const FilePath &buildDirectory) : m_buildDir(buildDirectory)
: m_futureInterface(futureInterface)
, m_buildDir(buildDirectory)
, m_id(testApplication ? testApplication->commandLine().executable().toUserOutput() : QString())
{ {
auto chopLineBreak = [](QByteArray line) { auto chopLineBreak = [](QByteArray line) {
if (line.endsWith('\n')) if (line.endsWith('\n'))
@@ -41,6 +33,9 @@ TestOutputReader::TestOutputReader(const QFutureInterface<TestResult> &futureInt
}; };
if (testApplication) { if (testApplication) {
connect(testApplication, &QtcProcess::started, this, [this, testApplication] {
m_id = testApplication->commandLine().executable().toUserOutput();
});
testApplication->setStdOutLineCallback([this, &chopLineBreak](const QString &line) { testApplication->setStdOutLineCallback([this, &chopLineBreak](const QString &line) {
processStdOutput(chopLineBreak(line.toUtf8())); processStdOutput(chopLineBreak(line.toUtf8()));
}); });

View File

@@ -5,9 +5,7 @@
#include "testresult.h" #include "testresult.h"
#include <QFutureInterface>
#include <QObject> #include <QObject>
#include <QString>
namespace Utils { class QtcProcess; } namespace Utils { class QtcProcess; }
@@ -17,8 +15,7 @@ class TestOutputReader : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
TestOutputReader(const QFutureInterface<TestResult> &futureInterface, TestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
virtual ~TestOutputReader(); virtual ~TestOutputReader();
void processStdOutput(const QByteArray &outputLine); void processStdOutput(const QByteArray &outputLine);
virtual void processStdError(const QByteArray &outputLine); virtual void processStdError(const QByteArray &outputLine);
@@ -46,7 +43,6 @@ protected:
void sendAndResetSanitizerResult(); void sendAndResetSanitizerResult();
void reportResult(const TestResult &result); void reportResult(const TestResult &result);
QFutureInterface<TestResult> m_futureInterface;
Utils::FilePath m_buildDir; Utils::FilePath m_buildDir;
QString m_id; QString m_id;
QHash<ResultType, int> m_summary; QHash<ResultType, int> m_summary;

View File

@@ -6,18 +6,15 @@
#include "autotestconstants.h" #include "autotestconstants.h"
#include "autotestplugin.h" #include "autotestplugin.h"
#include "autotesttr.h" #include "autotesttr.h"
#include "itestframework.h"
#include "testoutputreader.h" #include "testoutputreader.h"
#include "testprojectsettings.h" #include "testprojectsettings.h"
#include "testresultspane.h" #include "testresultspane.h"
#include "testrunconfiguration.h" #include "testrunconfiguration.h"
#include "testsettings.h"
#include "testtreeitem.h" #include "testtreeitem.h"
#include "testtreemodel.h" #include "testtreemodel.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/progressmanager/futureprogress.h> #include <coreplugin/progressmanager/taskprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <debugger/debuggerkitinformation.h> #include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerruncontrol.h> #include <debugger/debuggerruncontrol.h>
@@ -47,10 +44,9 @@
#include <QLabel> #include <QLabel>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPointer> #include <QPointer>
#include <QProcess>
#include <QPushButton> #include <QPushButton>
#include <QTimer>
using namespace Core;
using namespace ProjectExplorer; using namespace ProjectExplorer;
using namespace Utils; using namespace Utils;
@@ -72,14 +68,7 @@ TestRunner::TestRunner()
m_cancelTimer.setSingleShot(true); m_cancelTimer.setSingleShot(true);
connect(&m_cancelTimer, &QTimer::timeout, this, [this] { cancelCurrent(Timeout); }); connect(&m_cancelTimer, &QTimer::timeout, this, [this] { cancelCurrent(Timeout); });
connect(&m_futureWatcher, &QFutureWatcher<TestResult>::finished, connect(this, &TestRunner::requestStopTestRun, this, [this] { cancelCurrent(UserCanceled); });
this, &TestRunner::onFinished);
connect(this, &TestRunner::requestStopTestRun,
&m_futureWatcher, &QFutureWatcher<TestResult>::cancel);
connect(&m_futureWatcher, &QFutureWatcher<TestResult>::canceled, this, [this] {
cancelCurrent(UserCanceled);
reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user."));
});
connect(BuildManager::instance(), &BuildManager::buildQueueFinished, connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
this, &TestRunner::onBuildQueueFinished); this, &TestRunner::onBuildQueueFinished);
} }
@@ -93,7 +82,7 @@ TestRunner::~TestRunner()
void TestRunner::runTest(TestRunMode mode, const ITestTreeItem *item) void TestRunner::runTest(TestRunMode mode, const ITestTreeItem *item)
{ {
QTC_ASSERT(!m_executingTests, return); QTC_ASSERT(!isTestRunning(), return);
ITestConfiguration *configuration = item->asConfiguration(mode); ITestConfiguration *configuration = item->asConfiguration(mode);
if (configuration) if (configuration)
@@ -144,180 +133,21 @@ static QString constructOmittedVariablesDetailsString(const EnvironmentItems &di
+ '\n' + removedVars.join('\n'); + '\n' + removedVars.join('\n');
} }
bool TestRunner::currentConfigValid()
{
const FilePath commandFilePath = m_currentConfig->testExecutable();
if (!commandFilePath.isEmpty())
return true;
reportResult(ResultType::MessageFatal,
Tr::tr("Executable path is empty. (%1)").arg(m_currentConfig->displayName()));
delete m_currentConfig;
m_currentConfig = nullptr;
if (m_selectedTests.isEmpty()) {
if (m_fakeFutureInterface)
m_fakeFutureInterface->reportFinished();
onFinished();
} else {
onProcessDone();
}
return false;
}
void TestRunner::setUpProcessEnv()
{
CommandLine command = m_currentProcess->commandLine();
if (m_currentConfig->testBase()->type() == ITestBase::Framework) {
TestConfiguration *current = static_cast<TestConfiguration *>(m_currentConfig);
QStringList omitted;
command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw);
if (!omitted.isEmpty()) {
const QString &details = constructOmittedDetailsString(omitted);
reportResult(ResultType::MessageWarn, details.arg(current->displayName()));
}
} else {
TestToolConfiguration *current = static_cast<TestToolConfiguration *>(m_currentConfig);
command.setArguments(current->commandLine().arguments());
}
m_currentProcess->setCommand(command);
m_currentProcess->setWorkingDirectory(m_currentConfig->workingDirectory());
const Environment &original = m_currentConfig->environment();
Environment environment = m_currentConfig->filteredEnvironment(original);
const EnvironmentItems removedVariables = Utils::filtered(
original.diff(environment), [](const EnvironmentItem &it) {
return it.operation == EnvironmentItem::Unset;
});
if (!removedVariables.isEmpty()) {
const QString &details = constructOmittedVariablesDetailsString(removedVariables)
.arg(m_currentConfig->displayName());
reportResult(ResultType::MessageWarn, details);
}
m_currentProcess->setEnvironment(environment);
}
void TestRunner::scheduleNext()
{
QTC_ASSERT(!m_selectedTests.isEmpty(), onFinished(); return);
QTC_ASSERT(!m_currentConfig && !m_currentProcess, resetInternalPointers());
QTC_ASSERT(m_fakeFutureInterface, onFinished(); return);
QTC_ASSERT(!m_canceled, onFinished(); return);
m_currentConfig = m_selectedTests.takeFirst();
if (!currentConfigValid())
return;
if (!m_currentConfig->project())
onProcessDone();
m_currentProcess = new QtcProcess;
m_currentProcess->setCommand({m_currentConfig->testExecutable(), {}});
QTC_ASSERT(!m_currentOutputReader, delete m_currentOutputReader);
m_currentOutputReader = m_currentConfig->createOutputReader(*m_fakeFutureInterface, m_currentProcess);
QTC_ASSERT(m_currentOutputReader, onProcessDone(); return);
connect(m_currentOutputReader, &TestOutputReader::newResult, this, &TestRunner::testResultReady);
connect(m_currentOutputReader, &TestOutputReader::newOutputLineAvailable,
TestResultsPane::instance(), &TestResultsPane::addOutputLine);
setUpProcessEnv();
connect(m_currentProcess, &QtcProcess::done, this, &TestRunner::onProcessDone);
const int timeout = AutotestPlugin::settings()->timeout;
m_cancelTimer.setInterval(timeout);
m_cancelTimer.start();
qCInfo(runnerLog) << "Command:" << m_currentProcess->commandLine().executable();
qCInfo(runnerLog) << "Arguments:" << m_currentProcess->commandLine().arguments();
qCInfo(runnerLog) << "Working directory:" << m_currentProcess->workingDirectory();
qCDebug(runnerLog) << "Environment:" << m_currentProcess->environment().toStringList();
m_currentProcess->start();
}
void TestRunner::cancelCurrent(TestRunner::CancelReason reason) void TestRunner::cancelCurrent(TestRunner::CancelReason reason)
{ {
m_canceled = true;
if (m_fakeFutureInterface)
m_fakeFutureInterface->reportCanceled();
if (reason == KitChanged) if (reason == KitChanged)
reportResult(ResultType::MessageWarn, Tr::tr("Current kit has changed. Canceling test run.")); reportResult(ResultType::MessageWarn, Tr::tr("Current kit has changed. Canceling test run."));
else if (reason == Timeout) else if (reason == Timeout)
reportResult(ResultType::MessageFatal, Tr::tr("Test case canceled due to timeout.\nMaybe raise the timeout?")); reportResult(ResultType::MessageFatal, Tr::tr("Test case canceled due to timeout.\nMaybe raise the timeout?"));
else if (reason == UserCanceled)
// if user or timeout cancels the current run ensure to kill the running process reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user."));
if (m_currentProcess && m_currentProcess->state() != QProcess::NotRunning) { m_taskTree.reset();
m_currentProcess->kill(); onFinished();
m_currentProcess->waitForFinished();
}
}
void TestRunner::onProcessDone()
{
if (m_currentProcess->result() == ProcessResult::StartFailed) {
reportResult(ResultType::MessageFatal,
Tr::tr("Failed to start test for project \"%1\".").arg(m_currentConfig->displayName())
+ processInformation(m_currentProcess) + rcInfo(m_currentConfig));
}
if (m_executingTests && m_currentConfig) {
QTC_CHECK(m_fakeFutureInterface);
m_fakeFutureInterface->setProgressValue(m_fakeFutureInterface->progressValue()
+ m_currentConfig->testCaseCount());
if (m_currentProcess && !m_fakeFutureInterface->isCanceled()) {
if (m_currentProcess->exitStatus() == QProcess::CrashExit) {
if (m_currentOutputReader)
m_currentOutputReader->reportCrash();
reportResult(ResultType::MessageFatal,
Tr::tr("Test for project \"%1\" crashed.").arg(m_currentConfig->displayName())
+ processInformation(m_currentProcess) + rcInfo(m_currentConfig));
} else if (m_currentOutputReader && !m_currentOutputReader->hadValidOutput()) {
reportResult(ResultType::MessageFatal,
Tr::tr("Test for project \"%1\" did not produce any expected output.")
.arg(m_currentConfig->displayName()) + processInformation(m_currentProcess)
+ rcInfo(m_currentConfig));
}
}
}
if (m_currentOutputReader) {
const int disabled = m_currentOutputReader->disabledTests();
if (disabled > 0)
emit hadDisabledTests(disabled);
if (m_currentOutputReader->hasSummary())
emit reportSummary(m_currentOutputReader->id(), m_currentOutputReader->summary());
m_currentOutputReader->resetCommandlineColor();
}
resetInternalPointers();
if (!m_fakeFutureInterface) {
QTC_ASSERT(!m_executingTests, m_executingTests = false);
return;
}
if (!m_selectedTests.isEmpty() && !m_fakeFutureInterface->isCanceled())
scheduleNext();
else
m_fakeFutureInterface->reportFinished();
}
void TestRunner::resetInternalPointers()
{
delete m_currentOutputReader;
if (m_currentProcess)
m_currentProcess->deleteLater();
delete m_currentConfig;
m_currentOutputReader = nullptr;
m_currentProcess = nullptr;
m_currentConfig = nullptr;
} }
void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &selectedTests) void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &selectedTests)
{ {
QTC_ASSERT(!m_executingTests, return); QTC_ASSERT(!isTestRunning(), return);
qDeleteAll(m_selectedTests); qDeleteAll(m_selectedTests);
m_selectedTests = selectedTests; m_selectedTests = selectedTests;
@@ -332,8 +162,6 @@ void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &s
return; return;
} }
m_executingTests = true;
m_canceled = false;
emit testRunStarted(); emit testRunStarted();
// clear old log and output pane // clear old log and output pane
@@ -411,7 +239,7 @@ static RunConfiguration *getRunConfiguration(const QString &buildTargetKey)
if (runConfigurations.size() == 1) if (runConfigurations.size() == 1)
return runConfigurations.first(); return runConfigurations.first();
RunConfigurationSelectionDialog dialog(buildTargetKey, Core::ICore::dialogParent()); RunConfigurationSelectionDialog dialog(buildTargetKey, ICore::dialogParent());
if (dialog.exec() == QDialog::Accepted) { if (dialog.exec() == QDialog::Accepted) {
const QString dName = dialog.displayName(); const QString dName = dialog.displayName();
if (dName.isEmpty()) if (dName.isEmpty())
@@ -513,19 +341,136 @@ void TestRunner::runTestsHelper()
return; return;
} }
int testCaseCount = precheckTestConfigurations(); const int testCaseCount = precheckTestConfigurations();
Q_UNUSED(testCaseCount) // TODO: may be useful for fine-grained progress reporting, when fixed
// Fake future interface - destruction will be handled by QFuture/QFutureWatcher struct TestStorage {
m_fakeFutureInterface = new QFutureInterface<TestResult>(QFutureInterfaceBase::Running); std::unique_ptr<TestOutputReader> m_outputReader;
QFuture<TestResult> future = m_fakeFutureInterface->future(); };
m_fakeFutureInterface->setProgressRange(0, testCaseCount);
m_fakeFutureInterface->setProgressValue(0); using namespace Tasking;
m_futureWatcher.setFuture(future); QList<TaskItem> tasks{optional};
for (ITestConfiguration *config : m_selectedTests) {
QTC_ASSERT(config, continue);
const TreeStorage<TestStorage> storage;
const auto onGroupSetup = [this, config] {
if (!config->project())
return TaskAction::StopWithDone;
if (config->testExecutable().isEmpty()) {
reportResult(ResultType::MessageFatal,
Tr::tr("Executable path is empty. (%1)").arg(config->displayName()));
return TaskAction::StopWithDone;
}
return TaskAction::Continue;
};
const auto onSetup = [this, config, storage](QtcProcess &process) {
TestStorage *testStorage = storage.activeStorage();
QTC_ASSERT(testStorage, return);
testStorage->m_outputReader.reset(config->createOutputReader(&process));
QTC_ASSERT(testStorage->m_outputReader, return);
connect(testStorage->m_outputReader.get(), &TestOutputReader::newResult,
this, &TestRunner::testResultReady);
connect(testStorage->m_outputReader.get(), &TestOutputReader::newOutputLineAvailable,
TestResultsPane::instance(), &TestResultsPane::addOutputLine);
CommandLine command{config->testExecutable(), {}};
if (config->testBase()->type() == ITestBase::Framework) {
TestConfiguration *current = static_cast<TestConfiguration *>(config);
QStringList omitted;
command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw);
if (!omitted.isEmpty()) {
const QString &details = constructOmittedDetailsString(omitted);
reportResult(ResultType::MessageWarn, details.arg(current->displayName()));
}
} else {
TestToolConfiguration *current = static_cast<TestToolConfiguration *>(config);
command.setArguments(current->commandLine().arguments());
}
process.setCommand(command);
process.setWorkingDirectory(config->workingDirectory());
const Environment &original = config->environment();
Environment environment = config->filteredEnvironment(original);
const EnvironmentItems removedVariables = Utils::filtered(
original.diff(environment), [](const EnvironmentItem &it) {
return it.operation == EnvironmentItem::Unset;
});
if (!removedVariables.isEmpty()) {
const QString &details = constructOmittedVariablesDetailsString(removedVariables)
.arg(config->displayName());
reportResult(ResultType::MessageWarn, details);
}
process.setEnvironment(environment);
m_cancelTimer.setInterval(AutotestPlugin::settings()->timeout);
m_cancelTimer.start();
qCInfo(runnerLog) << "Command:" << process.commandLine().executable();
qCInfo(runnerLog) << "Arguments:" << process.commandLine().arguments();
qCInfo(runnerLog) << "Working directory:" << process.workingDirectory();
qCDebug(runnerLog) << "Environment:" << process.environment().toStringList();
};
const auto onDone = [this, config, storage](const QtcProcess &process) {
TestStorage *testStorage = storage.activeStorage();
QTC_ASSERT(testStorage, return);
if (process.result() == ProcessResult::StartFailed) {
reportResult(ResultType::MessageFatal,
Tr::tr("Failed to start test for project \"%1\".").arg(config->displayName())
+ processInformation(&process) + rcInfo(config));
}
if (process.exitStatus() == QProcess::CrashExit) {
if (testStorage->m_outputReader)
testStorage->m_outputReader->reportCrash();
reportResult(ResultType::MessageFatal,
Tr::tr("Test for project \"%1\" crashed.").arg(config->displayName())
+ processInformation(&process) + rcInfo(config));
} else if (testStorage->m_outputReader && !testStorage->m_outputReader->hadValidOutput()) {
reportResult(ResultType::MessageFatal,
Tr::tr("Test for project \"%1\" did not produce any expected output.")
.arg(config->displayName()) + processInformation(&process)
+ rcInfo(config));
}
if (testStorage->m_outputReader) {
const int disabled = testStorage->m_outputReader->disabledTests();
if (disabled > 0)
emit hadDisabledTests(disabled);
if (testStorage->m_outputReader->hasSummary())
emit reportSummary(testStorage->m_outputReader->id(), testStorage->m_outputReader->summary());
testStorage->m_outputReader->resetCommandlineColor();
}
};
const Group group {
optional,
Storage(storage),
OnGroupSetup(onGroupSetup),
Process(onSetup, onDone, onDone)
};
tasks.append(group);
}
m_taskTree.reset(new TaskTree(tasks));
connect(m_taskTree.get(), &TaskTree::done, this, &TestRunner::onFinished);
connect(m_taskTree.get(), &TaskTree::errorOccurred, this, &TestRunner::onFinished);
auto progress = new TaskProgress(m_taskTree.get());
progress->setDisplayName(tr("Running Tests"));
progress->setAutoStopOnCancel(false);
progress->setHalfLifeTimePerTask(10000); // 10 seconds
connect(progress, &TaskProgress::canceled, this, [this, progress] {
// progress was a child of task tree which is going to be deleted directly. Unwind properly.
progress->setParent(nullptr);
progress->deleteLater();
cancelCurrent(UserCanceled);
});
Core::ProgressManager::addTask(future, Tr::tr("Running Tests"), Autotest::Constants::TASK_INDEX);
if (AutotestPlugin::settings()->popupOnStart) if (AutotestPlugin::settings()->popupOnStart)
AutotestPlugin::popupResultsPane(); AutotestPlugin::popupResultsPane();
scheduleNext();
m_taskTree->start();
} }
static void processOutput(TestOutputReader *outputreader, const QString &msg, OutputFormat format) static void processOutput(TestOutputReader *outputreader, const QString &msg, OutputFormat format)
@@ -626,13 +571,8 @@ void TestRunner::debugTests()
} }
} }
// We need a fake QFuture for the results. TODO: replace with QtConcurrent::run
QFutureInterface<TestResult> *futureInterface
= new QFutureInterface<TestResult>(QFutureInterfaceBase::Running);
m_futureWatcher.setFuture(futureInterface->future());
if (useOutputProcessor) { if (useOutputProcessor) {
TestOutputReader *outputreader = config->createOutputReader(*futureInterface, nullptr); TestOutputReader *outputreader = config->createOutputReader(nullptr);
connect(outputreader, &TestOutputReader::newResult, this, &TestRunner::testResultReady); connect(outputreader, &TestOutputReader::newResult, this, &TestRunner::testResultReady);
outputreader->setId(inferior.command.executable().toString()); outputreader->setId(inferior.command.executable().toString());
connect(outputreader, &TestOutputReader::newOutputLineAvailable, connect(outputreader, &TestOutputReader::newOutputLineAvailable,
@@ -641,9 +581,7 @@ void TestRunner::debugTests()
this, [outputreader](const QString &msg, OutputFormat format) { this, [outputreader](const QString &msg, OutputFormat format) {
processOutput(outputreader, msg, format); processOutput(outputreader, msg, format);
}); });
connect(runControl, &RunControl::stopped, outputreader, &QObject::deleteLater);
connect(runControl, &RunControl::stopped,
outputreader, &QObject::deleteLater);
} }
m_stopDebugConnect = connect(this, &TestRunner::requestStopTestRun, m_stopDebugConnect = connect(this, &TestRunner::requestStopTestRun,
@@ -671,7 +609,7 @@ void TestRunner::runOrDebugTests()
if (!m_skipTargetsCheck) { if (!m_skipTargetsCheck) {
if (executablesEmpty()) { if (executablesEmpty()) {
m_skipTargetsCheck = true; m_skipTargetsCheck = true;
Target * target = SessionManager::startupTarget(); Target *target = SessionManager::startupTarget();
QTimer::singleShot(5000, this, [this, target = QPointer<Target>(target)] { QTimer::singleShot(5000, this, [this, target = QPointer<Target>(target)] {
if (target) { if (target) {
disconnect(target, &Target::buildSystemUpdated, disconnect(target, &Target::buildSystemUpdated,
@@ -706,8 +644,7 @@ void TestRunner::buildProject(Project *project)
BuildManager *buildManager = BuildManager::instance(); BuildManager *buildManager = BuildManager::instance();
m_buildConnect = connect(this, &TestRunner::requestStopTestRun, m_buildConnect = connect(this, &TestRunner::requestStopTestRun,
buildManager, &BuildManager::cancel); buildManager, &BuildManager::cancel);
connect(buildManager, &BuildManager::buildQueueFinished, connect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished);
this, &TestRunner::buildFinished);
BuildManager::buildProjectWithDependencies(project); BuildManager::buildProjectWithDependencies(project);
if (!BuildManager::isBuilding()) if (!BuildManager::isBuilding())
buildFinished(false); buildFinished(false);
@@ -717,18 +654,14 @@ void TestRunner::buildFinished(bool success)
{ {
disconnect(m_buildConnect); disconnect(m_buildConnect);
BuildManager *buildManager = BuildManager::instance(); BuildManager *buildManager = BuildManager::instance();
disconnect(buildManager, &BuildManager::buildQueueFinished, disconnect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished);
this, &TestRunner::buildFinished);
if (success) { if (success) {
if (!m_canceled)
runOrDebugTests(); runOrDebugTests();
else if (m_executingTests) return;
onFinished(); }
} else {
reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run.")); reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run."));
onFinished(); onFinished();
}
} }
static RunAfterBuildMode runAfterBuild() static RunAfterBuildMode runAfterBuild()
@@ -747,7 +680,7 @@ static RunAfterBuildMode runAfterBuild()
void TestRunner::onBuildQueueFinished(bool success) void TestRunner::onBuildQueueFinished(bool success)
{ {
if (m_executingTests || !m_selectedTests.isEmpty()) // paranoia! if (isTestRunning() || !m_selectedTests.isEmpty()) // paranoia!
return; return;
if (!success || m_runMode != TestRunMode::None) if (!success || m_runMode != TestRunMode::None)
@@ -768,17 +701,15 @@ void TestRunner::onBuildQueueFinished(bool success)
void TestRunner::onFinished() void TestRunner::onFinished()
{ {
m_cancelTimer.stop(); if (m_taskTree)
// if we've been canceled and we still have test configurations queued just throw them away m_taskTree.release()->deleteLater();
qDeleteAll(m_selectedTests);
m_selectedTests.clear();
disconnect(m_stopDebugConnect); disconnect(m_stopDebugConnect);
disconnect(m_finishDebugConnect); disconnect(m_finishDebugConnect);
disconnect(m_targetConnect); disconnect(m_targetConnect);
m_fakeFutureInterface = nullptr; qDeleteAll(m_selectedTests);
m_selectedTests.clear();
m_cancelTimer.stop();
m_runMode = TestRunMode::None; m_runMode = TestRunMode::None;
m_executingTests = false;
emit testRunFinished(); emit testRunFinished();
} }

View File

@@ -4,12 +4,11 @@
#pragma once #pragma once
#include "autotest_global.h" #include "autotest_global.h"
#include "testresult.h"
#include "autotestconstants.h"
#include <QDialog> #include <QDialog>
#include <QFutureWatcher>
#include <QList> #include <QList>
#include <QObject>
#include <QTimer> #include <QTimer>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -20,13 +19,14 @@ class QLabel;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace ProjectExplorer { class Project; } namespace ProjectExplorer { class Project; }
namespace Utils { class QtcProcess; } namespace Utils { class TaskTree; }
namespace Autotest { namespace Autotest {
enum class TestRunMode;
class ITestConfiguration; class ITestConfiguration;
class TestOutputReader; class ITestTreeItem;
class TestResult;
enum class ResultType;
namespace Internal { namespace Internal {
@@ -44,7 +44,7 @@ public:
void runTests(TestRunMode mode, const QList<ITestConfiguration *> &selectedTests); void runTests(TestRunMode mode, const QList<ITestConfiguration *> &selectedTests);
void runTest(TestRunMode mode, const ITestTreeItem *item); void runTest(TestRunMode mode, const ITestTreeItem *item);
bool isTestRunning() const { return m_executingTests; } bool isTestRunning() const { return m_buildConnect || m_stopDebugConnect || m_taskTree.get(); }
signals: signals:
void testRunStarted(); void testRunStarted();
@@ -61,12 +61,7 @@ private:
void onFinished(); void onFinished();
int precheckTestConfigurations(); int precheckTestConfigurations();
bool currentConfigValid();
void setUpProcessEnv();
void scheduleNext();
void cancelCurrent(CancelReason reason); void cancelCurrent(CancelReason reason);
void onProcessDone();
void resetInternalPointers();
void runTestsHelper(); void runTestsHelper();
void debugTests(); void debugTests();
@@ -75,14 +70,9 @@ private:
bool postponeTestRunWithEmptyExecutable(ProjectExplorer::Project *project); bool postponeTestRunWithEmptyExecutable(ProjectExplorer::Project *project);
void onBuildSystemUpdated(); void onBuildSystemUpdated();
QFutureWatcher<TestResult> m_futureWatcher; std::unique_ptr<Utils::TaskTree> m_taskTree;
QFutureInterface<TestResult> *m_fakeFutureInterface = nullptr;
QList<ITestConfiguration *> m_selectedTests; QList<ITestConfiguration *> m_selectedTests;
bool m_executingTests = false;
bool m_canceled = false;
ITestConfiguration *m_currentConfig = nullptr;
Utils::QtcProcess *m_currentProcess = nullptr;
TestOutputReader *m_currentOutputReader = nullptr;
TestRunMode m_runMode = TestRunMode::None; TestRunMode m_runMode = TestRunMode::None;
// temporarily used if building before running is necessary // temporarily used if building before running is necessary