26 #include "clang/Frontend/CompilerInvocation.h"
27 #include "clang/Frontend/Utils.h"
28 #include "clang/Index/IndexSymbol.h"
29 #include "clang/Lex/Preprocessor.h"
30 #include "clang/Tooling/CompilationDatabase.h"
31 #include "gmock/gmock.h"
32 #include "gtest/gtest.h"
36 using ::testing::AllOf;
37 using ::testing::Contains;
38 using ::testing::ElementsAre;
39 using ::testing::IsEmpty;
40 using ::testing::Pair;
41 using ::testing::UnorderedElementsAre;
44 return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(),
45 arg.Location.End.line(), arg.Location.End.column()) ==
46 std::make_tuple(
Range.start.line,
Range.start.character,
49 MATCHER_P(FileURI, F,
"") {
return llvm::StringRef(arg.Location.FileURI) == F; }
51 return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U;
54 return llvm::StringRef(arg.Definition.FileURI) == U;
56 MATCHER_P(QName, N,
"") {
return (arg.Scope + arg.Name).str() == N; }
57 MATCHER_P(NumReferences, N,
"") {
return arg.References == N; }
58 MATCHER_P(hasOrign, O,
"") {
return bool(arg.Origin & O); }
63 ::testing::Matcher<const RefSlab &>
64 RefsAre(std::vector<::testing::Matcher<Ref>> Matchers) {
65 return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers)));
68 Symbol
symbol(llvm::StringRef ID) {
75 std::unique_ptr<SymbolSlab> numSlab(
int Begin,
int End) {
77 for (
int i = Begin; i <= End; i++)
78 Slab.insert(
symbol(std::to_string(i)));
79 return std::make_unique<SymbolSlab>(std::move(Slab).build());
82 std::unique_ptr<RefSlab> refSlab(
const SymbolID &ID,
const char *
Path) {
85 R.Location.FileURI =
Path;
88 return std::make_unique<RefSlab>(std::move(Slab).build());
91 TEST(FileSymbolsTest, UpdateAndGet) {
95 FS.update(
"f1", numSlab(1, 3), refSlab(
SymbolID(
"1"),
"f1.cc"),
nullptr,
98 UnorderedElementsAre(QName(
"1"), QName(
"2"), QName(
"3")));
103 TEST(FileSymbolsTest, Overlap) {
105 FS.update(
"f1", numSlab(1, 3),
nullptr,
nullptr,
false);
106 FS.update(
"f2", numSlab(3, 5),
nullptr,
nullptr,
false);
109 UnorderedElementsAre(QName(
"1"), QName(
"2"), QName(
"3"),
110 QName(
"4"), QName(
"5")));
113 TEST(FileSymbolsTest, MergeOverlap) {
115 auto OneSymboSlab = [](Symbol Sym) {
118 return std::make_unique<SymbolSlab>(std::move(S).build());
121 X1.CanonicalDeclaration.FileURI =
"file:///x1";
123 X2.Definition.FileURI =
"file:///x2";
125 FS.update(
"f1", OneSymboSlab(X1),
nullptr,
nullptr,
false);
126 FS.update(
"f2", OneSymboSlab(X2),
nullptr,
nullptr,
false);
130 UnorderedElementsAre(
131 AllOf(QName(
"x"), DeclURI(
"file:///x1"), DefURI(
"file:///x2"))));
134 TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
138 FS.update(
"f1", numSlab(1, 3), refSlab(ID,
"f1.cc"),
nullptr,
false);
142 UnorderedElementsAre(QName(
"1"), QName(
"2"), QName(
"3")));
145 FS.update(
"f1",
nullptr,
nullptr,
nullptr,
false);
151 UnorderedElementsAre(QName(
"1"), QName(
"2"), QName(
"3")));
156 void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef
Code) {
158 File.Filename = (Basename +
".cpp").str();
159 File.HeaderFilename = (Basename +
".h").str();
160 File.HeaderCode = std::string(
Code);
161 auto AST = File.build();
162 M.updatePreamble(
testPath(File.Filename),
"null",
167 TEST(FileIndexTest, CustomizedURIScheme) {
169 update(M,
"f",
"class string {};");
171 EXPECT_THAT(
runFuzzyFind(M,
""), ElementsAre(DeclURI(
"unittest:///f.h")));
174 TEST(FileIndexTest, IndexAST) {
176 update(M,
"f1",
"namespace ns { void f() {} class X {}; }");
178 FuzzyFindRequest Req;
180 Req.Scopes = {
"ns::"};
182 UnorderedElementsAre(QName(
"ns::f"), QName(
"ns::X")));
185 TEST(FileIndexTest, NoLocal) {
187 update(M,
"f1",
"namespace ns { void f() { int local = 0; } class X {}; }");
191 UnorderedElementsAre(QName(
"ns"), QName(
"ns::f"), QName(
"ns::X")));
194 TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
196 update(M,
"f1",
"namespace ns { void f() {} class X {}; }");
197 update(M,
"f2",
"namespace ns { void ff() {} class X {}; }");
199 FuzzyFindRequest Req;
200 Req.Scopes = {
"ns::"};
203 UnorderedElementsAre(QName(
"ns::f"), QName(
"ns::X"), QName(
"ns::ff")));
206 TEST(FileIndexTest, ClassMembers) {
208 update(M,
"f1",
"class X { static int m1; int m2; static void f(); };");
211 UnorderedElementsAre(QName(
"X"), QName(
"X::m1"), QName(
"X::m2"),
215 TEST(FileIndexTest, IncludeCollected) {
219 "// IWYU pragma: private, include <the/good/header.h>\nclass string {};");
222 EXPECT_THAT(
Symbols, ElementsAre(_));
223 EXPECT_THAT(
Symbols.
begin()->IncludeHeaders.front().IncludeHeader,
224 "<the/good/header.h>");
227 TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) {
229 TU.HeaderCode =
"class Foo{};";
230 TU.HeaderFilename =
"algorithm";
233 EXPECT_THAT(
Symbols, ElementsAre(_));
234 EXPECT_THAT(
Symbols.
begin()->IncludeHeaders.front().IncludeHeader,
238 TEST(FileIndexTest, TemplateParamsInLabel) {
244 template <class Ty, class Arg>
245 vector<Ty> make_vector(Arg A) {}
249 update(M, "f", Source);
253 UnorderedElementsAre(QName(
"vector"), QName(
"make_vector")));
255 Symbol Vector = *It++;
256 Symbol MakeVector = *It++;
257 if (MakeVector.Name ==
"vector")
258 std::swap(MakeVector, Vector);
260 EXPECT_EQ(Vector.Signature,
"<class Ty>");
261 EXPECT_EQ(Vector.CompletionSnippetSuffix,
"<${1:class Ty}>");
263 EXPECT_EQ(MakeVector.Signature,
"<class Ty>(Arg A)");
264 EXPECT_EQ(MakeVector.CompletionSnippetSuffix,
"<${1:class Ty}>(${2:Arg A})");
267 TEST(FileIndexTest, RebuildWithPreamble) {
272 PI.CompileCommand.Directory =
testRoot();
273 PI.CompileCommand.Filename = FooCpp;
274 PI.CompileCommand.CommandLine = {
"clang",
"-xc++", FooCpp};
279 namespace ns_in_header {
280 int func_in_header();
287 namespace ns_in_source {
288 int func_in_source();
297 bool IndexUpdated =
false;
300 [&](ASTContext &
Ctx, std::shared_ptr<Preprocessor>
PP,
301 const CanonicalIncludes &CanonIncludes) {
302 EXPECT_FALSE(IndexUpdated)
303 <<
"Expected only a single index update";
305 Index.updatePreamble(FooCpp,
"null",
Ctx,
306 std::move(
PP), CanonIncludes);
308 ASSERT_TRUE(IndexUpdated);
312 FuzzyFindRequest Req;
314 Req.Scopes = {
"",
"ns_in_header::"};
317 UnorderedElementsAre(QName(
"ns_in_header"),
318 QName(
"ns_in_header::func_in_header")));
322 const char *HeaderCode =
"class Foo {};";
323 Annotations MainCode(R
"cpp(
333 Request.IDs = {Foo.ID};
338 Test.HeaderCode = HeaderCode;
339 Test.Code = std::string(MainCode.code());
340 Test.Filename =
"test.cc";
341 auto AST = Test.
build();
342 Index.updateMain(Test.Filename, AST);
345 Test2.HeaderCode = HeaderCode;
346 Test2.Code = std::string(MainCode.code());
347 Test2.Filename =
"test2.cc";
349 Index.updateMain(Test2.Filename, AST);
352 RefsAre({AllOf(RefRange(MainCode.range(
"foo")),
353 FileURI(
"unittest:///test.cc")),
354 AllOf(RefRange(MainCode.range(
"foo")),
355 FileURI(
"unittest:///test2.cc"))}));
358 TEST(FileIndexTest, MacroRefs) {
359 Annotations HeaderCode(R
"cpp(
360 #define $def1[[HEADER_MACRO]](X) (X+1)
362 Annotations MainCode(R"cpp(
363 #define $def2[[MAINFILE_MACRO]](X) (X+1)
365 int a = $ref1[[HEADER_MACRO]](2);
366 int b = $ref2[[MAINFILE_MACRO]](1);
373 Test.HeaderCode = std::string(HeaderCode.code());
374 Test.Code = std::string(MainCode.code());
375 Test.Filename =
"test.cc";
376 auto AST = Test.
build();
377 Index.updateMain(Test.Filename, AST);
379 auto HeaderMacro =
findSymbol(Test.headerSymbols(),
"HEADER_MACRO");
381 RefsAre({AllOf(RefRange(MainCode.range(
"ref1")),
382 FileURI(
"unittest:///test.cc"))}));
384 auto MainFileMacro =
findSymbol(Test.headerSymbols(),
"MAINFILE_MACRO");
386 RefsAre({AllOf(RefRange(MainCode.range(
"def2")),
387 FileURI(
"unittest:///test.cc")),
388 AllOf(RefRange(MainCode.range(
"ref2")),
389 FileURI(
"unittest:///test.cc"))}));
392 TEST(FileIndexTest, CollectMacros) {
394 update(M,
"f",
"#define CLANGD 1");
395 EXPECT_THAT(
runFuzzyFind(M,
""), Contains(QName(
"CLANGD")));
398 TEST(FileIndexTest, Relations) {
400 TU.Filename =
"f.cpp";
401 TU.HeaderFilename =
"f.h";
402 TU.HeaderCode =
"class A {}; class B : public A {};";
403 auto AST = TU.
build();
410 RelationsRequest Req;
411 Req.Subjects.insert(A);
417 TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
419 TU.HeaderCode =
"class Foo{};";
420 Annotations Main(R
"cpp(
425 TU.Code = std::string(Main.code());
426 auto AST = TU.
build();
433 RefsAre({RefRange(Main.range())}));
436 TEST(FileIndexTest, MergeMainFileSymbols) {
437 const char *CommonHeader =
"void foo();";
440 Cpp.Filename =
"foo.cpp";
441 Cpp.HeaderFilename =
"foo.h";
442 Cpp.HeaderCode = CommonHeader;
445 auto HeaderAST = Header.build();
446 auto CppAST = Cpp.build();
452 EXPECT_THAT(
Symbols, ElementsAre(AllOf(DeclURI(
"unittest:///foo.h"),
453 DefURI(
"unittest:///foo.cpp"),
457 TEST(FileSymbolsTest, CountReferencesNoRefSlabs) {
459 FS.update(
"f1", numSlab(1, 3),
nullptr,
nullptr,
true);
460 FS.update(
"f2", numSlab(1, 3),
nullptr,
nullptr,
false);
464 UnorderedElementsAre(AllOf(QName(
"1"), NumReferences(0u)),
465 AllOf(QName(
"2"), NumReferences(0u)),
466 AllOf(QName(
"3"), NumReferences(0u))));
469 TEST(FileSymbolsTest, CountReferencesWithRefSlabs) {
471 FS.update(
"f1cpp", numSlab(1, 3), refSlab(
SymbolID(
"1"),
"f1.cpp"),
nullptr,
473 FS.update(
"f1h", numSlab(1, 3), refSlab(
SymbolID(
"1"),
"f1.h"),
nullptr,
475 FS.update(
"f2cpp", numSlab(1, 3), refSlab(
SymbolID(
"2"),
"f2.cpp"),
nullptr,
477 FS.update(
"f2h", numSlab(1, 3), refSlab(
SymbolID(
"2"),
"f2.h"),
nullptr,
479 FS.update(
"f3cpp", numSlab(1, 3), refSlab(
SymbolID(
"3"),
"f3.cpp"),
nullptr,
481 FS.update(
"f3h", numSlab(1, 3), refSlab(
SymbolID(
"3"),
"f3.h"),
nullptr,
486 UnorderedElementsAre(AllOf(QName(
"1"), NumReferences(1u)),
487 AllOf(QName(
"2"), NumReferences(1u)),
488 AllOf(QName(
"3"), NumReferences(1u))));
491 TEST(FileIndexTest, StalePreambleSymbolsDeleted) {
494 File.HeaderFilename =
"a.h";
496 File.Filename =
"f1.cpp";
497 File.HeaderCode =
"int a;";
498 auto AST = File.build();
499 M.updatePreamble(
testPath(File.Filename),
"null",
502 EXPECT_THAT(
runFuzzyFind(M,
""), UnorderedElementsAre(QName(
"a")));
504 File.Filename =
"f2.cpp";
505 File.HeaderCode =
"int b;";
507 M.updatePreamble(
testPath(File.Filename),
"null",
510 EXPECT_THAT(
runFuzzyFind(M,
""), UnorderedElementsAre(QName(
"b")));
514 TEST(FileIndexTest, Threadsafety) {
518 constexpr
int Count = 10;
521 AsyncTaskRunner Pool;
522 for (
unsigned I = 0; I < Count; ++I) {
524 TU.Filename = llvm::formatv(
"x{0}.c", I).str();
526 AST(TU.build())]()
mutable {
535 EXPECT_THAT(
runFuzzyFind(M,
"xxx"), ::testing::SizeIs(Count));
538 TEST(FileShardedIndexTest, Sharding) {
544 Sym1.CanonicalDeclaration.FileURI = AHeaderUri.c_str();
547 Sym2.CanonicalDeclaration.FileURI = BHeaderUri.c_str();
548 Sym2.Definition.FileURI = BSourceUri.c_str();
557 IF.Symbols.emplace(std::move(B).build());
561 IF.Refs.emplace(std::move(*refSlab(Sym1.ID, BSourceUri.c_str())));
571 IF.Relations.emplace(std::move(B).build());
574 IF.Sources.emplace();
578 auto &Node = IG[BSourceUri];
579 Node.DirectIncludes = {BHeaderUri};
580 Node.URI = BSourceUri;
584 auto &Node = IG[BHeaderUri];
585 Node.DirectIncludes = {AHeaderUri};
586 Node.URI = BHeaderUri;
590 auto &Node = IG[AHeaderUri];
591 Node.DirectIncludes = {};
592 Node.URI = AHeaderUri;
595 IF.Cmd = tooling::CompileCommand(
testRoot(),
"b.cc", {
"clang"},
"out");
597 FileShardedIndex ShardedIndex(std::move(IF));
598 ASSERT_THAT(ShardedIndex.getAllSources(),
599 UnorderedElementsAre(AHeaderUri, BHeaderUri, BSourceUri));
602 auto Shard = ShardedIndex.getShard(AHeaderUri);
604 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(QName(
"1")));
605 EXPECT_THAT(*Shard->Refs, IsEmpty());
609 ASSERT_THAT(Shard->Sources->keys(), UnorderedElementsAre(AHeaderUri));
610 EXPECT_THAT(Shard->Sources->lookup(AHeaderUri).DirectIncludes, IsEmpty());
611 EXPECT_TRUE(Shard->Cmd.hasValue());
614 auto Shard = ShardedIndex.getShard(BHeaderUri);
616 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(QName(
"2")));
617 EXPECT_THAT(*Shard->Refs, IsEmpty());
621 ASSERT_THAT(Shard->Sources->keys(),
622 UnorderedElementsAre(BHeaderUri, AHeaderUri));
623 EXPECT_THAT(Shard->Sources->lookup(BHeaderUri).DirectIncludes,
624 UnorderedElementsAre(AHeaderUri));
625 EXPECT_TRUE(Shard->Cmd.hasValue());
628 auto Shard = ShardedIndex.getShard(BSourceUri);
630 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(QName(
"2")));
631 EXPECT_THAT(*Shard->Refs, UnorderedElementsAre(Pair(Sym1.ID, _)));
632 EXPECT_THAT(*Shard->Relations, IsEmpty());
633 ASSERT_THAT(Shard->Sources->keys(),
634 UnorderedElementsAre(BSourceUri, BHeaderUri));
635 EXPECT_THAT(Shard->Sources->lookup(BSourceUri).DirectIncludes,
636 UnorderedElementsAre(BHeaderUri));
637 EXPECT_TRUE(Shard->Cmd.hasValue());