2019-01-02 16:11:44 +01:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** 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"
|
|
|
|
|
|
2020-09-17 11:17:03 +02:00
|
|
|
#include <utils/fileutils.h>
|
2019-01-02 16:11:44 +01:00
|
|
|
#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";
|
2020-04-20 10:37:28 +02:00
|
|
|
const char BenchmarkResults[] = "BenchmarkResults";
|
|
|
|
|
const char MeanElement[] = "mean";
|
|
|
|
|
const char StandardDevElement[] = "standardDeviation";
|
2019-01-02 16:11:44 +01:00
|
|
|
const char SectionResultElement[] = "OverallResults";
|
|
|
|
|
const char TestCaseResultElement[] = "OverallResult";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CatchOutputReader::CatchOutputReader(const QFutureInterface<TestResultPtr> &futureInterface,
|
2022-06-10 10:18:34 +02:00
|
|
|
Utils::QtcProcess *testApplication,
|
|
|
|
|
const Utils::FilePath &buildDirectory,
|
2021-05-26 15:50:03 +02:00
|
|
|
const Utils::FilePath &projectFile)
|
2019-01-02 16:11:44 +01:00
|
|
|
: 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;
|
2020-04-20 10:37:28 +02:00
|
|
|
} 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"}}});
|
2019-01-02 16:11:44 +01:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case QXmlStreamReader::Characters: {
|
2020-09-18 13:18:30 +02:00
|
|
|
const auto text = m_xmlReader.text();
|
2019-01-02 16:11:44 +01:00
|
|
|
if (m_currentTagName == CatchXml::ExpandedElement) {
|
|
|
|
|
m_currentExpression.append(text);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case QXmlStreamReader::EndElement: {
|
2020-09-18 13:18:30 +02:00
|
|
|
const auto currentTag = m_xmlReader.name();
|
2019-01-02 16:11:44 +01:00
|
|
|
|
2020-09-28 17:29:50 +02:00
|
|
|
if (currentTag == QLatin1String(CatchXml::SectionElement)) {
|
2019-01-02 16:11:44 +01:00
|
|
|
sendResult(ResultType::TestEnd);
|
|
|
|
|
testOutputNodeFinished(SectionNode);
|
2020-09-28 17:29:50 +02:00
|
|
|
} else if (currentTag == QLatin1String(CatchXml::TestCaseElement)) {
|
2019-01-02 16:11:44 +01:00
|
|
|
sendResult(ResultType::TestEnd);
|
|
|
|
|
testOutputNodeFinished(TestCaseNode);
|
2020-09-28 17:29:50 +02:00
|
|
|
} else if (currentTag == QLatin1String(CatchXml::GroupElement)) {
|
2019-01-02 16:11:44 +01:00
|
|
|
testOutputNodeFinished(GroupNode);
|
2020-09-28 17:29:50 +02:00
|
|
|
} else if (currentTag == QLatin1String(CatchXml::ExpressionElement)
|
|
|
|
|
|| currentTag == QLatin1String(CatchXml::BenchmarkResults)) {
|
2019-01-02 16:11:44 +01:00
|
|
|
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);
|
2020-09-24 10:48:31 +02:00
|
|
|
const QString givenPath = m_testCaseInfo.last().filename;
|
|
|
|
|
if (!givenPath.isEmpty()) {
|
2021-05-26 15:50:03 +02:00
|
|
|
result->setFileName(constructSourceFilePath(m_buildDir, givenPath));
|
2019-01-02 16:11:44 +01:00
|
|
|
}
|
|
|
|
|
} 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]");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-20 10:37:28 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-02 16:11:44 +01:00
|
|
|
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()));
|
2020-04-20 10:37:28 +02:00
|
|
|
} else if (result == ResultType::Benchmark) {
|
|
|
|
|
catchResult->setDescription(m_currentExpression);
|
2019-01-02 16:11:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|