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;
65 Base.FeatureModules = &FeatureModules;
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;
93 FeatureModuleSet FeatureModules;
96 class Logger :
public clang::clangd::Logger {
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) {
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);
143TEST_F(LSPTest, Diagnostics) {
144 auto &Client = start();
145 Client.didOpen(
"foo.cpp",
"void main(int, char**);");
146 EXPECT_THAT(Client.diagnostics(
"foo.cpp"),
147 llvm::ValueIs(testing::ElementsAre(
148 diagMessage(
"'main' must return 'int' (fix available)"))));
150 Client.didChange(
"foo.cpp",
"int x = \"42\";");
151 EXPECT_THAT(Client.diagnostics(
"foo.cpp"),
152 llvm::ValueIs(testing::ElementsAre(
153 diagMessage(
"Cannot initialize a variable of type 'int' with "
154 "an lvalue of type 'const char[3]'"))));
156 Client.didClose(
"foo.cpp");
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(
166 EXPECT_THAT(Client.diagnostics("foo.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")}});
175 EXPECT_THAT(Client.diagnostics(
"foo.cpp"),
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")}});
184 EXPECT_THAT(Client.diagnostics(
"foo.cpp"),
185 llvm::ValueIs(testing::ElementsAre(
186 diagMessage(
"Use of undeclared identifier 'changed'"))));
189TEST_F(LSPTest, RecordsLatencies) {
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)
211 constexpr auto ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
213 ClangTidyOpts.Checks = {
"-*,readability-identifier-naming"};
214 ClangTidyOpts.CheckOptions[
"readability-identifier-naming.FunctionCase"] =
217 Opts.ClangTidyProvider = ClangTidyProvider;
218 auto &Client = start();
219 Client.didOpen(
"foo.hpp", Header.code());
220 Client.didOpen(
"foo.cpp", Source.code());
222 auto Diags = Client.diagnostics(
"foo.cpp");
223 ASSERT_TRUE(Diags && !Diags->empty());
224 auto RenameDiag = Diags->front();
228 .call(
"textDocument/codeAction",
230 {
"textDocument", Client.documentID(
"foo.cpp")},
233 {
"diagnostics", llvm::json::Array{RenameDiag}}}},
234 {
"range", Source.range()}})
238 ASSERT_EQ((*RenameCommand.getAsObject())[
"title"],
"change 'foo' to 'Foo'");
240 Client.expectServerCall(
"workspace/applyEdit");
241 Client.call(
"workspace/executeCommand", RenameCommand);
244 auto Params = Client.takeCallParams(
"workspace/applyEdit");
245 auto Uri = [&](llvm::StringRef
Path) {
246 return Client.uri(
Path).getAsString().value().str();
248 llvm::json::Object ExpectedEdit = llvm::json::Object{
249 {
"edit", llvm::json::Object{
252 {Uri(
"foo.hpp"), llvm::json::Array{llvm::json::Object{
253 {
"range", Header.range()},
257 {Uri(
"foo.cpp"), llvm::json::Array{llvm::json::Object{
258 {
"range", Source.range()},
263 EXPECT_EQ(Params, std::vector{llvm::json::Value(std::move(ExpectedEdit))});
266TEST_F(LSPTest, ClangTidyCrash_Issue109367) {
268 if (!CLANGD_TIDY_CHECKS)
270 constexpr auto ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
272 ClangTidyOpts.Checks = {
"-*,boost-use-ranges"};
274 Opts.ClangTidyProvider = ClangTidyProvider;
277 auto &Client = start();
278 Client.didOpen(
"a.cpp",
"");
279 Client.didOpen(
"b.cpp",
"");
283TEST_F(LSPTest, IncomingCalls) {
290 auto &Client = start();
291 Client.didOpen(
"foo.cpp", Code.code());
293 .call(
"textDocument/prepareCallHierarchy",
295 {
"textDocument", Client.documentID(
"foo.cpp")},
296 {
"position", Code.point()}})
298 auto FirstItem = (*Items.getAsArray())[0];
300 .call(
"callHierarchy/incomingCalls",
301 llvm::json::Object{{
"item", FirstItem}})
303 auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
304 EXPECT_EQ(FirstCall[
"fromRanges"], llvm::json::Value{Code.range()});
305 auto From = *FirstCall[
"from"].getAsObject();
306 EXPECT_EQ(From[
"name"],
"caller1");
309TEST_F(LSPTest, CDBConfigIntegration) {
312 Opts.ConfigProvider = CfgProvider.get();
315 FS.Files[
".clangd"] = R
"yaml(
319 CompilationDatabase: bar
321 FS.Files["bar/compile_flags.txt"] =
"-DFOO=BAR";
323 auto &Client = start();
325 Client.didOpen(
"foo.cpp",
"int x = FOO;");
326 EXPECT_THAT(Client.diagnostics(
"foo.cpp"),
327 llvm::ValueIs(testing::ElementsAre(
328 diagMessage(
"Use of undeclared identifier 'FOO'"))));
330 Client.didOpen(
"bar.cpp",
"int x = FOO;");
331 EXPECT_THAT(Client.diagnostics(
"bar.cpp"),
332 llvm::ValueIs(testing::ElementsAre(
333 diagMessage(
"Use of undeclared identifier 'BAR'"))));
336TEST_F(LSPTest, ModulesTest) {
338 OutgoingNotification<int>
Changed;
339 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
340 llvm::json::Object &ServerCaps)
override {
341 Bind.notification(
"add",
this, &MathModule::add);
342 Bind.method(
"get",
this, &MathModule::get);
343 Changed = Bind.outgoingNotification(
"changed");
348 void add(
const int &
X) {
352 void get(
const std::nullptr_t &, Callback<int> Reply) {
353 scheduler().runQuick(
355 [Reply(std::move(Reply)),
Value(Value)]()
mutable { Reply(Value); });
358 FeatureModules.add(std::make_unique<MathModule>());
360 auto &Client = start();
361 Client.notify(
"add", 2);
362 Client.notify(
"add", 8);
363 EXPECT_EQ(10, Client.call(
"get",
nullptr).takeValue());
364 EXPECT_THAT(Client.takeNotifications(
"changed"),
365 ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
371llvm::unique_function<void(llvm::Expected<T>)>
372capture(std::optional<llvm::Expected<T>> &Out) {
374 return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
377TEST_F(LSPTest, FeatureModulesThreadingTest) {
381 bool ShouldStop =
false;
383 std::deque<Callback<int>> Queue;
384 std::condition_variable CV;
389 std::unique_lock<std::mutex> Lock(Mu);
391 CV.wait(Lock, [&] {
return ShouldStop || !Queue.empty(); });
397 Callback<int> &Task = Queue.front();
407 bool blockUntilIdle(Deadline D)
override {
408 std::unique_lock<std::mutex> Lock(Mu);
409 return clangd::wait(Lock, CV, D, [
this] {
return Queue.empty(); });
412 void stop()
override {
414 std::lock_guard<std::mutex> Lock(Mu);
421 AsyncCounter() : Thread([this] { run(); }) {}
429 std::lock_guard<std::mutex> Lock(Mu);
430 EXPECT_TRUE(ShouldStop) <<
"ClangdServer should request shutdown";
431 EXPECT_EQ(Queue.size(), 0u) <<
"ClangdServer should block until idle";
436 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
437 llvm::json::Object &ServerCaps)
override {
438 Bind.notification(
"increment",
this, &AsyncCounter::increment);
444 std::lock_guard<std::mutex> Lock(Mu);
449 void increment(
const std::nullptr_t &) {
451 std::lock_guard<std::mutex> Lock(Mu);
452 Queue.push_back(
nullptr);
458 FeatureModules.add(std::make_unique<AsyncCounter>());
459 auto &Client = start();
461 Client.notify(
"increment",
nullptr);
462 Client.notify(
"increment",
nullptr);
463 Client.notify(
"increment",
nullptr);
465 EXPECT_EQ(3, FeatureModules.get<AsyncCounter>()->getSync());
467 Client.notify(
"increment",
nullptr);
468 Client.notify(
"increment",
nullptr);
469 Client.notify(
"increment",
nullptr);
473TEST_F(LSPTest, DiagModuleTest) {
474 static constexpr llvm::StringLiteral DiagMsg =
"DiagMsg";
476 struct DiagHooks :
public ASTListener {
477 void sawDiagnostic(
const clang::Diagnostic &, clangd::Diag &D)
override {
478 D.Message = DiagMsg.str();
483 std::unique_ptr<ASTListener> astListeners()
override {
484 return std::make_unique<DiagHooks>();
487 FeatureModules.add(std::make_unique<DiagModule>());
489 auto &Client = start();
490 Client.didOpen(
"foo.cpp",
"test;");
491 EXPECT_THAT(Client.diagnostics(
"foo.cpp"),
492 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg))));
static clang::FrontendPluginRegistry::Add< clang::tidy::ClangTidyPluginAction > X("clang-tidy", "clang-tidy")
Same as llvm::Annotations, but adjusts functions to LSP-specific types for positions and ranges.
static Options optsForTest()
A FeatureModule contributes a vertical feature to clangd.
static std::unique_ptr< Provider > fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath, const ThreadsafeFS &, bool Trusted=false)
A RAII Tracer that can be used by tests.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
@ Changed
The file got changed.
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
void log(const char *Fmt, Ts &&... Vals)
std::string Path
A typedef to represent a file path.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//