Report test results through QFutureInterface

Change-Id: Id5704ff2744ee925978c518f44069b26c66d682e
Reviewed-by: Christian Stenger <christian.stenger@theqtcompany.com>
This commit is contained in:
Eike Ziller
2016-01-19 14:24:09 +01:00
parent ca8f7c56cd
commit 3b423602ed
5 changed files with 64 additions and 88 deletions

View File

@@ -131,10 +131,15 @@ static QString constructBenchmarkInformation(const QString &metric, double value
.arg(iterations); .arg(iterations);
} }
TestOutputReader::TestOutputReader(QProcess *testApplication, OutputType type) TestOutputReader::TestOutputReader(QFutureInterface<TestResult *> futureInterface,
: m_testApplication(testApplication) QProcess *testApplication, TestType type)
, m_type(type) : m_testApplication(testApplication),
m_futureInterface(futureInterface)
{ {
if (type == TestTypeQt)
connect(testApplication, &QProcess::readyRead, this, &TestOutputReader::processOutput);
else
connect(testApplication, &QProcess::readyRead, this, &TestOutputReader::processGTestOutput);
} }
enum CDATAMode { enum CDATAMode {
@@ -186,7 +191,7 @@ void TestOutputReader::processOutput()
auto testResult = new QTestResult(className); auto testResult = new QTestResult(className);
testResult->setResult(Result::MessageTestCaseStart); testResult->setResult(Result::MessageTestCaseStart);
testResult->setDescription(tr("Executing test case %1").arg(className)); testResult->setDescription(tr("Executing test case %1").arg(className));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} else if (currentTag == QStringLiteral("TestFunction")) { } else if (currentTag == QStringLiteral("TestFunction")) {
testCase = xmlReader.attributes().value(QStringLiteral("name")).toString(); testCase = xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!testCase.isEmpty(), continue); QTC_ASSERT(!testCase.isEmpty(), continue);
@@ -194,7 +199,7 @@ void TestOutputReader::processOutput()
testResult->setResult(Result::MessageCurrentTest); testResult->setResult(Result::MessageCurrentTest);
testResult->setDescription(tr("Entering test function %1::%2").arg(className, testResult->setDescription(tr("Entering test function %1::%2").arg(className,
testCase)); testCase));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} else if (currentTag == QStringLiteral("Duration")) { } else if (currentTag == QStringLiteral("Duration")) {
duration = xmlReader.attributes().value(QStringLiteral("msecs")).toString(); duration = xmlReader.attributes().value(QStringLiteral("msecs")).toString();
QTC_ASSERT(!duration.isEmpty(), continue); QTC_ASSERT(!duration.isEmpty(), continue);
@@ -280,16 +285,16 @@ void TestOutputReader::processOutput()
testResult->setTestCase(testCase); testResult->setTestCase(testCase);
testResult->setResult(Result::MessageInternal); testResult->setResult(Result::MessageInternal);
testResult->setDescription(tr("Execution took %1 ms.").arg(duration)); testResult->setDescription(tr("Execution took %1 ms.").arg(duration));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} }
emit increaseProgress(); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
} else if (currentTag == QStringLiteral("TestCase")) { } else if (currentTag == QStringLiteral("TestCase")) {
auto testResult = new QTestResult(className); auto testResult = new QTestResult(className);
testResult->setResult(Result::MessageTestCaseEnd); testResult->setResult(Result::MessageTestCaseEnd);
testResult->setDescription( testResult->setDescription(
duration.isEmpty() ? tr("Test finished.") duration.isEmpty() ? tr("Test finished.")
: tr("Test execution took %1 ms.").arg(duration)); : tr("Test execution took %1 ms.").arg(duration));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} else if (validEndTags.contains(currentTag.toString())) { } else if (validEndTags.contains(currentTag.toString())) {
auto testResult = new QTestResult(className); auto testResult = new QTestResult(className);
testResult->setTestCase(testCase); testResult->setTestCase(testCase);
@@ -298,7 +303,7 @@ void TestOutputReader::processOutput()
testResult->setFileName(file); testResult->setFileName(file);
testResult->setLine(lineNumber); testResult->setLine(lineNumber);
testResult->setDescription(description); testResult->setDescription(description);
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} }
break; break;
} }
@@ -348,7 +353,7 @@ void TestOutputReader::processGTestOutput()
auto testResult = new GTestResult(); auto testResult = new GTestResult();
testResult->setResult(Result::MessageInternal); testResult->setResult(Result::MessageInternal);
testResult->setDescription(line); testResult->setDescription(line);
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
description.clear(); description.clear();
} else if (disabledTests.exactMatch(line)) { } else if (disabledTests.exactMatch(line)) {
auto testResult = new GTestResult(); auto testResult = new GTestResult();
@@ -356,7 +361,7 @@ void TestOutputReader::processGTestOutput()
int disabled = disabledTests.cap(1).toInt(); int disabled = disabledTests.cap(1).toInt();
testResult->setDescription(tr("You have %n disabled test(s).", 0, disabled)); testResult->setDescription(tr("You have %n disabled test(s).", 0, disabled));
testResult->setLine(disabled); // misuse line property to hold number of disabled testResult->setLine(disabled); // misuse line property to hold number of disabled
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
description.clear(); description.clear();
} }
continue; continue;
@@ -367,7 +372,7 @@ void TestOutputReader::processGTestOutput()
testResult->setTestCase(currentTestSet); testResult->setTestCase(currentTestSet);
testResult->setResult(Result::MessageTestCaseEnd); testResult->setResult(Result::MessageTestCaseEnd);
testResult->setDescription(tr("Test execution took %1").arg(testEnds.cap(2))); testResult->setDescription(tr("Test execution took %1").arg(testEnds.cap(2)));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
currentTestName.clear(); currentTestName.clear();
currentTestSet.clear(); currentTestSet.clear();
} else if (newTestStarts.exactMatch(line)) { } else if (newTestStarts.exactMatch(line)) {
@@ -375,24 +380,24 @@ void TestOutputReader::processGTestOutput()
auto testResult = new GTestResult(currentTestName); auto testResult = new GTestResult(currentTestName);
testResult->setResult(Result::MessageTestCaseStart); testResult->setResult(Result::MessageTestCaseStart);
testResult->setDescription(tr("Executing test case %1").arg(currentTestName)); testResult->setDescription(tr("Executing test case %1").arg(currentTestName));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} else if (newTestSetStarts.exactMatch(line)) { } else if (newTestSetStarts.exactMatch(line)) {
currentTestSet = newTestSetStarts.cap(1); currentTestSet = newTestSetStarts.cap(1);
auto testResult = new GTestResult(); auto testResult = new GTestResult();
testResult->setResult(Result::MessageCurrentTest); testResult->setResult(Result::MessageCurrentTest);
testResult->setDescription(tr("Entering test set %1").arg(currentTestSet)); testResult->setDescription(tr("Entering test set %1").arg(currentTestSet));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
} else if (testSetSuccess.exactMatch(line)) { } else if (testSetSuccess.exactMatch(line)) {
auto testResult = new GTestResult(currentTestName); auto testResult = new GTestResult(currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(currentTestSet);
testResult->setResult(Result::Pass); testResult->setResult(Result::Pass);
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
testResult = new GTestResult(currentTestName); testResult = new GTestResult(currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(currentTestSet);
testResult->setResult(Result::MessageInternal); testResult->setResult(Result::MessageInternal);
testResult->setDescription(tr("Execution took %1.").arg(testSetSuccess.cap(2))); testResult->setDescription(tr("Execution took %1.").arg(testSetSuccess.cap(2)));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
emit increaseProgress(); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
} else if (testSetFail.exactMatch(line)) { } else if (testSetFail.exactMatch(line)) {
auto testResult = new GTestResult(currentTestName); auto testResult = new GTestResult(currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(currentTestSet);
@@ -409,14 +414,14 @@ void TestOutputReader::processGTestOutput()
testResult->setFileName(file); testResult->setFileName(file);
testResult->setLine(line.toInt()); testResult->setLine(line.toInt());
} }
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
description.clear(); description.clear();
testResult = new GTestResult(currentTestName); testResult = new GTestResult(currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(currentTestSet);
testResult->setResult(Result::MessageInternal); testResult->setResult(Result::MessageInternal);
testResult->setDescription(tr("Execution took %1.").arg(testSetFail.cap(2))); testResult->setDescription(tr("Execution took %1.").arg(testSetFail.cap(2)));
testResultCreated(testResult); m_futureInterface.reportResult(testResult);
emit increaseProgress(); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
} }
} }
} }

