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