1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 Jochen Seemann
4 **
5 ** This file is part of Qt Creator.
6 **
7 ** Commercial License Usage
8 ** Licensees holding valid commercial Qt licenses may use this file in
9 ** accordance with the commercial license agreement provided with the
10 ** Software or, alternatively, in accordance with the terms contained in
11 ** a written agreement between you and The Qt Company. For licensing terms
12 ** and conditions see https://www.qt.io/terms-conditions. For further
13 ** information use the contact form at https://www.qt.io/contact-us.
14 **
15 ** GNU General Public License Usage
16 ** Alternatively, this file may be used under the terms of the GNU
17 ** General Public License version 3 as published by the Free Software
18 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
19 ** included in the packaging of this file. Please review the following
20 ** information to ensure the GNU General Public License requirements will
21 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
22 **
23 ****************************************************************************/
24 
25 #include "catchtestparser.h"
26 
27 #include "catchcodeparser.h"
28 #include "catchframework.h"
29 #include "catchtreeitem.h"
30 
31 #include <cpptools/cppmodelmanager.h>
32 #include <cpptools/projectpart.h>
33 #include <utils/qtcassert.h>
34 
35 #include <QRegularExpression>
36 
37 namespace Autotest {
38 namespace Internal {
39 
isCatchTestCaseMacro(const QString & macroName)40 static bool isCatchTestCaseMacro(const QString &macroName)
41 {
42     const QStringList validTestCaseMacros = {
43         QStringLiteral("TEST_CASE"), QStringLiteral("SCENARIO"),
44         QStringLiteral("TEMPLATE_TEST_CASE"), QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE"),
45         QStringLiteral("TEMPLATE_LIST_TEST_CASE"),
46         QStringLiteral("TEMPLATE_TEST_CASE_SIG"), QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_SIG"),
47         QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
48         QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_METHOD"),
49         QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD_SIG"),
50         QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG"),
51         QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
52         QStringLiteral("TEMPLATE_LIST_TEST_CASE_METHOD"),
53         QStringLiteral("METHOD_AS_TEST_CASE"), QStringLiteral("REGISTER_TEST_CASE")
54     };
55     return validTestCaseMacros.contains(macroName);
56 }
57 
isCatchMacro(const QString & macroName)58 static bool isCatchMacro(const QString &macroName)
59 {
60     const QStringList validSectionMacros = {
61         QStringLiteral("SECTION"), QStringLiteral("WHEN")
62     };
63     return isCatchTestCaseMacro(macroName) || validSectionMacros.contains(macroName);
64 }
65 
includesCatchHeader(const CPlusPlus::Document::Ptr & doc,const CPlusPlus::Snapshot & snapshot)66 static bool includesCatchHeader(const CPlusPlus::Document::Ptr &doc,
67                                 const CPlusPlus::Snapshot &snapshot)
68 {
69     static const QStringList catchHeaders{"catch.hpp", // v2
70                                           "catch_all.hpp", // v3 - new approach
71                                           "catch_amalgamated.hpp",
72                                           "catch_test_macros.hpp",
73                                           "catch_template_test_macros.hpp"
74                                          };
75     for (const CPlusPlus::Document::Include &inc : doc->resolvedIncludes()) {
76         for (const QString &catchHeader : catchHeaders) {
77             if (inc.resolvedFileName().endsWith(catchHeader))
78                 return true;
79         }
80     }
81 
82     for (const QString &include : snapshot.allIncludesForDocument(doc->fileName())) {
83         for (const QString &catchHeader : catchHeaders) {
84             if (include.endsWith(catchHeader))
85                 return true;
86         }
87     }
88 
89     for (const QString &catchHeader : catchHeaders) {
90         if (CppParser::precompiledHeaderContains(snapshot,
91                                                  Utils::FilePath::fromString(doc->fileName()),
92                                                  catchHeader))
93             return true;
94     }
95     return false;
96 }
97 
hasCatchNames(const CPlusPlus::Document::Ptr & document)98 static bool hasCatchNames(const CPlusPlus::Document::Ptr &document)
99 {
100     for (const CPlusPlus::Document::MacroUse &macro : document->macroUses()) {
101         if (!macro.isFunctionLike())
102             continue;
103 
104         if (isCatchMacro(QLatin1String(macro.macro().name()))) {
105             const QVector<CPlusPlus::Document::Block> args = macro.arguments();
106             if (args.size() < 1)
107                 continue;
108             return true;
109         }
110     }
111     return false;
112 }
113 
processDocument(QFutureInterface<TestParseResultPtr> futureInterface,const Utils::FilePath & fileName)114 bool CatchTestParser::processDocument(QFutureInterface<TestParseResultPtr> futureInterface,
115                                       const Utils::FilePath &fileName)
116 {
117     CPlusPlus::Document::Ptr doc = document(fileName);
118     if (doc.isNull() || !includesCatchHeader(doc, m_cppSnapshot))
119         return false;
120 
121     const CppTools::CppModelManager *modelManager = CppTools::CppModelManager::instance();
122     const QString &filePath = doc->fileName();
123     const QByteArray &fileContent = getFileContent(fileName);
124 
125     if (!hasCatchNames(doc)) {
126         const QRegularExpression regex("\\b(SCENARIO|(TEMPLATE_(PRODUCT_)?)?TEST_CASE(_METHOD)?|"
127                                        "TEMPLATE_TEST_CASE(_METHOD)?_SIG|"
128                                        "TEMPLATE_PRODUCT_TEST_CASE(_METHOD)?_SIG|"
129                                        "TEMPLATE_LIST_TEST_CASE_METHOD|METHOD_AS_TEST_CASE|"
130                                        "REGISTER_TEST_CASE)");
131         if (!regex.match(QString::fromUtf8(fileContent)).hasMatch())
132             return false;
133     }
134 
135 
136     const QList<CppTools::ProjectPart::Ptr> projectParts = modelManager->projectPart(fileName);
137     if (projectParts.isEmpty()) // happens if shutting down while parsing
138         return false;
139     Utils::FilePath proFile;
140     const CppTools::ProjectPart::Ptr projectPart = projectParts.first();
141     proFile = Utils::FilePath::fromString(projectPart->projectFile);
142 
143     CatchCodeParser codeParser(fileContent, projectPart->languageFeatures);
144     const CatchTestCodeLocationList foundTests = codeParser.findTests();
145 
146     CatchParseResult *parseResult = new CatchParseResult(framework());
147     parseResult->itemType = TestTreeItem::TestSuite;
148     parseResult->fileName = fileName;
149     parseResult->name = filePath;
150     parseResult->displayName = filePath;
151     parseResult->proFile = proFile;
152 
153     for (const CatchTestCodeLocationAndType & testLocation : foundTests) {
154         CatchParseResult *testCase = new CatchParseResult(framework());
155         testCase->fileName = fileName;
156         testCase->name = testLocation.m_name;
157         testCase->proFile = proFile;
158         testCase->itemType = testLocation.m_type;
159         testCase->line = testLocation.m_line;
160         testCase->column = testLocation.m_column;
161         testCase->states = testLocation.states;
162 
163         parseResult->children.append(testCase);
164     }
165 
166     futureInterface.reportResult(TestParseResultPtr(parseResult));
167 
168     return !foundTests.isEmpty();
169 }
170 
createTestTreeItem() const171 TestTreeItem *CatchParseResult::createTestTreeItem() const
172 {
173     if (itemType == TestTreeItem::Root)
174         return nullptr;
175 
176     CatchTreeItem *item = new CatchTreeItem(framework, name, fileName, itemType);
177     item->setProFile(proFile);
178     item->setLine(line);
179     item->setColumn(column);
180     item->setStates(states);
181 
182     for (const TestParseResult *testSet : children)
183         item->appendChild(testSet->createTestTreeItem());
184 
185     return item;
186 }
187 
188 } // namespace Internal
189 } // namespace Autotest
190