mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 11:26:07 +00:00

Summary: Some projects make use of clang plugins when building, but clangd is not aware of those plugins therefore can't work with the same compile command arguments. There were multiple places clangd performed commandline manipulations, this one also moves them all into OverlayCDB. Reviewers: ilya-biryukov Subscribers: klimek, sammccall, ioeric, MaskRay, jkorous, arphaman, cfe-commits Differential Revision: https://reviews.llvm.org/D56841 llvm-svn: 351788
433 lines
15 KiB
C++
433 lines
15 KiB
C++
#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 <thread>
|
|
|
|
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<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.IsTU && !arg.URI.empty() && arg.Digest == FileDigest{{0}} &&
|
|
arg.DirectIncludes.empty();
|
|
}
|
|
|
|
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);
|
|
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);
|
|
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<IndexFileIn>(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<std::string> 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<std::string> 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<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(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<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(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<std::string> 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<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(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<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(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
|