|  | #include "CompileCommands.h" | 
|  | #include "Config.h" | 
|  | #include "Headers.h" | 
|  | #include "SyncAPI.h" | 
|  | #include "TestFS.h" | 
|  | #include "TestIndex.h" | 
|  | #include "TestTU.h" | 
|  | #include "index/Background.h" | 
|  | #include "index/BackgroundRebuild.h" | 
|  | #include "clang/Tooling/ArgumentsAdjusters.h" | 
|  | #include "clang/Tooling/CompilationDatabase.h" | 
|  | #include "llvm/ADT/STLExtras.h" | 
|  | #include "llvm/Support/ScopedPrinter.h" | 
|  | #include "llvm/Support/Threading.h" | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  | #include <deque> | 
|  | #include <thread> | 
|  |  | 
|  | using ::testing::_; | 
|  | using ::testing::AllOf; | 
|  | using ::testing::Contains; | 
|  | using ::testing::ElementsAre; | 
|  | using ::testing::Not; | 
|  | using ::testing::Pair; | 
|  | using ::testing::UnorderedElementsAre; | 
|  |  | 
|  | namespace clang { | 
|  | namespace clangd { | 
|  |  | 
|  | MATCHER_P(named, N, "") { return arg.Name == N; } | 
|  | MATCHER_P(qName, N, "") { return (arg.Scope + arg.Name).str() == N; } | 
|  | MATCHER(declared, "") { | 
|  | return !StringRef(arg.CanonicalDeclaration.FileURI).empty(); | 
|  | } | 
|  | MATCHER(defined, "") { return !StringRef(arg.Definition.FileURI).empty(); } | 
|  | MATCHER_P(fileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } | 
|  | ::testing::Matcher<const RefSlab &> | 
|  | refsAre(std::vector<::testing::Matcher<Ref>> Matchers) { | 
|  | return ElementsAre(::testing::Pair(_, UnorderedElementsAreArray(Matchers))); | 
|  | } | 
|  | // URI cannot be empty since it references keys in the IncludeGraph. | 
|  | MATCHER(emptyIncludeNode, "") { | 
|  | return arg.Flags == IncludeGraphNode::SourceFlag::None && !arg.URI.empty() && | 
|  | arg.Digest == FileDigest{{0}} && arg.DirectIncludes.empty(); | 
|  | } | 
|  |  | 
|  | MATCHER(hadErrors, "") { | 
|  | return arg.Flags & IncludeGraphNode::SourceFlag::HadErrors; | 
|  | } | 
|  |  | 
|  | MATCHER_P(numReferences, N, "") { return arg.References == N; } | 
|  |  | 
|  | class MemoryShardStorage : public BackgroundIndexStorage { | 
|  | mutable std::mutex StorageMu; | 
|  | llvm::StringMap<std::string> &Storage; | 
|  | size_t &CacheHits; | 
|  |  | 
|  | public: | 
|  | MemoryShardStorage(llvm::StringMap<std::string> &Storage, size_t &CacheHits) | 
|  | : Storage(Storage), CacheHits(CacheHits) {} | 
|  | llvm::Error storeShard(llvm::StringRef ShardIdentifier, | 
|  | IndexFileOut Shard) const override { | 
|  | std::lock_guard<std::mutex> Lock(StorageMu); | 
|  | AccessedPaths.insert(ShardIdentifier); | 
|  | Storage[ShardIdentifier] = llvm::to_string(Shard); | 
|  | return llvm::Error::success(); | 
|  | } | 
|  | std::unique_ptr<IndexFileIn> | 
|  | loadShard(llvm::StringRef ShardIdentifier) const override { | 
|  | std::lock_guard<std::mutex> Lock(StorageMu); | 
|  | AccessedPaths.insert(ShardIdentifier); | 
|  | if (Storage.find(ShardIdentifier) == Storage.end()) { | 
|  | return nullptr; | 
|  | } | 
|  | auto IndexFile = | 
|  | readIndexFile(Storage[ShardIdentifier], SymbolOrigin::Background); | 
|  | if (!IndexFile) { | 
|  | ADD_FAILURE() << "Error while reading " << ShardIdentifier << ':' | 
|  | << IndexFile.takeError(); | 
|  | return nullptr; | 
|  | } | 
|  | CacheHits++; | 
|  | return std::make_unique<IndexFileIn>(std::move(*IndexFile)); | 
|  | } | 
|  |  | 
|  | mutable llvm::StringSet<> AccessedPaths; | 
|  | }; | 
|  |  | 
|  | class BackgroundIndexTest : public ::testing::Test { | 
|  | protected: | 
|  | BackgroundIndexTest() { BackgroundQueue::preventThreadStarvationInTests(); } | 
|  | }; | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, NoCrashOnErrorFile) { | 
|  | MockFS FS; | 
|  | FS.Files[testPath("root/A.cc")] = "error file"; | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  |  | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, Config) { | 
|  | MockFS FS; | 
|  | // Set up two identical TUs, foo and bar. | 
|  | // They define foo::one and bar::one. | 
|  | std::vector<tooling::CompileCommand> Cmds; | 
|  | for (std::string Name : {"foo", "bar", "baz"}) { | 
|  | std::string Filename = Name + ".cpp"; | 
|  | std::string Header = Name + ".h"; | 
|  | FS.Files[Filename] = "#include \"" + Header + "\""; | 
|  | FS.Files[Header] = "namespace " + Name + " { int one; }"; | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = Filename; | 
|  | Cmd.Directory = testRoot(); | 
|  | Cmd.CommandLine = {"clang++", Filename}; | 
|  | Cmds.push_back(std::move(Cmd)); | 
|  | } | 
|  | // Context provider that installs a configuration mutating foo's command. | 
|  | // This causes it to define foo::two instead of foo::one. | 
|  | // It also disables indexing of baz entirely. | 
|  | BackgroundIndex::Options Opts; | 
|  | Opts.ContextProvider = [](PathRef P) { | 
|  | Config C; | 
|  | if (P.endswith("foo.cpp")) | 
|  | C.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) { | 
|  | Argv = tooling::getInsertArgumentAdjuster("-Done=two")(Argv, ""); | 
|  | }); | 
|  | if (P.endswith("baz.cpp")) | 
|  | C.Index.Background = Config::BackgroundPolicy::Skip; | 
|  | return Context::current().derive(Config::Key, std::move(C)); | 
|  | }; | 
|  | // Create the background index. | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | // We need the CommandMangler, because that applies the config we're testing. | 
|  | OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{}, | 
|  | tooling::ArgumentsAdjuster(CommandMangler::forTests())); | 
|  |  | 
|  | BackgroundIndex Idx( | 
|  | FS, CDB, [&](llvm::StringRef) { return &MSS; }, std::move(Opts)); | 
|  | // Index the two files. | 
|  | for (auto &Cmd : Cmds) { | 
|  | std::string FullPath = testPath(Cmd.Filename); | 
|  | CDB.setCompileCommand(FullPath, std::move(Cmd)); | 
|  | } | 
|  | // Wait for both files to be indexed. | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | EXPECT_THAT(runFuzzyFind(Idx, ""), | 
|  | UnorderedElementsAre(qName("foo"), qName("foo::two"), | 
|  | qName("bar"), qName("bar::one"))); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, IndexTwoFiles) { | 
|  | MockFS FS; | 
|  | // a.h yields different symbols when included by A.cc vs B.cc. | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | void common(); | 
|  | void f_b(); | 
|  | #if A | 
|  | class A_CC {}; | 
|  | #else | 
|  | class B_CC{}; | 
|  | #endif | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/A.cc")] = | 
|  | "#include \"A.h\"\nstatic void g() { (void)common; }"; | 
|  | FS.Files[testPath("root/B.cc")] = | 
|  | R"cpp( | 
|  | #define A 0 | 
|  | #include "A.h" | 
|  | void f_b() { | 
|  | (void)common; | 
|  | (void)common; | 
|  | (void)common; | 
|  | (void)common; | 
|  | })cpp"; | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex::Options Opts; | 
|  | BackgroundIndex Idx( | 
|  | FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  |  | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | EXPECT_THAT(runFuzzyFind(Idx, ""), | 
|  | UnorderedElementsAre(AllOf(named("common"), numReferences(1U)), | 
|  | AllOf(named("A_CC"), numReferences(0U)), | 
|  | AllOf(named("g"), numReferences(1U)), | 
|  | AllOf(named("f_b"), declared(), | 
|  | Not(defined()), numReferences(0U)))); | 
|  |  | 
|  | Cmd.Filename = testPath("root/B.cc"); | 
|  | Cmd.CommandLine = {"clang++", Cmd.Filename}; | 
|  | CDB.setCompileCommand(testPath("root/B.cc"), Cmd); | 
|  |  | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | // B_CC is dropped as we don't collect symbols from A.h in this compilation. | 
|  | EXPECT_THAT(runFuzzyFind(Idx, ""), | 
|  | UnorderedElementsAre(AllOf(named("common"), numReferences(5U)), | 
|  | AllOf(named("A_CC"), numReferences(0U)), | 
|  | AllOf(named("g"), numReferences(1U)), | 
|  | AllOf(named("f_b"), declared(), defined(), | 
|  | numReferences(1U)))); | 
|  |  | 
|  | auto Syms = runFuzzyFind(Idx, "common"); | 
|  | EXPECT_THAT(Syms, UnorderedElementsAre(named("common"))); | 
|  | auto Common = *Syms.begin(); | 
|  | EXPECT_THAT(getRefs(Idx, Common.ID), | 
|  | refsAre({fileURI("unittest:///root/A.h"), | 
|  | fileURI("unittest:///root/A.cc"), | 
|  | fileURI("unittest:///root/B.cc"), | 
|  | fileURI("unittest:///root/B.cc"), | 
|  | fileURI("unittest:///root/B.cc"), | 
|  | fileURI("unittest:///root/B.cc")})); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, MainFileRefs) { | 
|  | MockFS FS; | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | void header_sym(); | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/A.cc")] = | 
|  | "#include \"A.h\"\nstatic void main_sym() { (void)header_sym; }"; | 
|  |  | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex::Options Opts; | 
|  | BackgroundIndex Idx( | 
|  | FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  |  | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | EXPECT_THAT( | 
|  | runFuzzyFind(Idx, ""), | 
|  | UnorderedElementsAre(AllOf(named("header_sym"), numReferences(1U)), | 
|  | AllOf(named("main_sym"), numReferences(1U)))); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, ShardStorageTest) { | 
|  | MockFS FS; | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | void common(); | 
|  | void f_b(); | 
|  | class A_CC {}; | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/A.cc")] = R"cpp( | 
|  | #include "A.h" | 
|  | void g() { (void)common; } | 
|  | class B_CC : public A_CC {}; | 
|  | )cpp"; | 
|  |  | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; | 
|  | // Check nothing is loaded from Storage, but A.cc and A.h has been stored. | 
|  | { | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_EQ(CacheHits, 0U); | 
|  | EXPECT_EQ(Storage.size(), 2U); | 
|  |  | 
|  | { | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. | 
|  | EXPECT_EQ(Storage.size(), 2U); | 
|  |  | 
|  | auto ShardHeader = MSS.loadShard(testPath("root/A.h")); | 
|  | EXPECT_NE(ShardHeader, nullptr); | 
|  | EXPECT_THAT( | 
|  | *ShardHeader->Symbols, | 
|  | UnorderedElementsAre(named("common"), named("A_CC"), | 
|  | AllOf(named("f_b"), declared(), Not(defined())))); | 
|  | for (const auto &Ref : *ShardHeader->Refs) | 
|  | EXPECT_THAT(Ref.second, | 
|  | UnorderedElementsAre(fileURI("unittest:///root/A.h"))); | 
|  |  | 
|  | auto ShardSource = MSS.loadShard(testPath("root/A.cc")); | 
|  | EXPECT_NE(ShardSource, nullptr); | 
|  | EXPECT_THAT(*ShardSource->Symbols, | 
|  | UnorderedElementsAre(named("g"), named("B_CC"))); | 
|  | for (const auto &Ref : *ShardSource->Refs) | 
|  | EXPECT_THAT(Ref.second, | 
|  | UnorderedElementsAre(fileURI("unittest:///root/A.cc"))); | 
|  |  | 
|  | // The BaseOf relationship between A_CC and B_CC is stored in both the file | 
|  | // containing the definition of the subject (A_CC) and the file containing | 
|  | // the definition of the object (B_CC). | 
|  | SymbolID A = findSymbol(*ShardHeader->Symbols, "A_CC").ID; | 
|  | SymbolID B = findSymbol(*ShardSource->Symbols, "B_CC").ID; | 
|  | EXPECT_THAT(*ShardHeader->Relations, | 
|  | UnorderedElementsAre(Relation{A, RelationKind::BaseOf, B})); | 
|  | EXPECT_THAT(*ShardSource->Relations, | 
|  | UnorderedElementsAre(Relation{A, RelationKind::BaseOf, B})); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, DirectIncludesTest) { | 
|  | MockFS FS; | 
|  | FS.Files[testPath("root/B.h")] = ""; | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | #include "B.h" | 
|  | void common(); | 
|  | void f_b(); | 
|  | class A_CC {}; | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/A.cc")] = | 
|  | "#include \"A.h\"\nvoid g() { (void)common; }"; | 
|  |  | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; | 
|  | { | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  |  | 
|  | auto ShardSource = MSS.loadShard(testPath("root/A.cc")); | 
|  | EXPECT_TRUE(ShardSource->Sources); | 
|  | EXPECT_EQ(ShardSource->Sources->size(), 2U); // A.cc, A.h | 
|  | EXPECT_THAT( | 
|  | ShardSource->Sources->lookup("unittest:///root/A.cc").DirectIncludes, | 
|  | UnorderedElementsAre("unittest:///root/A.h")); | 
|  | EXPECT_NE(ShardSource->Sources->lookup("unittest:///root/A.cc").Digest, | 
|  | FileDigest{{0}}); | 
|  | EXPECT_THAT(ShardSource->Sources->lookup("unittest:///root/A.h"), | 
|  | emptyIncludeNode()); | 
|  |  | 
|  | auto ShardHeader = MSS.loadShard(testPath("root/A.h")); | 
|  | EXPECT_TRUE(ShardHeader->Sources); | 
|  | EXPECT_EQ(ShardHeader->Sources->size(), 2U); // A.h, B.h | 
|  | EXPECT_THAT( | 
|  | ShardHeader->Sources->lookup("unittest:///root/A.h").DirectIncludes, | 
|  | UnorderedElementsAre("unittest:///root/B.h")); | 
|  | EXPECT_NE(ShardHeader->Sources->lookup("unittest:///root/A.h").Digest, | 
|  | FileDigest{{0}}); | 
|  | EXPECT_THAT(ShardHeader->Sources->lookup("unittest:///root/B.h"), | 
|  | emptyIncludeNode()); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, ShardStorageLoad) { | 
|  | MockFS FS; | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | void common(); | 
|  | void f_b(); | 
|  | class A_CC {}; | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/A.cc")] = | 
|  | "#include \"A.h\"\nvoid g() { (void)common; }"; | 
|  |  | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; | 
|  | // Check nothing is loaded from Storage, but A.cc and A.h has been stored. | 
|  | { | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  |  | 
|  | // Change header. | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | void common(); | 
|  | void f_b(); | 
|  | class A_CC {}; | 
|  | class A_CCnew {}; | 
|  | )cpp"; | 
|  | { | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. | 
|  |  | 
|  | // Check if the new symbol has arrived. | 
|  | auto ShardHeader = MSS.loadShard(testPath("root/A.h")); | 
|  | EXPECT_NE(ShardHeader, nullptr); | 
|  | EXPECT_THAT(*ShardHeader->Symbols, Contains(named("A_CCnew"))); | 
|  |  | 
|  | // Change source. | 
|  | FS.Files[testPath("root/A.cc")] = | 
|  | "#include \"A.h\"\nvoid g() { (void)common; }\nvoid f_b() {}"; | 
|  | { | 
|  | CacheHits = 0; | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. | 
|  |  | 
|  | // Check if the new symbol has arrived. | 
|  | ShardHeader = MSS.loadShard(testPath("root/A.h")); | 
|  | EXPECT_NE(ShardHeader, nullptr); | 
|  | EXPECT_THAT(*ShardHeader->Symbols, Contains(named("A_CCnew"))); | 
|  | auto ShardSource = MSS.loadShard(testPath("root/A.cc")); | 
|  | EXPECT_NE(ShardSource, nullptr); | 
|  | EXPECT_THAT(*ShardSource->Symbols, | 
|  | Contains(AllOf(named("f_b"), declared(), defined()))); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, ShardStorageEmptyFile) { | 
|  | MockFS FS; | 
|  | FS.Files[testPath("root/A.h")] = R"cpp( | 
|  | void common(); | 
|  | void f_b(); | 
|  | class A_CC {}; | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/B.h")] = R"cpp( | 
|  | #include "A.h" | 
|  | )cpp"; | 
|  | FS.Files[testPath("root/A.cc")] = | 
|  | "#include \"B.h\"\nvoid g() { (void)common; }"; | 
|  |  | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = testPath("root/A.cc"); | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; | 
|  | // Check that A.cc, A.h and B.h has been stored. | 
|  | { | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_THAT(Storage.keys(), | 
|  | UnorderedElementsAre(testPath("root/A.cc"), testPath("root/A.h"), | 
|  | testPath("root/B.h"))); | 
|  | auto ShardHeader = MSS.loadShard(testPath("root/B.h")); | 
|  | EXPECT_NE(ShardHeader, nullptr); | 
|  | EXPECT_TRUE(ShardHeader->Symbols->empty()); | 
|  |  | 
|  | // Check that A.cc, A.h and B.h has been loaded. | 
|  | { | 
|  | CacheHits = 0; | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_EQ(CacheHits, 3U); | 
|  |  | 
|  | // Update B.h to contain some symbols. | 
|  | FS.Files[testPath("root/B.h")] = R"cpp( | 
|  | #include "A.h" | 
|  | void new_func(); | 
|  | )cpp"; | 
|  | // Check that B.h has been stored with new contents. | 
|  | { | 
|  | CacheHits = 0; | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | CDB.setCompileCommand(testPath("root/A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  | } | 
|  | EXPECT_EQ(CacheHits, 3U); | 
|  | ShardHeader = MSS.loadShard(testPath("root/B.h")); | 
|  | EXPECT_NE(ShardHeader, nullptr); | 
|  | EXPECT_THAT(*ShardHeader->Symbols, | 
|  | Contains(AllOf(named("new_func"), declared(), Not(defined())))); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, NoDotsInAbsPath) { | 
|  | MockFS FS; | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | FS.Files[testPath("root/A.cc")] = ""; | 
|  | Cmd.Filename = "../A.cc"; | 
|  | Cmd.Directory = testPath("root/build"); | 
|  | Cmd.CommandLine = {"clang++", "../A.cc"}; | 
|  | CDB.setCompileCommand(testPath("root/build/../A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | FS.Files[testPath("root/B.cc")] = ""; | 
|  | Cmd.Filename = "./B.cc"; | 
|  | Cmd.Directory = testPath("root"); | 
|  | Cmd.CommandLine = {"clang++", "./B.cc"}; | 
|  | CDB.setCompileCommand(testPath("root/./B.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | for (llvm::StringRef AbsPath : MSS.AccessedPaths.keys()) { | 
|  | EXPECT_FALSE(AbsPath.contains("./")) << AbsPath; | 
|  | EXPECT_FALSE(AbsPath.contains("../")) << AbsPath; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, UncompilableFiles) { | 
|  | MockFS FS; | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | FS.Files[testPath("A.h")] = "void foo();"; | 
|  | FS.Files[testPath("B.h")] = "#include \"C.h\"\nasdf;"; | 
|  | FS.Files[testPath("C.h")] = ""; | 
|  | FS.Files[testPath("A.cc")] = R"cpp( | 
|  | #include "A.h" | 
|  | #include "B.h" | 
|  | #include "not_found_header.h" | 
|  |  | 
|  | void foo() {} | 
|  | )cpp"; | 
|  | Cmd.Filename = "../A.cc"; | 
|  | Cmd.Directory = testPath("build"); | 
|  | Cmd.CommandLine = {"clang++", "../A.cc"}; | 
|  | CDB.setCompileCommand(testPath("build/../A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | EXPECT_THAT(Storage.keys(), ElementsAre(testPath("A.cc"), testPath("A.h"), | 
|  | testPath("B.h"), testPath("C.h"))); | 
|  |  | 
|  | { | 
|  | auto Shard = MSS.loadShard(testPath("A.cc")); | 
|  | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(named("foo"))); | 
|  | EXPECT_THAT(Shard->Sources->keys(), | 
|  | UnorderedElementsAre("unittest:///A.cc", "unittest:///A.h", | 
|  | "unittest:///B.h")); | 
|  | EXPECT_THAT(Shard->Sources->lookup("unittest:///A.cc"), hadErrors()); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto Shard = MSS.loadShard(testPath("A.h")); | 
|  | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(named("foo"))); | 
|  | EXPECT_THAT(Shard->Sources->keys(), | 
|  | UnorderedElementsAre("unittest:///A.h")); | 
|  | EXPECT_THAT(Shard->Sources->lookup("unittest:///A.h"), hadErrors()); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto Shard = MSS.loadShard(testPath("B.h")); | 
|  | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre(named("asdf"))); | 
|  | EXPECT_THAT(Shard->Sources->keys(), | 
|  | UnorderedElementsAre("unittest:///B.h", "unittest:///C.h")); | 
|  | EXPECT_THAT(Shard->Sources->lookup("unittest:///B.h"), hadErrors()); | 
|  | } | 
|  |  | 
|  | { | 
|  | auto Shard = MSS.loadShard(testPath("C.h")); | 
|  | EXPECT_THAT(*Shard->Symbols, UnorderedElementsAre()); | 
|  | EXPECT_THAT(Shard->Sources->keys(), | 
|  | UnorderedElementsAre("unittest:///C.h")); | 
|  | EXPECT_THAT(Shard->Sources->lookup("unittest:///C.h"), hadErrors()); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, CmdLineHash) { | 
|  | MockFS FS; | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  |  | 
|  | tooling::CompileCommand Cmd; | 
|  | FS.Files[testPath("A.cc")] = "#include \"A.h\""; | 
|  | FS.Files[testPath("A.h")] = ""; | 
|  | Cmd.Filename = "../A.cc"; | 
|  | Cmd.Directory = testPath("build"); | 
|  | Cmd.CommandLine = {"clang++", "../A.cc", "-fsyntax-only"}; | 
|  | CDB.setCompileCommand(testPath("build/../A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | EXPECT_THAT(Storage.keys(), ElementsAre(testPath("A.cc"), testPath("A.h"))); | 
|  | // Make sure we only store the Cmd for main file. | 
|  | EXPECT_FALSE(MSS.loadShard(testPath("A.h"))->Cmd); | 
|  |  | 
|  | tooling::CompileCommand CmdStored = *MSS.loadShard(testPath("A.cc"))->Cmd; | 
|  | EXPECT_EQ(CmdStored.CommandLine, Cmd.CommandLine); | 
|  | EXPECT_EQ(CmdStored.Directory, Cmd.Directory); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexTest, Reindex) { | 
|  | MockFS FS; | 
|  | llvm::StringMap<std::string> Storage; | 
|  | size_t CacheHits = 0; | 
|  | MemoryShardStorage MSS(Storage, CacheHits); | 
|  | OverlayCDB CDB(/*Base=*/nullptr); | 
|  | BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, | 
|  | /*Opts=*/{}); | 
|  |  | 
|  | // Index a file. | 
|  | FS.Files[testPath("A.cc")] = "int theOldFunction();"; | 
|  | tooling::CompileCommand Cmd; | 
|  | Cmd.Filename = "../A.cc"; | 
|  | Cmd.Directory = testPath("build"); | 
|  | Cmd.CommandLine = {"clang++", "../A.cc", "-fsyntax-only"}; | 
|  | CDB.setCompileCommand(testPath("A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | // Verify the result is indexed and stored. | 
|  | EXPECT_EQ(1u, runFuzzyFind(Idx, "theOldFunction").size()); | 
|  | EXPECT_EQ(0u, runFuzzyFind(Idx, "theNewFunction").size()); | 
|  | std::string OldShard = Storage.lookup(testPath("A.cc")); | 
|  | EXPECT_NE("", OldShard); | 
|  |  | 
|  | // Change the content and command, and notify to reindex it. | 
|  | Cmd.CommandLine.push_back("-DFOO"); | 
|  | FS.Files[testPath("A.cc")] = "int theNewFunction();"; | 
|  | CDB.setCompileCommand(testPath("A.cc"), Cmd); | 
|  | ASSERT_TRUE(Idx.blockUntilIdleForTest()); | 
|  |  | 
|  | // Currently, we will never index the same main file again. | 
|  | EXPECT_EQ(1u, runFuzzyFind(Idx, "theOldFunction").size()); | 
|  | EXPECT_EQ(0u, runFuzzyFind(Idx, "theNewFunction").size()); | 
|  | EXPECT_EQ(OldShard, Storage.lookup(testPath("A.cc"))); | 
|  | } | 
|  |  | 
|  | class BackgroundIndexRebuilderTest : public testing::Test { | 
|  | protected: | 
|  | BackgroundIndexRebuilderTest() | 
|  | : Source(IndexContents::All), Target(std::make_unique<MemIndex>()), | 
|  | Rebuilder(&Target, &Source, /*Threads=*/10) { | 
|  | // Prepare FileSymbols with TestSymbol in it, for checkRebuild. | 
|  | TestSymbol.ID = SymbolID("foo"); | 
|  | } | 
|  |  | 
|  | // Perform Action and determine whether it rebuilt the index or not. | 
|  | bool checkRebuild(std::function<void()> Action) { | 
|  | // Update name so we can tell if the index updates. | 
|  | VersionStorage.push_back("Sym" + std::to_string(++VersionCounter)); | 
|  | TestSymbol.Name = VersionStorage.back(); | 
|  | SymbolSlab::Builder SB; | 
|  | SB.insert(TestSymbol); | 
|  | Source.update("", std::make_unique<SymbolSlab>(std::move(SB).build()), | 
|  | nullptr, nullptr, false); | 
|  | // Now maybe update the index. | 
|  | Action(); | 
|  | // Now query the index to get the name count. | 
|  | std::string ReadName; | 
|  | LookupRequest Req; | 
|  | Req.IDs.insert(TestSymbol.ID); | 
|  | Target.lookup(Req, | 
|  | [&](const Symbol &S) { ReadName = std::string(S.Name); }); | 
|  | // The index was rebuild if the name is up to date. | 
|  | return ReadName == VersionStorage.back(); | 
|  | } | 
|  |  | 
|  | Symbol TestSymbol; | 
|  | FileSymbols Source; | 
|  | SwapIndex Target; | 
|  | BackgroundIndexRebuilder Rebuilder; | 
|  |  | 
|  | unsigned VersionCounter = 0; | 
|  | std::deque<std::string> VersionStorage; | 
|  | }; | 
|  |  | 
|  | TEST_F(BackgroundIndexRebuilderTest, IndexingTUs) { | 
|  | for (unsigned I = 0; I < Rebuilder.TUsBeforeFirstBuild - 1; ++I) | 
|  | EXPECT_FALSE(checkRebuild([&] { Rebuilder.indexedTU(); })); | 
|  | EXPECT_TRUE(checkRebuild([&] { Rebuilder.indexedTU(); })); | 
|  | for (unsigned I = 0; I < Rebuilder.TUsBeforeRebuild - 1; ++I) | 
|  | EXPECT_FALSE(checkRebuild([&] { Rebuilder.indexedTU(); })); | 
|  | EXPECT_TRUE(checkRebuild([&] { Rebuilder.indexedTU(); })); | 
|  | } | 
|  |  | 
|  | TEST_F(BackgroundIndexRebuilderTest, LoadingShards) { | 
|  | Rebuilder.startLoading(); | 
|  | Rebuilder.loadedShard(10); | 
|  | Rebuilder.loadedShard(20); | 
|  | EXPECT_TRUE(checkRebuild([&] { Rebuilder.doneLoading(); })); | 
|  |  | 
|  | // No rebuild for no shards. | 
|  | Rebuilder.startLoading(); | 
|  | EXPECT_FALSE(checkRebuild([&] { Rebuilder.doneLoading(); })); | 
|  |  | 
|  | // Loads can overlap. | 
|  | Rebuilder.startLoading(); | 
|  | Rebuilder.loadedShard(1); | 
|  | Rebuilder.startLoading(); | 
|  | Rebuilder.loadedShard(1); | 
|  | EXPECT_FALSE(checkRebuild([&] { Rebuilder.doneLoading(); })); | 
|  | Rebuilder.loadedShard(1); | 
|  | EXPECT_TRUE(checkRebuild([&] { Rebuilder.doneLoading(); })); | 
|  |  | 
|  | // No rebuilding for indexed files while loading. | 
|  | Rebuilder.startLoading(); | 
|  | for (unsigned I = 0; I < 3 * Rebuilder.TUsBeforeRebuild; ++I) | 
|  | EXPECT_FALSE(checkRebuild([&] { Rebuilder.indexedTU(); })); | 
|  | // But they get indexed when we're done, even if no shards were loaded. | 
|  | EXPECT_TRUE(checkRebuild([&] { Rebuilder.doneLoading(); })); | 
|  | } | 
|  |  | 
|  | TEST(BackgroundQueueTest, Priority) { | 
|  | // Create high and low priority tasks. | 
|  | // Once a bunch of high priority tasks have run, the queue is stopped. | 
|  | // So the low priority tasks should never run. | 
|  | BackgroundQueue Q; | 
|  | std::atomic<unsigned> HiRan(0), LoRan(0); | 
|  | BackgroundQueue::Task Lo([&] { ++LoRan; }); | 
|  | BackgroundQueue::Task Hi([&] { | 
|  | if (++HiRan >= 10) | 
|  | Q.stop(); | 
|  | }); | 
|  | Hi.QueuePri = 100; | 
|  |  | 
|  | // Enqueuing the low-priority ones first shouldn't make them run first. | 
|  | Q.append(std::vector<BackgroundQueue::Task>(30, Lo)); | 
|  | for (unsigned I = 0; I < 30; ++I) | 
|  | Q.push(Hi); | 
|  |  | 
|  | AsyncTaskRunner ThreadPool; | 
|  | for (unsigned I = 0; I < 5; ++I) | 
|  | ThreadPool.runAsync("worker", [&] { Q.work(); }); | 
|  | // We should test enqueue with active workers, but it's hard to avoid races. | 
|  | // Just make sure we don't crash. | 
|  | Q.push(Lo); | 
|  | Q.append(std::vector<BackgroundQueue::Task>(2, Hi)); | 
|  |  | 
|  | // After finishing, check the tasks that ran. | 
|  | ThreadPool.wait(); | 
|  | EXPECT_GE(HiRan, 10u); | 
|  | EXPECT_EQ(LoRan, 0u); | 
|  | } | 
|  |  | 
|  | TEST(BackgroundQueueTest, Boost) { | 
|  | std::string Sequence; | 
|  |  | 
|  | BackgroundQueue::Task A([&] { Sequence.push_back('A'); }); | 
|  | A.Tag = "A"; | 
|  | A.QueuePri = 1; | 
|  |  | 
|  | BackgroundQueue::Task B([&] { Sequence.push_back('B'); }); | 
|  | B.QueuePri = 2; | 
|  | B.Tag = "B"; | 
|  |  | 
|  | { | 
|  | BackgroundQueue Q; | 
|  | Q.append({A, B}); | 
|  | Q.work([&] { Q.stop(); }); | 
|  | EXPECT_EQ("BA", Sequence) << "priority order"; | 
|  | } | 
|  | Sequence.clear(); | 
|  | { | 
|  | BackgroundQueue Q; | 
|  | Q.boost("A", 3); | 
|  | Q.append({A, B}); | 
|  | Q.work([&] { Q.stop(); }); | 
|  | EXPECT_EQ("AB", Sequence) << "A was boosted before enqueueing"; | 
|  | } | 
|  | Sequence.clear(); | 
|  | { | 
|  | BackgroundQueue Q; | 
|  | Q.append({A, B}); | 
|  | Q.boost("A", 3); | 
|  | Q.work([&] { Q.stop(); }); | 
|  | EXPECT_EQ("AB", Sequence) << "A was boosted after enqueueing"; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(BackgroundQueueTest, Duplicates) { | 
|  | std::string Sequence; | 
|  | BackgroundQueue::Task A([&] { Sequence.push_back('A'); }); | 
|  | A.QueuePri = 100; | 
|  | A.Key = 1; | 
|  | BackgroundQueue::Task B([&] { Sequence.push_back('B'); }); | 
|  | // B has no key, and is not subject to duplicate detection. | 
|  | B.QueuePri = 50; | 
|  |  | 
|  | BackgroundQueue Q; | 
|  | Q.append({A, B, A, B}); // One A is dropped, the other is high priority. | 
|  | Q.work(/*OnIdle=*/[&] { | 
|  | // The first time we go idle, we enqueue the same task again. | 
|  | if (!llvm::is_contained(Sequence, ' ')) { | 
|  | Sequence.push_back(' '); | 
|  | Q.append({A, B, A, B}); // Both As are dropped. | 
|  | } else { | 
|  | Q.stop(); | 
|  | } | 
|  | }); | 
|  |  | 
|  | // This could reasonably be "ABB BBA", if we had good *re*indexing support. | 
|  | EXPECT_EQ("ABB BB", Sequence); | 
|  | } | 
|  |  | 
|  | TEST(BackgroundQueueTest, Progress) { | 
|  | using testing::AnyOf; | 
|  | BackgroundQueue::Stats S; | 
|  | BackgroundQueue Q([&](BackgroundQueue::Stats New) { | 
|  | // Verify values are sane. | 
|  | // Items are enqueued one at a time (at least in this test). | 
|  | EXPECT_THAT(New.Enqueued, AnyOf(S.Enqueued, S.Enqueued + 1)); | 
|  | // Items are completed one at a time. | 
|  | EXPECT_THAT(New.Completed, AnyOf(S.Completed, S.Completed + 1)); | 
|  | // Items are started or completed one at a time. | 
|  | EXPECT_THAT(New.Active, AnyOf(S.Active - 1, S.Active, S.Active + 1)); | 
|  | // Idle point only advances in time. | 
|  | EXPECT_GE(New.LastIdle, S.LastIdle); | 
|  | // Idle point is a task that has been completed in the past. | 
|  | EXPECT_LE(New.LastIdle, New.Completed); | 
|  | // LastIdle is now only if we're really idle. | 
|  | EXPECT_EQ(New.LastIdle == New.Enqueued, | 
|  | New.Completed == New.Enqueued && New.Active == 0u); | 
|  | S = New; | 
|  | }); | 
|  |  | 
|  | // Two types of tasks: a ping task enqueues a pong task. | 
|  | // This avoids all enqueues followed by all completions (boring!) | 
|  | std::atomic<int> PingCount(0), PongCount(0); | 
|  | BackgroundQueue::Task Pong([&] { ++PongCount; }); | 
|  | BackgroundQueue::Task Ping([&] { | 
|  | ++PingCount; | 
|  | Q.push(Pong); | 
|  | }); | 
|  |  | 
|  | for (int I = 0; I < 1000; ++I) | 
|  | Q.push(Ping); | 
|  | // Spin up some workers and stop while idle. | 
|  | AsyncTaskRunner ThreadPool; | 
|  | for (unsigned I = 0; I < 5; ++I) | 
|  | ThreadPool.runAsync("worker", [&] { Q.work([&] { Q.stop(); }); }); | 
|  | ThreadPool.wait(); | 
|  |  | 
|  | // Everything's done, check final stats. | 
|  | // Assertions above ensure we got from 0 to 2000 in a reasonable way. | 
|  | EXPECT_EQ(PingCount.load(), 1000); | 
|  | EXPECT_EQ(PongCount.load(), 1000); | 
|  | EXPECT_EQ(S.Active, 0u); | 
|  | EXPECT_EQ(S.Enqueued, 2000u); | 
|  | EXPECT_EQ(S.Completed, 2000u); | 
|  | EXPECT_EQ(S.LastIdle, 2000u); | 
|  | } | 
|  |  | 
|  | TEST(BackgroundIndex, Profile) { | 
|  | MockFS FS; | 
|  | MockCompilationDatabase CDB; | 
|  | BackgroundIndex Idx(FS, CDB, [](llvm::StringRef) { return nullptr; }, | 
|  | /*Opts=*/{}); | 
|  |  | 
|  | llvm::BumpPtrAllocator Alloc; | 
|  | MemoryTree MT(&Alloc); | 
|  | Idx.profile(MT); | 
|  | ASSERT_THAT(MT.children(), | 
|  | UnorderedElementsAre(Pair("slabs", _), Pair("index", _))); | 
|  | } | 
|  |  | 
|  | } // namespace clangd | 
|  | } // namespace clang |