From 23bdcf77b6ee8c0799934c79db2d10564983f838 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 23 Sep 2015 07:47:12 +0200 Subject: 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 --- plugins/autotest/testresult.cpp | 20 +- plugins/autotest/testresult.h | 8 +- plugins/autotest/testxmloutputreader.cpp | 314 +++++++++++++++---------------- plugins/autotest/testxmloutputreader.h | 9 +- 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 +#include #include #include @@ -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(""), 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(""), dataTag)) - continue; - if (xmlCData(line, QLatin1String(""), description)) { - if (!line.endsWith(QLatin1String(""))) - readingDescription = true; - continue; - } - if (xmlExtractTypeFileLine(line, 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("") || line == 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); - description = QString(); - } else if (line == QLatin1String("") && !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("") && !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.append(QLatin1Char('\n')); - description.append(line.left(line.indexOf(QLatin1String("]]>")))); - 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)) { - testResultCreated(new TestResult(className, QString(), QString(), Result::MESSAGE_INTERNAL, - QObject::tr("Qt version: %1").arg(qtVersion))); - } else if (xmlStartsWith(line, QLatin1String(""), 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 #include +#include 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 -- cgit v1.2.1