36 changes: 35 additions & 1 deletion clang-tools-extra/clangd/tool/ClangdMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include "Protocol.h"
#include "Transport.h"
#include "index/Background.h"
#include "index/Index.h"
#include "index/Merge.h"
#include "index/ProjectAware.h"
#include "index/Serialization.h"
#include "index/remote/Client.h"
#include "refactor/Rename.h"
Expand Down Expand Up @@ -40,6 +43,7 @@
#include <mutex>
#include <string>
#include <thread>
#include <vector>

#ifndef _WIN32
#include <unistd.h>
Expand Down Expand Up @@ -551,6 +555,27 @@ const char TestScheme::TestDir[] = "C:\\clangd-test";
const char TestScheme::TestDir[] = "/clangd-test";
#endif

std::unique_ptr<SymbolIndex>
loadExternalIndex(const Config::ExternalIndexSpec &External,
AsyncTaskRunner &Tasks) {
switch (External.Kind) {
case Config::ExternalIndexSpec::Server:
log("Associating {0} with remote index at {1}.", External.MountPoint,
External.Location);
return remote::getClient(External.Location, External.MountPoint);
case Config::ExternalIndexSpec::File:
log("Associating {0} with monolithic index at {1}.", External.MountPoint,
External.Location);
auto NewIndex = std::make_unique<SwapIndex>(std::make_unique<MemIndex>());
Tasks.runAsync("Load-index:" + External.Location,
[File = External.Location, PlaceHolder = NewIndex.get()] {
if (auto Idx = loadIndex(File, /*UseDex=*/true))
PlaceHolder->reset(std::move(Idx));
});
return std::move(NewIndex);
}
llvm_unreachable("Invalid ExternalIndexKind.");
}
} // namespace
} // namespace clangd
} // namespace clang
Expand Down Expand Up @@ -726,6 +751,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
Opts.ResourceDir = ResourceDir;
Opts.BuildDynamicSymbolIndex = EnableIndex;
Opts.CollectMainFileRefs = CollectMainFileRefs;
std::vector<std::unique_ptr<SymbolIndex>> IdxStack;
std::unique_ptr<SymbolIndex> StaticIdx;
std::future<void> AsyncIndexLoad; // Block exit while loading the index.
if (EnableIndex && !IndexFile.empty()) {
Expand Down Expand Up @@ -757,7 +783,15 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
}
#endif
Opts.BackgroundIndex = EnableBackgroundIndex;
Opts.StaticIndex = StaticIdx.get();
auto PAI = createProjectAwareIndex(loadExternalIndex);
if (StaticIdx) {
IdxStack.emplace_back(std::move(StaticIdx));
IdxStack.emplace_back(
std::make_unique<MergedIndex>(PAI.get(), IdxStack.back().get()));
Opts.StaticIndex = IdxStack.back().get();
} else {
Opts.StaticIndex = PAI.get();
}
Opts.AsyncThreadsCount = WorkerThreadsCount;
Opts.BuildRecoveryAST = RecoveryAST;
Opts.PreserveRecoveryASTType = RecoveryASTType;
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ add_unittest(ClangdUnitTests ClangdTests
PathMappingTests.cpp
PreambleTests.cpp
PrintASTTests.cpp
ProjectAwareIndexTests.cpp
QualityTests.cpp
RenameTests.cpp
RIFFTests.cpp
Expand Down
104 changes: 104 additions & 0 deletions clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
#include "Config.h"
#include "ConfigFragment.h"
#include "ConfigTesting.h"
#include "Features.inc"
#include "TestFS.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SourceMgr.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <string>
Expand All @@ -20,6 +24,8 @@ namespace clang {
namespace clangd {
namespace config {
namespace {
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::SizeIs;
Expand Down Expand Up @@ -196,6 +202,104 @@ TEST_F(ConfigCompileTests, Tidy) {
"0");
}

TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) {
Fragment::IndexBlock::ExternalBlock External;
External.File.emplace("");
External.Server.emplace("");
Frag.Index.External = std::move(External);
compileAndApply();
llvm::StringLiteral ExpectedDiag =
#ifdef CLANGD_ENABLE_REMOTE
"Exactly one of File or Server must be set.";
#else
"Clangd isn't compiled with remote index support, ignoring Server.";
#endif
EXPECT_THAT(Diags.Diagnostics,
Contains(AllOf(DiagMessage(ExpectedDiag),
DiagKind(llvm::SourceMgr::DK_Error))));
}

TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) {
Frag.Index.External.emplace(Fragment::IndexBlock::ExternalBlock{});
compileAndApply();
EXPECT_THAT(
Diags.Diagnostics,
Contains(AllOf(DiagMessage("Exactly one of File or Server must be set."),
DiagKind(llvm::SourceMgr::DK_Error))));
}

TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) {
Parm.Path = "/foo/bar/baz.h";
Frag.Index.Background.emplace("Build");
Fragment::IndexBlock::ExternalBlock External;
External.File.emplace("/foo");
External.MountPoint.emplace("/foo/bar");
Frag.Index.External = std::move(External);
compileAndApply();
EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip);
}

