aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Stenger <christian.stenger@theqtcompany.com>2015-09-23 07:47:12 +0200
committerDavid Schulz <david.schulz@theqtcompany.com>2015-10-09 09:31:51 +0300
commit23bdcf77b6ee8c0799934c79db2d10564983f838 (patch)
tree0388385bb9bab3096f0cf3d714ee164602674116
parent17fbe92100d194250dd7567232b58830d9d447e4 (diff)
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: David Schulz <david.schulz@theqtcompany.com>
-rw-r--r--plugins/autotest/testresult.cpp20
-rw-r--r--plugins/autotest/testresult.h8
-rw-r--r--plugins/autotest/testxmloutputreader.cpp314
-rw-r--r--plugins/autotest/testxmloutputreader.h9
4 files changed, 176 insertions, 175 deletions
diff --git a/plugins/autotest/testresult.cpp b/plugins/autotest/testresult.cpp
index 8859d8fb1a..293145f3e9 100644
--- a/plugins/autotest/testresult.cpp
+++ b/plugins/autotest/testresult.cpp
@@ -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)
{
}
diff --git a/plugins/autotest/testresult.h b/plugins/autotest/testresult.h
index cc10cf5fe3..1ad8d51a4a 100644
--- a/plugins/autotest/testresult.h
+++ b/plugins/autotest/testresult.h
@@ -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);
diff --git a/plugins/autotest/testxmloutputreader.cpp b/plugins/autotest/testxmloutputreader.cpp
index b3221f123d..8bff855772 100644
--- a/plugins/autotest/testxmloutputreader.cpp
+++ b/plugins/autotest/testxmloutputreader.cpp
@@ -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
}
}
}
diff --git a/plugins/autotest/testxmloutputreader.h b/plugins/autotest/testxmloutputreader.h
index 6dfcef51cc..feb99c46b2 100644
--- a/plugins/autotest/testxmloutputreader.h
+++ b/plugins/autotest/testxmloutputreader.h
@@ -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