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:
LSPTest()40   LSPTest() : LogSession(L) {
41     ClangdServer::Options &Base = Opts;
42     Base = ClangdServer::optsForTest();
43     // This is needed to we can test index-based operations like call hierarchy.
44     Base.BuildDynamicSymbolIndex = true;
45     Base.FeatureModules = &FeatureModules;
46   }
47 
start()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 
stop()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 
~LSPTest()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.
log(Level L,const char * Fmt,const llvm::formatv_object_base & Message)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 
TEST_F(LSPTest,GoToDefinition)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 
TEST_F(LSPTest,Diagnostics)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 
TEST_F(LSPTest,DiagnosticsHeaderSaved)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 
TEST_F(LSPTest,RecordsLatencies)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 
TEST_F(LSPTest,IncomingCalls)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 
TEST_F(LSPTest,CDBConfigIntegration)205 TEST_F(LSPTest, CDBConfigIntegration) {
206   auto CfgProvider =
207       config::Provider::fromAncestorRelativeYAMLFiles(".clangd", FS);
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 
TEST_F(LSPTest,ModulesTest)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>)>
capture(llvm::Optional<llvm::Expected<T>> & Out)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 
TEST_F(LSPTest,FeatureModulesThreadingTest)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 
TEST_F(LSPTest,DiagModuleTest)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
392