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,
50 Range.end.line, Range.end.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) {
85 for (
int I = Begin; I <= End; I++)
87 return std::make_unique<SymbolSlab>(std::move(Slab).build());
90std::unique_ptr<RefSlab> refSlab(
const SymbolID &ID,
const char *
Path) {
96 return std::make_unique<RefSlab>(std::move(Slab).build());
99std::unique_ptr<RelationSlab> relSlab(llvm::ArrayRef<const Relation> Rels) {
101 for (
auto &Rel : Rels)
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) {
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 {}; }");
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 {}; }");
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) {
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")));
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) {
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();
338 bool IndexUpdated =
false;
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);
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 {};";
375 Request.
IDs = {Foo.ID};
381 Test.Code = std::string(MainCode.code());
382 Test.Filename =
"test.cc";
383 auto AST = Test.build();
388 Test2.Code = std::string(MainCode.code());
389 Test2.Filename =
"test2.cc";
393 EXPECT_THAT(
getRefs(Index, Foo.ID),
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) {
402 #define $def1[[HEADER_MACRO]](X) (X+1)
405 #define $def2[[MAINFILE_MACRO]](X) (X+1)
407 int a = $ref1[[HEADER_MACRO]](2);
408 int b = $ref2[[MAINFILE_MACRO]](1);
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");
437 EXPECT_THAT(
runFuzzyFind(M,
""), Contains(qName(
"CLANGD")));
440TEST(FileIndexTest, Relations) {
443 TU.HeaderFilename =
"f.h";
444 TU.HeaderCode =
"class A {}; class B : public A {};";
445 auto AST = TU.build();
447 Index.updatePreamble(
testPath(TU.Filename),
"null",
448 AST.getASTContext(),
AST.getPreprocessor(),
449 AST.getPragmaIncludes());
451 uint32_t Results = 0;
455 Index.relations(Req, [&](
const SymbolID &,
const Symbol &) { ++Results; });
456 EXPECT_EQ(Results, 1u);
459TEST(FileIndexTest, RelationsMultiFile) {
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();
473 FFReq.
Query =
"Base";
474 FFReq.AnyScope =
true;
476 Index->fuzzyFind(FFReq, [&](
const Symbol &S) { Base = S.ID; });
481 uint32_t Results = 0;
482 Index->relations(Req, [&](
const SymbolID &,
const Symbol &) { ++Results; });
483 EXPECT_EQ(Results, 2u);
486TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
494 TU.Code = std::string(Main.code());
495 auto AST = TU.build();
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;
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;
591 for (
unsigned I = 0; I < Count; ++I) {
593 TU.Filename = llvm::formatv(
"x{0}.c", I).str();
594 Pool.runAsync(TU.Filename, [&, Filename(
testPath(TU.Filename)),
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();
628 IF.Symbols.emplace(std::move(B).build());
632 IF.Refs.emplace(std::move(*refSlab(Sym1.ID, BSourceUri.c_str())));
643 IF.Relations.emplace(std::move(B).build());
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");
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) {
719 auto FileName =
testPath(
"foo.cpp");
721 FI.updateMain(FileName,
AST);
722 FI.updatePreamble(FileName,
"v1",
AST.getASTContext(),
AST.getPreprocessor(),
723 AST.getPragmaIncludes());
725 llvm::BumpPtrAllocator 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;
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();
MATCHER_P(refRange, Range, "")
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
Runs tasks on separate (detached) threads and wait for all tasks to finish.
This manages symbols from files and an in-memory index on all symbols.
A container of slabs associated with a key.
llvm::StringMap< std::string > Files
A threadsafe flag that is initially clear.
RefSlab::Builder is a mutable container that can 'freeze' to RefSlab.
RelationSlab::Builder is a mutable container that can 'freeze' to RelationSlab.
void insert(const Relation &R)
Adds a relation to the slab.
SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab.
void insert(const Symbol &S)
Adds a symbol, overwriting any existing one with the same ID.
void addSource(llvm::StringRef Filename, llvm::StringRef Code)
static llvm::Expected< URI > create(llvm::StringRef AbsolutePath, llvm::StringRef Scheme)
Creates a URI for a file in the given scheme.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
::testing::Matcher< const RefSlab & > refsAre(std::vector<::testing::Matcher< Ref > > Matchers)
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.
std::string Path
A typedef to represent a file path.
SymbolSlab runFuzzyFind(const SymbolIndex &Index, llvm::StringRef Query)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
The captured AST context.
Takes slabs coming from a TU (multiple files) and shards them per declaration location.
std::vector< std::string > Scopes
If this is non-empty, symbols must be in at least one of the scopes (e.g.
std::string Query
A query string for the fuzzy find.
std::optional< IncludeGraph > Sources
A tree that can be used to represent memory usage of nested components while preserving the hierarchy...
Represents a symbol occurrence in the source file.
SymbolLocation Location
The source location where the symbol is named.
llvm::DenseSet< SymbolID > IDs
Represents a relation between two symbols.
llvm::DenseSet< SymbolID > Subjects
The class presents a C++ symbol, e.g.
@ 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)