clang-tools  14.0.0git
ClangdLSPServerTests.cpp
Go to the documentation of this file.
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 "LSPClient.h"
12 #include "Protocol.h"
13 #include "TestFS.h"
14 #include "support/Logger.h"
15 #include "support/TestTracer.h"
16 #include "llvm/ADT/StringRef.h"
17 #include "llvm/Support/Error.h"
18 #include "llvm/Support/JSON.h"
19 #include "llvm/Testing/Support/Error.h"
20 #include "llvm/Testing/Support/SupportHelpers.h"
21 #include "gmock/gmock.h"
22 #include "gtest/gtest.h"
23 
24 namespace clang {
25 namespace clangd {
26 namespace {
27 using llvm::Succeeded;
28 using testing::ElementsAre;
29 
30 MATCHER_P(DiagMessage, M, "") {
31  if (const auto *O = arg.getAsObject()) {
32  if (const auto Msg = O->getString("message"))
33  return *Msg == M;
34  }
35  return false;
36 }
37 
38 class LSPTest : public ::testing::Test {
39 protected:
40  LSPTest() : LogSession(L) {
41  ClangdServer::Options &Base = Opts;
43  // This is needed to we can test index-based operations like call hierarchy.
44  Base.BuildDynamicSymbolIndex = true;
45  Base.FeatureModules = &FeatureModules;
46  }
47 
48  LSPClient &start() {
49  EXPECT_FALSE(Server.hasValue()) << "Already initialized";
50  Server.emplace(Client.transport(), FS, Opts);
51  ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
52  Client.call("initialize", llvm::json::Object{});
53  return Client;
54  }
55 
56  void stop() {
57  assert(Server);
58  Client.call("shutdown", nullptr);
59  Client.notify("exit", nullptr);
60  Client.stop();
61  ServerThread->join();
62  Server.reset();
63  ServerThread.reset();
64  }
65 
66  ~LSPTest() {
67  if (Server)
68  stop();
69  }
70 
71  MockFS FS;
72  ClangdLSPServer::Options Opts;
73  FeatureModuleSet FeatureModules;
74 
75 private:
76  class Logger : public clang::clangd::Logger {
77  // Color logs so we can distinguish them from test output.
78  void log(Level L, const char *Fmt,
79  const llvm::formatv_object_base &Message) override {
80  raw_ostream::Colors Color;
81  switch (L) {
82  case Level::Verbose:
83  Color = raw_ostream::BLUE;
84  break;
85  case Level::Error:
86  Color = raw_ostream::RED;
87  break;
88  default:
89  Color = raw_ostream::YELLOW;
90  break;
91  }
92  std::lock_guard<std::mutex> Lock(LogMu);
93  (llvm::outs().changeColor(Color) << Message << "\n").resetColor();
94  }
95  std::mutex LogMu;
96  };
97 
98  Logger L;
99  LoggingSession LogSession;
100  llvm::Optional<ClangdLSPServer> Server;
101  llvm::Optional<std::thread> ServerThread;
102  LSPClient Client;
103 };
104 
105 TEST_F(LSPTest, GoToDefinition) {
106  Annotations Code(R"cpp(
107  int [[fib]](int n) {
108  return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1;
109  }
110  )cpp");
111  auto &Client = start();
112  Client.didOpen("foo.cpp", Code.code());
113  auto &Def = Client.call("textDocument/definition",
114  llvm::json::Object{
115  {"textDocument", Client.documentID("foo.cpp")},
116  {"position", Code.point()},
117  });
118  llvm::json::Value Want = llvm::json::Array{llvm::json::Object{
119  {"uri", Client.uri("foo.cpp")}, {"range", Code.range()}}};
120  EXPECT_EQ(Def.takeValue(), Want);
121 }
122 
123 TEST_F(LSPTest, Diagnostics) {
124  auto &Client = start();
125  Client.didOpen("foo.cpp", "void main(int, char**);");
126  EXPECT_THAT(Client.diagnostics("foo.cpp"),
127  llvm::ValueIs(testing::ElementsAre(
128  DiagMessage("'main' must return 'int' (fix available)"))));
129 
130  Client.didChange("foo.cpp", "int x = \"42\";");
131  EXPECT_THAT(Client.diagnostics("foo.cpp"),
132  llvm::ValueIs(testing::ElementsAre(
133  DiagMessage("Cannot initialize a variable of type 'int' with "
134  "an lvalue of type 'const char[3]'"))));
135 
136  Client.didClose("foo.cpp");
137  EXPECT_THAT(Client.diagnostics("foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
138 }
139 
140 TEST_F(LSPTest, DiagnosticsHeaderSaved) {
141  auto &Client = start();
142  Client.didOpen("foo.cpp", R"cpp(
143  #include "foo.h"
144  int x = VAR;
145  )cpp");
146  EXPECT_THAT(Client.diagnostics("foo.cpp"),
147  llvm::ValueIs(testing::ElementsAre(
148  DiagMessage("'foo.h' file not found"),
149  DiagMessage("Use of undeclared identifier 'VAR'"))));
150  // Now create the header.
151  FS.Files["foo.h"] = "#define VAR original";
152  Client.notify(
153  "textDocument/didSave",
154  llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
155  EXPECT_THAT(Client.diagnostics("foo.cpp"),
156  llvm::ValueIs(testing::ElementsAre(
157  DiagMessage("Use of undeclared identifier 'original'"))));
158  // Now modify the header from within the "editor".
159  FS.Files["foo.h"] = "#define VAR changed";
160  Client.notify(
161  "textDocument/didSave",
162  llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
163  // Foo.cpp should be rebuilt with new diagnostics.
164  EXPECT_THAT(Client.diagnostics("foo.cpp"),
165  llvm::ValueIs(testing::ElementsAre(
166  DiagMessage("Use of undeclared identifier 'changed'"))));
167 }
168 
169 TEST_F(LSPTest, RecordsLatencies) {
170  trace::TestTracer Tracer;
171  auto &Client = start();
172  llvm::StringLiteral MethodName = "method_name";
173  EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(0));
174  llvm::consumeError(Client.call(MethodName, {}).take().takeError());
175  stop();
176  EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(1));
177 }
178 
179 TEST_F(LSPTest, IncomingCalls) {
180  Annotations Code(R"cpp(
181  void calle^e(int);
182  void caller1() {
183  [[callee]](42);
184  }
185  )cpp");
186  auto &Client = start();
187  Client.didOpen("foo.cpp", Code.code());
188  auto Items = Client
189  .call("textDocument/prepareCallHierarchy",
190  llvm::json::Object{
191  {"textDocument", Client.documentID("foo.cpp")},
192  {"position", Code.point()}})
193  .takeValue();
194  auto FirstItem = (*Items.getAsArray())[0];
195  auto Calls = Client
196  .call("callHierarchy/incomingCalls",
197  llvm::json::Object{{"item", FirstItem}})
198  .takeValue();
199  auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
200  EXPECT_EQ(FirstCall["fromRanges"], llvm::json::Value{Code.range()});
201  auto From = *FirstCall["from"].getAsObject();
202  EXPECT_EQ(From["name"], "caller1");
203 }
204 
205 TEST_F(LSPTest, CDBConfigIntegration) {
206  auto CfgProvider =
208  Opts.ConfigProvider = CfgProvider.get();
209 
210  // Map bar.cpp to a different compilation database which defines FOO->BAR.
211  FS.Files[".clangd"] = R"yaml(
212 If:
213  PathMatch: bar.cpp
214 CompileFlags:
215  CompilationDatabase: bar
216 )yaml";
217  FS.Files["bar/compile_flags.txt"] = "-DFOO=BAR";
218 
219  auto &Client = start();
220  // foo.cpp gets parsed as normal.
221  Client.didOpen("foo.cpp", "int x = FOO;");
222  EXPECT_THAT(Client.diagnostics("foo.cpp"),
223  llvm::ValueIs(testing::ElementsAre(
224  DiagMessage("Use of undeclared identifier 'FOO'"))));
225  // bar.cpp shows the configured compile command.
226  Client.didOpen("bar.cpp", "int x = FOO;");
227  EXPECT_THAT(Client.diagnostics("bar.cpp"),
228  llvm::ValueIs(testing::ElementsAre(
229  DiagMessage("Use of undeclared identifier 'BAR'"))));
230 }
231 
232 TEST_F(LSPTest, ModulesTest) {
233  class MathModule final : public FeatureModule {
234  OutgoingNotification<int> Changed;
235  void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps,
236  llvm::json::Object &ServerCaps) override {
237  Bind.notification("add", this, &MathModule::add);
238  Bind.method("get", this, &MathModule::get);
239  Changed = Bind.outgoingNotification("changed");
240  }
241 
242  int Value = 0;
243 
244  void add(const int &X) {
245  Value += X;
246  Changed(Value);
247  }
248  void get(const std::nullptr_t &, Callback<int> Reply) {
249  scheduler().runQuick(
250  "get", "",
251  [Reply(std::move(Reply)), Value(Value)]() mutable { Reply(Value); });
252  }
253  };
254  FeatureModules.add(std::make_unique<MathModule>());
255 
256  auto &Client = start();
257  Client.notify("add", 2);
258  Client.notify("add", 8);
259  EXPECT_EQ(10, Client.call("get", nullptr).takeValue());
260  EXPECT_THAT(Client.takeNotifications("changed"),
261  ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
262 }
263 
264 // Creates a Callback that writes its received value into an Optional<Expected>.
265 template <typename T>
266 llvm::unique_function<void(llvm::Expected<T>)>
267 capture(llvm::Optional<llvm::Expected<T>> &Out) {
268  Out.reset();
269  return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
270 }
271 
272 TEST_F(LSPTest, FeatureModulesThreadingTest) {
273  // A feature module that does its work on a background thread, and so
274  // exercises the block/shutdown protocol.
275  class AsyncCounter final : public FeatureModule {
276  bool ShouldStop = false;
277  int State = 0;
278  std::deque<Callback<int>> Queue; // null = increment, non-null = read.
279  std::condition_variable CV;
280  std::mutex Mu;
281  std::thread Thread;
282 
283  void run() {
284  std::unique_lock<std::mutex> Lock(Mu);
285  while (true) {
286  CV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
287  if (ShouldStop) {
288  Queue.clear();
289  CV.notify_all();
290  return;
291  }
292  Callback<int> &Task = Queue.front();
293  if (Task)
294  Task(State);
295  else
296  ++State;
297  Queue.pop_front();
298  CV.notify_all();
299  }
300  }
301 
302  bool blockUntilIdle(Deadline D) override {
303  std::unique_lock<std::mutex> Lock(Mu);
304  return clangd::wait(Lock, CV, D, [this] { return Queue.empty(); });
305  }
306 
307  void stop() override {
308  {
309  std::lock_guard<std::mutex> Lock(Mu);
310  ShouldStop = true;
311  }
312  CV.notify_all();
313  }
314 
315  public:
316  AsyncCounter() : Thread([this] { run(); }) {}
317  ~AsyncCounter() {
318  // Verify shutdown sequence was performed.
319  // Real modules would not do this, to be robust to no ClangdServer.
320  {
321  // We still need the lock here, as Queue might be empty when
322  // ClangdServer calls blockUntilIdle, but run() might not have returned
323  // yet.
324  std::lock_guard<std::mutex> Lock(Mu);
325  EXPECT_TRUE(ShouldStop) << "ClangdServer should request shutdown";
326  EXPECT_EQ(Queue.size(), 0u) << "ClangdServer should block until idle";
327  }
328  Thread.join();
329  }
330 
331  void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps,
332  llvm::json::Object &ServerCaps) override {
333  Bind.notification("increment", this, &AsyncCounter::increment);
334  }
335 
336  // Get the current value, bypassing the queue.
337  // Used to verify that sync->blockUntilIdle avoids races in tests.
338  int getSync() {
339  std::lock_guard<std::mutex> Lock(Mu);
340  return State;
341  }
342 
343  // Increment the current value asynchronously.
344  void increment(const std::nullptr_t &) {
345  {
346  std::lock_guard<std::mutex> Lock(Mu);
347  Queue.push_back(nullptr);
348  }
349  CV.notify_all();
350  }
351  };
352 
353  FeatureModules.add(std::make_unique<AsyncCounter>());
354  auto &Client = start();
355 
356  Client.notify("increment", nullptr);
357  Client.notify("increment", nullptr);
358  Client.notify("increment", nullptr);
359  EXPECT_THAT_EXPECTED(Client.call("sync", nullptr).take(), Succeeded());
360  EXPECT_EQ(3, FeatureModules.get<AsyncCounter>()->getSync());
361  // Throw some work on the queue to make sure shutdown blocks on it.
362  Client.notify("increment", nullptr);
363  Client.notify("increment", nullptr);
364  Client.notify("increment", nullptr);
365  // And immediately shut down. FeatureModule destructor verifies we blocked.
366 }
367 
368 TEST_F(LSPTest, DiagModuleTest) {
369  static constexpr llvm::StringLiteral DiagMsg = "DiagMsg";
370  class DiagModule final : public FeatureModule {
371  struct DiagHooks : public ASTListener {
372  void sawDiagnostic(const clang::Diagnostic &, clangd::Diag &D) override {
373  D.Message = DiagMsg.str();
374  }
375  };
376 
377  public:
378  std::unique_ptr<ASTListener> astListeners() override {
379  return std::make_unique<DiagHooks>();
380  }
381  };
382  FeatureModules.add(std::make_unique<DiagModule>());
383 
384  auto &Client = start();
385  Client.didOpen("foo.cpp", "test;");
386  EXPECT_THAT(Client.diagnostics("foo.cpp"),
387  llvm::ValueIs(testing::ElementsAre(DiagMessage(DiagMsg))));
388 }
389 } // namespace
390 } // namespace clangd
391 } // namespace clang
clang::clangd::LSPClient::didOpen
void didOpen(llvm::StringRef Path, llvm::StringRef Content)
Definition: LSPClient.cpp:176
Base
std::unique_ptr< GlobalCompilationDatabase > Base
Definition: GlobalCompilationDatabaseTests.cpp:90
clang::clangd::LSPClient::diagnostics
llvm::Optional< std::vector< llvm::json::Value > > diagnostics(llvm::StringRef Path)
Definition: LSPClient.cpp:194
clang::clangd::LSPClient::CallResult::takeValue
llvm::json::Value takeValue()
Definition: LSPClient.cpp:28
clang::clangd::FeatureModuleSet::get
Mod * get()
Definition: FeatureModule.h:172
LSPClient.h
Tracer
std::unique_ptr< trace::EventTracer > Tracer
Definition: TraceTests.cpp:164
Diagnostics
WantDiagnostics Diagnostics
Definition: TUScheduler.cpp:565
clang::clangd::Logger
Interface to allow custom logging in clangd.
Definition: Logger.h:23
clang::tidy::bugprone::Message
static const char Message[]
Definition: ReservedIdentifierCheck.cpp:31
clang::clangd::X
static URISchemeRegistry::Add< TestScheme > X(TestScheme::Scheme, "Test schema")
clang::clangd::ClangdServer::optsForTest
static Options optsForTest()
Definition: ClangdServer.cpp:135
clang::clangd::LSPClient::takeNotifications
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
Definition: LSPClient.cpp:156
clang::clangd::TEST_F
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
Definition: BackgroundIndexTests.cpp:94
clang::clangd::increment
static void increment(std::string &S)
Definition: ClangdLSPServer.cpp:1339
Protocol.h
M
const google::protobuf::Message & M
Definition: Server.cpp:309
Error
constexpr static llvm::SourceMgr::DiagKind Error
Definition: ConfigCompile.cpp:500
clang::clangd::wait
void wait(std::unique_lock< std::mutex > &Lock, std::condition_variable &CV, Deadline D)
Wait once on CV for the specified duration.
Definition: Threading.cpp:113
Code
std::string Code
Definition: FindTargetTests.cpp:67
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
Diagnostic
DiagnosticCallback Diagnostic
Definition: ConfigCompile.cpp:101
ClangdLSPServer.h
Logger.h
FS
MockFS FS
Definition: ClangdLSPServerTests.cpp:71
TestFS.h
clang::clangd::LSPClient::notify
void notify(llvm::StringRef Method, llvm::json::Value Params)
Definition: LSPClient.cpp:147
clang::clangd::LSPClient::call
CallResult & call(llvm::StringRef Method, llvm::json::Value Params)
Definition: LSPClient.cpp:136
clang::clangd::LSPClient::didChange
void didChange(llvm::StringRef Path, llvm::StringRef Content)
Definition: LSPClient.cpp:182
clang::clangd::config::Provider::fromAncestorRelativeYAMLFiles
static std::unique_ptr< Provider > fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath, const ThreadsafeFS &, bool Trusted=false)
Definition: ConfigProvider.cpp:84
clang::clangd::ClangdLSPServer::Options::ConfigProvider
config::Provider * ConfigProvider
Supplies configuration (overrides ClangdServer::ContextProvider).
Definition: ClangdLSPServer.h:46
FeatureModules
FeatureModuleSet FeatureModules
Definition: ClangdLSPServerTests.cpp:73
Annotations.h
clang::clangd::log
void log(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:68
TestTracer.h
clang::clangd::LSPClient::didClose
void didClose(llvm::StringRef Path)
Definition: LSPClient.cpp:187
clang::clangd::LSPClient::documentID
static llvm::json::Value documentID(llvm::StringRef Path)
Definition: LSPClient.cpp:172
clang::clangd::MATCHER_P
MATCHER_P(Named, N, "")
Definition: BackgroundIndexTests.cpp:31
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::FeatureModuleSet::add
bool add(std::unique_ptr< Mod > M)
Definition: FeatureModule.h:169
clang::clangd::LSPClient::CallResult::take
llvm::Expected< llvm::json::Value > take()
Definition: LSPClient.cpp:16
clang::clangd::MockFS::Files
llvm::StringMap< std::string > Files
Definition: TestFS.h:47
Opts
ClangdLSPServer::Options Opts
Definition: ClangdLSPServerTests.cpp:72
Out
CompiledFragmentImpl & Out
Definition: ConfigCompile.cpp:100
clang::clangd::FileChangeType::Changed
@ Changed
The file got changed.
clang::clangd::LSPClient::uri
static llvm::json::Value uri(llvm::StringRef Path)
Definition: LSPClient.cpp:166
Value
static constexpr bool Value
Definition: SuspiciousCallArgumentCheck.cpp:72