737 changes: 518 additions & 219 deletions clang-tools-extra/clangd/TUScheduler.cpp

Large diffs are not rendered by default.

26 changes: 16 additions & 10 deletions clang-tools-extra/clangd/TUScheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,22 @@ struct DebouncePolicy {
static DebouncePolicy fixed(clock::duration);
};

struct TUAction {
enum State {
Queued, // The TU is pending in the thread task queue to be built.
RunningAction, // Starting running actions on the TU.
BuildingPreamble, // The preamble of the TU is being built.
BuildingFile, // The TU is being built. It is only emitted when building
// the AST for diagnostics in write action (update).
enum class PreambleAction {
Idle,
Building,
};

struct ASTAction {
enum Kind {
Queued, // The action is pending in the thread task queue to be run.
RunningAction, // Started running actions on the TU.
Building, // The AST is being built.
Idle, // Indicates the worker thread is idle, and ready to run any upcoming
// actions.
};
TUAction(State S, llvm::StringRef Name) : S(S), Name(Name) {}
State S;
ASTAction() = default;
ASTAction(Kind K, llvm::StringRef Name) : K(K), Name(Name) {}
Kind K = ASTAction::Idle;
/// The name of the action currently running, e.g. Update, GoToDef, Hover.
/// Empty if we are in the idle state.
std::string Name;
Expand All @@ -111,7 +115,9 @@ struct TUStatus {
/// Serialize this to an LSP file status item.
FileStatus render(PathRef File) const;

TUAction Action;
PreambleAction PreambleActivity = PreambleAction::Idle;
ASTAction ASTActivity;
/// Stores status of the last build for the translation unit.
BuildDetails Details;
};

Expand Down
31 changes: 14 additions & 17 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,8 @@ CodeCompleteResult completions(const TestTU &TU, Position Point,
ADD_FAILURE() << "Couldn't build CompilerInvocation";
return {};
}
auto Preamble =
buildPreamble(testPath(TU.Filename), *CI, /*OldPreamble=*/nullptr, Inputs,
/*InMemory=*/true, /*Callback=*/nullptr);
auto Preamble = buildPreamble(testPath(TU.Filename), *CI, Inputs,
/*InMemory=*/true, /*Callback=*/nullptr);
return codeComplete(testPath(TU.Filename), Inputs.CompileCommand,
Preamble.get(), TU.Code, Point, Inputs.FS, Opts);
}
Expand Down Expand Up @@ -518,16 +517,16 @@ TEST(CompletionTest, Kinds) {
AllOf(Named("complete_static_member"),
Kind(CompletionItemKind::Property))));

Results = completions(
Results = completions(
R"cpp(
enum Color {
Red
};
Color u = ^
)cpp");
EXPECT_THAT(Results.Completions,
Contains(
AllOf(Named("Red"), Kind(CompletionItemKind::EnumMember))));
EXPECT_THAT(
Results.Completions,
Contains(AllOf(Named("Red"), Kind(CompletionItemKind::EnumMember))));
}

TEST(CompletionTest, NoDuplicates) {
Expand Down Expand Up @@ -1046,9 +1045,8 @@ SignatureHelp signatures(llvm::StringRef Text, Position Point,
ADD_FAILURE() << "Couldn't build CompilerInvocation";
return {};
}
auto Preamble =
buildPreamble(testPath(TU.Filename), *CI, /*OldPreamble=*/nullptr, Inputs,
/*InMemory=*/true, /*Callback=*/nullptr);
auto Preamble = buildPreamble(testPath(TU.Filename), *CI, Inputs,
/*InMemory=*/true, /*Callback=*/nullptr);
if (!Preamble) {
ADD_FAILURE() << "Couldn't build Preamble";
return {};
Expand Down Expand Up @@ -1712,7 +1710,7 @@ TEST(CompletionTest, FixItForArrowToDot) {

CodeCompleteOptions Opts;
Opts.IncludeFixIts = true;
const char* Code =
const char *Code =
R"cpp(
class Auxilary {
public:
Expand Down Expand Up @@ -1746,7 +1744,7 @@ TEST(CompletionTest, FixItForArrowToDot) {
TEST(CompletionTest, FixItForDotToArrow) {
CodeCompleteOptions Opts;
Opts.IncludeFixIts = true;
const char* Code =
const char *Code =
R"cpp(
class Auxilary {
public:
Expand Down Expand Up @@ -1850,7 +1848,7 @@ TEST(CompletionTest, CompletionTokenRange) {
R"cpp(
#include "foo/abc/[[fo^o.h"]]
)cpp",
};
};
for (const auto &Text : TestCodes) {
Annotations TestCode(Text);
TU.Code = TestCode.code().str();
Expand Down Expand Up @@ -2222,10 +2220,9 @@ TEST(CompletionTest, NoInsertIncludeIfOnePresent) {
Sym.IncludeHeaders.emplace_back("\"foo.h\"", 2);
Sym.IncludeHeaders.emplace_back("\"bar.h\"", 1000);

EXPECT_THAT(
completions(TU, Test.point(), {Sym}).Completions,
UnorderedElementsAre(
AllOf(Named("Func"), HasInclude("\"foo.h\""), Not(InsertInclude()))));
EXPECT_THAT(completions(TU, Test.point(), {Sym}).Completions,
UnorderedElementsAre(AllOf(Named("Func"), HasInclude("\"foo.h\""),
Not(InsertInclude()))));
}

TEST(CompletionTest, MergeMacrosFromIndexAndSema) {
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/unittests/FileIndexTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ TEST(FileIndexTest, RebuildWithPreamble) {

FileIndex Index;
bool IndexUpdated = false;
buildPreamble(FooCpp, *CI, /*OldPreamble=*/nullptr, PI,
buildPreamble(FooCpp, *CI, PI,
/*StoreInMemory=*/true,
[&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP,
const CanonicalIncludes &CanonIncludes) {
Expand Down Expand Up @@ -424,7 +424,7 @@ TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
}

TEST(FileIndexTest, MergeMainFileSymbols) {
const char* CommonHeader = "void foo();";
const char *CommonHeader = "void foo();";
TestTU Header = TestTU::withCode(CommonHeader);
TestTU Cpp = TestTU::withCode("void foo() {}");
Cpp.Filename = "foo.cpp";
Expand Down
85 changes: 53 additions & 32 deletions clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <algorithm>
#include <atomic>
#include <bits/stdint-uintn.h>
#include <chrono>
#include <utility>

Expand All @@ -40,13 +42,14 @@ using ::testing::IsEmpty;
using ::testing::Pointee;
using ::testing::UnorderedElementsAre;

MATCHER_P2(TUState, State, ActionName, "") {
if (arg.Action.S != State) {
*result_listener << "state is " << arg.Action.S;
MATCHER_P2(TUState, PreambleActivity, ASTActivity, "") {
if (arg.PreambleActivity != PreambleActivity) {
*result_listener << "preamblestate is "
<< static_cast<uint8_t>(arg.PreambleActivity);
return false;
}
if (arg.Action.Name != ActionName) {
*result_listener << "name is " << arg.Action.Name;
if (arg.ASTActivity.K != ASTActivity) {
*result_listener << "aststate is " << arg.ASTActivity.K;
return false;
}
return true;
Expand Down Expand Up @@ -281,6 +284,7 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
[&](Expected<InputsAndPreamble> Pre) {
ASSERT_TRUE(bool(Pre));
ASSERT_TRUE(Pre->Preamble);
EXPECT_EQ(Pre->Preamble->Version, "A");
EXPECT_THAT(includes(Pre->Preamble),
ElementsAre("<A>"));
Expand All @@ -290,11 +294,13 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
[&](Expected<InputsAndPreamble> Pre) {
ASSERT_TRUE(bool(Pre));
ASSERT_TRUE(Pre->Preamble);
EXPECT_EQ(Pre->Preamble->Version, "B");
EXPECT_THAT(includes(Pre->Preamble),
ElementsAre("<B>"));
++CallbackCount;
});
S.blockUntilIdle(timeoutSeconds(10));
}
EXPECT_EQ(2, CallbackCount);
}
Expand Down Expand Up @@ -729,17 +735,19 @@ TEST_F(TUSchedulerTests, ForceRebuild) {
)cpp";

ParseInputs Inputs = getInputs(Source, SourceContents);
std::atomic<size_t> DiagCount(0);

// Update the source contents, which should trigger an initial build with
// the header file missing.
updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
[](std::vector<Diag> Diags) {
EXPECT_THAT(
Diags,
ElementsAre(
Field(&Diag::Message, "'foo.h' file not found"),
Field(&Diag::Message, "use of undeclared identifier 'a'")));
});
updateWithDiags(
S, Source, Inputs, WantDiagnostics::Yes,
[&DiagCount](std::vector<Diag> Diags) {
++DiagCount;
EXPECT_THAT(Diags,
ElementsAre(Field(&Diag::Message, "'foo.h' file not found"),
Field(&Diag::Message,
"use of undeclared identifier 'a'")));
});

// Add the header file. We need to recreate the inputs since we changed a
// file from underneath the test FS.
Expand All @@ -749,20 +757,24 @@ TEST_F(TUSchedulerTests, ForceRebuild) {

// The addition of the missing header file shouldn't trigger a rebuild since
// we don't track missing files.
updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
[](std::vector<Diag> Diags) {
ADD_FAILURE() << "Did not expect diagnostics for missing header update";
});
updateWithDiags(
S, Source, Inputs, WantDiagnostics::Yes,
[&DiagCount](std::vector<Diag> Diags) {
++DiagCount;
ADD_FAILURE() << "Did not expect diagnostics for missing header update";
});

// Forcing the reload should should cause a rebuild which no longer has any
// errors.
Inputs.ForceRebuild = true;
updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes,
[](std::vector<Diag> Diags) {
EXPECT_THAT(Diags, IsEmpty());
});
[&DiagCount](std::vector<Diag> Diags) {
++DiagCount;
EXPECT_THAT(Diags, IsEmpty());
});

ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10)));
EXPECT_EQ(DiagCount, 2U);
}
TEST_F(TUSchedulerTests, NoChangeDiags) {
TUScheduler S(CDB, optsForTest(), captureDiags());
Expand Down Expand Up @@ -848,14 +860,25 @@ TEST_F(TUSchedulerTests, TUStatus) {

EXPECT_THAT(CaptureTUStatus.allStatus(),
ElementsAre(
// Statuses of "Update" action.
TUState(TUAction::RunningAction, "Update (1)"),
TUState(TUAction::BuildingPreamble, "Update (1)"),
TUState(TUAction::BuildingFile, "Update (1)"),

// Statuses of "Definitions" action
TUState(TUAction::RunningAction, "Definitions"),
TUState(TUAction::Idle, /*No action*/ "")));
// Everything starts with ASTWorker starting to execute an
// update
TUState(PreambleAction::Idle, ASTAction::RunningAction),
// We build the preamble
TUState(PreambleAction::Building, ASTAction::RunningAction),
// We built the preamble, and issued ast built on ASTWorker
// thread. Preambleworker goes idle afterwards.
TUState(PreambleAction::Idle, ASTAction::RunningAction),
// Start task for building the ast, as a result of building
// preamble, on astworker thread.
TUState(PreambleAction::Idle, ASTAction::RunningAction),
// AST build starts.
TUState(PreambleAction::Idle, ASTAction::Building),
// AST built finished successfully
TUState(PreambleAction::Idle, ASTAction::Building),
// Running go to def
TUState(PreambleAction::Idle, ASTAction::RunningAction),
// ASTWorker goes idle.
TUState(PreambleAction::Idle, ASTAction::Idle)));
}

TEST_F(TUSchedulerTests, CommandLineErrors) {
Expand All @@ -868,8 +891,7 @@ TEST_F(TUSchedulerTests, CommandLineErrors) {
TUScheduler S(CDB, optsForTest(), captureDiags());
std::vector<Diag> Diagnostics;
updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
WantDiagnostics::Yes,
[&](std::vector<Diag> D) {
WantDiagnostics::Yes, [&](std::vector<Diag> D) {
Diagnostics = std::move(D);
Ready.notify();
});
Expand All @@ -893,8 +915,7 @@ TEST_F(TUSchedulerTests, CommandLineWarnings) {
TUScheduler S(CDB, optsForTest(), captureDiags());
std::vector<Diag> Diagnostics;
updateWithDiags(S, testPath("foo.cpp"), "void test() {}",
WantDiagnostics::Yes,
[&](std::vector<Diag> D) {
WantDiagnostics::Yes, [&](std::vector<Diag> D) {
Diagnostics = std::move(D);
Ready.notify();
});
Expand Down
7 changes: 3 additions & 4 deletions clang-tools-extra/clangd/unittests/TestTU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ParseInputs TestTU::inputs() const {
Files[ImportThunk] = ThunkContents;

ParseInputs Inputs;
auto& Argv = Inputs.CompileCommand.CommandLine;
auto &Argv = Inputs.CompileCommand.CommandLine;
Argv = {"clang"};
// FIXME: this shouldn't need to be conditional, but it breaks a
// GoToDefinition test for some reason (getMacroArgExpandedLocation fails).
Expand Down Expand Up @@ -71,8 +71,7 @@ ParsedAST TestTU::build() const {
auto CI = buildCompilerInvocation(Inputs, Diags);
assert(CI && "Failed to build compilation invocation.");
auto Preamble =
buildPreamble(testPath(Filename), *CI,
/*OldPreamble=*/nullptr, Inputs,
buildPreamble(testPath(Filename), *CI, Inputs,
/*StoreInMemory=*/true, /*PreambleCallback=*/nullptr);
auto AST = buildAST(testPath(Filename), std::move(CI), Diags.take(), Inputs,
Preamble);
Expand All @@ -89,7 +88,7 @@ ParsedAST TestTU::build() const {
if (llvm::StringRef(Code).contains(Marker) ||
llvm::StringRef(HeaderCode).contains(Marker))
return true;
for (const auto& KV : this->AdditionalFiles)
for (const auto &KV : this->AdditionalFiles)
if (llvm::StringRef(KV.second).contains(Marker))
return true;
return false;
Expand Down