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 &macro)
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 &macro : 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