aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/autotest/catch/catchoutputreader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/autotest/catch/catchoutputreader.cpp')
-rw-r--r--src/plugins/autotest/catch/catchoutputreader.cpp323
1 files changed, 323 insertions, 0 deletions
diff --git a/src/plugins/autotest/catch/catchoutputreader.cpp b/src/plugins/autotest/catch/catchoutputreader.cpp
new file mode 100644
index 0000000000..68d4f0b260
--- /dev/null
+++ b/src/plugins/autotest/catch/catchoutputreader.cpp
@@ -0,0 +1,323 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 Jochen Seemann
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "catchoutputreader.h"
+#include "catchresult.h"
+
+#include "../testtreeitem.h"
+
+#include <utils/qtcassert.h>
+
+#include <QFileInfo>
+
+namespace Autotest {
+namespace Internal {
+
+namespace CatchXml {
+ const char GroupElement[] = "Group";
+ const char TestCaseElement[] = "TestCase";
+ const char SectionElement[] = "Section";
+ const char ExpressionElement[] = "Expression";
+ const char ExpandedElement[] = "Expanded";
+ const char BenchmarkResults[] = "BenchmarkResults";
+ const char MeanElement[] = "mean";
+ const char StandardDevElement[] = "standardDeviation";
+ const char SectionResultElement[] = "OverallResults";
+ const char TestCaseResultElement[] = "OverallResult";
+}
+
+CatchOutputReader::CatchOutputReader(const QFutureInterface<TestResultPtr> &futureInterface,
+ QProcess *testApplication, const QString &buildDirectory,
+ const QString &projectFile)
+ : TestOutputReader (futureInterface, testApplication, buildDirectory)
+ , m_projectFile(projectFile)
+{
+}
+
+void CatchOutputReader::processOutputLine(const QByteArray &outputLineWithNewLine)
+{
+ if (outputLineWithNewLine.trimmed().isEmpty())
+ return;
+
+ m_xmlReader.addData(QString::fromUtf8(outputLineWithNewLine));
+ while (!m_xmlReader.atEnd()) {
+ QXmlStreamReader::TokenType token = m_xmlReader.readNext();
+
+ switch (token) {
+ case QXmlStreamReader::StartDocument:
+ break;
+ case QXmlStreamReader::EndDocument:
+ m_xmlReader.clear();
+ break;
+ case QXmlStreamReader::StartElement: {
+ m_currentTagName = m_xmlReader.name().toString();
+
+ if (m_currentTagName == CatchXml::GroupElement) {
+ testOutputNodeStarted(GroupNode);
+ } else if (m_currentTagName == CatchXml::TestCaseElement) {
+ m_reportedResult = false;
+ testOutputNodeStarted(TestCaseNode);
+ recordTestInformation(m_xmlReader.attributes());
+ sendResult(ResultType::TestStart);
+ } else if (m_currentTagName == CatchXml::SectionElement) {
+ testOutputNodeStarted(SectionNode);
+ recordTestInformation(m_xmlReader.attributes());
+ sendResult(ResultType::TestStart);
+ } else if (m_currentTagName == CatchXml::TestCaseResultElement) {
+ if (m_currentTestNode == OverallNode || m_currentTestNode == GroupNode)
+ continue;
+ if (m_reportedResult)
+ continue;
+ if (m_xmlReader.attributes().value("success").toString() == QStringLiteral("true"))
+ sendResult(ResultType::Pass);
+ else if (m_shouldFail)
+ sendResult(ResultType::UnexpectedPass);
+ } else if (m_currentTagName == CatchXml::SectionResultElement) {
+ const QXmlStreamAttributes attributes = m_xmlReader.attributes();
+ if (m_currentTestNode == OverallNode) { // the final results for the executable
+ int passes = attributes.value("successes").toInt();
+ int fails = attributes.value("failures").toInt();
+ int xfails = attributes.value("expectedFailures").toInt();
+ m_summary[ResultType::Pass] = passes - m_xpassCount;
+ m_summary[ResultType::Fail] = fails;
+ if (xfails)
+ m_summary[ResultType::ExpectedFail] = xfails;
+ if (m_xpassCount)
+ m_summary[ResultType::UnexpectedPass] = m_xpassCount;
+ }
+ if (m_currentTestNode == OverallNode || m_currentTestNode == GroupNode)
+ continue;
+ if (attributes.value("failures").toInt() == 0)
+ if (!m_reportedSectionResult)
+ sendResult(ResultType::Pass);
+ } else if (m_currentTagName == CatchXml::ExpressionElement) {
+ recordTestInformation(m_xmlReader.attributes());
+ if (m_xmlReader.attributes().value("success").toString() == QStringLiteral("true"))
+ m_currentResult = m_shouldFail ? ResultType::UnexpectedPass : ResultType::Pass;
+ else
+ m_currentResult = m_mayFail || m_shouldFail ? ResultType::ExpectedFail : ResultType::Fail;
+ } else if (m_currentTagName == CatchXml::BenchmarkResults) {
+ recordBenchmarkInformation(m_xmlReader.attributes());
+ m_currentResult = ResultType::Benchmark;
+ } else if (m_currentTagName == CatchXml::MeanElement) {
+ recordBenchmarkDetails(m_xmlReader.attributes(), {{{"mean"}, {"value"}},
+ {{"low mean"}, {"lowerBound"}},
+ {{"high mean"}, {"upperBound"}}});
+ } else if (m_currentTagName == CatchXml::StandardDevElement) {
+ recordBenchmarkDetails(m_xmlReader.attributes(), {
+ {{"standard deviation"}, {"value"}},
+ {{"low std dev"}, {"lowerBound"}},
+ {{"high std dev"}, {"upperBound"}}});
+ }
+ break;
+ }
+ case QXmlStreamReader::Characters: {
+ const QStringRef text = m_xmlReader.text();
+ if (m_currentTagName == CatchXml::ExpandedElement) {
+ m_currentExpression.append(text);
+ }
+ break;
+ }
+ case QXmlStreamReader::EndElement: {
+ const QStringRef currentTag = m_xmlReader.name();
+
+ if (currentTag == CatchXml::SectionElement) {
+ sendResult(ResultType::TestEnd);
+ testOutputNodeFinished(SectionNode);
+ } else if (currentTag == CatchXml::TestCaseElement) {
+ sendResult(ResultType::TestEnd);
+ testOutputNodeFinished(TestCaseNode);
+ } else if (currentTag == CatchXml::GroupElement) {
+ testOutputNodeFinished(GroupNode);
+ } else if (currentTag == CatchXml::ExpressionElement
+ || currentTag == CatchXml::BenchmarkResults) {
+ sendResult(m_currentResult);
+ m_currentExpression.clear();
+ m_testCaseInfo.pop();
+ }
+ break;
+ }
+ default:
+ // ignore
+ break;
+ }
+ }
+}
+
+TestResultPtr CatchOutputReader::createDefaultResult() const
+{
+ CatchResult *result = nullptr;
+ if (m_testCaseInfo.size() > 0) {
+ result = new CatchResult(id(), m_testCaseInfo.first().name);
+ result->setDescription(m_testCaseInfo.last().name);
+ result->setLine(m_testCaseInfo.last().line);
+ const QString &relativePathFromBuildDir = m_testCaseInfo.last().filename;
+ if (!relativePathFromBuildDir.isEmpty()) {
+ const QFileInfo fileInfo(m_buildDir + '/' + relativePathFromBuildDir);
+ result->setFileName(fileInfo.canonicalFilePath());
+ }
+ } else {
+ result = new CatchResult(id(), QString());
+ }
+ result->setSectionDepth(m_sectionDepth);
+
+ return TestResultPtr(result);
+}
+
+void CatchOutputReader::recordTestInformation(const QXmlStreamAttributes &attributes)
+{
+ QString name;
+ if (attributes.hasAttribute("name")) // successful expressions do not have one
+ name = attributes.value("name").toString();
+ else if (!m_testCaseInfo.isEmpty())
+ name = m_testCaseInfo.top().name;
+
+ m_testCaseInfo.append(TestOutputNode{
+ name,
+ attributes.value("filename").toString(),
+ attributes.value("line").toInt()
+ });
+ if (attributes.hasAttribute("tags")) {
+ const QString tags = attributes.value("tags").toString();
+ m_mayFail = tags.contains("[!mayfail]");
+ m_shouldFail = tags.contains("[!shouldfail]");
+ }
+}
+
+void CatchOutputReader::recordBenchmarkInformation(const QXmlStreamAttributes &attributes)
+{
+ QString name = attributes.value("name").toString();
+ QString fileName;
+ int line = 0;
+ if (!m_testCaseInfo.isEmpty()) {
+ fileName = m_testCaseInfo.top().filename;
+ line = m_testCaseInfo.top().line;
+ }
+ m_testCaseInfo.append(TestOutputNode{name, fileName, line});
+
+ m_currentExpression.append(name);
+ recordBenchmarkDetails(attributes, {{{"samples"}, {"samples"}},
+ {{"iterations"}, {"iterations"}},
+ {{"estimated duration"}, {"estimatedDuration"}}});
+ m_currentExpression.append(" ms"); // ugly
+}
+
+void CatchOutputReader::recordBenchmarkDetails(
+ const QXmlStreamAttributes &attributes,
+ const QList<QPair<QString, QString>> &stringAndAttrNames)
+{
+ m_currentExpression.append('\n');
+ int counter = 0;
+ for (const QPair<QString, QString> &curr : stringAndAttrNames) {
+ m_currentExpression.append(curr.first).append(": ");
+ m_currentExpression.append(attributes.value(curr.second).toString());
+ if (counter < stringAndAttrNames.size() - 1)
+ m_currentExpression.append(", ");
+ ++counter;
+ }
+}
+
+void CatchOutputReader::sendResult(const ResultType result)
+{
+ TestResultPtr catchResult = createDefaultResult();
+ catchResult->setResult(result);
+
+ if (result == ResultType::TestStart && m_testCaseInfo.size() > 0) {
+ catchResult->setDescription(tr("Executing %1 \"%2\"").arg(testOutputNodeToString().toLower())
+ .arg(catchResult->description()));
+ } else if (result == ResultType::Pass || result == ResultType::UnexpectedPass) {
+ if (result == ResultType::UnexpectedPass)
+ ++m_xpassCount;
+
+ if (m_currentExpression.isEmpty()) {
+ catchResult->setDescription(tr("%1 \"%2\" passed").arg(testOutputNodeToString())
+ .arg(catchResult->description()));
+ } else {
+ catchResult->setDescription(tr("Expression passed")
+ .append('\n').append(m_currentExpression));
+ }
+ m_reportedSectionResult = true;
+ m_reportedResult = true;
+ } else if (result == ResultType::Fail || result == ResultType::ExpectedFail) {
+ catchResult->setDescription(tr("Expression failed: %1").arg(m_currentExpression.trimmed()));
+ if (!m_reportedSectionResult)
+ m_reportedSectionResult = true;
+ m_reportedResult = true;
+ } else if (result == ResultType::TestEnd) {
+ catchResult->setDescription(tr("Finished executing %1 \"%2\"").arg(testOutputNodeToString().toLower())
+ .arg(catchResult->description()));
+ } else if (result == ResultType::Benchmark) {
+ catchResult->setDescription(m_currentExpression);
+ }
+
+ reportResult(catchResult);
+}
+
+void CatchOutputReader::testOutputNodeStarted(CatchOutputReader::TestOutputNodeType type)
+{
+ m_currentTestNode = type;
+ if (type == SectionNode) {
+ ++m_sectionDepth;
+ m_reportedSectionResult = false;
+ }
+}
+
+void CatchOutputReader::testOutputNodeFinished(CatchOutputReader::TestOutputNodeType type)
+{
+ switch (type) {
+ case GroupNode:
+ m_currentTestNode = OverallNode;
+ return;
+ case TestCaseNode:
+ m_currentTestNode = GroupNode;
+ m_testCaseInfo.pop();
+ return;
+ case SectionNode:
+ --m_sectionDepth;
+ m_testCaseInfo.pop();
+ m_currentTestNode = m_sectionDepth == 0 ? TestCaseNode : SectionNode;
+ return;
+ default:
+ return;
+ }
+}
+
+QString CatchOutputReader::testOutputNodeToString() const
+{
+ switch (m_currentTestNode) {
+ case OverallNode:
+ return QStringLiteral("Overall");
+ case GroupNode:
+ return QStringLiteral("Group");
+ case TestCaseNode:
+ return QStringLiteral("Test case");
+ case SectionNode:
+ return QStringLiteral("Section");
+ }
+
+ return QString();
+}
+
+} // namespace Internal
+} // namespace Autotest