Fix issue with manually canceling running tests...

...by restructuring TestOutputReader.

Change-Id: Ia58b755a5b50afb66d53e9d254e450cce01621f4
Reviewed-by: Niels Weber <niels.weber@theqtcompany.com>
This commit is contained in:
Christian Stenger
2016-01-20 08:26:10 +01:00
parent e60a7a6923
commit 166bf38a11
3 changed files with 179 additions and 136 deletions

View File

@@ -28,7 +28,6 @@
#include <QProcess> #include <QProcess>
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
#include <QXmlStreamReader>
namespace Autotest { namespace Autotest {
namespace Internal { namespace Internal {
@@ -132,26 +131,20 @@ static QString constructBenchmarkInformation(const QString &metric, double value
} }
TestOutputReader::TestOutputReader(QFutureInterface<TestResult *> futureInterface, TestOutputReader::TestOutputReader(QFutureInterface<TestResult *> futureInterface,
QProcess *testApplication, TestType type) QProcess *testApplication)
: m_testApplication(testApplication), : m_futureInterface(futureInterface)
m_futureInterface(futureInterface) , m_testApplication(testApplication)
{ {
if (type == TestTypeQt) connect(m_testApplication, &QProcess::readyRead, this, &TestOutputReader::processOutput);
connect(testApplication, &QProcess::readyRead, this, &TestOutputReader::processOutput);
else
connect(testApplication, &QProcess::readyRead, this, &TestOutputReader::processGTestOutput);
} }
enum CDATAMode { QtTestOutputReader::QtTestOutputReader(QFutureInterface<TestResult *> futureInterface,
None, QProcess *testApplication)
DataTag, : TestOutputReader(futureInterface, testApplication)
Description, {
QtVersion, }
QtBuild,
QTestVersion
};
void TestOutputReader::processOutput() void QtTestOutputReader::processOutput()
{ {
if (!m_testApplication || m_testApplication->state() != QProcess::Running) if (!m_testApplication || m_testApplication->state() != QProcess::Running)
return; return;
@@ -161,115 +154,107 @@ void TestOutputReader::processOutput()
QStringLiteral("QtVersion"), QStringLiteral("QtVersion"),
QStringLiteral("QtBuild"), QStringLiteral("QtBuild"),
QStringLiteral("QTestVersion") }; QStringLiteral("QTestVersion") };
static CDATAMode cdataMode = None;
static QString className;
static QString testCase;
static QString dataTag;
static Result::Type result = Result::Invalid;
static QString description;
static QString file;
static int lineNumber = 0;
static QString duration;
static QXmlStreamReader xmlReader;
while (m_testApplication->canReadLine()) { while (m_testApplication->canReadLine()) {
xmlReader.addData(m_testApplication->readLine()); m_xmlReader.addData(m_testApplication->readLine());
while (!xmlReader.atEnd()) { while (!m_xmlReader.atEnd()) {
QXmlStreamReader::TokenType token = xmlReader.readNext(); if (m_futureInterface.isCanceled())
return;
QXmlStreamReader::TokenType token = m_xmlReader.readNext();
switch (token) { switch (token) {
case QXmlStreamReader::StartDocument: case QXmlStreamReader::StartDocument:
className.clear(); m_className.clear();
break; break;
case QXmlStreamReader::EndDocument: case QXmlStreamReader::EndDocument:
xmlReader.clear(); m_xmlReader.clear();
return; return;
case QXmlStreamReader::StartElement: { case QXmlStreamReader::StartElement: {
const QString currentTag = xmlReader.name().toString(); const QString currentTag = m_xmlReader.name().toString();
if (currentTag == QStringLiteral("TestCase")) { if (currentTag == QStringLiteral("TestCase")) {
className = xmlReader.attributes().value(QStringLiteral("name")).toString(); m_className = m_xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!className.isEmpty(), continue); QTC_ASSERT(!m_className.isEmpty(), continue);
auto testResult = new QTestResult(className); auto testResult = new QTestResult(m_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(m_className));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} else if (currentTag == QStringLiteral("TestFunction")) { } else if (currentTag == QStringLiteral("TestFunction")) {
testCase = xmlReader.attributes().value(QStringLiteral("name")).toString(); m_testCase = m_xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!testCase.isEmpty(), continue); QTC_ASSERT(!m_testCase.isEmpty(), continue);
auto testResult = new QTestResult(); auto testResult = new QTestResult();
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(m_className,
testCase)); m_testCase));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} else if (currentTag == QStringLiteral("Duration")) { } else if (currentTag == QStringLiteral("Duration")) {
duration = xmlReader.attributes().value(QStringLiteral("msecs")).toString(); m_duration = m_xmlReader.attributes().value(QStringLiteral("msecs")).toString();
QTC_ASSERT(!duration.isEmpty(), continue); QTC_ASSERT(!m_duration.isEmpty(), continue);
} else if (currentTag == QStringLiteral("Message") } else if (currentTag == QStringLiteral("Message")
|| currentTag == QStringLiteral("Incident")) { || currentTag == QStringLiteral("Incident")) {
dataTag.clear(); m_dataTag.clear();
description.clear(); m_description.clear();
duration.clear(); m_duration.clear();
file.clear(); m_file.clear();
result = Result::Invalid; m_result = Result::Invalid;
lineNumber = 0; m_lineNumber = 0;
const QXmlStreamAttributes &attributes = xmlReader.attributes(); const QXmlStreamAttributes &attributes = m_xmlReader.attributes();
result = TestResult::resultFromString( m_result = TestResult::resultFromString(
attributes.value(QStringLiteral("type")).toString()); attributes.value(QStringLiteral("type")).toString());
file = decode(attributes.value(QStringLiteral("file")).toString()); m_file = decode(attributes.value(QStringLiteral("file")).toString());
if (!file.isEmpty()) { if (!m_file.isEmpty()) {
const QString base = QFileInfo(m_testApplication->program()).absolutePath(); const QString base = QFileInfo(m_testApplication->program()).absolutePath();
file = constructSourceFilePath(base, file, m_file = constructSourceFilePath(base, m_file,
m_testApplication->program()); m_testApplication->program());
} }
lineNumber = attributes.value(QStringLiteral("line")).toInt(); m_lineNumber = attributes.value(QStringLiteral("line")).toInt();
} else if (currentTag == QStringLiteral("BenchmarkResult")) { } else if (currentTag == QStringLiteral("BenchmarkResult")) {
const QXmlStreamAttributes &attributes = xmlReader.attributes(); const QXmlStreamAttributes &attributes = m_xmlReader.attributes();
const QString metric = attributes.value(QStringLiteral("metrics")).toString(); const QString metric = attributes.value(QStringLiteral("metrics")).toString();
const double value = attributes.value(QStringLiteral("value")).toDouble(); const double value = attributes.value(QStringLiteral("value")).toDouble();
const int iterations = attributes.value(QStringLiteral("iterations")).toInt(); const int iterations = attributes.value(QStringLiteral("iterations")).toInt();
description = constructBenchmarkInformation(metric, value, iterations); m_description = constructBenchmarkInformation(metric, value, iterations);
result = Result::Benchmark; m_result = Result::Benchmark;
} else if (currentTag == QStringLiteral("DataTag")) { } else if (currentTag == QStringLiteral("DataTag")) {
cdataMode = DataTag; m_cdataMode = DataTag;
} else if (currentTag == QStringLiteral("Description")) { } else if (currentTag == QStringLiteral("Description")) {
cdataMode = Description; m_cdataMode = Description;
} else if (currentTag == QStringLiteral("QtVersion")) { } else if (currentTag == QStringLiteral("QtVersion")) {
result = Result::MessageInternal; m_result = Result::MessageInternal;
cdataMode = QtVersion; m_cdataMode = QtVersion;
} else if (currentTag == QStringLiteral("QtBuild")) { } else if (currentTag == QStringLiteral("QtBuild")) {
result = Result::MessageInternal; m_result = Result::MessageInternal;
cdataMode = QtBuild; m_cdataMode = QtBuild;
} else if (currentTag == QStringLiteral("QTestVersion")) { } else if (currentTag == QStringLiteral("QTestVersion")) {
result = Result::MessageInternal; m_result = Result::MessageInternal;
cdataMode = QTestVersion; m_cdataMode = QTestVersion;
} }
break; break;
} }
case QXmlStreamReader::Characters: { case QXmlStreamReader::Characters: {
QStringRef text = xmlReader.text().trimmed(); QStringRef text = m_xmlReader.text().trimmed();
if (text.isEmpty()) if (text.isEmpty())
break; break;
switch (cdataMode) { switch (m_cdataMode) {
case DataTag: case DataTag:
dataTag = text.toString(); m_dataTag = text.toString();
break; break;
case Description: case Description:
if (!description.isEmpty()) if (!m_description.isEmpty())
description.append(QLatin1Char('\n')); m_description.append(QLatin1Char('\n'));
description.append(text); m_description.append(text);
break; break;
case QtVersion: case QtVersion:
description = tr("Qt version: %1").arg(text.toString()); m_description = tr("Qt version: %1").arg(text.toString());
break; break;
case QtBuild: case QtBuild:
description = tr("Qt build: %1").arg(text.toString()); m_description = tr("Qt build: %1").arg(text.toString());
break; break;
case QTestVersion: case QTestVersion:
description = tr("QTest version: %1").arg(text.toString()); m_description = tr("QTest version: %1").arg(text.toString());
break; break;
default: default:
QString message = QString::fromLatin1("unexpected cdatamode %1 for text \"%2\"") QString message = QString::fromLatin1("unexpected cdatamode %1 for text \"%2\"")
.arg(cdataMode) .arg(m_cdataMode)
.arg(text.toString()); .arg(text.toString());
QTC_ASSERT(false, qWarning() << message); QTC_ASSERT(false, qWarning() << message);
break; break;
@@ -277,32 +262,32 @@ void TestOutputReader::processOutput()
break; break;
} }
case QXmlStreamReader::EndElement: { case QXmlStreamReader::EndElement: {
cdataMode = None; m_cdataMode = None;
const QStringRef currentTag = xmlReader.name(); const QStringRef currentTag = m_xmlReader.name();
if (currentTag == QStringLiteral("TestFunction")) { if (currentTag == QStringLiteral("TestFunction")) {
if (!duration.isEmpty()) { if (!m_duration.isEmpty()) {
auto testResult = new QTestResult(className); auto testResult = new QTestResult(m_className);
testResult->setTestCase(testCase); testResult->setTestCase(m_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(m_duration));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} }
m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); 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(m_className);
testResult->setResult(Result::MessageTestCaseEnd); testResult->setResult(Result::MessageTestCaseEnd);
testResult->setDescription( testResult->setDescription(
duration.isEmpty() ? tr("Test finished.") m_duration.isEmpty() ? tr("Test finished.")
: tr("Test execution took %1 ms.").arg(duration)); : tr("Test execution took %1 ms.").arg(m_duration));
m_futureInterface.reportResult(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(m_className);
testResult->setTestCase(testCase); testResult->setTestCase(m_testCase);
testResult->setDataTag(dataTag); testResult->setDataTag(m_dataTag);
testResult->setResult(result); testResult->setResult(m_result);
testResult->setFileName(file); testResult->setFileName(m_file);
testResult->setLine(lineNumber); testResult->setLine(m_lineNumber);
testResult->setDescription(description); testResult->setDescription(m_description);
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} }
break; break;
@@ -314,7 +299,13 @@ void TestOutputReader::processOutput()
} }
} }
void TestOutputReader::processGTestOutput() GTestOutputReader::GTestOutputReader(QFutureInterface<TestResult *> futureInterface,
QProcess *testApplication)
: TestOutputReader(futureInterface, testApplication)
{
}
void GTestOutputReader::processOutput()
{ {
if (!m_testApplication || m_testApplication->state() != QProcess::Running) if (!m_testApplication || m_testApplication->state() != QProcess::Running)
return; return;
@@ -326,19 +317,16 @@ void TestOutputReader::processGTestOutput()
static QRegExp testSetFail(QStringLiteral("^\\\[ FAILED \\] (.*) \\((.*)\\)$")); static QRegExp testSetFail(QStringLiteral("^\\\[ FAILED \\] (.*) \\((.*)\\)$"));
static QRegExp disabledTests(QStringLiteral("^ YOU HAVE (\\d+) DISABLED TESTS?$")); static QRegExp disabledTests(QStringLiteral("^ YOU HAVE (\\d+) DISABLED TESTS?$"));
static QString currentTestName;
static QString currentTestSet;
static QString description;
static QByteArray unprocessed;
while (m_testApplication->canReadLine()) { while (m_testApplication->canReadLine()) {
if (m_futureInterface.isCanceled())
return;
QByteArray read = m_testApplication->readLine(); QByteArray read = m_testApplication->readLine();
if (!unprocessed.isEmpty()) { if (!m_unprocessed.isEmpty()) {
read = unprocessed + read; read = m_unprocessed + read;
unprocessed.clear(); m_unprocessed.clear();
} }
if (!read.endsWith('\n')) { if (!read.endsWith('\n')) {
unprocessed = read; m_unprocessed = read;
continue; continue;
} }
read.chop(1); // remove the newline from the output read.chop(1); // remove the newline from the output
@@ -348,13 +336,13 @@ void TestOutputReader::processGTestOutput()
continue; continue;
if (!line.startsWith(QLatin1Char('['))) { if (!line.startsWith(QLatin1Char('['))) {
description.append(line).append(QLatin1Char('\n')); m_description.append(line).append(QLatin1Char('\n'));
if (line.startsWith(QStringLiteral("Note:"))) { if (line.startsWith(QStringLiteral("Note:"))) {
auto testResult = new GTestResult(); auto testResult = new GTestResult();
testResult->setResult(Result::MessageInternal); testResult->setResult(Result::MessageInternal);
testResult->setDescription(line); testResult->setDescription(line);
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
description.clear(); m_description.clear();
} else if (disabledTests.exactMatch(line)) { } else if (disabledTests.exactMatch(line)) {
auto testResult = new GTestResult(); auto testResult = new GTestResult();
testResult->setResult(Result::MessageDisabledTests); testResult->setResult(Result::MessageDisabledTests);
@@ -362,62 +350,62 @@ void TestOutputReader::processGTestOutput()
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
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
description.clear(); m_description.clear();
} }
continue; continue;
} }
if (testEnds.exactMatch(line)) { if (testEnds.exactMatch(line)) {
auto testResult = new GTestResult(currentTestName); auto testResult = new GTestResult(m_currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(m_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)));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
currentTestName.clear(); m_currentTestName.clear();
currentTestSet.clear(); m_currentTestSet.clear();
} else if (newTestStarts.exactMatch(line)) { } else if (newTestStarts.exactMatch(line)) {
currentTestName = newTestStarts.cap(1); m_currentTestName = newTestStarts.cap(1);
auto testResult = new GTestResult(currentTestName); auto testResult = new GTestResult(m_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(m_currentTestName));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} else if (newTestSetStarts.exactMatch(line)) { } else if (newTestSetStarts.exactMatch(line)) {
currentTestSet = newTestSetStarts.cap(1); m_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(m_currentTestSet));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
} else if (testSetSuccess.exactMatch(line)) { } else if (testSetSuccess.exactMatch(line)) {
auto testResult = new GTestResult(currentTestName); auto testResult = new GTestResult(m_currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(m_currentTestSet);
testResult->setResult(Result::Pass); testResult->setResult(Result::Pass);
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
testResult = new GTestResult(currentTestName); testResult = new GTestResult(m_currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(m_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)));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); 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(m_currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(m_currentTestSet);
testResult->setResult(Result::Fail); testResult->setResult(Result::Fail);
description.chop(1); m_description.chop(1);
testResult->setDescription(description); testResult->setDescription(m_description);
int firstColon = description.indexOf(QLatin1Char(':')); int firstColon = m_description.indexOf(QLatin1Char(':'));
if (firstColon != -1) { if (firstColon != -1) {
int secondColon = description.indexOf(QLatin1Char(':'), firstColon + 1); int secondColon = m_description.indexOf(QLatin1Char(':'), firstColon + 1);
const QString base = QFileInfo(m_testApplication->program()).absolutePath(); const QString base = QFileInfo(m_testApplication->program()).absolutePath();
QString file = constructSourceFilePath(base, description.left(firstColon), QString file = constructSourceFilePath(base, m_description.left(firstColon),
m_testApplication->program()); m_testApplication->program());
QString line = description.mid(firstColon + 1, secondColon - firstColon - 1); QString line = m_description.mid(firstColon + 1, secondColon - firstColon - 1);
testResult->setFileName(file); testResult->setFileName(file);
testResult->setLine(line.toInt()); testResult->setLine(line.toInt());
} }
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);
description.clear(); m_description.clear();
testResult = new GTestResult(currentTestName); testResult = new GTestResult(m_currentTestName);
testResult->setTestCase(currentTestSet); testResult->setTestCase(m_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)));
m_futureInterface.reportResult(testResult); m_futureInterface.reportResult(testResult);

View File

@@ -25,6 +25,7 @@
#include <QFutureInterface> #include <QFutureInterface>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QXmlStreamReader>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QProcess; class QProcess;
@@ -38,16 +39,63 @@ class TestOutputReader : public QObject
Q_OBJECT Q_OBJECT
public: public:
TestOutputReader(QFutureInterface<TestResult *> futureInterface, TestOutputReader(QFutureInterface<TestResult *> futureInterface,
QProcess *testApplication, TestType type); QProcess *testApplication);
protected:
virtual void processOutput() = 0;
QFutureInterface<TestResult *> m_futureInterface;
QProcess *m_testApplication; // not owned
};
class QtTestOutputReader : public TestOutputReader
{
public:
QtTestOutputReader(QFutureInterface<TestResult *> futureInterface,
QProcess *testApplication);
protected:
void processOutput() override;
private: private:
void processOutput(); enum CDATAMode
void processGTestOutput(); {
None,
DataTag,
Description,
QtVersion,
QtBuild,
QTestVersion
};
QProcess *m_testApplication; // not owned CDATAMode m_cdataMode = None;
QFutureInterface<TestResult *> m_futureInterface; QString m_className;
QString m_testCase;
QString m_dataTag;
Result::Type m_result = Result::Invalid;
QString m_description;
QString m_file;
int m_lineNumber = 0;
QString m_duration;
QXmlStreamReader m_xmlReader;
}; };
class GTestOutputReader : public TestOutputReader
{
public:
GTestOutputReader(QFutureInterface<TestResult *> futureInterface,
QProcess *testApplication);
protected:
void processOutput() override;
private:
QString m_currentTestName;
QString m_currentTestSet;
QString m_description;
QByteArray m_unprocessed;
};
} // namespace Internal } // namespace Internal
} // namespace Autotest } // namespace Autotest

View File

@@ -135,8 +135,15 @@ static void performTestRun(QFutureInterface<TestResult *> &futureInterface,
futureInterface.setProgressValue(0); futureInterface.setProgressValue(0);
foreach (const TestConfiguration *testConfiguration, selectedTests) { foreach (const TestConfiguration *testConfiguration, selectedTests) {
TestOutputReader outputReader(futureInterface, &testProcess, testConfiguration->testType()); QScopedPointer<TestOutputReader> outputReader;
Q_UNUSED(outputReader); switch (testConfiguration->testType()) {
case TestTypeQt:
outputReader.reset(new QtTestOutputReader(futureInterface, &testProcess));
break;
case TestTypeGTest:
outputReader.reset(new GTestOutputReader(futureInterface, &testProcess));
break;
}
if (futureInterface.isCanceled()) if (futureInterface.isCanceled())
break; break;