View File

@@ -22,6 +22,7 @@
#include "testresult.h" #include "testresult.h"
#include <QFutureInterface>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
@@ -36,24 +37,15 @@ class TestOutputReader : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum OutputType { TestOutputReader(QFutureInterface<TestResult *> futureInterface,
Qt, QProcess *testApplication, TestType type);
GTest
};
TestOutputReader(QProcess *testApplication, OutputType type = Qt); private:
public slots:
void processOutput(); void processOutput();
void processGTestOutput(); void processGTestOutput();
signals:
void testResultCreated(TestResult *testResult);
void increaseProgress();
private:
QProcess *m_testApplication; // not owned QProcess *m_testApplication; // not owned
OutputType m_type; QFutureInterface<TestResult *> m_futureInterface;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -128,6 +128,8 @@ TestResultsPane::TestResultsPane(QObject *parent) :
this, &TestResultsPane::onTestRunStarted); this, &TestResultsPane::onTestRunStarted);
connect(TestRunner::instance(), &TestRunner::testRunFinished, connect(TestRunner::instance(), &TestRunner::testRunFinished,
this, &TestResultsPane::onTestRunFinished); this, &TestResultsPane::onTestRunFinished);
connect(TestRunner::instance(), &TestRunner::testResultReady,
this, &TestResultsPane::addTestResult);
} }
void TestResultsPane::createToolButtons() void TestResultsPane::createToolButtons()

