1 /* This file is part of KDevelop
2     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
3     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "duchaintestbase.h"
9 
10 #include <QtTest/QtTest>
11 
12 #include <language/duchain/parsingenvironment.h>
13 #include <language/duchain/duchainlock.h>
14 #include <language/duchain/topducontext.h>
15 #include <language/duchain/indexedstring.h>
16 #include <language/duchain/dumpchain.h>
17 #include <kstandarddirs.h>
18 #include <kcomponentdata.h>
19 
20 #include "dumptypes.h"
21 #include "parsesession.h"
22 #include "phpdebugvisitor.h"
23 #include "../builders/declarationbuilder.h"
24 #include "../builders/usebuilder.h"
25 #include "../helper.h"
26 #include <completion/codemodelitem.h>
27 
28 #include <tests/autotestshell.h>
29 #include <tests/testcore.h>
30 #include <language/codegen/coderepresentation.h>
31 
32 using namespace KDevelop;
33 
34 
35 namespace Php
36 {
37 
DUChainTestBase()38 DUChainTestBase::DUChainTestBase()
39 {
40     KComponentData kd("kdevphpsupport");
41 }
42 
initTestCase()43 void DUChainTestBase::initTestCase()
44 {
45     AutoTestShell::init();
46     TestCore::initialize(Core::NoUi);
47 
48     DUChain::self()->disablePersistentStorage();
49     CodeRepresentation::setDiskChangesForbidden(true);
50 
51     //yeah... adding a testcase here is kinda strange, but anyways - we have to check for special
52     //handling of the internal functions file
53     //see e.g. testDeclarationReturnTypeDocBlock
54     QByteArray content("<?php "
55                         "class Exception {} "
56                         //test start
57                         "/** @return Exception **/ function should_return_exception() {}\n"
58                        "class internal_test_class {/** @return Exception **/ function should_return_exception() {}}\n"
59                         // test end
60                        "function define() {} function substr() {} class stdClass {}\n"
61                        "/**\n * @superglobal\n **/\n$_GET = array();\n"
62                        "interface testInterface {}\n");
63     content.append("interface Iterator { function rewind(); function current(); function key(); function next(); function valid(); } ");
64     TopDUContext* ctx = parseAdditionalFile(internalFunctionFile(), content);
65     QVERIFY(ctx);
66 
67     DUChainWriteLocker lock;
68     QVERIFY(ctx->problems().isEmpty());
69 
70     // set features and modification revision, to prevent test cases that use
71     // the full language plugin from re-parsing the big internal function file
72     ctx->setFeatures(TopDUContext::AllDeclarationsAndContexts);
73     ParsingEnvironmentFilePointer file = ctx->parsingEnvironmentFile();
74     QVERIFY(file);
75     file->setModificationRevision(ModificationRevision::revisionForFile(internalFunctionFile()));
76     DUChain::self()->updateContextEnvironment(ctx, file.data());
77 }
78 
cleanupTestCase()79 void DUChainTestBase::cleanupTestCase()
80 {
81     TestCore::shutdown();
82 }
83 
searchDeclaration(QList<CompletionTreeItemPointer> items,Declaration * declaration)84 CompletionTreeItemPointer DUChainTestBase::searchDeclaration(QList<CompletionTreeItemPointer> items, Declaration* declaration)
85 {
86     foreach(const CompletionTreeItemPointer &item, items) {
87         if (item->declaration().data() == declaration) {
88             return item;
89         }
90     }
91     return CompletionTreeItemPointer();
92 }
93 
hasImportedParentContext(TopDUContext * top,DUContext * lookingFor)94 bool DUChainTestBase::hasImportedParentContext(TopDUContext* top, DUContext* lookingFor)
95 {
96     kDebug() << "this topcontext has " << top->importedParentContexts().count() << " imported parent contexts"
97     << "\n we are looking for: " << lookingFor->url().byteArray();
98     foreach(const DUContext::Import &import, top->importedParentContexts()) {
99         if (import.context(top)) {
100             kDebug() << import.context(top)->url().byteArray();
101         }
102         if (import.context(top) == lookingFor) {
103             return true;
104         }
105     }
106     return false;
107 }
108 
parseAdditionalFile(const IndexedString & fileName,const QByteArray & contents)109 TopDUContext* DUChainTestBase::parseAdditionalFile(const IndexedString& fileName, const QByteArray& contents)
110 {
111     ParseSession session;
112     session.setContents(contents);
113     StartAst* ast = 0;
114     if (!session.parse(&ast)) qFatal("can't parse");
115 
116     EditorIntegrator editor(&session);
117     session.setCurrentDocument(fileName);
118     DeclarationBuilder declarationBuilder(&editor);
119     TopDUContext* top = declarationBuilder.build(fileName, ast);
120 
121     if ( fileName != internalFunctionFile() ) {
122         UseBuilder useBuilder(&editor);
123         useBuilder.buildUses(ast);
124     }
125 
126     if (!session.problems().isEmpty()) {
127         DUChainWriteLocker lock;
128         foreach( const ProblemPointer& p, session.problems() ) {
129             top->addProblem(p);
130         }
131     }
132 
133     return top;
134 }
135 
parse(const QByteArray & unit,DumpAreas dump,QString url,TopDUContext * update)136 TopDUContext* DUChainTestBase::parse(const QByteArray& unit, DumpAreas dump,
137                                      QString url, TopDUContext* update)
138 {
139     if (dump)
140         kDebug() << "==== Beginning new test case...:" << endl << unit;
141 
142     ParseSession session;
143     session.setContents(unit);
144     StartAst* ast = 0;
145     if (!session.parse(&ast)) {
146         kDebug() << "Parse failed";
147         return 0;
148     }
149 
150     if (dump & DumpAST) {
151         kDebug() << "===== AST:";
152         DebugVisitor debugVisitor(session.tokenStream(), session.contents());
153         debugVisitor.visitNode(ast);
154     }
155 
156     static int testNumber = 0;
157     if (url.isEmpty()) url = QString("file:///internal/%1").arg(testNumber++);
158 
159     EditorIntegrator editor(&session);
160     session.setCurrentDocument(IndexedString(url));
161     DeclarationBuilder declarationBuilder(&editor);
162     TopDUContext* top = declarationBuilder.build(session.currentDocument(), ast, ReferencedTopDUContext(update));
163 
164     if (!session.problems().isEmpty()) {
165         DUChainWriteLocker lock;
166         foreach( const ProblemPointer& p, session.problems() ) {
167             top->addProblem(p);
168         }
169     }
170 
171     if ( IndexedString(url) != internalFunctionFile() ) {
172         UseBuilder useBuilder(&editor);
173         useBuilder.buildUses(ast);
174     }
175 
176     if (dump & DumpDUChain) {
177         kDebug() << "===== DUChain:";
178 
179         DUChainWriteLocker lock(DUChain::lock());
180         dumpDUContext(top);
181     }
182 
183     if (dump & DumpType) {
184         kDebug() << "===== Types:";
185         DUChainWriteLocker lock(DUChain::lock());
186         DumpTypes dt;
187         foreach(const AbstractType::Ptr& type, declarationBuilder.topTypes()) {
188             dt.dump(type.unsafeData());
189         }
190     }
191 
192 
193 
194     if (dump)
195         kDebug() << "===== Finished test case.";
196 
197     return top;
198 }
199 }
200 
201 #include "duchaintestbase.moc"
202 
203