[clang-tidy] Avoid capturing a local variable in a static lambda in UseRangesCheck...
[llvm-project.git] / clang-tools-extra / clangd / unittests / ClangdLSPServerTests.cpp
blob49a94045ea48785f785687ef7c3a9298bb65eb27
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 //===----------------------------------------------------------------------===//
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>
45 namespace clang {
46 namespace clangd {
47 namespace {
48 using testing::ElementsAre;
50 MATCHER_P(diagMessage, M, "") {
51 if (const auto *O = arg.getAsObject()) {
52 if (const auto Msg = O->getString("message"))
53 return *Msg == M;
55 return false;
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;
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;
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();
86 ~LSPTest() {
87 if (Server)
88 stop();
91 MockFS FS;
92 ClangdLSPServer::Options Opts;
93 FeatureModuleSet FeatureModules;
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;
112 std::lock_guard<std::mutex> Lock(LogMu);
113 (llvm::outs().changeColor(Color) << Message << "\n").resetColor();
115 std::mutex LogMu;
118 Logger L;
119 LoggingSession LogSession;
120 std::optional<ClangdLSPServer> Server;
121 std::optional<std::thread> ServerThread;
122 LSPClient Client;
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;
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()},
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);
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)"))));
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()));
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'"))));
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));
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 Opts.ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
212 llvm::StringRef) {
213 ClangTidyOpts.Checks = {"-*,readability-identifier-naming"};
214 ClangTidyOpts.CheckOptions["readability-identifier-naming.FunctionCase"] =
215 "CamelCase";
217 auto &Client = start();
218 Client.didOpen("foo.hpp", Header.code());
219 Client.didOpen("foo.cpp", Source.code());
221 auto Diags = Client.diagnostics("foo.cpp");
222 ASSERT_TRUE(Diags && !Diags->empty());
223 auto RenameDiag = Diags->front();
225 auto RenameCommand =
226 (*Client
227 .call("textDocument/codeAction",
228 llvm::json::Object{
229 {"textDocument", Client.documentID("foo.cpp")},
230 {"context",
231 llvm::json::Object{
232 {"diagnostics", llvm::json::Array{RenameDiag}}}},
233 {"range", Source.range()}})
234 .takeValue()
235 .getAsArray())[0];
237 ASSERT_EQ((*RenameCommand.getAsObject())["title"], "change 'foo' to 'Foo'");
239 Client.expectServerCall("workspace/applyEdit");
240 Client.call("workspace/executeCommand", RenameCommand);
241 Client.sync();
243 auto Params = Client.takeCallParams("workspace/applyEdit");
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{
249 {"changes",
250 llvm::json::Object{
251 {Uri("foo.hpp"), llvm::json::Array{llvm::json::Object{
252 {"range", Header.range()},
253 {"newText", "Foo"},
254 }}},
256 {Uri("foo.cpp"), llvm::json::Array{llvm::json::Object{
257 {"range", Source.range()},
258 {"newText", "Foo"},
261 }}}}};
262 EXPECT_EQ(Params, std::vector{llvm::json::Value(std::move(ExpectedEdit))});
265 TEST_F(LSPTest, ClangTidyCrash_Issue109367) {
266 // This test requires clang-tidy checks to be linked in.
267 if (!CLANGD_TIDY_CHECKS)
268 return;
269 Opts.ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
270 llvm::StringRef) {
271 ClangTidyOpts.Checks = {"-*,boost-use-ranges"};
273 // Check that registering the boost-use-ranges checker's matchers
274 // on two different threads does not cause a crash.
275 auto &Client = start();
276 Client.didOpen("a.cpp", "");
277 Client.didOpen("b.cpp", "");
278 Client.sync();
281 TEST_F(LSPTest, IncomingCalls) {
282 Annotations Code(R"cpp(
283 void calle^e(int);
284 void caller1() {
285 [[callee]](42);
287 )cpp");
288 auto &Client = start();
289 Client.didOpen("foo.cpp", Code.code());
290 auto Items = Client
291 .call("textDocument/prepareCallHierarchy",
292 llvm::json::Object{
293 {"textDocument", Client.documentID("foo.cpp")},
294 {"position", Code.point()}})
295 .takeValue();
296 auto FirstItem = (*Items.getAsArray())[0];
297 auto Calls = Client
298 .call("callHierarchy/incomingCalls",
299 llvm::json::Object{{"item", FirstItem}})
300 .takeValue();
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");
307 TEST_F(LSPTest, CDBConfigIntegration) {
308 auto CfgProvider =
309 config::Provider::fromAncestorRelativeYAMLFiles(".clangd", FS);
310 Opts.ConfigProvider = CfgProvider.get();
312 // Map bar.cpp to a different compilation database which defines FOO->BAR.
313 FS.Files[".clangd"] = R"yaml(
315 PathMatch: bar.cpp
316 CompileFlags:
317 CompilationDatabase: bar
318 )yaml";
319 FS.Files["bar/compile_flags.txt"] = "-DFOO=BAR";
321 auto &Client = start();
322 // foo.cpp gets parsed as normal.
323 Client.didOpen("foo.cpp", "int x = FOO;");
324 EXPECT_THAT(Client.diagnostics("foo.cpp"),
325 llvm::ValueIs(testing::ElementsAre(
326 diagMessage("Use of undeclared identifier 'FOO'"))));
327 // bar.cpp shows the configured compile command.
328 Client.didOpen("bar.cpp", "int x = FOO;");
329 EXPECT_THAT(Client.diagnostics("bar.cpp"),
330 llvm::ValueIs(testing::ElementsAre(
331 diagMessage("Use of undeclared identifier 'BAR'"))));
334 TEST_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");
344 int Value = 0;
346 void add(const int &X) {
347 Value += X;
348 Changed(Value);
350 void get(const std::nullptr_t &, Callback<int> Reply) {
351 scheduler().runQuick(
352 "get", "",
353 [Reply(std::move(Reply)), Value(Value)]() mutable { Reply(Value); });
356 FeatureModules.add(std::make_unique<MathModule>());
358 auto &Client = start();
359 Client.notify("add", 2);
360 Client.notify("add", 8);
361 EXPECT_EQ(10, Client.call("get", nullptr).takeValue());
362 EXPECT_THAT(Client.takeNotifications("changed"),
363 ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
366 // Creates a Callback that writes its received value into an
367 // std::optional<Expected>.
368 template <typename T>
369 llvm::unique_function<void(llvm::Expected<T>)>
370 capture(std::optional<llvm::Expected<T>> &Out) {
371 Out.reset();
372 return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
375 TEST_F(LSPTest, FeatureModulesThreadingTest) {
376 // A feature module that does its work on a background thread, and so
377 // exercises the block/shutdown protocol.
378 class AsyncCounter final : public FeatureModule {
379 bool ShouldStop = false;
380 int State = 0;
381 std::deque<Callback<int>> Queue; // null = increment, non-null = read.
382 std::condition_variable CV;
383 std::mutex Mu;
384 std::thread Thread;
386 void run() {
387 std::unique_lock<std::mutex> Lock(Mu);
388 while (true) {
389 CV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
390 if (ShouldStop) {
391 Queue.clear();
392 CV.notify_all();
393 return;
395 Callback<int> &Task = Queue.front();
396 if (Task)
397 Task(State);
398 else
399 ++State;
400 Queue.pop_front();
401 CV.notify_all();
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);
413 ShouldStop = true;
415 CV.notify_all();
418 public:
419 AsyncCounter() : Thread([this] { run(); }) {}
420 ~AsyncCounter() {
421 // Verify shutdown sequence was performed.
422 // Real modules would not do this, to be robust to no ClangdServer.
424 // We still need the lock here, as Queue might be empty when
425 // ClangdServer calls blockUntilIdle, but run() might not have returned
426 // yet.
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";
431 Thread.join();
434 void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps,
435 llvm::json::Object &ServerCaps) override {
436 Bind.notification("increment", this, &AsyncCounter::increment);
439 // Get the current value, bypassing the queue.
440 // Used to verify that sync->blockUntilIdle avoids races in tests.
441 int getSync() {
442 std::lock_guard<std::mutex> Lock(Mu);
443 return State;
446 // Increment the current value asynchronously.
447 void increment(const std::nullptr_t &) {
449 std::lock_guard<std::mutex> Lock(Mu);
450 Queue.push_back(nullptr);
452 CV.notify_all();
456 FeatureModules.add(std::make_unique<AsyncCounter>());
457 auto &Client = start();
459 Client.notify("increment", nullptr);
460 Client.notify("increment", nullptr);
461 Client.notify("increment", nullptr);
462 Client.sync();
463 EXPECT_EQ(3, FeatureModules.get<AsyncCounter>()->getSync());
464 // Throw some work on the queue to make sure shutdown blocks on it.
465 Client.notify("increment", nullptr);
466 Client.notify("increment", nullptr);
467 Client.notify("increment", nullptr);
468 // And immediately shut down. FeatureModule destructor verifies we blocked.
471 TEST_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();
480 public:
481 std::unique_ptr<ASTListener> astListeners() override {
482 return std::make_unique<DiagHooks>();
485 FeatureModules.add(std::make_unique<DiagModule>());
487 auto &Client = start();
488 Client.didOpen("foo.cpp", "test;");
489 EXPECT_THAT(Client.diagnostics("foo.cpp"),
490 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg))));
492 } // namespace
493 } // namespace clangd
494 } // namespace clang