AutoTest: add support for Catch framework

This adds a basic test and test output parser for the
Catch2 test framework.

Task-number: QTCREATORBUG-19740
Done-with: Christian Stenger <christian.stenger@qt.io>
Change-Id: Ic3322905ab82f414d2a26c325e130142233f72aa
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Jochen Seemann
2019-01-02 16:11:44 +01:00
committed by Christian Stenger
parent 13daf11d03
commit de38285bc3
16 changed files with 1365 additions and 0 deletions

View File

@@ -21,6 +21,10 @@ add_qtc_plugin(AutoTest
boost/boosttestsettings.cpp boost/boosttestsettings.h boost/boosttestsettings.cpp boost/boosttestsettings.h
boost/boosttestsettingspage.cpp boost/boosttestsettingspage.h boost/boosttestsettingspage.ui boost/boosttestsettingspage.cpp boost/boosttestsettingspage.h boost/boosttestsettingspage.ui
boost/boosttesttreeitem.cpp boost/boosttesttreeitem.h boost/boosttesttreeitem.cpp boost/boosttesttreeitem.h
catch/catchconfiguration.h catch/catchconfiguration.cpp
catch/catchframework.h catch/catchframework.cpp catch/catchoutputreader.h
catch/catchoutputreader.cpp catch/catchresult.h catch/catchresult.cpp catch/catchtestparser.h
catch/catchtestparser.cpp catch/catchtreeitem.h catch/catchtreeitem.cpp
gtest/gtest_utils.cpp gtest/gtest_utils.h gtest/gtest_utils.cpp gtest/gtest_utils.h
gtest/gtestconfiguration.cpp gtest/gtestconfiguration.h gtest/gtestconfiguration.cpp gtest/gtestconfiguration.h
gtest/gtestconstants.h gtest/gtestconstants.h

View File

@@ -27,6 +27,12 @@ SOURCES += \
testtreeitemdelegate.cpp \ testtreeitemdelegate.cpp \
testtreemodel.cpp \ testtreemodel.cpp \
testtreeview.cpp \ testtreeview.cpp \
catch/catchconfiguration.cpp \
catch/catchframework.cpp \
catch/catchoutputreader.cpp \
catch/catchresult.cpp \
catch/catchtestparser.cpp \
catch/catchtreeitem.cpp \
gtest/gtestconfiguration.cpp \ gtest/gtestconfiguration.cpp \
gtest/gtestparser.cpp \ gtest/gtestparser.cpp \
gtest/gtesttreeitem.cpp \ gtest/gtesttreeitem.cpp \
@@ -91,6 +97,12 @@ HEADERS += \
testtreeitemdelegate.h \ testtreeitemdelegate.h \
testtreemodel.h \ testtreemodel.h \
testtreeview.h \ testtreeview.h \
catch/catchconfiguration.h \
catch/catchframework.h \
catch/catchoutputreader.h \
catch/catchresult.h \
catch/catchtestparser.h \
catch/catchtreeitem.h \
gtest/gtestconfiguration.h \ gtest/gtestconfiguration.h \
gtest/gtestparser.h \ gtest/gtestparser.h \
gtest/gtesttreeitem.h \ gtest/gtesttreeitem.h \

View File

@@ -111,6 +111,13 @@ QtcPlugin {
] ]
} }
Group {
name: "Catch framework files"
files: [
"catch/*"
]
}
Group { Group {
name: "Test sources" name: "Test sources"
condition: qtc.testsEnabled condition: qtc.testsEnabled

View File

@@ -43,6 +43,7 @@
#include "quick/quicktestframework.h" #include "quick/quicktestframework.h"
#include "gtest/gtestframework.h" #include "gtest/gtestframework.h"
#include "boost/boosttestframework.h" #include "boost/boosttestframework.h"
#include "catch/catchframework.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/icontext.h> #include <coreplugin/icontext.h>
@@ -133,6 +134,7 @@ AutotestPluginPrivate::AutotestPluginPrivate()
m_frameworkManager.registerTestFramework(new QuickTestFramework); m_frameworkManager.registerTestFramework(new QuickTestFramework);
m_frameworkManager.registerTestFramework(new GTestFramework); m_frameworkManager.registerTestFramework(new GTestFramework);
m_frameworkManager.registerTestFramework(new BoostTestFramework); m_frameworkManager.registerTestFramework(new BoostTestFramework);
m_frameworkManager.registerTestFramework(new CatchFramework);
m_frameworkManager.synchronizeSettings(ICore::settings()); m_frameworkManager.synchronizeSettings(ICore::settings());
m_navigationWidgetFactory = new TestNavigationWidgetFactory; m_navigationWidgetFactory = new TestNavigationWidgetFactory;

View File

@@ -0,0 +1,55 @@
/****************************************************************************
**
** 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 "catchconfiguration.h"
#include "catchoutputreader.h"
namespace Autotest {
namespace Internal {
TestOutputReader *CatchConfiguration::outputReader(const QFutureInterface<TestResultPtr> &fi, QProcess *app) const
{
return new CatchOutputReader(fi, app, buildDirectory(), projectFile());
}
QStringList CatchConfiguration::argumentsForTestRunner(QStringList *omitted) const
{
Q_UNUSED(omitted)
QStringList arguments;
arguments << "--reporter" << "xml";
if (testCaseCount())
arguments << "\"" + testCases().join("\",\"") + "\"";
return arguments;
}
Utils::Environment CatchConfiguration::filteredEnvironment(const Utils::Environment &original) const
{
return original;
}
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,43 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "../testconfiguration.h"
namespace Autotest {
namespace Internal {
class CatchConfiguration : public DebuggableTestConfiguration
{
public:
CatchConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {}
TestOutputReader *outputReader(const QFutureInterface<TestResultPtr> &fi,
QProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
};
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,56 @@
/****************************************************************************
**
** 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 "catchframework.h"
#include "catchtestparser.h"
#include "catchtreeitem.h"
namespace Autotest {
namespace Internal {
const char *CatchFramework::name() const
{
return "Catch";
}
unsigned CatchFramework::priority() const
{
return 12;
}
ITestParser *CatchFramework::createTestParser()
{
return new CatchTestParser(this);
}
TestTreeItem *CatchFramework::createRootNode()
{
return new CatchTreeItem(this,
QCoreApplication::translate("CatchFramework", "Catch Test"),
QString(), TestTreeItem::Root);
}
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,46 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "../itestframework.h"
namespace Autotest {
namespace Internal {
class CatchFramework : public ITestFramework
{
public:
CatchFramework() : ITestFramework(true) {}
const char *name() const override;
unsigned priority() const override;
protected:
ITestParser *createTestParser() override;
TestTreeItem *createRootNode() override;
};
} // namepsace Internal
} // namespace Autotest

View File

@@ -0,0 +1,272 @@
/****************************************************************************
**
** 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 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;
}
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) {
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::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()));
}
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

View File

@@ -0,0 +1,87 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "../testoutputreader.h"
#include <QCoreApplication>
#include <QStack>
#include <QXmlStreamReader>
namespace Autotest {
namespace Internal {
class CatchOutputReader : public TestOutputReader
{
Q_DECLARE_TR_FUNCTIONS(Autotest::Internal::CatchOutputReader)
public:
CatchOutputReader(const QFutureInterface<TestResultPtr> &futureInterface,
QProcess *testApplication, const QString &buildDirectory,
const QString &projectFile);
protected:
void processOutputLine(const QByteArray &outputLineWithNewLine) override;
TestResultPtr createDefaultResult() const override;
private:
enum TestOutputNodeType {
OverallNode,
GroupNode,
TestCaseNode,
SectionNode
} m_currentTestNode = OverallNode;
struct TestOutputNode {
QString name;
QString filename;
int line;
};
void recordTestInformation(const QXmlStreamAttributes &attributes);
void sendResult(const ResultType result);
void testOutputNodeStarted(TestOutputNodeType type);
void testOutputNodeFinished(TestOutputNodeType type);
QString testOutputNodeToString() const;
QStack<TestOutputNode> m_testCaseInfo;
int m_sectionDepth = 0;
QString m_projectFile;
QString m_currentTagName;
QString m_currentExpression;
QXmlStreamReader m_xmlReader;
ResultType m_currentResult = ResultType::Invalid;
int m_xpassCount = 0;
bool m_mayFail = false;
bool m_shouldFail = false;
bool m_reportedResult = false;
bool m_reportedSectionResult = false;
};
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,61 @@
/****************************************************************************
**
** 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 "catchresult.h"
namespace Autotest {
namespace Internal {
CatchResult::CatchResult(const QString &id, const QString &name)
: TestResult(id, name)
{
}
bool CatchResult::isDirectParentOf(const TestResult *other, bool *needsIntermediate) const
{
if (!TestResult::isDirectParentOf(other, needsIntermediate))
return false;
const CatchResult *catchOther = static_cast<const CatchResult *>(other);
if (result() != ResultType::TestStart)
return false;
if (catchOther->result() == ResultType::TestStart) {
if (fileName() != catchOther->fileName())
return false;
return sectionDepth() + 1 == catchOther->sectionDepth();
}
if (sectionDepth() <= catchOther->sectionDepth() && catchOther->result() == ResultType::Pass)
return true;
if (fileName() != catchOther->fileName() || sectionDepth() > catchOther->sectionDepth())
return false;
return name() == catchOther->name();
}
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,47 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "../testresult.h"
namespace Autotest {
namespace Internal {
class CatchResult : public TestResult
{
public:
CatchResult(const QString &id, const QString &name);
bool isDirectParentOf(const TestResult *other, bool *needsIntermediate) const override;
void setSectionDepth(int depth) { m_sectionDepth = depth; }
int sectionDepth() const { return m_sectionDepth; }
private:
int m_sectionDepth = 0;
};
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,288 @@
/****************************************************************************
**
** 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 "catchtestparser.h"
#include "catchtreeitem.h"
#include <cpptools/cppmodelmanager.h>
#include <cpptools/projectpart.h>
#include <cplusplus/LookupContext.h>
#include <cplusplus/Overview.h>
#include <utils/qtcassert.h>
#include <QRegularExpression>
namespace Autotest {
namespace Internal {
struct CatchTestCaseSpec
{
QString name;
QStringList tags;
};
static bool isCatchTestCaseMacro(const QString &macroName)
{
const QStringList validTestCaseMacros = {
QStringLiteral("TEST_CASE"), QStringLiteral("SCENARIO")
};
return validTestCaseMacros.contains(macroName);
}
static bool isCatchMacro(const QString &macroName)
{
const QStringList validSectionMacros = {
QStringLiteral("SECTION"), QStringLiteral("WHEN")
};
return isCatchTestCaseMacro(macroName) || validSectionMacros.contains(macroName);
}
static inline QString stringLiteralToQString(const CPlusPlus::StringLiteral *literal)
{
return QString::fromLatin1(literal->chars(), int(literal->size()));
}
static QStringList parseTags(const QString &tagsString)
{
QStringList tagsList;
const QRegularExpression tagRegEx("\\[(.*?)\\]",QRegularExpression::CaseInsensitiveOption);
int pos = 0;
QRegularExpressionMatch it = tagRegEx.match(tagsString, pos);
while (it.hasMatch()) {
tagsList.append(it.captured(1));
pos += it.capturedLength();
it = tagRegEx.match(tagsString, pos);
}
return tagsList;
}
class CatchTestVisitor : public CPlusPlus::ASTVisitor
{
public:
explicit CatchTestVisitor(CPlusPlus::Document::Ptr doc)
: CPlusPlus::ASTVisitor(doc->translationUnit()) , m_document(doc) {}
bool visit(CPlusPlus::FunctionDefinitionAST *ast) override;
QMap<CatchTestCaseSpec, TestCodeLocationAndType> testFunctions() const { return m_testFunctions; }
private:
bool isValidTestCase(unsigned int macroTokenIndex, QString &testCaseName, QStringList &tags);
CPlusPlus::Document::Ptr m_document;
CPlusPlus::Overview m_overview;
QMap<CatchTestCaseSpec, TestCodeLocationAndType> m_testFunctions;
};
bool CatchTestVisitor::visit(CPlusPlus::FunctionDefinitionAST *ast)
{
if (!ast || !ast->declarator || !ast->declarator->core_declarator)
return false;
CPlusPlus::DeclaratorIdAST *id = ast->declarator->core_declarator->asDeclaratorId();
if (!id || !id->name)
return false;
const QString prettyName = m_overview.prettyName(id->name->name);
if (!isCatchTestCaseMacro(prettyName))
return false;
QString testName;
QStringList tags;
if (!isValidTestCase(ast->firstToken(), testName, tags))
return false;
CatchTestCaseSpec spec;
spec.name = testName;
if (prettyName == "SCENARIO")
spec.name.prepend("Scenario: "); // TODO maybe better as a flag?
spec.tags = tags;
TestCodeLocationAndType location;
location.m_type = TestTreeItem::TestCase;
location.m_name = m_document->fileName();
getTokenStartPosition(ast->firstToken(), &location.m_line, &location.m_column);
m_testFunctions.insert(spec, location);
return true;
}
bool CatchTestVisitor::isValidTestCase(unsigned int macroTokenIndex, QString &testCaseName, QStringList &tags)
{
QTC_ASSERT(testCaseName.isEmpty(), return false);
QTC_ASSERT(tags.isEmpty(), return false);
unsigned int end = tokenCount();
++macroTokenIndex;
enum ParseMode { TestCaseMode, TagsMode, Ignored } mode = TestCaseMode;
QString captured;
while (macroTokenIndex < end) {
CPlusPlus::Token token = tokenAt(macroTokenIndex);
if (token.kind() == CPlusPlus::T_RPAREN) {
if (mode == TagsMode)
tags = parseTags(captured);
else
testCaseName = captured;
break;
} else if (token.kind() == CPlusPlus::T_COMMA) {
if (mode == TestCaseMode) {
mode = TagsMode;
testCaseName = captured;
captured = QString();
} else if (mode == TagsMode) {
mode = Ignored;
}
} else if (token.kind() == CPlusPlus::T_STRING_LITERAL) {
if (mode != Ignored)
captured += stringLiteralToQString(stringLiteral(macroTokenIndex));
}
++macroTokenIndex;
}
return !testCaseName.isEmpty();
}
inline bool operator<(const CatchTestCaseSpec &spec1, const CatchTestCaseSpec &spec2)
{
if (spec1.name != spec2.name)
return spec1.name < spec2.name;
if (spec1.tags != spec2.tags)
return spec1.tags < spec2.tags;
return false;
}
static bool includesCatchHeader(const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot)
{
static const QString catchHeader("catch.hpp");
for (const CPlusPlus::Document::Include &inc : doc->resolvedIncludes()) {
if (inc.resolvedFileName().endsWith(catchHeader))
return true;
}
for (const QString &include : snapshot.allIncludesForDocument(doc->fileName())) {
if (include.endsWith(catchHeader))
return true;
}
return false;
}
static bool hasCatchNames(const CPlusPlus::Document::Ptr &document)
{
for (const CPlusPlus::Document::MacroUse &macro : document->macroUses()) {
if (!macro.isFunctionLike())
continue;
if (isCatchMacro(QLatin1String(macro.macro().name()))) {
const QVector<CPlusPlus::Document::Block> args = macro.arguments();
if (args.size() < 1)
continue;
return true;
}
}
return false;
}
static bool handleCatchDocument(QFutureInterface<TestParseResultPtr> futureInterface,
const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot,
ITestFramework *framework)
{
const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
const QString &filePath = doc->fileName();
const QByteArray &fileContent = CppParser::getFileContent(filePath);
CPlusPlus::Document::Ptr document = snapshot.preprocessedDocument(fileContent, filePath);
document->check();
CPlusPlus::AST *ast = document->translationUnit()->ast();
CatchTestVisitor visitor(document);
visitor.accept(ast);
QMap<CatchTestCaseSpec, TestCodeLocationAndType> result = visitor.testFunctions();
QString proFile;
const QList<CppTools::ProjectPart::Ptr> &ppList = modelManager->projectPart(filePath);
if (ppList.size())
proFile = ppList.first()->projectFile;
else
return false;
CatchParseResult *parseResult = new CatchParseResult(framework);
parseResult->itemType = TestTreeItem::TestCase;
parseResult->fileName = filePath;
parseResult->name = filePath;
parseResult->displayName = filePath;
QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(doc->fileName());
if (projectParts.isEmpty()) // happens if shutting down while parsing
return false;
parseResult->proFile = projectParts.first()->projectFile;
for (const auto &testSpec : result.keys()) {
const TestCodeLocationAndType &testLocation = result.value(testSpec);
CatchParseResult *testCase = new CatchParseResult(framework);
testCase->fileName = filePath;
testCase->name = testSpec.name;
testCase->proFile = proFile;
testCase->itemType = TestTreeItem::TestFunction;
testCase->line = testLocation.m_line;
testCase->column = testLocation.m_column;
parseResult->children.append(testCase);
}
futureInterface.reportResult(TestParseResultPtr(parseResult));
return !result.keys().isEmpty();
}
bool CatchTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface, const QString &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
if (doc.isNull() || !includesCatchHeader(doc, m_cppSnapshot) || !hasCatchNames(doc))
return false;
return handleCatchDocument(futureInterface, doc, m_cppSnapshot, framework());
}
TestTreeItem *CatchParseResult::createTestTreeItem() const
{
if (itemType == TestTreeItem::Root)
return nullptr;
CatchTreeItem *item = new CatchTreeItem(framework, name, fileName, itemType);
item->setProFile(proFile);
item->setLine(line);
item->setColumn(column);
for (const TestParseResult *testSet : children)
item->appendChild(testSet->createTestTreeItem());
return item;
}
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,50 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "../itestparser.h"
namespace Autotest {
namespace Internal {
class CatchParseResult : public TestParseResult
{
public:
explicit CatchParseResult(ITestFramework *framework)
: TestParseResult(framework) {}
TestTreeItem *createTestTreeItem() const override;
};
class CatchTestParser : public CppParser
{
public:
CatchTestParser(ITestFramework *framework)
: CppParser(framework) {}
bool processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
const QString &fileName) override;
};
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,275 @@
/****************************************************************************
**
** 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 "catchtreeitem.h"
#include "catchtestparser.h"
#include "catchconfiguration.h"
#include "catchframework.h"
#include <projectexplorer/session.h>
#include <utils/qtcassert.h>
namespace Autotest {
namespace Internal {
QVariant CatchTreeItem::data(int column, int role) const
{
switch (role) {
case Qt::DisplayRole:
if (type() == Root)
break;
return name();
case Qt::CheckStateRole:
switch (type()) {
case Root:
case GroupNode:
case TestCase:
case TestFunction:
return checked();
default:
return QVariant();
}
}
return TestTreeItem::data(column, role);
}
TestTreeItem *CatchTreeItem::copyWithoutChildren()
{
CatchTreeItem *copied = new CatchTreeItem(framework());
copied->copyBasicDataFrom(this);
return copied;
}
TestTreeItem *CatchTreeItem::find(const TestParseResult *result)
{
QTC_ASSERT(result, return nullptr);
switch (type()) {
case Root:
if (result->framework->grouping()) {
const QString path = QFileInfo(result->fileName).absolutePath();
for (int row = 0; row < childCount(); ++row) {
TestTreeItem *group = childAt(row);
if (group->filePath() != path)
continue;
if (auto groupChild = group->findChildByFile(result->fileName))
return groupChild;
}
return nullptr;
}
return findChildByFile(result->fileName);
case GroupNode:
return findChildByFile(result->fileName);
case TestCase:
return findChildByNameAndFile(result->name, result->fileName);
default:
return nullptr;
}
}
TestTreeItem *CatchTreeItem::findChild(const TestTreeItem *other)
{
QTC_ASSERT(other, return nullptr);
switch (type()) {
case Root:
return findChildByFileAndType(other->filePath(), other->type());
case GroupNode:
return other->type() == TestCase ? findChildByFile(other->filePath()) : nullptr;
case TestCase:
return findChildByNameAndFile(other->name(), other->filePath());
default:
return nullptr;
}
}
bool CatchTreeItem::modify(const TestParseResult *result)
{
QTC_ASSERT(result, return false);
switch (type()) {
case TestCase:
case TestFunction:
return modifyTestFunctionContent(result);
default:
return false;
}
}
TestTreeItem *CatchTreeItem::createParentGroupNode() const
{
const QFileInfo fileInfo(filePath());
const QFileInfo base(fileInfo.absolutePath());
return new CatchTreeItem(framework(), base.baseName(), fileInfo.absolutePath(), TestTreeItem::GroupNode);
}
bool CatchTreeItem::canProvideTestConfiguration() const
{
return type() == TestFunction;
}
bool CatchTreeItem::canProvideDebugConfiguration() const
{
return canProvideTestConfiguration();
}
TestConfiguration *CatchTreeItem::testConfiguration() const
{
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
QTC_ASSERT(project, return nullptr);
if (type() != TestFunction)
return nullptr;
CatchConfiguration *config = nullptr;
config = new CatchConfiguration(framework());
config->setTestCaseCount(childCount());
config->setProjectFile(proFile());
config->setProject(project);
config->setTestCases(QStringList(name()));
config->setInternalTargets(internalTargets());
return config;
}
TestConfiguration *CatchTreeItem::debugConfiguration() const
{
CatchConfiguration *config = static_cast<CatchConfiguration *>(testConfiguration());
if (config)
config->setRunMode(TestRunMode::Debug);
return config;
}
struct CatchTestCases
{
QStringList names;
QSet<QString> internalTargets;
};
static void collectTestInfo(const TestTreeItem *item,
QHash<QString, CatchTestCases> &testCasesForProfile,
bool ignoreCheckState)
{
QTC_ASSERT(item, return);
const int childCount = item->childCount();
if (item->type() == TestTreeItem::GroupNode) {
item->forFirstLevelChildren([&testCasesForProfile, ignoreCheckState](TestTreeItem *it) {
collectTestInfo(it, testCasesForProfile, ignoreCheckState);
});
return;
}
QTC_ASSERT(childCount != 0, return);
QTC_ASSERT(item->type() == TestTreeItem::TestCase, return);
if (ignoreCheckState || item->checked() == Qt::Checked) {
const QString &projectFile = item->childAt(0)->proFile();
item->forAllChildren([&testCasesForProfile, &projectFile](TestTreeItem *it) {
testCasesForProfile[projectFile].names.append(it->name());
});
testCasesForProfile[projectFile].internalTargets.unite(item->internalTargets());
} else if (item->checked() == Qt::PartiallyChecked) {
item->forFirstLevelChildren([&testCasesForProfile](TestTreeItem *child) {
QTC_ASSERT(child->type() == TestTreeItem::TestFunction, return);
if (child->checked() == Qt::Checked) {
testCasesForProfile[child->proFile()].names.append(child->name());
testCasesForProfile[child->proFile()].internalTargets.unite(
child->internalTargets());
}
});
}
}
QList<TestConfiguration *> CatchTreeItem::getAllTestConfigurations() const
{
return getTestConfigurations(true);
}
QList<TestConfiguration *> CatchTreeItem::getSelectedTestConfigurations() const
{
return getTestConfigurations(false);
}
QList<TestConfiguration *> CatchTreeItem::getTestConfigurationsForFile(const Utils::FilePath &fileName) const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project || type() != Root)
return result;
const QString filePath = fileName.toString();
for (int row = 0, count = childCount(); row < count; ++row) {
const TestTreeItem *item = childAt(row);
QTC_ASSERT(item, continue);
if (childAt(row)->name() != filePath)
continue;
CatchConfiguration *testConfig = nullptr;
QStringList testCases;
item->forFirstLevelChildren([&testCases](TestTreeItem *child) {
testCases << child->name();
});
testConfig = new CatchConfiguration(framework());
testConfig->setTestCases(testCases);
testConfig->setProjectFile(item->proFile());
testConfig->setProject(ProjectExplorer::SessionManager::startupProject());
testConfig->setInternalTargets(item->internalTargets());
result << testConfig;
}
return result;
}
QList<TestConfiguration *> CatchTreeItem::getTestConfigurations(bool ignoreCheckState) const
{
QList<TestConfiguration *> result;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project || type() != Root)
return result;
QHash<QString, CatchTestCases> testCasesForProfile;
forFirstLevelChildren([&testCasesForProfile, ignoreCheckState](TestTreeItem *item) {
collectTestInfo(item, testCasesForProfile, ignoreCheckState);
});
for (auto it = testCasesForProfile.begin(), end = testCasesForProfile.end(); it != end; ++it) {
for (const QString &target : qAsConst(it.value().internalTargets)) {
CatchConfiguration *tc = new CatchConfiguration(framework());
tc->setTestCases(it.value().names);
if (ignoreCheckState)
tc->setTestCaseCount(0);
tc->setProjectFile(it.key());
tc->setProject(project);
tc->setInternalTarget(target);
result << tc;
}
}
return result;
}
} // namespace Internal
} // namespace Autotest

View File

@@ -0,0 +1,60 @@
/****************************************************************************
**
** 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.
**
****************************************************************************/
#pragma once
#include "../testtreeitem.h"
namespace Autotest {
namespace Internal {
class CatchTreeItem : public TestTreeItem
{
public:
explicit CatchTreeItem(ITestFramework *framework, const QString &name = QString(),
const QString &filePath = QString(), Type type = Root)
: TestTreeItem(framework, name, filePath, type) {}
QVariant data(int column, int role) const override;
TestTreeItem *copyWithoutChildren() override;
TestTreeItem *find(const TestParseResult *result) override;
TestTreeItem *findChild(const TestTreeItem *other) override;
bool modify(const TestParseResult *result) override;
TestTreeItem *createParentGroupNode() const override;
bool canProvideTestConfiguration() const override;
bool canProvideDebugConfiguration() const override;
TestConfiguration *testConfiguration() const override;
TestConfiguration *debugConfiguration() const override;
QList<TestConfiguration *> getAllTestConfigurations() const override;
QList<TestConfiguration *> getSelectedTestConfigurations() const override;
QList<TestConfiguration *> getTestConfigurationsForFile(const Utils::FilePath &fileName) const override;
private:
QList<TestConfiguration *> getTestConfigurations(bool ignoreCheckState) const;
};
} // namespace Internal
} // namespace Autotest