1 /* This file is part of KDevelop
2
3 SPDX-FileCopyrightText: 2010 Niko Sams <niko.sams@gmail.com>
4 SPDX-FileCopyrightText: 2011 Milian Wolff <mail@milianw.de>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7 */
8
9 #include "duchain_multiplefiles.h"
10
11 #include <QtTest/QtTest>
12
13 #include <qtest_kde.h>
14
15 #include <tests/testcore.h>
16 #include <interfaces/ilanguagecontroller.h>
17 #include <language/backgroundparser/backgroundparser.h>
18 #include <tests/testproject.h>
19 #include <tests/testfile.h>
20 #include <language/duchain/declaration.h>
21 #include <language/duchain/problem.h>
22
23 QTEST_KDEMAIN(Php::TestDUChainMultipleFiles, GUI)
24
25 using namespace KDevelop;
26 using namespace Php;
27
initTestCase()28 void TestDUChainMultipleFiles::initTestCase()
29 {
30 DUChainTestBase::initTestCase();
31 TestCore* core = dynamic_cast<TestCore*>(ICore::self());
32 Q_ASSERT(core);
33 m_projectController = new TestProjectController(core);
34 core->setProjectController(m_projectController);
35 }
36
testImportsGlobalFunction()37 void TestDUChainMultipleFiles::testImportsGlobalFunction()
38 {
39 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
40
41 TestProject* project = new TestProject;
42 m_projectController->clearProjects();
43 m_projectController->addProject(project);
44
45 TestFile f1("<? function foo() {}", "php", project);
46 f1.parse(features);
47 QVERIFY(f1.waitForParsed());
48
49 TestFile f2("<? foo();", "php", project);
50 f2.parse(features);
51 f2.waitForParsed();
52
53 DUChainWriteLocker lock(DUChain::lock());
54 QVERIFY(f1.topContext());
55 QVERIFY(f2.topContext());
56 QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
57 }
58
testImportsBaseClassNotYetParsed()59 void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed()
60 {
61 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
62
63 TestProject* project = new TestProject;
64 m_projectController->clearProjects();
65 m_projectController->addProject(project);
66
67
68 TestFile f2("<? class B extends A {}", "php", project);
69 f2.parse(features);
70
71 TestFile f1("<? class A {}", "php", project);
72 f1.parse(features, 100); //low priority, to make sure f2 is parsed first
73
74 f1.waitForParsed();
75 QTest::qWait(100);
76
77 DUChainWriteLocker lock(DUChain::lock());
78 QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
79 QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
80 }
81
testNonExistingBaseClass()82 void TestDUChainMultipleFiles::testNonExistingBaseClass()
83 {
84 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
85
86 TestProject* project = new TestProject;
87 m_projectController->clearProjects();
88 m_projectController->addProject(project);
89
90 TestFile f1("<? class B extends A {}", "php", project);
91 f1.parse(features);
92 f1.waitForParsed();
93
94 //there must not be a re-enqueued parsejob
95 QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
96 }
97
testImportsGlobalFunctionNotYetParsed()98 void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed()
99 {
100 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
101
102 TestProject* project = new TestProject;
103 m_projectController->clearProjects();
104 m_projectController->addProject(project);
105
106 TestFile f2("<? foo2();", "php", project);
107 f2.parse(features);
108
109 TestFile f1("<? function foo2() {}", "php", project);
110 f1.parse(features, 100); //low priority, to make sure f2 is parsed first
111
112 f2.waitForParsed();
113 QTest::qWait(100);
114
115 DUChainWriteLocker lock(DUChain::lock());
116 QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
117
118 }
119
testNonExistingGlobalFunction()120 void TestDUChainMultipleFiles::testNonExistingGlobalFunction()
121 {
122 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
123
124 TestProject* project = new TestProject;
125 m_projectController->clearProjects();
126 m_projectController->addProject(project);
127
128 TestFile f2("<? foo3();", "php", project);
129 f2.parse(features);
130
131 f2.waitForParsed();
132 //there must not be a re-enqueued parsejob
133 QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
134 }
135
testImportsStaticFunctionNotYetParsed()136 void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed()
137 {
138 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
139
140 TestProject* project = new TestProject;
141 m_projectController->clearProjects();
142 m_projectController->addProject(project);
143
144 TestFile f2("<? C::foo();", "php", project);
145 f2.parse(features);
146
147 TestFile f1("<? class C { public static function foo() {} }", "php", project);
148 f1.parse(features, 100); //low priority, to make sure f2 is parsed first
149
150 f2.waitForParsed();
151 QTest::qWait(100);
152
153 DUChainWriteLocker lock(DUChain::lock());
154 QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
155 }
156
testNonExistingStaticFunction()157 void TestDUChainMultipleFiles::testNonExistingStaticFunction()
158 {
159 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
160
161 TestProject* project = new TestProject;
162 m_projectController->clearProjects();
163 m_projectController->addProject(project);
164
165 TestFile f2("<? D::foo();", "php", project);
166 f2.parse(features);
167
168 f2.waitForParsed();
169 //there must not be a re-enqueued parsejob
170 QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0);
171 }
172
testForeachImportedIdentifier()173 void TestDUChainMultipleFiles::testForeachImportedIdentifier()
174 {
175 // see https://bugs.kde.org/show_bug.cgi?id=269369
176
177 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts;
178
179 TestProject* project = new TestProject;
180 m_projectController->clearProjects();
181 m_projectController->addProject(project);
182
183 // build dependency
184 TestFile f1("<? class SomeIterator implements Countable, Iterator { }", "php", project);
185 f1.parse(features);
186 f1.waitForParsed();
187
188 TestFile f2("<?\n"
189 "class A {\n"
190 " public function foo() { $i = $this->bar(); foreach($i as $a => $b) {} } \n"
191 " public function bar() { $a = new SomeIterator(); return $a; }\n"
192 " }\n", "php", project);
193
194 for(int i = 0; i < 2; ++i) {
195 if (i > 0) {
196 features = static_cast<TopDUContext::Features>(features | TopDUContext::ForceUpdate);
197 }
198 f2.parse(features);
199 f2.waitForParsed();
200 QTest::qWait(100);
201
202 DUChainWriteLocker lock(DUChain::lock());
203 DUContext* ACtx = f2.topContext()->childContexts().first();
204 QVERIFY(ACtx);
205 Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first();
206 QVERIFY(iDec);
207 Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first();
208 QVERIFY(SomeIteratorDec);
209 if (i == 0) {
210 QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue);
211 }
212 QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData()));
213 QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0)));
214 }
215 }
216
testUpdateForeach()217 void TestDUChainMultipleFiles::testUpdateForeach()
218 {
219 TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses;
220
221 TestProject* project = new TestProject;
222 m_projectController->clearProjects();
223 m_projectController->addProject(project);
224
225 TestFile f("<?\n$k = null;\nforeach(array() as $i => $k) {}\n", "php", project);
226
227 f.parse(features);
228 f.waitForParsed();
229 QVERIFY(f.topContext());
230
231 {
232 DUChainWriteLocker lock;
233 QVERIFY(f.topContext()->problems().isEmpty());
234 QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1);
235 Declaration* kDec = f.topContext()->findDeclarations(Identifier("k")).first();
236 QCOMPARE(kDec->rangeInCurrentRevision().start.line, 1);
237 QCOMPARE(kDec->rangeInCurrentRevision().start.column, 0);
238 QCOMPARE(kDec->uses().count(), 1);
239 QCOMPARE(kDec->uses().begin()->count(), 1);
240 QCOMPARE(kDec->uses().begin()->begin()->start.line, 2);
241 }
242
243 // delete $k = null; line
244 f.setFileContents("<?\nforeach(array() as $i => $k) {}\n");
245 f.parse(static_cast<TopDUContext::Features>(features | TopDUContext::ForceUpdate));
246 f.waitForParsed();
247 QVERIFY(f.topContext());
248
249 {
250 DUChainWriteLocker lock;
251 QVERIFY(f.topContext()->problems().isEmpty());
252 QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1);
253 Declaration* kDec = f.topContext()->findDeclarations(Identifier("k")).first();
254 QCOMPARE(kDec->rangeInCurrentRevision().start.line, 1);
255 QCOMPARE(kDec->rangeInCurrentRevision().start.column, 25);
256 QCOMPARE(kDec->uses().count(), 0);
257 }
258 }
259
260 #include "duchain_multiplefiles.moc"
261