TEST_F(ConfigCompileTests, ExternalBlockMountPoint) {
auto GetFrag = [](llvm::StringRef Directory,
llvm::Optional<const char *> MountPoint) {
Fragment Frag;
Frag.Source.Directory = Directory.str();
Fragment::IndexBlock::ExternalBlock External;
External.File.emplace("/foo");
if (MountPoint)
External.MountPoint.emplace(*MountPoint);
Frag.Index.External = std::move(External);
return Frag;
};

Parm.Path = "/foo/bar.h";
// Non-absolute MountPoint without a directory raises an error.
Frag = GetFrag("", "foo");
compileAndApply();
ASSERT_THAT(
Diags.Diagnostics,
ElementsAre(
AllOf(DiagMessage("MountPoint must be an absolute path, because this "
"fragment is not associated with any directory."),
DiagKind(llvm::SourceMgr::DK_Error))));
ASSERT_FALSE(Conf.Index.External);

// Ok when relative.
Frag = GetFrag("/", "foo");
compileAndApply();
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_TRUE(Conf.Index.External);
EXPECT_THAT(Conf.Index.External->MountPoint, "/foo");

// None defaults to ".".
Frag = GetFrag("/", llvm::None);
compileAndApply();
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_TRUE(Conf.Index.External);
EXPECT_THAT(Conf.Index.External->MountPoint, "/");

// Without a file, external index is empty.
Parm.Path = "";
Frag = GetFrag("", "/foo");
compileAndApply();
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_FALSE(Conf.Index.External);

// File outside MountPoint, no index.
Parm.Path = "/bar/baz.h";
Frag = GetFrag("", "/foo");
compileAndApply();
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_FALSE(Conf.Index.External);

// File under MountPoint, index should be set.
Parm.Path = "/foo/baz.h";
Frag = GetFrag("", "/foo");
compileAndApply();
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_TRUE(Conf.Index.External);
EXPECT_THAT(Conf.Index.External->MountPoint, "/foo");
}
} // namespace
} // namespace config
} // namespace clangd
Expand Down
20 changes: 20 additions & 0 deletions clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ConfigFragment.h"
#include "ConfigTesting.h"
#include "Protocol.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/SMLoc.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/SourceMgr.h"
Expand Down Expand Up @@ -142,6 +143,25 @@ horrible
ASSERT_THAT(Results, IsEmpty());
}

TEST(ParseYAML, ExternalBlock) {
CapturedDiags Diags;
Annotations YAML(R"yaml(
Index:
External:
File: "foo"
Server: ^"bar"
MountPoint: "baz"
)yaml");
auto Results =
Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
ASSERT_EQ(Results.size(), 1u);
ASSERT_TRUE(Results[0].Index.External);
EXPECT_THAT(*Results[0].Index.External.getValue()->File, Val("foo"));
EXPECT_THAT(*Results[0].Index.External.getValue()->MountPoint, Val("baz"));
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
EXPECT_THAT(*Results[0].Index.External.getValue()->Server, Val("bar"));
}

} // namespace
} // namespace config
} // namespace clangd
Expand Down
86 changes: 86 additions & 0 deletions clang-tools-extra/clangd/unittests/ProjectAwareIndexTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//===-- ProjectAwareIndexTests.cpp -------------------*- C++ -*-----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Config.h"
#include "TestIndex.h"
#include "index/Index.h"
#include "index/MemIndex.h"
#include "index/ProjectAware.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "support/Context.h"
#include "support/Threading.h"
#include "llvm/ADT/StringRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
#include <utility>

namespace clang {
namespace clangd {
using testing::ElementsAre;
using testing::IsEmpty;

std::unique_ptr<SymbolIndex> createIndex() {
std::vector<Symbol> Symbols = {symbol("1")};
return std::make_unique<MemIndex>(std::move(Symbols), RefSlab(),
RelationSlab());
}

TEST(ProjectAware, Test) {
IndexFactory Gen = [](const Config::ExternalIndexSpec &, AsyncTaskRunner &) {
return createIndex();
};

auto Idx = createProjectAwareIndex(std::move(Gen));
FuzzyFindRequest Req;
Req.Query = "1";
Req.AnyScope = true;

EXPECT_THAT(match(*Idx, Req), IsEmpty());

Config C;
C.Index.External.emplace();
C.Index.External->Location = "test";
WithContextValue With(Config::Key, std::move(C));
EXPECT_THAT(match(*Idx, Req), ElementsAre("1"));
return;
}

TEST(ProjectAware, CreatedOnce) {
unsigned InvocationCount = 0;
IndexFactory Gen = [&](const Config::ExternalIndexSpec &, AsyncTaskRunner &) {
++InvocationCount;
return createIndex();
};

auto Idx = createProjectAwareIndex(std::move(Gen));
// No invocation at start.
EXPECT_EQ(InvocationCount, 0U);
FuzzyFindRequest Req;
Req.Query = "1";
Req.AnyScope = true;

// Cannot invoke without proper config.
match(*Idx, Req);
EXPECT_EQ(InvocationCount, 0U);

Config C;
C.Index.External.emplace();
C.Index.External->Location = "test";
WithContextValue With(Config::Key, std::move(C));
match(*Idx, Req);
// Now it should be created.
EXPECT_EQ(InvocationCount, 1U);
match(*Idx, Req);
// It is cached afterwards.
EXPECT_EQ(InvocationCount, 1U);
return;
}
} // namespace clangd
} // namespace clang