18#include "clang-include-cleaner/Record.h"
27#include "clang/Frontend/CompilerInvocation.h"
28#include "clang/Tooling/CompilationDatabase.h"
29#include "llvm/ADT/ArrayRef.h"
30#include "llvm/Support/Allocator.h"
31#include "gmock/gmock.h"
32#include "gtest/gtest.h"
38using ::testing::AllOf;
39using ::testing::Contains;
40using ::testing::ElementsAre;
42using ::testing::IsEmpty;
44using ::testing::UnorderedElementsAre;
47 return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(),
48 arg.Location.End.line(), arg.Location.End.column()) ==
49 std::make_tuple(
Range.start.line,
Range.start.character,
52MATCHER_P(fileURI, F,
"") {
return llvm::StringRef(arg.Location.FileURI) == F; }
54 return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U;
57 return llvm::StringRef(arg.Definition.FileURI) == U;
59MATCHER_P(qName, N,
"") {
return (arg.Scope + arg.Name).str() == N; }
60MATCHER_P(numReferences, N,
"") {
return arg.References == N; }
61MATCHER_P(hasOrign, O,
"") {
return bool(arg.Origin & O); }
64 return (arg.IncludeHeaders.size() == 1) &&
65 (arg.IncludeHeaders.begin()->IncludeHeader == P);
71::testing::Matcher<const RefSlab &>
72refsAre(std::vector<::testing::Matcher<Ref>> Matchers) {
73 return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers)));
83std::unique_ptr<SymbolSlab> numSlab(
int Begin,
int End) {
84 SymbolSlab::Builder Slab;
85 for (
int I = Begin; I <= End; I++)
86 Slab.insert(
symbol(std::to_string(I)));
87 return std::make_unique<SymbolSlab>(std::move(Slab).build());
90std::unique_ptr<RefSlab> refSlab(
const SymbolID &
ID,
const char *
Path) {
91 RefSlab::Builder Slab;
93 R.Location.FileURI =
Path;
96 return std::make_unique<RefSlab>(std::move(Slab).build());
99std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) {
100 RelationSlab::Builder RelBuilder;
101 for (
auto &Rel : Rels)
102 RelBuilder.insert(Rel);
103 return std::make_unique<RelationSlab>(std::move(RelBuilder).build());
106TEST(FileSymbolsTest, UpdateAndGet) {
110 FS.update(
"f1", numSlab(1, 3), refSlab(
SymbolID(
"1"),
"f1.cc"),
nullptr,
113 UnorderedElementsAre(qName(
"1"), qName(
"2"), qName(
"3")));
118TEST(FileSymbolsTest, Overlap) {
120 FS.update(
"f1", numSlab(1, 3),
nullptr,
nullptr,
false);
121 FS.update(
"f2", numSlab(3, 5),
nullptr,
nullptr,
false);
124 UnorderedElementsAre(qName(
"1"), qName(
"2"), qName(
"3"),
125 qName(
"4"), qName(
"5")));
128TEST(FileSymbolsTest, MergeOverlap) {
130 auto OneSymboSlab = [](Symbol Sym) {
131 SymbolSlab::Builder S;
133 return std::make_unique<SymbolSlab>(std::move(S).build());
136 X1.CanonicalDeclaration.FileURI =
"file:///x1";
138 X2.Definition.FileURI =
"file:///x2";
140 FS.update(
"f1", OneSymboSlab(X1),
nullptr,
nullptr,
false);
141 FS.update(
"f2", OneSymboSlab(X2),
nullptr,
nullptr,
false);
145 UnorderedElementsAre(
146 AllOf(qName(
"x"), declURI(
"file:///x1"), defURI(
"file:///x2"))));
149TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
153 FS.update(
"f1", numSlab(1, 3), refSlab(
ID,
"f1.cc"),
nullptr,
false);
157 UnorderedElementsAre(qName(
"1"), qName(
"2"), qName(
"3")));
160 FS.update(
"f1",
nullptr,
nullptr,
nullptr,
false);
166 UnorderedElementsAre(qName(
"1"), qName(
"2"), qName(
"3")));
171void update(FileIndex &
M, llvm::StringRef Basename, llvm::StringRef Code) {
173 File.Filename = (Basename +
".cpp").str();
174 File.HeaderFilename = (Basename +
".h").str();
175 File.HeaderCode = std::string(Code);
178 AST.getASTContext(),
AST.getPreprocessor(),
179 AST.getPragmaIncludes());
182TEST(FileIndexTest, CustomizedURIScheme) {
184 update(
M,
"f",
"class string {};");
186 EXPECT_THAT(
runFuzzyFind(
M,
""), ElementsAre(declURI(
"unittest:///f.h")));
189TEST(FileIndexTest, IndexAST) {
191 update(
M,
"f1",
"namespace ns { void f() {} class X {}; }");
193 FuzzyFindRequest Req;
195 Req.Scopes = {
"ns::"};
197 UnorderedElementsAre(qName(
"ns::f"), qName(
"ns::X")));
200TEST(FileIndexTest, NoLocal) {
202 update(
M,
"f1",
"namespace ns { void f() { int local = 0; } class X {}; }");
206 UnorderedElementsAre(qName(
"ns"), qName(
"ns::f"), qName(
"ns::X")));
209TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
211 update(
M,
"f1",
"namespace ns { void f() {} class X {}; }");
212 update(
M,
"f2",
"namespace ns { void ff() {} class X {}; }");
214 FuzzyFindRequest Req;
215 Req.Scopes = {
"ns::"};
218 UnorderedElementsAre(qName(
"ns::f"), qName(
"ns::X"), qName(
"ns::ff")));
221TEST(FileIndexTest, ClassMembers) {
223 update(
M,
"f1",
"class X { static int m1; int m2; static void f(); };");
226 UnorderedElementsAre(qName(
"X"), qName(
"X::m1"), qName(
"X::m2"),
230TEST(FileIndexTest, IncludeCollected) {
234 "// IWYU pragma: private, include <the/good/header.h>\nclass string {};");
237 EXPECT_THAT(
Symbols, ElementsAre(_));
238 EXPECT_THAT(
Symbols.begin()->IncludeHeaders.front().IncludeHeader,
239 "<the/good/header.h>");
242TEST(FileIndexTest, IWYUPragmaExport) {
246 File.Code = R
"cpp(#pragma once
247 #include "exporter.h"
249 File.HeaderFilename = "exporter.h";
250 File.HeaderCode = R
"cpp(#pragma once
251 #include "private.h" // IWYU pragma: export
253 File.AdditionalFiles["private.h"] =
"class Foo{};";
256 AST.getASTContext(),
AST.getPreprocessor(),
257 AST.getPragmaIncludes());
262 UnorderedElementsAre(AllOf(
268TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) {
270 TU.HeaderCode =
"class Foo{};";
271 TU.HeaderFilename =
"algorithm";
274 EXPECT_THAT(
Symbols, ElementsAre(_));
275 EXPECT_THAT(
Symbols.begin()->IncludeHeaders.front().IncludeHeader,
279TEST(FileIndexTest, TemplateParamsInLabel) {
280 auto *Source = R
"cpp(
285template <class Ty, class Arg>
286vector<Ty> make_vector(Arg A) {}
290 update(
M,
"f", Source);
294 UnorderedElementsAre(qName(
"vector"), qName(
"make_vector")));
296 Symbol Vector = *It++;
297 Symbol MakeVector = *It++;
298 if (MakeVector.Name ==
"vector")
299 std::swap(MakeVector, Vector);
301 EXPECT_EQ(Vector.Signature,
"<class Ty>");
302 EXPECT_EQ(Vector.CompletionSnippetSuffix,
"<${1:class Ty}>");
304 EXPECT_EQ(MakeVector.Signature,
"<class Ty>(Arg A)");
305 EXPECT_EQ(MakeVector.CompletionSnippetSuffix,
"<${1:class Ty}>(${2:Arg A})");
308TEST(FileIndexTest, RebuildWithPreamble) {
313 PI.CompileCommand.Directory =
testRoot();
314 PI.CompileCommand.Filename = FooCpp;
315 PI.CompileCommand.CommandLine = {
"clang",
"-xc++", FooCpp};
318 FS.Files[FooCpp] =
"";
319 FS.Files[FooH] = R
"cpp(
320 namespace ns_in_header {
321 int func_in_header();
328 namespace ns_in_source {
329 int func_in_source();
337 FileIndex Index(
true);
338 bool IndexUpdated =
false;
342 [&](CapturedASTCtx ASTCtx,
343 std::shared_ptr<const include_cleaner::PragmaIncludes> PI) {
344 auto &Ctx = ASTCtx.getASTContext();
345 auto &PP = ASTCtx.getPreprocessor();
346 EXPECT_FALSE(IndexUpdated) <<
"Expected only a single index update";
348 Index.updatePreamble(FooCpp,
"null", Ctx, PP, *PI);
350 ASSERT_TRUE(IndexUpdated);
354 FuzzyFindRequest Req;
356 Req.Scopes = {
"",
"ns_in_header::"};
359 UnorderedElementsAre(qName(
"ns_in_header"),
360 qName(
"ns_in_header::func_in_header")));
363TEST(FileIndexTest, Refs) {
364 const char *HeaderCode =
"class Foo {};";
365 Annotations MainCode(R
"cpp(
375 Request.IDs = {
Foo.ID};
377 FileIndex Index(
true);
380 Test.HeaderCode = HeaderCode;
381 Test.Code = std::string(MainCode.code());
382 Test.Filename =
"test.cc";
383 auto AST = Test.build();
387 Test2.HeaderCode = HeaderCode;
388 Test2.Code = std::string(MainCode.code());
389 Test2.Filename =
"test2.cc";
394 refsAre({AllOf(refRange(MainCode.range(
"foo")),
395 fileURI(
"unittest:///test.cc")),
396 AllOf(refRange(MainCode.range(
"foo")),
397 fileURI(
"unittest:///test2.cc"))}));
400TEST(FileIndexTest, MacroRefs) {
401 Annotations HeaderCode(R
"cpp(
402 #define $def1[[HEADER_MACRO]](X) (X+1)
404 Annotations MainCode(R"cpp(
405 #define $def2[[MAINFILE_MACRO]](X) (X+1)
407 int a = $ref1[[HEADER_MACRO]](2);
408 int b = $ref2[[MAINFILE_MACRO]](1);
412 FileIndex Index(true);
415 Test.HeaderCode = std::string(HeaderCode.code());
416 Test.Code = std::string(MainCode.code());
417 Test.Filename =
"test.cc";
418 auto AST = Test.build();
421 auto HeaderMacro =
findSymbol(Test.headerSymbols(),
"HEADER_MACRO");
422 EXPECT_THAT(
getRefs(Index, HeaderMacro.ID),
423 refsAre({AllOf(refRange(MainCode.range(
"ref1")),
424 fileURI(
"unittest:///test.cc"))}));
426 auto MainFileMacro =
findSymbol(Test.headerSymbols(),
"MAINFILE_MACRO");
427 EXPECT_THAT(
getRefs(Index, MainFileMacro.ID),
428 refsAre({AllOf(refRange(MainCode.range(
"def2")),
429 fileURI(
"unittest:///test.cc")),
430 AllOf(refRange(MainCode.range(
"ref2")),
431 fileURI(
"unittest:///test.cc"))}));
434TEST(FileIndexTest, CollectMacros) {
436 update(
M,
"f",
"#define CLANGD 1");
440TEST(FileIndexTest, Relations) {
442 TU.Filename =
"f.cpp";
443 TU.HeaderFilename =
"f.h";
444 TU.HeaderCode =
"class A {}; class B : public A {};";
445 auto AST = TU.build();
446 FileIndex Index(
true);
447 Index.updatePreamble(
testPath(TU.Filename),
"null",
448 AST.getASTContext(),
AST.getPreprocessor(),
449 AST.getPragmaIncludes());
452 RelationsRequest Req;
453 Req.Subjects.insert(A);
455 Index.relations(Req, [&](
const SymbolID &,
const Symbol &) { ++
Results; });
459TEST(FileIndexTest, RelationsMultiFile) {
460 TestWorkspace Workspace;
461 Workspace.addSource(
"Base.h",
"class Base {};");
462 Workspace.addMainFile(
"A.cpp", R
"cpp(
464 class A : public Base {};
466 Workspace.addMainFile("B.cpp", R
"cpp(
468 class B : public Base {};
471 auto Index = Workspace.index();
472 FuzzyFindRequest FFReq;
473 FFReq.Query =
"Base";
474 FFReq.AnyScope =
true;
476 Index->fuzzyFind(FFReq, [&](
const Symbol &S) { Base = S.ID; });
478 RelationsRequest Req;
479 Req.Subjects.insert(Base);
482 Index->relations(Req, [&](
const SymbolID &,
const Symbol &) { ++
Results; });
486TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
488 TU.HeaderCode =
"class Foo{};";
489 Annotations Main(R
"cpp(
494 TU.Code = std::string(Main.code());
495 auto AST = TU.build();
496 FileIndex Index(
true);
502 refsAre({refRange(Main.range())}));
505TEST(FileIndexTest, MergeMainFileSymbols) {
506 const char *CommonHeader =
"void foo();";
509 Cpp.Filename =
"foo.cpp";
510 Cpp.HeaderFilename =
"foo.h";
511 Cpp.HeaderCode = CommonHeader;
513 FileIndex Index(
true);
514 auto HeaderAST = Header.build();
515 auto CppAST = Cpp.build();
516 Index.updateMain(
testPath(
"foo.h"), HeaderAST);
517 Index.updateMain(
testPath(
"foo.cpp"), CppAST);
521 EXPECT_THAT(
Symbols, ElementsAre(AllOf(declURI(
"unittest:///foo.h"),
522 defURI(
"unittest:///foo.cpp"),
526TEST(FileSymbolsTest, CountReferencesNoRefSlabs) {
528 FS.update(
"f1", numSlab(1, 3),
nullptr,
nullptr,
true);
529 FS.update(
"f2", numSlab(1, 3),
nullptr,
nullptr,
false);
533 UnorderedElementsAre(AllOf(qName(
"1"), numReferences(0u)),
534 AllOf(qName(
"2"), numReferences(0u)),
535 AllOf(qName(
"3"), numReferences(0u))));
538TEST(FileSymbolsTest, CountReferencesWithRefSlabs) {
540 FS.update(
"f1cpp", numSlab(1, 3), refSlab(
SymbolID(
"1"),
"f1.cpp"),
nullptr,
542 FS.update(
"f1h", numSlab(1, 3), refSlab(
SymbolID(
"1"),
"f1.h"),
nullptr,
544 FS.update(
"f2cpp", numSlab(1, 3), refSlab(
SymbolID(
"2"),
"f2.cpp"),
nullptr,
546 FS.update(
"f2h", numSlab(1, 3), refSlab(
SymbolID(
"2"),
"f2.h"),
nullptr,
548 FS.update(
"f3cpp", numSlab(1, 3), refSlab(
SymbolID(
"3"),
"f3.cpp"),
nullptr,
550 FS.update(
"f3h", numSlab(1, 3), refSlab(
SymbolID(
"3"),
"f3.h"),
nullptr,
555 UnorderedElementsAre(AllOf(qName(
"1"), numReferences(1u)),
556 AllOf(qName(
"2"), numReferences(1u)),
557 AllOf(qName(
"3"), numReferences(1u))));
560TEST(FileIndexTest, StalePreambleSymbolsDeleted) {
563 File.HeaderFilename =
"a.h";
565 File.Filename =
"f1.cpp";
566 File.HeaderCode =
"int a;";
569 AST.getASTContext(),
AST.getPreprocessor(),
570 AST.getPragmaIncludes());
571 EXPECT_THAT(
runFuzzyFind(
M,
""), UnorderedElementsAre(qName(
"a")));
573 File.Filename =
"f2.cpp";
574 File.HeaderCode =
"int b;";
577 AST.getASTContext(),
AST.getPreprocessor(),
578 AST.getPragmaIncludes());
579 EXPECT_THAT(
runFuzzyFind(
M,
""), UnorderedElementsAre(qName(
"b")));
583TEST(FileIndexTest, Threadsafety) {
587 constexpr int Count = 10;
590 AsyncTaskRunner Pool;
591 for (
unsigned I = 0; I < Count; ++I) {
593 TU.Filename = llvm::formatv(
"x{0}.c", I).str();
595 AST(TU.build())]()
mutable {
597 M.updateMain(Filename, AST);
604 EXPECT_THAT(
runFuzzyFind(
M,
"xxx"), ::testing::SizeIs(Count));
607TEST(FileShardedIndexTest, Sharding) {
613 Sym1.CanonicalDeclaration.FileURI = AHeaderUri.c_str();
616 Sym2.CanonicalDeclaration.FileURI = BHeaderUri.c_str();
617 Sym2.Definition.FileURI = BSourceUri.c_str();
623 SymbolSlab::Builder
B;
628 IF.Symbols.emplace(std::move(B).build());
632 IF.Refs.emplace(std::move(*refSlab(Sym1.ID, BSourceUri.c_str())));
635 RelationSlab::Builder
B;
643 IF.Relations.emplace(std::move(B).build());
646 IF.Sources.emplace();
650 auto &
Node = IG[BSourceUri];
651 Node.DirectIncludes = {BHeaderUri};
652 Node.URI = BSourceUri;
656 auto &
Node = IG[BHeaderUri];
657 Node.DirectIncludes = {AHeaderUri};
658 Node.URI = BHeaderUri;
662 auto &
Node = IG[AHeaderUri];
663 Node.DirectIncludes = {};
664 Node.URI = AHeaderUri;
667 IF.Cmd = tooling::CompileCommand(
testRoot(),
"b.cc", {
"clang"},
"out");
669 FileShardedIndex ShardedIndex(std::move(IF));
670 ASSERT_THAT(ShardedIndex.getAllSources(),
671 UnorderedElementsAre(AHeaderUri, BHeaderUri, BSourceUri));
674 auto Shard = ShardedIndex.getShard(AHeaderUri);
676 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName(
"1")));
677 EXPECT_THAT(*Shard->Refs, IsEmpty());
683 ASSERT_THAT(Shard->Sources->keys(), UnorderedElementsAre(AHeaderUri));
684 EXPECT_THAT(Shard->Sources->lookup(AHeaderUri).DirectIncludes, IsEmpty());
685 EXPECT_TRUE(Shard->Cmd);
688 auto Shard = ShardedIndex.getShard(BHeaderUri);
690 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName(
"2")));
691 EXPECT_THAT(*Shard->Refs, IsEmpty());
696 ASSERT_THAT(Shard->Sources->keys(),
697 UnorderedElementsAre(BHeaderUri, AHeaderUri));
698 EXPECT_THAT(Shard->Sources->lookup(BHeaderUri).DirectIncludes,
699 UnorderedElementsAre(AHeaderUri));
700 EXPECT_TRUE(Shard->Cmd);
703 auto Shard = ShardedIndex.getShard(BSourceUri);
705 EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(qName(
"2")));
706 EXPECT_THAT(*Shard->Refs, UnorderedElementsAre(
Pair(Sym1.ID, _)));
707 EXPECT_THAT(*Shard->Relations, IsEmpty());
708 ASSERT_THAT(Shard->Sources->keys(),
709 UnorderedElementsAre(BSourceUri, BHeaderUri));
710 EXPECT_THAT(Shard->Sources->lookup(BSourceUri).DirectIncludes,
711 UnorderedElementsAre(BHeaderUri));
712 EXPECT_TRUE(Shard->Cmd);
716TEST(FileIndexTest, Profile) {
722 FI.updatePreamble(
FileName,
"v1",
AST.getASTContext(),
AST.getPreprocessor(),
723 AST.getPragmaIncludes());
725 llvm::BumpPtrAllocator Alloc;
726 MemoryTree MT(&Alloc);
728 ASSERT_THAT(MT.children(),
729 UnorderedElementsAre(
Pair(
"preamble", _),
Pair(
"main_file", _)));
731 ASSERT_THAT(MT.child(
"preamble").children(),
732 UnorderedElementsAre(
Pair(
"index", _),
Pair(
"slabs", _)));
733 ASSERT_THAT(MT.child(
"main_file").children(),
734 UnorderedElementsAre(
Pair(
"index", _),
Pair(
"slabs", _)));
736 ASSERT_THAT(MT.child(
"preamble").child(
"index").total(), Gt(0U));
737 ASSERT_THAT(MT.child(
"main_file").child(
"index").total(), Gt(0U));
740TEST(FileSymbolsTest, Profile) {
742 FS.update(
"f1", numSlab(1, 2),
nullptr,
nullptr,
false);
743 FS.update(
"f2",
nullptr, refSlab(
SymbolID(
"1"),
"f1"),
nullptr,
false);
744 FS.update(
"f3",
nullptr,
nullptr,
747 llvm::BumpPtrAllocator Alloc;
748 MemoryTree MT(&Alloc);
750 ASSERT_THAT(MT.children(), UnorderedElementsAre(
Pair(
"f1", _),
Pair(
"f2", _),
752 EXPECT_THAT(MT.child(
"f1").children(), ElementsAre(
Pair(
"symbols", _)));
753 EXPECT_THAT(MT.child(
"f1").total(), Gt(0U));
754 EXPECT_THAT(MT.child(
"f2").children(), ElementsAre(
Pair(
"references", _)));
755 EXPECT_THAT(MT.child(
"f2").total(), Gt(0U));
756 EXPECT_THAT(MT.child(
"f3").children(), ElementsAre(
Pair(
"relations", _)));
757 EXPECT_THAT(MT.child(
"f3").total(), Gt(0U));
760TEST(FileIndexTest, MacrosFromMainFile) {
763 TU.Code =
"#pragma once\n#define FOO";
764 TU.Filename =
"foo.h";
765 auto AST = TU.build();
std::vector< CodeCompletionResult > Results
MATCHER_P(refRange, Range, "")
CharSourceRange Range
SourceRange for the file name.
std::string Filename
Filename as a string.
::clang::DynTypedNode Node
const google::protobuf::Message & M
std::unique_ptr< CompilerInvocation > CI
static llvm::Expected< URI > create(llvm::StringRef AbsolutePath, llvm::StringRef Scheme)
Creates a URI for a file in the given scheme.
::testing::Matcher< const RefSlab & > refsAre(std::vector<::testing::Matcher< Ref > > Matchers)
std::string Path
A typedef to represent a file path.
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
static const char * toString(OffsetEncoding OE)
std::string testPath(PathRef File, llvm::sys::path::Style Style)
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats)
Build a preamble for the new inputs unless an old one can be reused.
TEST(BackgroundQueueTest, Priority)
RefSlab getRefs(const SymbolIndex &Index, SymbolID ID)
Symbol symbol(llvm::StringRef QName)
llvm::StringMap< IncludeGraphNode > IncludeGraph
const Symbol & findSymbol(const SymbolSlab &Slab, llvm::StringRef QName)
@ Type
An inlay hint that for a type annotation.
SymbolSlab runFuzzyFind(const SymbolIndex &Index, llvm::StringRef Query)
std::array< uint8_t, 20 > SymbolID
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
@ IndexedForCodeCompletion
Whether or not this symbol is meant to be used for the code completion.
SymbolID ID
The ID of the symbol.
static TestTU withHeaderCode(llvm::StringRef HeaderCode)
static TestTU withCode(llvm::StringRef Code)