23#include "clang/Basic/Diagnostic.h"
24#include "clang/Basic/LLVM.h"
25#include "llvm/ADT/FunctionExtras.h"
26#include "llvm/ADT/StringRef.h"
27#include "llvm/Support/Error.h"
28#include "llvm/Support/FormatVariadic.h"
29#include "llvm/Support/JSON.h"
30#include "llvm/Support/raw_ostream.h"
31#include "llvm/Testing/Support/Error.h"
32#include "llvm/Testing/Support/SupportHelpers.h"
33#include "gmock/gmock.h"
34#include "gtest/gtest.h"
36#include <condition_variable>
48using testing::ElementsAre;
51 if (
const auto *O = arg.getAsObject()) {
52 if (
const auto Msg = O->getString(
"message"))
58class LSPTest :
public ::testing::Test {
60 LSPTest() : LogSession(L) {
61 ClangdServer::Options &Base = Opts;
64 Base.BuildDynamicSymbolIndex =
true;
69 EXPECT_FALSE(Server) <<
"Already initialized";
70 Server.emplace(Client.transport(), FS, Opts);
71 ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
72 Client.call(
"initialize", llvm::json::Object{});
78 Client.call(
"shutdown",
nullptr);
79 Client.notify(
"exit",
nullptr);
92 ClangdLSPServer::Options Opts;
98 void log(Level L,
const char *Fmt,
99 const llvm::formatv_object_base &Message)
override {
100 raw_ostream::Colors
Color;
103 Color = raw_ostream::BLUE;
106 Color = raw_ostream::RED;
109 Color = raw_ostream::YELLOW;
112 std::lock_guard<std::mutex> Lock(LogMu);
113 (llvm::outs().changeColor(
Color) << Message <<
"\n").resetColor();
119 LoggingSession LogSession;
120 std::optional<ClangdLSPServer> Server;
121 std::optional<std::thread> ServerThread;
125TEST_F(LSPTest, GoToDefinition) {
126 Annotations
Code(R
"cpp(
128 return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1;
131 auto &Client = start();
133 auto &Def = Client.
call(
"textDocument/definition",
135 {
"textDocument", Client.
documentID(
"foo.cpp")},
136 {
"position",
Code.point()},
138 llvm::json::Value Want = llvm::json::Array{llvm::json::Object{
139 {
"uri", Client.
uri(
"foo.cpp")}, {
"range",
Code.range()}}};
140 EXPECT_EQ(Def.takeValue(), Want);
144 auto &Client = start();
145 Client.
didOpen(
"foo.cpp",
"void main(int, char**);");
147 llvm::ValueIs(testing::ElementsAre(
148 diagMessage(
"'main' must return 'int' (fix available)"))));
150 Client.
didChange(
"foo.cpp",
"int x = \"42\";");
152 llvm::ValueIs(testing::ElementsAre(
153 diagMessage(
"Cannot initialize a variable of type 'int' with "
154 "an lvalue of type 'const char[3]'"))));
157 EXPECT_THAT(Client.
diagnostics(
"foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
160TEST_F(LSPTest, DiagnosticsHeaderSaved) {
161 auto &Client = start();
162 Client.
didOpen(
"foo.cpp", R
"cpp(
167 llvm::ValueIs(testing::ElementsAre(
168 diagMessage(
"'foo.h' file not found"),
169 diagMessage(
"Use of undeclared identifier 'VAR'"))));
171 FS.Files[
"foo.h"] =
"#define VAR original";
173 "textDocument/didSave",
174 llvm::json::Object{{
"textDocument", Client.
documentID(
"foo.h")}});
176 llvm::ValueIs(testing::ElementsAre(
177 diagMessage(
"Use of undeclared identifier 'original'"))));
179 FS.Files[
"foo.h"] =
"#define VAR changed";
181 "textDocument/didSave",
182 llvm::json::Object{{
"textDocument", Client.
documentID(
"foo.h")}});
185 llvm::ValueIs(testing::ElementsAre(
186 diagMessage(
"Use of undeclared identifier 'changed'"))));
189TEST_F(LSPTest, RecordsLatencies) {
190 trace::TestTracer Tracer;
191 auto &Client = start();
192 llvm::StringLiteral MethodName =
"method_name";
193 EXPECT_THAT(Tracer.takeMetric(
"lsp_latency", MethodName), testing::SizeIs(0));
194 llvm::consumeError(Client.
call(MethodName, {}).
take().takeError());
196 EXPECT_THAT(Tracer.takeMetric(
"lsp_latency", MethodName), testing::SizeIs(1));
201TEST_F(LSPTest, ClangTidyRename) {
203 if (!CLANGD_TIDY_CHECKS)
205 Annotations Header(R
"cpp(
208 Annotations Source(R"cpp(
211 Opts.ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
213 ClangTidyOpts.Checks = {"-*,readability-identifier-naming"};
214 ClangTidyOpts.CheckOptions[
"readability-identifier-naming.FunctionCase"] =
217 auto &Client = start();
218 Client.
didOpen(
"foo.hpp", Header.code());
219 Client.
didOpen(
"foo.cpp", Source.code());
222 ASSERT_TRUE(Diags && !Diags->empty());
223 auto RenameDiag = Diags->front();
227 .
call(
"textDocument/codeAction",
229 {
"textDocument", Client.
documentID(
"foo.cpp")},
232 {
"diagnostics", llvm::json::Array{RenameDiag}}}},
233 {
"range", Source.range()}})
237 ASSERT_EQ((*RenameCommand.getAsObject())[
"title"],
"change 'foo' to 'Foo'");
240 Client.
call(
"workspace/executeCommand", RenameCommand);
244 auto Uri = [&](llvm::StringRef
Path) {
245 return Client.
uri(
Path).getAsString().value().str();
247 llvm::json::Object ExpectedEdit = llvm::json::Object{
248 {
"edit", llvm::json::Object{
251 {Uri(
"foo.hpp"), llvm::json::Array{llvm::json::Object{
252 {
"range", Header.range()},
256 {Uri(
"foo.cpp"), llvm::json::Array{llvm::json::Object{
257 {
"range", Source.range()},
262 EXPECT_EQ(Params, std::vector{llvm::json::Value(std::move(ExpectedEdit))});
265TEST_F(LSPTest, IncomingCalls) {
266 Annotations
Code(R
"cpp(
272 auto &Client = start();
275 .
call(
"textDocument/prepareCallHierarchy",
277 {
"textDocument", Client.
documentID(
"foo.cpp")},
278 {
"position",
Code.point()}})
280 auto FirstItem = (*Items.getAsArray())[0];
282 .
call(
"callHierarchy/incomingCalls",
283 llvm::json::Object{{
"item", FirstItem}})
285 auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
286 EXPECT_EQ(FirstCall[
"fromRanges"], llvm::json::Value{
Code.range()});
287 auto From = *FirstCall[
"from"].getAsObject();
288 EXPECT_EQ(From[
"name"],
"caller1");
291TEST_F(LSPTest, CDBConfigIntegration) {
294 Opts.ConfigProvider = CfgProvider.get();
297 FS.Files[
".clangd"] = R
"yaml(
301 CompilationDatabase: bar
303 FS.Files["bar/compile_flags.txt"] =
"-DFOO=BAR";
305 auto &Client = start();
307 Client.
didOpen(
"foo.cpp",
"int x = FOO;");
309 llvm::ValueIs(testing::ElementsAre(
310 diagMessage(
"Use of undeclared identifier 'FOO'"))));
312 Client.
didOpen(
"bar.cpp",
"int x = FOO;");
314 llvm::ValueIs(testing::ElementsAre(
315 diagMessage(
"Use of undeclared identifier 'BAR'"))));
318TEST_F(LSPTest, ModulesTest) {
319 class MathModule final :
public FeatureModule {
320 OutgoingNotification<int>
Changed;
321 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
322 llvm::json::Object &ServerCaps)
override {
323 Bind.notification(
"add",
this, &MathModule::add);
324 Bind.method(
"get",
this, &MathModule::get);
325 Changed = Bind.outgoingNotification(
"changed");
330 void add(
const int &
X) {
334 void get(
const std::nullptr_t &, Callback<int> Reply) {
335 scheduler().runQuick(
337 [Reply(std::move(Reply)), Value(Value)]()
mutable { Reply(Value); });
342 auto &Client = start();
347 ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
353llvm::unique_function<void(llvm::Expected<T>)>
354capture(std::optional<llvm::Expected<T>> &
Out) {
356 return [&
Out](llvm::Expected<T> V) {
Out.emplace(std::move(V)); };
359TEST_F(LSPTest, FeatureModulesThreadingTest) {
362 class AsyncCounter final :
public FeatureModule {
363 bool ShouldStop =
false;
365 std::deque<Callback<int>> Queue;
366 std::condition_variable CV;
371 std::unique_lock<std::mutex> Lock(Mu);
373 CV.wait(Lock, [&] {
return ShouldStop || !Queue.empty(); });
379 Callback<int> &Task = Queue.front();
389 bool blockUntilIdle(Deadline D)
override {
390 std::unique_lock<std::mutex> Lock(Mu);
391 return clangd::wait(Lock, CV, D, [
this] {
return Queue.empty(); });
394 void stop()
override {
396 std::lock_guard<std::mutex> Lock(Mu);
403 AsyncCounter() : Thread([this] {
run(); }) {}
411 std::lock_guard<std::mutex> Lock(Mu);
412 EXPECT_TRUE(ShouldStop) <<
"ClangdServer should request shutdown";
413 EXPECT_EQ(Queue.size(), 0u) <<
"ClangdServer should block until idle";
418 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
419 llvm::json::Object &ServerCaps)
override {
420 Bind.notification(
"increment",
this, &AsyncCounter::increment);
426 std::lock_guard<std::mutex> Lock(Mu);
431 void increment(
const std::nullptr_t &) {
433 std::lock_guard<std::mutex> Lock(Mu);
434 Queue.push_back(
nullptr);
441 auto &Client = start();
443 Client.
notify(
"increment",
nullptr);
444 Client.
notify(
"increment",
nullptr);
445 Client.
notify(
"increment",
nullptr);
449 Client.
notify(
"increment",
nullptr);
450 Client.
notify(
"increment",
nullptr);
451 Client.
notify(
"increment",
nullptr);
455TEST_F(LSPTest, DiagModuleTest) {
456 static constexpr llvm::StringLiteral DiagMsg =
"DiagMsg";
457 class DiagModule final :
public FeatureModule {
458 struct DiagHooks :
public ASTListener {
459 void sawDiagnostic(
const clang::Diagnostic &, clangd::Diag &D)
override {
460 D.Message = DiagMsg.str();
465 std::unique_ptr<ASTListener> astListeners()
override {
466 return std::make_unique<DiagHooks>();
471 auto &Client = start();
472 Client.
didOpen(
"foo.cpp",
"test;");
474 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg))));
FeatureModuleSet FeatureModules
CompiledFragmentImpl & Out
const google::protobuf::Message & M
WantDiagnostics Diagnostics
static Options optsForTest()
bool add(std::unique_ptr< Mod > M)
llvm::Expected< llvm::json::Value > take()
llvm::json::Value takeValue()
CallResult & call(llvm::StringRef Method, llvm::json::Value Params)
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
static llvm::json::Value uri(llvm::StringRef Path)
std::vector< llvm::json::Value > takeCallParams(llvm::StringRef Method)
static llvm::json::Value documentID(llvm::StringRef Path)
void didClose(llvm::StringRef Path)
void notify(llvm::StringRef Method, llvm::json::Value Params)
void expectServerCall(llvm::StringRef Method)
void didOpen(llvm::StringRef Path, llvm::StringRef Content)
void didChange(llvm::StringRef Path, llvm::StringRef Content)
std::optional< std::vector< llvm::json::Value > > diagnostics(llvm::StringRef Path)
Interface to allow custom logging in clangd.
static std::unique_ptr< Provider > fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath, const ThreadsafeFS &, bool Trusted=false)
@ Changed
The file got changed.
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
std::string Path
A typedef to represent a file path.
void log(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//