summaryrefslogtreecommitdiff
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)
downloadqt-creator-23bdcf77b6ee8c0799934c79db2d10564983f838.tar.gz
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