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