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();
132 Client.
didOpen(
"foo.cpp", Code.code());
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, ClangTidyCrash_Issue109367) {
267 if (!CLANGD_TIDY_CHECKS)
269 Opts.ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
271 ClangTidyOpts.Checks = {
"-*,boost-use-ranges"};
275 auto &Client = start();
281TEST_F(LSPTest, IncomingCalls) {
282 Annotations Code(R
"cpp(
288 auto &Client = start();
289 Client.
didOpen(
"foo.cpp", Code.code());
291 .
call(
"textDocument/prepareCallHierarchy",
293 {
"textDocument", Client.
documentID(
"foo.cpp")},
294 {
"position", Code.point()}})
296 auto FirstItem = (*Items.getAsArray())[0];
298 .
call(
"callHierarchy/incomingCalls",
299 llvm::json::Object{{
"item", FirstItem}})
301 auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
302 EXPECT_EQ(FirstCall[
"fromRanges"], llvm::json::Value{Code.range()});
303 auto From = *FirstCall[
"from"].getAsObject();
304 EXPECT_EQ(From[
"name"],
"caller1");
307TEST_F(LSPTest, CDBConfigIntegration) {
310 Opts.ConfigProvider = CfgProvider.get();
313 FS.Files[
".clangd"] = R
"yaml(
317 CompilationDatabase: bar
319 FS.Files["bar/compile_flags.txt"] =
"-DFOO=BAR";
321 auto &Client = start();
323 Client.
didOpen(
"foo.cpp",
"int x = FOO;");
325 llvm::ValueIs(testing::ElementsAre(
326 diagMessage(
"Use of undeclared identifier 'FOO'"))));
328 Client.
didOpen(
"bar.cpp",
"int x = FOO;");
330 llvm::ValueIs(testing::ElementsAre(
331 diagMessage(
"Use of undeclared identifier 'BAR'"))));
334TEST_F(LSPTest, ModulesTest) {
335 class MathModule final :
public FeatureModule {
336 OutgoingNotification<int>
Changed;
337 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
338 llvm::json::Object &ServerCaps)
override {
339 Bind.notification(
"add",
this, &MathModule::add);
340 Bind.method(
"get",
this, &MathModule::get);
341 Changed = Bind.outgoingNotification(
"changed");
346 void add(
const int &
X) {
350 void get(
const std::nullptr_t &, Callback<int> Reply) {
351 scheduler().runQuick(
353 [Reply(std::move(Reply)), Value(Value)]()
mutable { Reply(Value); });
358 auto &Client = start();
363 ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
369llvm::unique_function<void(llvm::Expected<T>)>
370capture(std::optional<llvm::Expected<T>> &
Out) {
372 return [&
Out](llvm::Expected<T> V) {
Out.emplace(std::move(V)); };
375TEST_F(LSPTest, FeatureModulesThreadingTest) {
378 class AsyncCounter final :
public FeatureModule {
379 bool ShouldStop =
false;
381 std::deque<Callback<int>> Queue;
382 std::condition_variable CV;
387 std::unique_lock<std::mutex> Lock(Mu);
389 CV.wait(Lock, [&] {
return ShouldStop || !Queue.empty(); });
395 Callback<int> &Task = Queue.front();
405 bool blockUntilIdle(Deadline D)
override {
406 std::unique_lock<std::mutex> Lock(Mu);
407 return clangd::wait(Lock, CV, D, [
this] {
return Queue.empty(); });
410 void stop()
override {
412 std::lock_guard<std::mutex> Lock(Mu);
419 AsyncCounter() : Thread([this] {
run(); }) {}
427 std::lock_guard<std::mutex> Lock(Mu);
428 EXPECT_TRUE(ShouldStop) <<
"ClangdServer should request shutdown";
429 EXPECT_EQ(Queue.size(), 0u) <<
"ClangdServer should block until idle";
434 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
435 llvm::json::Object &ServerCaps)
override {
436 Bind.notification(
"increment",
this, &AsyncCounter::increment);
442 std::lock_guard<std::mutex> Lock(Mu);
447 void increment(
const std::nullptr_t &) {
449 std::lock_guard<std::mutex> Lock(Mu);
450 Queue.push_back(
nullptr);
457 auto &Client = start();
459 Client.
notify(
"increment",
nullptr);
460 Client.
notify(
"increment",
nullptr);
461 Client.
notify(
"increment",
nullptr);
465 Client.
notify(
"increment",
nullptr);
466 Client.
notify(
"increment",
nullptr);
467 Client.
notify(
"increment",
nullptr);
471TEST_F(LSPTest, DiagModuleTest) {
472 static constexpr llvm::StringLiteral DiagMsg =
"DiagMsg";
473 class DiagModule final :
public FeatureModule {
474 struct DiagHooks :
public ASTListener {
475 void sawDiagnostic(
const clang::Diagnostic &, clangd::Diag &D)
override {
476 D.Message = DiagMsg.str();
481 std::unique_ptr<ASTListener> astListeners()
override {
482 return std::make_unique<DiagHooks>();
487 auto &Client = start();
488 Client.
didOpen(
"foo.cpp",
"test;");
490 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++ -*-===//