1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "boosttestparser.h"
27 #include "boostcodeparser.h"
28 #include "boosttestframework.h"
29 #include "boosttesttreeitem.h"
30
31 #include <cpptools/cppmodelmanager.h>
32
33 #include <QMap>
34 #include <QRegularExpression>
35 #include <QRegularExpressionMatch>
36
37 namespace Autotest {
38 namespace Internal {
39
40 namespace BoostTestUtils {
41 static const QStringList relevant = {
42 QStringLiteral("BOOST_AUTO_TEST_CASE"), QStringLiteral("BOOST_TEST_CASE"),
43 QStringLiteral("BOOST_DATA_TEST_CASE"), QStringLiteral("BOOST_FIXTURE_TEST_CASE"),
44 QStringLiteral("BOOST_PARAM_TEST_CASE"), QStringLiteral("BOOST_DATA_TEST_CASE_F"),
45 QStringLiteral("BOOST_AUTO_TEST_CASE_TEMPLATE"),
46 QStringLiteral("BOOST_FIXTURE_TEST_CASE_TEMPLATE"),
47 };
48
isBoostTestMacro(const QString & macro)49 bool isBoostTestMacro(const QString ¯o)
50 {
51 return relevant.contains(macro);
52 }
53 } // BoostTestUtils
54
createTestTreeItem() const55 TestTreeItem *BoostTestParseResult::createTestTreeItem() const
56 {
57 if (itemType == TestTreeItem::Root)
58 return nullptr;
59
60 BoostTestTreeItem *item = new BoostTestTreeItem(framework, displayName, fileName, itemType);
61 item->setProFile(proFile);
62 item->setLine(line);
63 item->setColumn(column);
64 item->setStates(state);
65 item->setFullName(name);
66
67 for (const TestParseResult *funcParseResult : children)
68 item->appendChild(funcParseResult->createTestTreeItem());
69 return item;
70 }
71
72
includesBoostTest(const CPlusPlus::Document::Ptr & doc,const CPlusPlus::Snapshot & snapshot)73 static bool includesBoostTest(const CPlusPlus::Document::Ptr &doc,
74 const CPlusPlus::Snapshot &snapshot)
75 {
76 static const QRegularExpression boostTestHpp("^.*/boost/test/.*\\.hpp$");
77 for (const CPlusPlus::Document::Include &inc : doc->resolvedIncludes()) {
78 if (boostTestHpp.match(inc.resolvedFileName()).hasMatch())
79 return true;
80 }
81
82 for (const QString &include : snapshot.allIncludesForDocument(doc->fileName())) {
83 if (boostTestHpp.match(include).hasMatch())
84 return true;
85 }
86
87 return CppParser::precompiledHeaderContains(snapshot,
88 Utils::FilePath::fromString(doc->fileName()),
89 boostTestHpp);
90 }
91
hasBoostTestMacros(const CPlusPlus::Document::Ptr & doc)92 static bool hasBoostTestMacros(const CPlusPlus::Document::Ptr &doc)
93 {
94 for (const CPlusPlus::Document::MacroUse ¯o : doc->macroUses()) {
95 if (!macro.isFunctionLike())
96 continue;
97 if (BoostTestUtils::isBoostTestMacro(QLatin1String(macro.macro().name())))
98 return true;
99 }
100 return false;
101 }
102
createParseResult(const QString & name,const Utils::FilePath & filePath,const Utils::FilePath & projectFile,ITestFramework * framework,TestTreeItem::Type type,const BoostTestInfo & info)103 static BoostTestParseResult *createParseResult(const QString &name, const Utils::FilePath &filePath,
104 const Utils::FilePath &projectFile,
105 ITestFramework *framework,
106 TestTreeItem::Type type, const BoostTestInfo &info)
107 {
108 BoostTestParseResult *partialSuite = new BoostTestParseResult(framework);
109 partialSuite->itemType = type;
110 partialSuite->fileName = filePath;
111 partialSuite->name = info.fullName;
112 partialSuite->displayName = name;
113 partialSuite->line = info.line;
114 partialSuite->column = 0;
115 partialSuite->proFile = projectFile;
116 partialSuite->state = info.state;
117 return partialSuite;
118
119 }
120
processDocument(QFutureInterface<TestParseResultPtr> futureInterface,const Utils::FilePath & fileName)121 bool BoostTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
122 const Utils::FilePath &fileName)
123 {
124 CPlusPlus::Document::Ptr doc = document(fileName);
125 if (doc.isNull() || !includesBoostTest(doc, m_cppSnapshot) || !hasBoostTestMacros(doc))
126 return false;
127
128 const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
129 const QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(fileName);
130 if (projectParts.isEmpty()) // happens if shutting down while parsing
131 return false;
132 const CppTools::ProjectPart::Ptr projectPart = projectParts.first();
133 const auto projectFile = Utils::FilePath::fromString(projectPart->projectFile);
134 const QByteArray &fileContent = getFileContent(fileName);
135
136 BoostCodeParser codeParser(fileContent, projectPart->languageFeatures, doc, m_cppSnapshot);
137 const BoostTestCodeLocationList foundTests = codeParser.findTests();
138 if (foundTests.isEmpty())
139 return false;
140
141 for (const BoostTestCodeLocationAndType &locationAndType : foundTests) {
142 BoostTestInfoList suitesStates = locationAndType.m_suitesState;
143 BoostTestInfo firstSuite = suitesStates.first();
144 QStringList suites = firstSuite.fullName.split('/');
145 BoostTestParseResult *topLevelSuite = createParseResult(suites.first(), fileName,
146 projectFile, framework(),
147 TestTreeItem::TestSuite,
148 firstSuite);
149 BoostTestParseResult *currentSuite = topLevelSuite;
150 suitesStates.removeFirst();
151 while (!suitesStates.isEmpty()) {
152 firstSuite = suitesStates.first();
153 suites = firstSuite.fullName.split('/');
154 BoostTestParseResult *suiteResult = createParseResult(suites.last(), fileName,
155 projectFile, framework(),
156 TestTreeItem::TestSuite,
157 firstSuite);
158 currentSuite->children.append(suiteResult);
159 suitesStates.removeFirst();
160 currentSuite = suiteResult;
161 }
162
163 if (currentSuite) {
164 BoostTestInfo tmpInfo{
165 locationAndType.m_suitesState.last().fullName + "::" + locationAndType.m_name,
166 locationAndType.m_state, locationAndType.m_line};
167 BoostTestParseResult *funcResult = createParseResult(locationAndType.m_name, fileName,
168 projectFile, framework(),
169 locationAndType.m_type,
170 tmpInfo);
171 currentSuite->children.append(funcResult);
172 futureInterface.reportResult(TestParseResultPtr(topLevelSuite));
173 }
174 }
175 return true;
176 }
177
178 } // namespace Internal
179 } // namespace Autotest
180