forked from OSchip/llvm-project
941 lines
32 KiB
C++
941 lines
32 KiB
C++
#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]);
|
|
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.push_back("-Done=two"); });
|
|
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;
|
|
Opts.CollectMainFileRefs = true;
|
|
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; }";
|
|
|
|
// Check the behaviour with CollectMainFileRefs = false (the default
|
|
// at the SymbolCollector level).
|
|
{
|
|
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++", 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(0U))));
|
|
}
|
|
|
|
// Check the behaviour with CollectMainFileRefs = true.
|
|
{
|
|
llvm::StringMap<std::string> Storage;
|
|
size_t CacheHits = 0;
|
|
MemoryShardStorage MSS(Storage, CacheHits);
|
|
OverlayCDB CDB(/*Base=*/nullptr);
|
|
BackgroundIndex::Options Opts;
|
|
Opts.CollectMainFileRefs = true;
|
|
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";
|
|
std::string A_CC = "";
|
|
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";
|
|
std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }";
|
|
FS.Files[testPath("root/A.cc")] = A_CC;
|
|
|
|
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()
|
|
: 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
|