diff --git a/src/plugins/autotest/CMakeLists.txt b/src/plugins/autotest/CMakeLists.txt index 0180d722386..55adc4a923e 100644 --- a/src/plugins/autotest/CMakeLists.txt +++ b/src/plugins/autotest/CMakeLists.txt @@ -21,6 +21,10 @@ add_qtc_plugin(AutoTest boost/boosttestsettings.cpp boost/boosttestsettings.h boost/boosttestsettingspage.cpp boost/boosttestsettingspage.h boost/boosttestsettingspage.ui 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/gtestconfiguration.cpp gtest/gtestconfiguration.h gtest/gtestconstants.h diff --git a/src/plugins/autotest/autotest.pro b/src/plugins/autotest/autotest.pro index c816a32a44f..e6c0fb980ee 100644 --- a/src/plugins/autotest/autotest.pro +++ b/src/plugins/autotest/autotest.pro @@ -27,6 +27,12 @@ SOURCES += \ testtreeitemdelegate.cpp \ testtreemodel.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/gtestparser.cpp \ gtest/gtesttreeitem.cpp \ @@ -91,6 +97,12 @@ HEADERS += \ testtreeitemdelegate.h \ testtreemodel.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/gtestparser.h \ gtest/gtesttreeitem.h \ diff --git a/src/plugins/autotest/autotest.qbs b/src/plugins/autotest/autotest.qbs index 737776dfc7c..5e7bee484b3 100644 --- a/src/plugins/autotest/autotest.qbs +++ b/src/plugins/autotest/autotest.qbs @@ -111,6 +111,13 @@ QtcPlugin { ] } + Group { + name: "Catch framework files" + files: [ + "catch/*" + ] + } + Group { name: "Test sources" condition: qtc.testsEnabled diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp index a0084ee65cf..faeb46df38d 100644 --- a/src/plugins/autotest/autotestplugin.cpp +++ b/src/plugins/autotest/autotestplugin.cpp @@ -43,6 +43,7 @@ #include "quick/quicktestframework.h" #include "gtest/gtestframework.h" #include "boost/boosttestframework.h" +#include "catch/catchframework.h" #include #include @@ -133,6 +134,7 @@ AutotestPluginPrivate::AutotestPluginPrivate() m_frameworkManager.registerTestFramework(new QuickTestFramework); m_frameworkManager.registerTestFramework(new GTestFramework); m_frameworkManager.registerTestFramework(new BoostTestFramework); + m_frameworkManager.registerTestFramework(new CatchFramework); m_frameworkManager.synchronizeSettings(ICore::settings()); m_navigationWidgetFactory = new TestNavigationWidgetFactory; diff --git a/src/plugins/autotest/catch/catchconfiguration.cpp b/src/plugins/autotest/catch/catchconfiguration.cpp new file mode 100644 index 00000000000..4033f9a4bb7 --- /dev/null +++ b/src/plugins/autotest/catch/catchconfiguration.cpp @@ -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 &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 diff --git a/src/plugins/autotest/catch/catchconfiguration.h b/src/plugins/autotest/catch/catchconfiguration.h new file mode 100644 index 00000000000..2a198558098 --- /dev/null +++ b/src/plugins/autotest/catch/catchconfiguration.h @@ -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 &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 diff --git a/src/plugins/autotest/catch/catchframework.cpp b/src/plugins/autotest/catch/catchframework.cpp new file mode 100644 index 00000000000..9ccff39cbe4 --- /dev/null +++ b/src/plugins/autotest/catch/catchframework.cpp @@ -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 diff --git a/src/plugins/autotest/catch/catchframework.h b/src/plugins/autotest/catch/catchframework.h new file mode 100644 index 00000000000..5f226da4bc6 --- /dev/null +++ b/src/plugins/autotest/catch/catchframework.h @@ -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 diff --git a/src/plugins/autotest/catch/catchoutputreader.cpp b/src/plugins/autotest/catch/catchoutputreader.cpp new file mode 100644 index 00000000000..a21ac657217 --- /dev/null +++ b/src/plugins/autotest/catch/catchoutputreader.cpp @@ -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 + +#include + +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 &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 diff --git a/src/plugins/autotest/catch/catchoutputreader.h b/src/plugins/autotest/catch/catchoutputreader.h new file mode 100644 index 00000000000..697cabde10c --- /dev/null +++ b/src/plugins/autotest/catch/catchoutputreader.h @@ -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 +#include +#include + +namespace Autotest { +namespace Internal { + +class CatchOutputReader : public TestOutputReader +{ + Q_DECLARE_TR_FUNCTIONS(Autotest::Internal::CatchOutputReader) + +public: + CatchOutputReader(const QFutureInterface &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 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 diff --git a/src/plugins/autotest/catch/catchresult.cpp b/src/plugins/autotest/catch/catchresult.cpp new file mode 100644 index 00000000000..12b701af5dc --- /dev/null +++ b/src/plugins/autotest/catch/catchresult.cpp @@ -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(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 diff --git a/src/plugins/autotest/catch/catchresult.h b/src/plugins/autotest/catch/catchresult.h new file mode 100644 index 00000000000..65e996f84b7 --- /dev/null +++ b/src/plugins/autotest/catch/catchresult.h @@ -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 diff --git a/src/plugins/autotest/catch/catchtestparser.cpp b/src/plugins/autotest/catch/catchtestparser.cpp new file mode 100644 index 00000000000..7b8772d6d35 --- /dev/null +++ b/src/plugins/autotest/catch/catchtestparser.cpp @@ -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 +#include +#include +#include +#include + +#include + +namespace Autotest { +namespace Internal { + +struct CatchTestCaseSpec +{ + QString name; + QStringList tags; +}; + +static bool isCatchTestCaseMacro(const QString ¯oName) +{ + const QStringList validTestCaseMacros = { + QStringLiteral("TEST_CASE"), QStringLiteral("SCENARIO") + }; + return validTestCaseMacros.contains(macroName); +} + +static bool isCatchMacro(const QString ¯oName) +{ + 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 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 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 ¯o : document->macroUses()) { + if (!macro.isFunctionLike()) + continue; + + if (isCatchMacro(QLatin1String(macro.macro().name()))) { + const QVector args = macro.arguments(); + if (args.size() < 1) + continue; + return true; + } + } + return false; +} + +static bool handleCatchDocument(QFutureInterface 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 result = visitor.testFunctions(); + QString proFile; + const QList &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 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 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 diff --git a/src/plugins/autotest/catch/catchtestparser.h b/src/plugins/autotest/catch/catchtestparser.h new file mode 100644 index 00000000000..704c3491aff --- /dev/null +++ b/src/plugins/autotest/catch/catchtestparser.h @@ -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 futureInterface, + const QString &fileName) override; +}; + +} // namespace Internal +} // namespace Autotest diff --git a/src/plugins/autotest/catch/catchtreeitem.cpp b/src/plugins/autotest/catch/catchtreeitem.cpp new file mode 100644 index 00000000000..5068f153201 --- /dev/null +++ b/src/plugins/autotest/catch/catchtreeitem.cpp @@ -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 +#include + +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(testConfiguration()); + if (config) + config->setRunMode(TestRunMode::Debug); + return config; +} + +struct CatchTestCases +{ + QStringList names; + QSet internalTargets; +}; + +static void collectTestInfo(const TestTreeItem *item, + QHash &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 CatchTreeItem::getAllTestConfigurations() const +{ + return getTestConfigurations(true); +} + +QList CatchTreeItem::getSelectedTestConfigurations() const +{ + return getTestConfigurations(false); +} + +QList CatchTreeItem::getTestConfigurationsForFile(const Utils::FilePath &fileName) const +{ + QList 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 CatchTreeItem::getTestConfigurations(bool ignoreCheckState) const +{ + QList result; + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project || type() != Root) + return result; + + QHash 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 diff --git a/src/plugins/autotest/catch/catchtreeitem.h b/src/plugins/autotest/catch/catchtreeitem.h new file mode 100644 index 00000000000..c9708acdba9 --- /dev/null +++ b/src/plugins/autotest/catch/catchtreeitem.h @@ -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 getAllTestConfigurations() const override; + QList getSelectedTestConfigurations() const override; + QList getTestConfigurationsForFile(const Utils::FilePath &fileName) const override; + +private: + QList getTestConfigurations(bool ignoreCheckState) const; +}; + +} // namespace Internal +} // namespace Autotest