View File

@@ -36,7 +36,6 @@
#include <QFuture> #include <QFuture>
#include <QFutureInterface> #include <QFutureInterface>
#include <QMetaObject>
#include <QTime> #include <QTime>
namespace Autotest { namespace Autotest {
@@ -44,11 +43,6 @@ namespace Internal {
static TestRunner *m_instance = 0; static TestRunner *m_instance = 0;
static void emitTestResultCreated(TestResult *testResult)
{
emit m_instance->testResultCreated(testResult);
}
static QString executableFilePath(const QString &command, const QProcessEnvironment &environment) static QString executableFilePath(const QString &command, const QProcessEnvironment &environment)
{ {
if (command.isEmpty()) if (command.isEmpty())
@@ -89,6 +83,17 @@ TestRunner::TestRunner(QObject *parent) :
QObject(parent), QObject(parent),
m_executingTests(false) m_executingTests(false)
{ {
connect(&m_futureWatcher, &QFutureWatcher<TestResult *>::resultReadyAt,
this, [this](int index) { emit testResultReady(m_futureWatcher.resultAt(index)); });
connect(&m_futureWatcher, &QFutureWatcher<TestResult *>::finished,
this, &TestRunner::onFinished);
connect(this, &TestRunner::requestStopTestRun,
&m_futureWatcher, &QFutureWatcher<TestResult *>::cancel);
connect(&m_futureWatcher, &QFutureWatcher<TestResult *>::canceled,
this, [this]() { emit testResultReady(new FaultyTestResult(
Result::MessageFatal,
QObject::tr("Test run canceled by user.")));
});
} }
TestRunner::~TestRunner() TestRunner::~TestRunner()
@@ -105,7 +110,7 @@ void TestRunner::setSelectedTests(const QList<TestConfiguration *> &selected)
m_selectedTests = selected; m_selectedTests = selected;
} }
static void performTestRun(QFutureInterface<void> &futureInterface, static void performTestRun(QFutureInterface<TestResult *> &futureInterface,
const QList<TestConfiguration *> selectedTests, const int timeout, const QList<TestConfiguration *> selectedTests, const int timeout,
const QString metricsOption) const QString metricsOption)
{ {
@@ -116,7 +121,7 @@ static void performTestRun(QFutureInterface<void> &futureInterface,
if (config->project()) { if (config->project()) {
testCaseCount += config->testCaseCount(); testCaseCount += config->testCaseCount();
} else { } else {
emitTestResultCreated(new FaultyTestResult(Result::MessageWarn, futureInterface.reportResult(new FaultyTestResult(Result::MessageWarn,
QObject::tr("Project is null for \"%1\". Removing from test run.\n" QObject::tr("Project is null for \"%1\". Removing from test run.\n"
"Check the test environment.").arg(config->displayName()))); "Check the test environment.").arg(config->displayName())));
} }
@@ -126,28 +131,12 @@ static void performTestRun(QFutureInterface<void> &futureInterface,
testProcess.setReadChannelMode(QProcess::MergedChannels); testProcess.setReadChannelMode(QProcess::MergedChannels);
testProcess.setReadChannel(QProcess::StandardOutput); testProcess.setReadChannel(QProcess::StandardOutput);
TestOutputReader outputReader(&testProcess);
QObject::connect(&outputReader, &TestOutputReader::increaseProgress, [&] () {
futureInterface.setProgressValue(futureInterface.progressValue() + 1);
});
QObject::connect(&outputReader, &TestOutputReader::testResultCreated, &emitTestResultCreated);
futureInterface.setProgressRange(0, testCaseCount); futureInterface.setProgressRange(0, testCaseCount);
futureInterface.setProgressValue(0); futureInterface.setProgressValue(0);
QMetaObject::Connection connection;
foreach (const TestConfiguration *testConfiguration, selectedTests) { foreach (const TestConfiguration *testConfiguration, selectedTests) {
if (connection) TestOutputReader outputReader(futureInterface, &testProcess, testConfiguration->testType());
QObject::disconnect(connection); Q_UNUSED(outputReader);
TestType testType = testConfiguration->testType();
if (testType == TestTypeQt) {
connection = QObject::connect(&testProcess, &QProcess::readyRead, &outputReader,
&TestOutputReader::processOutput);
} else {
connection = QObject::connect(&testProcess, &QProcess::readyRead, &outputReader,
&TestOutputReader::processGTestOutput);
}
if (futureInterface.isCanceled()) if (futureInterface.isCanceled())
break; break;
@@ -157,14 +146,14 @@ static void performTestRun(QFutureInterface<void> &futureInterface,
QProcessEnvironment environment = testConfiguration->environment().toProcessEnvironment(); QProcessEnvironment environment = testConfiguration->environment().toProcessEnvironment();
QString commandFilePath = executableFilePath(testConfiguration->targetFile(), environment); QString commandFilePath = executableFilePath(testConfiguration->targetFile(), environment);
if (commandFilePath.isEmpty()) { if (commandFilePath.isEmpty()) {
emitTestResultCreated(new FaultyTestResult(Result::MessageFatal, futureInterface.reportResult(new FaultyTestResult(Result::MessageFatal,
QObject::tr("Could not find command \"%1\". (%2)") QObject::tr("Could not find command \"%1\". (%2)")
.arg(testConfiguration->targetFile()) .arg(testConfiguration->targetFile())
.arg(testConfiguration->displayName()))); .arg(testConfiguration->displayName())));
continue; continue;
} }
if (testType == TestTypeQt) { if (testConfiguration->testType() == TestTypeQt) {
QStringList argumentList(QLatin1String("-xml")); QStringList argumentList(QLatin1String("-xml"));
if (!metricsOption.isEmpty()) if (!metricsOption.isEmpty())
argumentList << metricsOption; argumentList << metricsOption;
@@ -196,8 +185,7 @@ static void performTestRun(QFutureInterface<void> &futureInterface,
if (futureInterface.isCanceled()) { if (futureInterface.isCanceled()) {
testProcess.kill(); testProcess.kill();
testProcess.waitForFinished(); testProcess.waitForFinished();
emitTestResultCreated(new FaultyTestResult(Result::MessageFatal, return;
QObject::tr("Test run canceled by user.")));
} }
eventLoop.processEvents(); eventLoop.processEvents();
} }
@@ -207,7 +195,7 @@ static void performTestRun(QFutureInterface<void> &futureInterface,
if (testProcess.state() != QProcess::NotRunning) { if (testProcess.state() != QProcess::NotRunning) {
testProcess.kill(); testProcess.kill();
testProcess.waitForFinished(); testProcess.waitForFinished();
emitTestResultCreated(new FaultyTestResult(Result::MessageFatal, QObject::tr( futureInterface.reportResult(new FaultyTestResult(Result::MessageFatal, QObject::tr(
"Test case canceled due to timeout. \nMaybe raise the timeout?"))); "Test case canceled due to timeout. \nMaybe raise the timeout?")));
} }
} }
@@ -227,14 +215,14 @@ void TestRunner::prepareToRunTests()
foreach (TestConfiguration *config, m_selectedTests) { foreach (TestConfiguration *config, m_selectedTests) {
if (!omitRunConfigWarnings && config->guessedConfiguration()) { if (!omitRunConfigWarnings && config->guessedConfiguration()) {
TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MessageWarn, emit testResultReady(new FaultyTestResult(Result::MessageWarn,
tr("Project's run configuration was guessed for \"%1\".\n" tr("Project's run configuration was guessed for \"%1\".\n"
"This might cause trouble during execution.").arg(config->displayName()))); "This might cause trouble during execution.").arg(config->displayName())));
} }
} }
if (m_selectedTests.empty()) { if (m_selectedTests.empty()) {
TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MessageWarn, emit testResultReady(new FaultyTestResult(Result::MessageWarn,
tr("No tests selected. Canceling test run."))); tr("No tests selected. Canceling test run.")));
onFinished(); onFinished();
return; return;
@@ -242,7 +230,7 @@ void TestRunner::prepareToRunTests()
ProjectExplorer::Project *project = m_selectedTests.at(0)->project(); ProjectExplorer::Project *project = m_selectedTests.at(0)->project();
if (!project) { if (!project) {
TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MessageWarn, emit testResultReady(new FaultyTestResult(Result::MessageWarn,
tr("Project is null. Canceling test run.\n" tr("Project is null. Canceling test run.\n"
"Only desktop kits are supported. Make sure the " "Only desktop kits are supported. Make sure the "
"currently active kit is a desktop kit."))); "currently active kit is a desktop kit.")));
@@ -258,7 +246,7 @@ void TestRunner::prepareToRunTests()
if (project->hasActiveBuildSettings()) { if (project->hasActiveBuildSettings()) {
buildProject(project); buildProject(project);
} else { } else {
TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MessageFatal, emit testResultReady(new FaultyTestResult(Result::MessageFatal,
tr("Project is not configured. Canceling test run."))); tr("Project is not configured. Canceling test run.")));
onFinished(); onFinished();
return; return;
@@ -271,20 +259,10 @@ void TestRunner::runTests()
const QSharedPointer<TestSettings> settings = AutotestPlugin::instance()->settings(); const QSharedPointer<TestSettings> settings = AutotestPlugin::instance()->settings();
const QString &metricsOption = TestSettings::metricsTypeToOption(settings->metrics); const QString &metricsOption = TestSettings::metricsTypeToOption(settings->metrics);
connect(this, &TestRunner::testResultCreated, QFuture<TestResult *> future = Utils::runAsync<TestResult *>(&performTestRun, m_selectedTests,
TestResultsPane::instance(), &TestResultsPane::addTestResult, settings->timeout, metricsOption);
Qt::QueuedConnection); m_futureWatcher.setFuture(future);
Core::ProgressManager::addTask(future, tr("Running Tests"), Autotest::Constants::TASK_INDEX);
QFuture<void> future = Utils::runAsync<void>(&performTestRun, m_selectedTests, settings->timeout,
metricsOption);
Core::FutureProgress *progress = Core::ProgressManager::addTask(future, tr("Running Tests"),
Autotest::Constants::TASK_INDEX);
connect(progress, &Core::FutureProgress::finished,
TestRunner::instance(), &TestRunner::onFinished);
connect(this, &TestRunner::requestStopTestRun, progress, [progress]() {
progress->future().cancel();
});
} }
void TestRunner::buildProject(ProjectExplorer::Project *project) void TestRunner::buildProject(ProjectExplorer::Project *project)
@@ -307,7 +285,7 @@ void TestRunner::buildFinished(bool success)
if (success) { if (success) {
runTests(); runTests();
} else { } else {
TestResultsPane::instance()->addTestResult(new FaultyTestResult(Result::MessageFatal, emit testResultReady(new FaultyTestResult(Result::MessageFatal,
tr("Build failed. Canceling test run."))); tr("Build failed. Canceling test run.")));
onFinished(); onFinished();
} }
@@ -315,9 +293,6 @@ void TestRunner::buildFinished(bool success)
void TestRunner::onFinished() void TestRunner::onFinished()
{ {
disconnect(this, &TestRunner::testResultCreated,
TestResultsPane::instance(), &TestResultsPane::addTestResult);
m_executingTests = false; m_executingTests = false;
emit testRunFinished(); emit testRunFinished();
} }

View File

@@ -23,6 +23,7 @@
#include "testconfiguration.h" #include "testconfiguration.h"
#include "testresult.h" #include "testresult.h"
#include <QFutureWatcher>
#include <QObject> #include <QObject>
#include <QProcess> #include <QProcess>
@@ -47,8 +48,8 @@ public:
signals: signals:
void testRunStarted(); void testRunStarted();
void testRunFinished(); void testRunFinished();
void testResultCreated(TestResult *testResult);
void requestStopTestRun(); void requestStopTestRun();
void testResultReady(TestResult *result);
public slots: public slots:
void prepareToRunTests(); void prepareToRunTests();
@@ -62,6 +63,7 @@ private:
void runTests(); void runTests();
explicit TestRunner(QObject *parent = 0); explicit TestRunner(QObject *parent = 0);
QFutureWatcher<TestResult *> m_futureWatcher;
QList<TestConfiguration *> m_selectedTests; QList<TestConfiguration *> m_selectedTests;
bool m_executingTests; bool m_executingTests;