Commit 23bdcf77 authored by Christian Stenger's avatar Christian Stenger Committed by David Schulz
Browse files

Use QXmlStreamReader to parse test run output



Using the QXmlStreamReader will be easier to extend current
functionality and should be more robust than parsing on our own.

Change-Id: I9e1df7083a1af7681987f3971550e19a35b29df9
Reviewed-by: default avatarDavid Schulz <david.schulz@theqtcompany.com>
parent 17fbe921
......@@ -23,18 +23,20 @@ namespace Autotest {
namespace Internal {
FaultyTestResult::FaultyTestResult(Result::Type result, const QString &description)
: TestResult(QString(), QString(), QString(), result, description)
{
setResult(result);
setDescription(description);
}
TestResult::TestResult(const QString &className, const QString &testCase, const QString &dataTag,
Result::Type result, const QString &description)
: m_class(className),
m_case(testCase),
m_dataTag(dataTag),
m_result(result),
m_description(description),
m_line(0)
TestResult::TestResult()
: TestResult(QString())
{
}
TestResult::TestResult(const QString &className)
: m_class(className)
, m_result(Result::INVALID)
, m_line(0)
{
}
......
......@@ -57,10 +57,8 @@ enum Type {
class TestResult
{
public:
TestResult(const QString &className = QString(), const QString &testCase = QString(),
const QString &dataTag = QString(),
Result::Type result = Result::INVALID, const QString &description = QString());
TestResult();
TestResult(const QString &className);
QString className() const { return m_class; }
QString testCase() const { return m_case; }
......@@ -74,6 +72,8 @@ public:
void setFileName(const QString &fileName) { m_file = fileName; }
void setLine(int line) { m_line = line; }
void setResult(Result::Type type) { m_result = type; }
void setTestCase(const QString &testCase) { m_case = testCase; }
void setDataTag(const QString &dataTag) { m_dataTag = dataTag; }
static Result::Type resultFromString(const QString &resultString);
static Result::Type toResultType(int rt);
......
......@@ -21,6 +21,7 @@
#include "testresult.h"
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <QRegExp>
#include <QProcess>
......@@ -60,44 +61,6 @@ static QString constructSourceFilePath(const QString &path, const QString &fileP
return QFileInfo(path, filePath).canonicalFilePath();
}
static bool xmlStartsWith(const QString &code, const QString &start, QString &result)
{
if (code.startsWith(start)) {
result = code.mid(start.length());
result = result.left(result.indexOf(QLatin1Char('"')));
result = result.left(result.indexOf(QLatin1String("</")));
return !result.isEmpty();
}
return false;
}
static bool xmlCData(const QString &code, const QString &start, QString &result)
{
if (code.startsWith(start)) {
int index = code.indexOf(QLatin1String("<![CDATA[")) + 9;
result = code.mid(index, code.indexOf(QLatin1String("]]>"), index) - index);
return !result.isEmpty();
}
return false;
}
static bool xmlExtractTypeFileLine(const QString &code, const QString &tagStart,
Result::Type &result, QString &file, int &line)
{
if (code.startsWith(tagStart)) {
int start = code.indexOf(QLatin1String(" type=\"")) + 7;
result = TestResult::resultFromString(
code.mid(start, code.indexOf(QLatin1Char('"'), start) - start));
start = code.indexOf(QLatin1String(" file=\"")) + 7;
file = decode(code.mid(start, code.indexOf(QLatin1Char('"'), start) - start));
start = code.indexOf(QLatin1String(" line=\"")) + 7;
line = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toInt();
return true;
}
return false;
}
// adapted from qplaintestlogger.cpp
static QString formatResult(double value)
{
......@@ -146,35 +109,24 @@ static QString formatResult(double value)
return result;
}
static bool xmlExtractBenchmarkInformation(const QString &code, const QString &tagStart,
QString &description)
static QString constructBenchmarkInformation(const QString &metric, double value, int iterations)
{
if (code.startsWith(tagStart)) {
int start = code.indexOf(QLatin1String(" metric=\"")) + 9;
const QString metric = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start);
start = code.indexOf(QLatin1String(" value=\"")) + 8;
const double value = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toDouble();
start = code.indexOf(QLatin1String(" iterations=\"")) + 13;
const int iterations = code.mid(start, code.indexOf(QLatin1Char('"'), start) - start).toInt();
QString metricsText;
if (metric == QLatin1String("WalltimeMilliseconds")) // default
metricsText = QLatin1String("msecs");
else if (metric == QLatin1String("CPUTicks")) // -tickcounter
metricsText = QLatin1String("CPU ticks");
else if (metric == QLatin1String("Events")) // -eventcounter
metricsText = QLatin1String("events");
else if (metric == QLatin1String("InstructionReads")) // -callgrind
metricsText = QLatin1String("instruction reads");
else if (metric == QLatin1String("CPUCycles")) // -perf
metricsText = QLatin1String("CPU cycles");
description = QObject::tr("%1 %2 per iteration (total: %3, iterations: %4)")
.arg(formatResult(value))
.arg(metricsText)
.arg(formatResult(value * (double)iterations))
.arg(iterations);
return true;
}
return false;
QString metricsText;
if (metric == QLatin1String("WalltimeMilliseconds")) // default
metricsText = QLatin1String("msecs");
else if (metric == QLatin1String("CPUTicks")) // -tickcounter
metricsText = QLatin1String("CPU ticks");
else if (metric == QLatin1String("Events")) // -eventcounter
metricsText = QLatin1String("events");
else if (metric == QLatin1String("InstructionReads")) // -callgrind
metricsText = QLatin1String("instruction reads");
else if (metric == QLatin1String("CPUCycles")) // -perf
metricsText = QLatin1String("CPU cycles");
return QObject::tr("%1 %2 per iteration (total: %3, iterations: %4)")
.arg(formatResult(value))
.arg(metricsText)
.arg(formatResult(value * (double)iterations))
.arg(iterations);
}
TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication)
......@@ -184,14 +136,24 @@ TestXmlOutputReader::TestXmlOutputReader(QProcess *testApplication)
this, &TestXmlOutputReader::processOutput);
}
TestXmlOutputReader::~TestXmlOutputReader()
{
}
enum CDATAMode {
None,
DataTag,
Description,
QtVersion,
QTestVersion
};
void TestXmlOutputReader::processOutput()
{
if (!m_testApplication || m_testApplication->state() != QProcess::Running)
return;
static QStringList validEndTags = { QStringLiteral("Incident"),
QStringLiteral("Message"),
QStringLiteral("BenchmarkResult"),
QStringLiteral("QtVersion"),
QStringLiteral("QTestVersion") };
static CDATAMode cdataMode = None;
static QString className;
static QString testCase;
static QString dataTag;
......@@ -200,98 +162,136 @@ void TestXmlOutputReader::processOutput()
static QString file;
static int lineNumber = 0;
static QString duration;
static bool readingDescription = false;
static QString qtVersion;
static QString qtestVersion;
static QString benchmarkDescription;
static QXmlStreamReader xmlReader;
while (m_testApplication->canReadLine()) {
// TODO Qt5 uses UTF-8 - while Qt4 uses ISO-8859-1 - could this be a problem?
const QString line = QString::fromUtf8(m_testApplication->readLine()).trimmed();
if (line.isEmpty() || xmlStartsWith(line, QLatin1String("<TestCase name=\""), className)) {
testResultCreated(new TestResult(className, QString(), QString(),
Result::MESSAGE_TEST_CASE_START,
QObject::tr("Executing test case %1").arg(className)));
continue;
}
if (line.startsWith(QLatin1String("<?xml version"))) {
className = QString();
continue;
}
if (xmlStartsWith(line, QLatin1String("<TestFunction name=\""), testCase)) {
dataTag = QString();
description = QString();
duration = QString();
file = QString();
result = Result::INVALID;
lineNumber = 0;
readingDescription = false;
testResultCreated(new TestResult(QString(), QString(), QString(), Result::MESSAGE_CURRENT_TEST,
QObject::tr("Entering test function %1::%2").arg(className).arg(testCase)));
continue;
}
if (xmlStartsWith(line, QLatin1String("<Duration msecs=\""), duration)) {
continue;
}
if (xmlExtractTypeFileLine(line, QLatin1String("<Message"), result, file, lineNumber))
continue;
if (xmlCData(line, QLatin1String("<DataTag>"), dataTag))
continue;
if (xmlCData(line, QLatin1String("<Description>"), description)) {
if (!line.endsWith(QLatin1String("</Description>")))
readingDescription = true;
continue;
}
if (xmlExtractTypeFileLine(line, QLatin1String("<Incident"), result, file, lineNumber)) {
if (line.endsWith(QLatin1String("/>"))) {
TestResult *testResult = new TestResult(className, testCase, dataTag, result, description);
if (!file.isEmpty())
file = constructSourceFilePath(m_testApplication->workingDirectory(), file,
m_testApplication->program());
testResult->setFileName(file);
testResult->setLine(lineNumber);
testResultCreated(testResult);
xmlReader.addData(m_testApplication->readLine());
while (!xmlReader.atEnd()) {
QXmlStreamReader::TokenType token = xmlReader.readNext();
switch (token) {
case QXmlStreamReader::StartDocument:
className.clear();
break;
case QXmlStreamReader::EndDocument:
xmlReader.clear();
return;
case QXmlStreamReader::StartElement: {
const QString currentTag = xmlReader.name().toString();
if (currentTag == QStringLiteral("TestCase")) {
className = xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!className.isEmpty(), continue);
auto testResult = new TestResult(className);
testResult->setResult(Result::MESSAGE_TEST_CASE_START);
testResult->setDescription(tr("Executing test case %1").arg(className));
testResultCreated(testResult);
} else if (currentTag == QStringLiteral("TestFunction")) {
testCase = xmlReader.attributes().value(QStringLiteral("name")).toString();
QTC_ASSERT(!testCase.isEmpty(), continue);
auto testResult = new TestResult();
testResult->setResult(Result::MESSAGE_CURRENT_TEST);
testResult->setDescription(tr("Entering test function %1::%2").arg(className,
testCase));
testResultCreated(testResult);
} else if (currentTag == QStringLiteral("Duration")) {
duration = xmlReader.attributes().value(QStringLiteral("msecs")).toString();
QTC_ASSERT(!duration.isEmpty(), continue);
} else if (currentTag == QStringLiteral("Message")
|| currentTag == QStringLiteral("Incident")) {
dataTag.clear();
description.clear();
duration.clear();
file.clear();
result = Result::INVALID;
lineNumber = 0;
const QXmlStreamAttributes &attributes = xmlReader.attributes();
result = TestResult::resultFromString(
attributes.value(QStringLiteral("type")).toString());
file = decode(attributes.value(QStringLiteral("file")).toString());
if (!file.isEmpty())
file = constructSourceFilePath(m_testApplication->workingDirectory(), file,
m_testApplication->program());
lineNumber = attributes.value(QStringLiteral("line")).toInt();
} else if (currentTag == QStringLiteral("BenchmarkResult")) {
const QXmlStreamAttributes &attributes = xmlReader.attributes();
const QString metric = attributes.value(QStringLiteral("metrics")).toString();
const double value = attributes.value(QStringLiteral("value")).toDouble();
const int iterations = attributes.value(QStringLiteral("iterations")).toInt();
description = constructBenchmarkInformation(metric, value, iterations);
result = Result::BENCHMARK;
} else if (currentTag == QStringLiteral("DataTag")) {
cdataMode = DataTag;
} else if (currentTag == QStringLiteral("Description")) {
cdataMode = Description;
} else if (currentTag == QStringLiteral("QtVersion")) {
result = Result::MESSAGE_INTERNAL;
cdataMode = QtVersion;
} else if (currentTag == QStringLiteral("QTestVersion")) {
result = Result::MESSAGE_INTERNAL;
cdataMode = QTestVersion;
}
break;
}
continue;
}
if (xmlExtractBenchmarkInformation(line, QLatin1String("<BenchmarkResult"), benchmarkDescription)) {
testResultCreated(new TestResult(className, testCase, dataTag, Result::BENCHMARK,
benchmarkDescription));
continue;
}
if (line == QLatin1String("</Message>") || line == QLatin1String("</Incident>")) {
TestResult *testResult = new TestResult(className, testCase, dataTag, result, description);
if (!file.isEmpty())
file = constructSourceFilePath(m_testApplication->workingDirectory(), file,
m_testApplication->program());
testResult->setFileName(file);
testResult->setLine(lineNumber);
testResultCreated(testResult);
description = QString();
} else if (line == QLatin1String("</TestFunction>") && !duration.isEmpty()) {
testResultCreated(new TestResult(className, testCase, QString(), Result::MESSAGE_INTERNAL,
QObject::tr("Execution took %1 ms.").arg(duration)));
emit increaseProgress();
} else if (line == QLatin1String("</TestCase>") && !duration.isEmpty()) {
testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_TEST_CASE_END,
QObject::tr("Test execution took %1 ms.").arg(duration)));
} else if (readingDescription) {
if (line.endsWith(QLatin1String("]]></Description>"))) {
description.append(QLatin1Char('\n'));
description.append(line.left(line.indexOf(QLatin1String("]]></Description>"))));
readingDescription = false;
} else {
description.append(QLatin1Char('\n'));
description.append(line);
case QXmlStreamReader::Characters: {
QStringRef text = xmlReader.text().trimmed();
if (text.isEmpty())
break;
switch (cdataMode) {
case DataTag:
dataTag = text.toString();
break;
case Description:
if (!description.isEmpty())
description.append(QLatin1Char('\n'));
description.append(text);
break;
case QtVersion:
description = tr("Qt version: %1").arg(text.toString());
break;
case QTestVersion:
description = tr("QTest version: %1").arg(text.toString());
break;
default:
QString message = QString::fromLatin1("unexpected cdatamode %1 for text \"%2\"")
.arg(cdataMode)
.arg(text.toString());
QTC_ASSERT(false, qWarning() << message);
break;
}
break;
}
case QXmlStreamReader::EndElement: {
cdataMode = None;
const QStringRef currentTag = xmlReader.name();
if (currentTag == QStringLiteral("TestFunction")) {
if (!duration.isEmpty()) {
auto testResult = new TestResult(className);
testResult->setTestCase(testCase);
testResult->setResult(Result::MESSAGE_INTERNAL);
testResult->setDescription(tr("Execution took %1 ms.").arg(duration));
testResultCreated(testResult);
}
emit increaseProgress();
} else if (currentTag == QStringLiteral("TestCase") && !duration.isEmpty()) {
auto testResult = new TestResult(className);
testResult->setResult(Result::MESSAGE_TEST_CASE_END);
testResult->setDescription(tr("Test execution took %1 ms.").arg(duration));
testResultCreated(testResult);
} else if (validEndTags.contains(currentTag.toString())) {
auto testResult = new TestResult(className);
testResult->setTestCase(testCase);
testResult->setDataTag(dataTag);
testResult->setResult(result);
testResult->setFileName(file);
testResult->setLine(lineNumber);
testResult->setDescription(description);
testResultCreated(testResult);
}
break;
}
default:
break;
}
} else if (xmlStartsWith(line, QLatin1String("<QtVersion>"), qtVersion)) {
testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL,
QObject::tr("Qt version: %1").arg(qtVersion)));
} else if (xmlStartsWith(line, QLatin1String("<QTestVersion>"), qtestVersion)) {
testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL,
QObject::tr("QTest version: %1").arg(qtestVersion)));
} else {
// qDebug() << "Unhandled line:" << line; // TODO remove
}
}
}
......
......@@ -24,10 +24,9 @@
#include <QObject>
#include <QString>
#include <QXmlStreamReader>
QT_BEGIN_NAMESPACE
class QXmlStreamReader;
class QIODevice;
class QProcess;
QT_END_NAMESPACE
......@@ -37,18 +36,18 @@ namespace Internal {
class TestXmlOutputReader : public QObject
{
Q_OBJECT
public:
TestXmlOutputReader(QProcess *testApplication);
~TestXmlOutputReader();
public slots:
void processOutput();
signals:
void testResultCreated(TestResult *testResult);
void increaseProgress();
private:
QProcess *m_testApplication;
QProcess *m_testApplication; // not owned
};
} // namespace Internal
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment