1 //===-- ClangdLSPServerTests.cpp ------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "Annotations.h" 10 #include "ClangdLSPServer.h" 11 #include "ClangdServer.h" 12 #include "ConfigProvider.h" 13 #include "Diagnostics.h" 14 #include "Feature.h" 15 #include "FeatureModule.h" 16 #include "LSPBinder.h" 17 #include "LSPClient.h" 18 #include "TestFS.h" 19 #include "support/Function.h" 20 #include "support/Logger.h" 21 #include "support/TestTracer.h" 22 #include "support/Threading.h" 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" 35 #include <cassert> 36 #include <condition_variable> 37 #include <cstddef> 38 #include <deque> 39 #include <memory> 40 #include <mutex> 41 #include <optional> 42 #include <thread> 43 #include <utility> 44 45 namespace clang { 46 namespace clangd { 47 namespace { 48 using testing::ElementsAre; 49 50 MATCHER_P(diagMessage, M, "") { 51 if (const auto *O = arg.getAsObject()) { 52 if (const auto Msg = O->getString("message")) 53 return *Msg == M; 54 } 55 return false; 56 } 57 58 class LSPTest : public ::testing::Test { 59 protected: 60 LSPTest() : LogSession(L) { 61 ClangdServer::Options &Base = Opts; 62 Base = ClangdServer::optsForTest(); 63 // This is needed to we can test index-based operations like call hierarchy. 64 Base.BuildDynamicSymbolIndex = true; 65 Base.FeatureModules = &FeatureModules; 66 } 67 68 LSPClient &start() { 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{}); 73 return Client; 74 } 75 76 void stop() { 77 assert(Server); 78 Client.call("shutdown", nullptr); 79 Client.notify("exit", nullptr); 80 Client.stop(); 81 ServerThread->join(); 82 Server.reset(); 83 ServerThread.reset(); 84 } 85 86 ~LSPTest() { 87 if (Server) 88 stop(); 89 } 90 91 MockFS FS; 92 ClangdLSPServer::Options Opts; 93 FeatureModuleSet FeatureModules; 94 95 private: 96 class Logger : public clang::clangd::Logger { 97 // Color logs so we can distinguish them from test output. 98 void log(Level L, const char *Fmt, 99 const llvm::formatv_object_base &Message) override { 100 raw_ostream::Colors Color; 101 switch (L) { 102 case Level::Verbose: 103 Color = raw_ostream::BLUE; 104 break; 105 case Level::Error: 106 Color = raw_ostream::RED; 107 break; 108 default: 109 Color = raw_ostream::YELLOW; 110 break; 111 } 112 std::lock_guard<std::mutex> Lock(LogMu); 113 (llvm::outs().changeColor(Color) << Message << "\n").resetColor(); 114 } 115 std::mutex LogMu; 116 }; 117 118 Logger L; 119 LoggingSession LogSession; 120 std::optional<ClangdLSPServer> Server; 121 std::optional<std::thread> ServerThread; 122 LSPClient Client; 123 }; 124 125 TEST_F(LSPTest, GoToDefinition) { 126 Annotations Code(R"cpp( 127 int [[fib]](int n) { 128 return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1; 129 } 130 )cpp"); 131 auto &Client = start(); 132 Client.didOpen("foo.cpp", Code.code()); 133 auto &Def = Client.call("textDocument/definition", 134 llvm::json::Object{ 135 {"textDocument", Client.documentID("foo.cpp")}, 136 {"position", Code.point()}, 137 }); 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); 141 } 142 143 TEST_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)")))); 149 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]'")))); 155 156 Client.didClose("foo.cpp"); 157 EXPECT_THAT(Client.diagnostics("foo.cpp"), llvm::ValueIs(testing::IsEmpty())); 158 } 159 160 TEST_F(LSPTest, DiagnosticsHeaderSaved) { 161 auto &Client = start(); 162 Client.didOpen("foo.cpp", R"cpp( 163 #include "foo.h" 164 int x = VAR; 165 )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'")))); 170 // Now create the header. 171 FS.Files["foo.h"] = "#define VAR original"; 172 Client.notify( 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'")))); 178 // Now modify the header from within the "editor". 179 FS.Files["foo.h"] = "#define VAR changed"; 180 Client.notify( 181 "textDocument/didSave", 182 llvm::json::Object{{"textDocument", Client.documentID("foo.h")}}); 183 // Foo.cpp should be rebuilt with new diagnostics. 184 EXPECT_THAT(Client.diagnostics("foo.cpp"), 185 llvm::ValueIs(testing::ElementsAre( 186 diagMessage("Use of undeclared identifier 'changed'")))); 187 } 188 189 TEST_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()); 195 stop(); 196 EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(1)); 197 } 198 199 // clang-tidy's renames are converted to clangd's internal rename functionality, 200 // see clangd#1589 and clangd#741 201 TEST_F(LSPTest, ClangTidyRename) { 202 // This test requires clang-tidy checks to be linked in. 203 if (!CLANGD_TIDY_CHECKS) 204 return; 205 Annotations Header(R"cpp( 206 void [[foo]](); 207 )cpp"); 208 Annotations Source(R"cpp( 209 void [[foo]]() {} 210 )cpp"); 211 constexpr auto ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts, 212 llvm::StringRef) { 213 ClangTidyOpts.Checks = {"-*,readability-identifier-naming"}; 214 ClangTidyOpts.CheckOptions["readability-identifier-naming.FunctionCase"] = 215 "CamelCase"; 216 }; 217 Opts.ClangTidyProvider = ClangTidyProvider; 218 auto &Client = start(); 219 Client.didOpen("foo.hpp", Header.code()); 220 Client.didOpen("foo.cpp", Source.code()); 221 222 auto Diags = Client.diagnostics("foo.cpp"); 223 ASSERT_TRUE(Diags && !Diags->empty()); 224 auto RenameDiag = Diags->front(); 225 226 auto RenameCommand = 227 (*Client 228 .call("textDocument/codeAction", 229 llvm::json::Object{ 230 {"textDocument", Client.documentID("foo.cpp")}, 231 {"context", 232 llvm::json::Object{ 233 {"diagnostics", llvm::json::Array{RenameDiag}}}}, 234 {"range", Source.range()}}) 235 .takeValue() 236 .getAsArray())[0]; 237 238 ASSERT_EQ((*RenameCommand.getAsObject())["title"], "change 'foo' to 'Foo'"); 239 240 Client.expectServerCall("workspace/applyEdit"); 241 Client.call("workspace/executeCommand", RenameCommand); 242 Client.sync(); 243 244 auto Params = Client.takeCallParams("workspace/applyEdit"); 245 auto Uri = [&](llvm::StringRef Path) { 246 return Client.uri(Path).getAsString().value().str(); 247 }; 248 llvm::json::Object ExpectedEdit = llvm::json::Object{ 249 {"edit", llvm::json::Object{ 250 {"changes", 251 llvm::json::Object{ 252 {Uri("foo.hpp"), llvm::json::Array{llvm::json::Object{ 253 {"range", Header.range()}, 254 {"newText", "Foo"}, 255 }}}, 256 257 {Uri("foo.cpp"), llvm::json::Array{llvm::json::Object{ 258 {"range", Source.range()}, 259 {"newText", "Foo"}, 260 }}} 261 262 }}}}}; 263 EXPECT_EQ(Params, std::vector{llvm::json::Value(std::move(ExpectedEdit))}); 264 } 265 266 TEST_F(LSPTest, ClangTidyCrash_Issue109367) { 267 // This test requires clang-tidy checks to be linked in. 268 if (!CLANGD_TIDY_CHECKS) 269 return; 270 constexpr auto ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts, 271 llvm::StringRef) { 272 ClangTidyOpts.Checks = {"-*,boost-use-ranges"}; 273 }; 274 Opts.ClangTidyProvider = ClangTidyProvider; 275 // Check that registering the boost-use-ranges checker's matchers 276 // on two different threads does not cause a crash. 277 auto &Client = start(); 278 Client.didOpen("a.cpp", ""); 279 Client.didOpen("b.cpp", ""); 280 Client.sync(); 281 } 282 283 TEST_F(LSPTest, IncomingCalls) { 284 Annotations Code(R"cpp( 285 void calle^e(int); 286 void caller1() { 287 [[callee]](42); 288 } 289 )cpp"); 290 auto &Client = start(); 291 Client.didOpen("foo.cpp", Code.code()); 292 auto Items = Client 293 .call("textDocument/prepareCallHierarchy", 294 llvm::json::Object{ 295 {"textDocument", Client.documentID("foo.cpp")}, 296 {"position", Code.point()}}) 297 .takeValue(); 298 auto FirstItem = (*Items.getAsArray())[0]; 299 auto Calls = Client 300 .call("callHierarchy/incomingCalls", 301 llvm::json::Object{{"item", FirstItem}}) 302 .takeValue(); 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"); 307 } 308 309 TEST_F(LSPTest, CDBConfigIntegration) { 310 auto CfgProvider = 311 config::Provider::fromAncestorRelativeYAMLFiles(".clangd", FS); 312 Opts.ConfigProvider = CfgProvider.get(); 313 314 // Map bar.cpp to a different compilation database which defines FOO->BAR. 315 FS.Files[".clangd"] = R"yaml( 316 If: 317 PathMatch: bar.cpp 318 CompileFlags: 319 CompilationDatabase: bar 320 )yaml"; 321 FS.Files["bar/compile_flags.txt"] = "-DFOO=BAR"; 322 323 auto &Client = start(); 324 // foo.cpp gets parsed as normal. 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'")))); 329 // bar.cpp shows the configured compile command. 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'")))); 334 } 335 336 TEST_F(LSPTest, ModulesTest) { 337 class MathModule final : public FeatureModule { 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"); 344 } 345 346 int Value = 0; 347 348 void add(const int &X) { 349 Value += X; 350 Changed(Value); 351 } 352 void get(const std::nullptr_t &, Callback<int> Reply) { 353 scheduler().runQuick( 354 "get", "", 355 [Reply(std::move(Reply)), Value(Value)]() mutable { Reply(Value); }); 356 } 357 }; 358 FeatureModules.add(std::make_unique<MathModule>()); 359 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))); 366 } 367 368 // Creates a Callback that writes its received value into an 369 // std::optional<Expected>. 370 template <typename T> 371 llvm::unique_function<void(llvm::Expected<T>)> 372 capture(std::optional<llvm::Expected<T>> &Out) { 373 Out.reset(); 374 return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); }; 375 } 376 377 TEST_F(LSPTest, FeatureModulesThreadingTest) { 378 // A feature module that does its work on a background thread, and so 379 // exercises the block/shutdown protocol. 380 class AsyncCounter final : public FeatureModule { 381 bool ShouldStop = false; 382 int State = 0; 383 std::deque<Callback<int>> Queue; // null = increment, non-null = read. 384 std::condition_variable CV; 385 std::mutex Mu; 386 std::thread Thread; 387 388 void run() { 389 std::unique_lock<std::mutex> Lock(Mu); 390 while (true) { 391 CV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); }); 392 if (ShouldStop) { 393 Queue.clear(); 394 CV.notify_all(); 395 return; 396 } 397 Callback<int> &Task = Queue.front(); 398 if (Task) 399 Task(State); 400 else 401 ++State; 402 Queue.pop_front(); 403 CV.notify_all(); 404 } 405 } 406 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(); }); 410 } 411 412 void stop() override { 413 { 414 std::lock_guard<std::mutex> Lock(Mu); 415 ShouldStop = true; 416 } 417 CV.notify_all(); 418 } 419 420 public: 421 AsyncCounter() : Thread([this] { run(); }) {} 422 ~AsyncCounter() { 423 // Verify shutdown sequence was performed. 424 // Real modules would not do this, to be robust to no ClangdServer. 425 { 426 // We still need the lock here, as Queue might be empty when 427 // ClangdServer calls blockUntilIdle, but run() might not have returned 428 // yet. 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"; 432 } 433 Thread.join(); 434 } 435 436 void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps, 437 llvm::json::Object &ServerCaps) override { 438 Bind.notification("increment", this, &AsyncCounter::increment); 439 } 440 441 // Get the current value, bypassing the queue. 442 // Used to verify that sync->blockUntilIdle avoids races in tests. 443 int getSync() { 444 std::lock_guard<std::mutex> Lock(Mu); 445 return State; 446 } 447 448 // Increment the current value asynchronously. 449 void increment(const std::nullptr_t &) { 450 { 451 std::lock_guard<std::mutex> Lock(Mu); 452 Queue.push_back(nullptr); 453 } 454 CV.notify_all(); 455 } 456 }; 457 458 FeatureModules.add(std::make_unique<AsyncCounter>()); 459 auto &Client = start(); 460 461 Client.notify("increment", nullptr); 462 Client.notify("increment", nullptr); 463 Client.notify("increment", nullptr); 464 Client.sync(); 465 EXPECT_EQ(3, FeatureModules.get<AsyncCounter>()->getSync()); 466 // Throw some work on the queue to make sure shutdown blocks on it. 467 Client.notify("increment", nullptr); 468 Client.notify("increment", nullptr); 469 Client.notify("increment", nullptr); 470 // And immediately shut down. FeatureModule destructor verifies we blocked. 471 } 472 473 TEST_F(LSPTest, DiagModuleTest) { 474 static constexpr llvm::StringLiteral DiagMsg = "DiagMsg"; 475 class DiagModule final : public FeatureModule { 476 struct DiagHooks : public ASTListener { 477 void sawDiagnostic(const clang::Diagnostic &, clangd::Diag &D) override { 478 D.Message = DiagMsg.str(); 479 } 480 }; 481 482 public: 483 std::unique_ptr<ASTListener> astListeners() override { 484 return std::make_unique<DiagHooks>(); 485 } 486 }; 487 FeatureModules.add(std::make_unique<DiagModule>()); 488 489 auto &Client = start(); 490 Client.didOpen("foo.cpp", "test;"); 491 EXPECT_THAT(Client.diagnostics("foo.cpp"), 492 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg)))); 493 } 494 } // namespace 495 } // namespace clangd 496 } // namespace clang 497