#include "SyncAPI.h" #include "TestFS.h" #include "index/Background.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/Threading.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include using testing::_; using testing::AllOf; using testing::Contains; using testing::ElementsAre; using testing::Not; using testing::UnorderedElementsAre; namespace clang { namespace clangd { MATCHER_P(Named, N, "") { return arg.Name == 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 RefsAre(std::vector> Matchers) { return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); } // URI cannot be empty since it references keys in the IncludeGraph. MATCHER(EmptyIncludeNode, "") { return !arg.IsTU && !arg.URI.empty() && arg.Digest == FileDigest{{0}} && arg.DirectIncludes.empty(); } class MemoryShardStorage : public BackgroundIndexStorage { mutable std::mutex StorageMu; llvm::StringMap &Storage; size_t &CacheHits; public: MemoryShardStorage(llvm::StringMap &Storage, size_t &CacheHits) : Storage(Storage), CacheHits(CacheHits) {} llvm::Error storeShard(llvm::StringRef ShardIdentifier, IndexFileOut Shard) const override { std::lock_guard Lock(StorageMu); Storage[ShardIdentifier] = llvm::to_string(Shard); return llvm::Error::success(); } std::unique_ptr loadShard(llvm::StringRef ShardIdentifier) const override { std::lock_guard Lock(StorageMu); if (Storage.find(ShardIdentifier) == Storage.end()) { return nullptr; } auto IndexFile = readIndexFile(Storage[ShardIdentifier]); if (!IndexFile) { ADD_FAILURE() << "Error while reading " << ShardIdentifier << ':' << IndexFile.takeError(); return nullptr; } CacheHits++; return llvm::make_unique(std::move(*IndexFile)); } }; class BackgroundIndexTest : public ::testing::Test { protected: BackgroundIndexTest() { preventThreadStarvationInTests(); } }; TEST_F(BackgroundIndexTest, NoCrashOnErrorFile) { MockFSProvider FS; FS.Files[testPath("root/A.cc")] = "error file"; llvm::StringMap Storage; size_t CacheHits = 0; MemoryShardStorage MSS(Storage, CacheHits); OverlayCDB CDB(/*Base=*/nullptr); BackgroundIndex Idx(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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, IndexTwoFiles) { MockFSProvider 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\"\nvoid g() { (void)common; }"; FS.Files[testPath("root/B.cc")] = R"cpp( #define A 0 #include "A.h" void f_b() { (void)common; })cpp"; llvm::StringMap Storage; size_t CacheHits = 0; MemoryShardStorage MSS(Storage, CacheHits); OverlayCDB CDB(/*Base=*/nullptr); BackgroundIndex Idx(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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(Named("common"), Named("A_CC"), Named("g"), AllOf(Named("f_b"), Declared(), Not(Defined())))); Cmd.Filename = testPath("root/B.cc"); Cmd.CommandLine = {"clang++", Cmd.Filename}; CDB.setCompileCommand(testPath("root/A.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(Named("common"), Named("A_CC"), Named("g"), AllOf(Named("f_b"), Declared(), Defined()))); 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")})); } TEST_F(BackgroundIndexTest, ShardStorageTest) { MockFSProvider FS; FS.Files[testPath("root/A.h")] = R"cpp( void common(); void f_b(); class A_CC {}; )cpp"; std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }"; FS.Files[testPath("root/A.cc")] = A_CC; llvm::StringMap 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); CDB.setCompileCommand(testPath("root"), 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"))); EXPECT_THAT(*ShardSource->Refs, RefsAre({FileURI("unittest:///root/A.cc")})); } TEST_F(BackgroundIndexTest, DirectIncludesTest) { MockFSProvider 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"; std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }"; FS.Files[testPath("root/A.cc")] = A_CC; llvm::StringMap 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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()); } // FIXME: figure out the right timeouts or rewrite to not use the timeouts and // re-enable. TEST_F(BackgroundIndexTest, DISABLED_PeriodicalIndex) { MockFSProvider FS; llvm::StringMap Storage; size_t CacheHits = 0; MemoryShardStorage MSS(Storage, CacheHits); OverlayCDB CDB(/*Base=*/nullptr); BackgroundIndex Idx( Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }, /*BuildIndexPeriodMs=*/500); FS.Files[testPath("root/A.cc")] = "#include \"A.h\""; tooling::CompileCommand Cmd; FS.Files[testPath("root/A.h")] = "class X {};"; Cmd.Filename = testPath("root/A.cc"); Cmd.CommandLine = {"clang++", Cmd.Filename}; CDB.setCompileCommand(testPath("root/A.cc"), Cmd); ASSERT_TRUE(Idx.blockUntilIdleForTest()); EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre()); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("X"))); FS.Files[testPath("root/A.h")] = "class Y {};"; FS.Files[testPath("root/A.cc")] += " "; // Force reindex the file. Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; CDB.setCompileCommand(testPath("root/A.cc"), Cmd); ASSERT_TRUE(Idx.blockUntilIdleForTest()); EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("X"))); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("Y"))); } TEST_F(BackgroundIndexTest, ShardStorageLoad) { MockFSProvider 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 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); CDB.setCompileCommand(testPath("root"), 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); CDB.setCompileCommand(testPath("root"), 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) { MockFSProvider 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 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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(Context::empty(), "", FS, CDB, [&](llvm::StringRef) { return &MSS; }); 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())))); } } // namespace clangd } // namespace clang