1 //===-- FileIndexTests.cpp ---------------------------*- C++ -*-----------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
10 #include "ClangdUnit.h"
11 #include "TestFS.h"
12 #include "TestTU.h"
13 #include "index/FileIndex.h"
14 #include "clang/Frontend/CompilerInvocation.h"
15 #include "clang/Frontend/PCHContainerOperations.h"
16 #include "clang/Lex/Preprocessor.h"
17 #include "clang/Tooling/CompilationDatabase.h"
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
20
21 using testing::UnorderedElementsAre;
22
23 namespace clang {
24 namespace clangd {
25
26 namespace {
27
symbol(llvm::StringRef ID)28 Symbol symbol(llvm::StringRef ID) {
29 Symbol Sym;
30 Sym.ID = SymbolID(ID);
31 Sym.Name = ID;
32 return Sym;
33 }
34
numSlab(int Begin,int End)35 std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) {
36 SymbolSlab::Builder Slab;
37 for (int i = Begin; i <= End; i++)
38 Slab.insert(symbol(std::to_string(i)));
39 return llvm::make_unique<SymbolSlab>(std::move(Slab).build());
40 }
41
42 std::vector<std::string>
getSymbolNames(const std::vector<const Symbol * > & Symbols)43 getSymbolNames(const std::vector<const Symbol *> &Symbols) {
44 std::vector<std::string> Names;
45 for (const Symbol *Sym : Symbols)
46 Names.push_back(Sym->Name);
47 return Names;
48 }
49
TEST(FileSymbolsTest,UpdateAndGet)50 TEST(FileSymbolsTest, UpdateAndGet) {
51 FileSymbols FS;
52 EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre());
53
54 FS.update("f1", numSlab(1, 3));
55 EXPECT_THAT(getSymbolNames(*FS.allSymbols()),
56 UnorderedElementsAre("1", "2", "3"));
57 }
58
TEST(FileSymbolsTest,Overlap)59 TEST(FileSymbolsTest, Overlap) {
60 FileSymbols FS;
61 FS.update("f1", numSlab(1, 3));
62 FS.update("f2", numSlab(3, 5));
63 EXPECT_THAT(getSymbolNames(*FS.allSymbols()),
64 UnorderedElementsAre("1", "2", "3", "3", "4", "5"));
65 }
66
TEST(FileSymbolsTest,SnapshotAliveAfterRemove)67 TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
68 FileSymbols FS;
69
70 FS.update("f1", numSlab(1, 3));
71
72 auto Symbols = FS.allSymbols();
73 EXPECT_THAT(getSymbolNames(*Symbols), UnorderedElementsAre("1", "2", "3"));
74
75 FS.update("f1", nullptr);
76 EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre());
77 EXPECT_THAT(getSymbolNames(*Symbols), UnorderedElementsAre("1", "2", "3"));
78 }
79
match(const SymbolIndex & I,const FuzzyFindRequest & Req)80 std::vector<std::string> match(const SymbolIndex &I,
81 const FuzzyFindRequest &Req) {
82 std::vector<std::string> Matches;
83 I.fuzzyFind(Req, [&](const Symbol &Sym) {
84 Matches.push_back((Sym.Scope + Sym.Name).str());
85 });
86 return Matches;
87 }
88
89 // Adds Basename.cpp, which includes Basename.h, which contains Code.
update(FileIndex & M,llvm::StringRef Basename,llvm::StringRef Code)90 void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
91 TestTU File;
92 File.Filename = (Basename + ".cpp").str();
93 File.HeaderFilename = (Basename + ".h").str();
94 File.HeaderCode = Code;
95 auto AST = File.build();
96 M.update(File.Filename, &AST.getASTContext(), AST.getPreprocessorPtr());
97 }
98
TEST(FileIndexTest,CustomizedURIScheme)99 TEST(FileIndexTest, CustomizedURIScheme) {
100 FileIndex M({"unittest"});
101 update(M, "f", "class string {};");
102
103 FuzzyFindRequest Req;
104 Req.Query = "";
105 bool SeenSymbol = false;
106 M.fuzzyFind(Req, [&](const Symbol &Sym) {
107 EXPECT_EQ(Sym.CanonicalDeclaration.FileURI, "unittest:///f.h");
108 SeenSymbol = true;
109 });
110 EXPECT_TRUE(SeenSymbol);
111 }
112
TEST(FileIndexTest,IndexAST)113 TEST(FileIndexTest, IndexAST) {
114 FileIndex M;
115 update(M, "f1", "namespace ns { void f() {} class X {}; }");
116
117 FuzzyFindRequest Req;
118 Req.Query = "";
119 Req.Scopes = {"ns::"};
120 EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns::f", "ns::X"));
121 }
122
TEST(FileIndexTest,NoLocal)123 TEST(FileIndexTest, NoLocal) {
124 FileIndex M;
125 update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }");
126
127 FuzzyFindRequest Req;
128 Req.Query = "";
129 EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns", "ns::f", "ns::X"));
130 }
131
TEST(FileIndexTest,IndexMultiASTAndDeduplicate)132 TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
133 FileIndex M;
134 update(M, "f1", "namespace ns { void f() {} class X {}; }");
135 update(M, "f2", "namespace ns { void ff() {} class X {}; }");
136
137 FuzzyFindRequest Req;
138 Req.Query = "";
139 Req.Scopes = {"ns::"};
140 EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns::f", "ns::X", "ns::ff"));
141 }
142
TEST(FileIndexTest,RemoveAST)143 TEST(FileIndexTest, RemoveAST) {
144 FileIndex M;
145 update(M, "f1", "namespace ns { void f() {} class X {}; }");
146
147 FuzzyFindRequest Req;
148 Req.Query = "";
149 Req.Scopes = {"ns::"};
150 EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns::f", "ns::X"));
151
152 M.update("f1.cpp", nullptr, nullptr);
153 EXPECT_THAT(match(M, Req), UnorderedElementsAre());
154 }
155
TEST(FileIndexTest,RemoveNonExisting)156 TEST(FileIndexTest, RemoveNonExisting) {
157 FileIndex M;
158 M.update("no.cpp", nullptr, nullptr);
159 EXPECT_THAT(match(M, FuzzyFindRequest()), UnorderedElementsAre());
160 }
161
TEST(FileIndexTest,ClassMembers)162 TEST(FileIndexTest, ClassMembers) {
163 FileIndex M;
164 update(M, "f1", "class X { static int m1; int m2; static void f(); };");
165
166 FuzzyFindRequest Req;
167 Req.Query = "";
168 EXPECT_THAT(match(M, Req),
169 UnorderedElementsAre("X", "X::m1", "X::m2", "X::f"));
170 }
171
TEST(FileIndexTest,NoIncludeCollected)172 TEST(FileIndexTest, NoIncludeCollected) {
173 FileIndex M;
174 update(M, "f", "class string {};");
175
176 FuzzyFindRequest Req;
177 Req.Query = "";
178 bool SeenSymbol = false;
179 M.fuzzyFind(Req, [&](const Symbol &Sym) {
180 EXPECT_TRUE(Sym.Detail->IncludeHeader.empty());
181 SeenSymbol = true;
182 });
183 EXPECT_TRUE(SeenSymbol);
184 }
185
TEST(FileIndexTest,TemplateParamsInLabel)186 TEST(FileIndexTest, TemplateParamsInLabel) {
187 auto Source = R"cpp(
188 template <class Ty>
189 class vector {
190 };
191
192 template <class Ty, class Arg>
193 vector<Ty> make_vector(Arg A) {}
194 )cpp";
195
196 FileIndex M;
197 update(M, "f", Source);
198
199 FuzzyFindRequest Req;
200 Req.Query = "";
201 bool SeenVector = false;
202 bool SeenMakeVector = false;
203 M.fuzzyFind(Req, [&](const Symbol &Sym) {
204 if (Sym.Name == "vector") {
205 EXPECT_EQ(Sym.Signature, "<class Ty>");
206 EXPECT_EQ(Sym.CompletionSnippetSuffix, "<${1:class Ty}>");
207 SeenVector = true;
208 return;
209 }
210
211 if (Sym.Name == "make_vector") {
212 EXPECT_EQ(Sym.Signature, "<class Ty>(Arg A)");
213 EXPECT_EQ(Sym.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})");
214 SeenMakeVector = true;
215 }
216 });
217 EXPECT_TRUE(SeenVector);
218 EXPECT_TRUE(SeenMakeVector);
219 }
220
TEST(FileIndexTest,RebuildWithPreamble)221 TEST(FileIndexTest, RebuildWithPreamble) {
222 auto FooCpp = testPath("foo.cpp");
223 auto FooH = testPath("foo.h");
224 // Preparse ParseInputs.
225 ParseInputs PI;
226 PI.CompileCommand.Directory = testRoot();
227 PI.CompileCommand.Filename = FooCpp;
228 PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp};
229
230 llvm::StringMap<std::string> Files;
231 Files[FooCpp] = "";
232 Files[FooH] = R"cpp(
233 namespace ns_in_header {
234 int func_in_header();
235 }
236 )cpp";
237 PI.FS = buildTestFS(std::move(Files));
238
239 PI.Contents = R"cpp(
240 #include "foo.h"
241 namespace ns_in_source {
242 int func_in_source();
243 }
244 )cpp";
245
246 // Rebuild the file.
247 auto CI = buildCompilerInvocation(PI);
248
249 FileIndex Index;
250 bool IndexUpdated = false;
251 buildPreamble(
252 FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI,
253 std::make_shared<PCHContainerOperations>(), /*StoreInMemory=*/true,
254 [&Index, &IndexUpdated](PathRef FilePath, ASTContext &Ctx,
255 std::shared_ptr<Preprocessor> PP) {
256 EXPECT_FALSE(IndexUpdated) << "Expected only a single index update";
257 IndexUpdated = true;
258 Index.update(FilePath, &Ctx, std::move(PP));
259 });
260 ASSERT_TRUE(IndexUpdated);
261
262 // Check the index contains symbols from the preamble, but not from the main
263 // file.
264 FuzzyFindRequest Req;
265 Req.Query = "";
266 Req.Scopes = {"", "ns_in_header::"};
267
268 EXPECT_THAT(
269 match(Index, Req),
270 UnorderedElementsAre("ns_in_header", "ns_in_header::func_in_header"));
271 }
272
273 } // namespace
274 } // namespace clangd
275 } // namespace clang
276