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/SupportHelpers.h"
20 #include "gmock/gmock.h"
21 #include "gtest/gtest.h"
22 
23 namespace clang {
24 namespace clangd {
25 namespace {
26 
27 MATCHER_P(DiagMessage, M, "") {
28   if (const auto *O = arg.getAsObject()) {
29     if (const auto Msg = O->getString("message"))
30       return *Msg == M;
31   }
32   return false;
33 }
34 
35 class LSPTest : public ::testing::Test, private clangd::Logger {
36 protected:
LSPTest()37   LSPTest() : LogSession(*this) {
38     ClangdServer::Options &Base = Opts;
39     Base = ClangdServer::optsForTest();
40     // This is needed to we can test index-based operations like call hierarchy.
41     Base.BuildDynamicSymbolIndex = true;
42   }
43 
start()44   LSPClient &start() {
45     EXPECT_FALSE(Server.hasValue()) << "Already initialized";
46     Server.emplace(Client.transport(), FS, Opts);
47     ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
48     Client.call("initialize", llvm::json::Object{});
49     return Client;
50   }
51 
stop()52   void stop() {
53     assert(Server);
54     Client.call("shutdown", nullptr);
55     Client.notify("exit", nullptr);
56     Client.stop();
57     ServerThread->join();
58     Server.reset();
59     ServerThread.reset();
60   }
61 
~LSPTest()62   ~LSPTest() {
63     if (Server)
64       stop();
65   }
66 
67   MockFS FS;
68   ClangdLSPServer::Options Opts;
69 
70 private:
71   // Color logs so we can distinguish them from test output.
log(Level L,const char * Fmt,const llvm::formatv_object_base & Message)72   void log(Level L, const char *Fmt,
73            const llvm::formatv_object_base &Message) override {
74     raw_ostream::Colors Color;
75     switch (L) {
76     case Level::Verbose:
77       Color = raw_ostream::BLUE;
78       break;
79     case Level::Error:
80       Color = raw_ostream::RED;
81       break;
82     default:
83       Color = raw_ostream::YELLOW;
84       break;
85     }
86     std::lock_guard<std::mutex> Lock(LogMu);
87     (llvm::outs().changeColor(Color) << Message << "\n").resetColor();
88   }
89   std::mutex LogMu;
90 
91   LoggingSession LogSession;
92   llvm::Optional<ClangdLSPServer> Server;
93   llvm::Optional<std::thread> ServerThread;
94   LSPClient Client;
95 };
96 
TEST_F(LSPTest,GoToDefinition)97 TEST_F(LSPTest, GoToDefinition) {
98   Annotations Code(R"cpp(
99     int [[fib]](int n) {
100       return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1;
101     }
102   )cpp");
103   auto &Client = start();
104   Client.didOpen("foo.cpp", Code.code());
105   auto &Def = Client.call("textDocument/definition",
106                           llvm::json::Object{
107                               {"textDocument", Client.documentID("foo.cpp")},
108                               {"position", Code.point()},
109                           });
110   llvm::json::Value Want = llvm::json::Array{llvm::json::Object{
111       {"uri", Client.uri("foo.cpp")}, {"range", Code.range()}}};
112   EXPECT_EQ(Def.takeValue(), Want);
113 }
114 
TEST_F(LSPTest,Diagnostics)115 TEST_F(LSPTest, Diagnostics) {
116   auto &Client = start();
117   Client.didOpen("foo.cpp", "void main(int, char**);");
118   EXPECT_THAT(Client.diagnostics("foo.cpp"),
119               llvm::ValueIs(testing::ElementsAre(
120                   DiagMessage("'main' must return 'int' (fix available)"))));
121 
122   Client.didChange("foo.cpp", "int x = \"42\";");
123   EXPECT_THAT(Client.diagnostics("foo.cpp"),
124               llvm::ValueIs(testing::ElementsAre(
125                   DiagMessage("Cannot initialize a variable of type 'int' with "
126                               "an lvalue of type 'const char [3]'"))));
127 
128   Client.didClose("foo.cpp");
129   EXPECT_THAT(Client.diagnostics("foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
130 }
131 
TEST_F(LSPTest,DiagnosticsHeaderSaved)132 TEST_F(LSPTest, DiagnosticsHeaderSaved) {
133   auto &Client = start();
134   Client.didOpen("foo.cpp", R"cpp(
135     #include "foo.h"
136     int x = VAR;
137   )cpp");
138   EXPECT_THAT(Client.diagnostics("foo.cpp"),
139               llvm::ValueIs(testing::ElementsAre(
140                   DiagMessage("'foo.h' file not found"),
141                   DiagMessage("Use of undeclared identifier 'VAR'"))));
142   // Now create the header.
143   FS.Files["foo.h"] = "#define VAR original";
144   Client.notify(
145       "textDocument/didSave",
146       llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
147   EXPECT_THAT(Client.diagnostics("foo.cpp"),
148               llvm::ValueIs(testing::ElementsAre(
149                   DiagMessage("Use of undeclared identifier 'original'"))));
150   // Now modify the header from within the "editor".
151   FS.Files["foo.h"] = "#define VAR changed";
152   Client.notify(
153       "textDocument/didSave",
154       llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
155   // Foo.cpp should be rebuilt with new diagnostics.
156   EXPECT_THAT(Client.diagnostics("foo.cpp"),
157               llvm::ValueIs(testing::ElementsAre(
158                   DiagMessage("Use of undeclared identifier 'changed'"))));
159 }
160 
TEST_F(LSPTest,RecordsLatencies)161 TEST_F(LSPTest, RecordsLatencies) {
162   trace::TestTracer Tracer;
163   auto &Client = start();
164   llvm::StringLiteral MethodName = "method_name";
165   EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(0));
166   llvm::consumeError(Client.call(MethodName, {}).take().takeError());
167   stop();
168   EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(1));
169 }
170 
TEST_F(LSPTest,IncomingCalls)171 TEST_F(LSPTest, IncomingCalls) {
172   Annotations Code(R"cpp(
173     void calle^e(int);
174     void caller1() {
175       [[callee]](42);
176     }
177   )cpp");
178   auto &Client = start();
179   Client.didOpen("foo.cpp", Code.code());
180   auto Items = Client
181                    .call("textDocument/prepareCallHierarchy",
182                          llvm::json::Object{
183                              {"textDocument", Client.documentID("foo.cpp")},
184                              {"position", Code.point()}})
185                    .takeValue();
186   auto FirstItem = (*Items.getAsArray())[0];
187   auto Calls = Client
188                    .call("callHierarchy/incomingCalls",
189                          llvm::json::Object{{"item", FirstItem}})
190                    .takeValue();
191   auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
192   EXPECT_EQ(FirstCall["fromRanges"], llvm::json::Value{Code.range()});
193   auto From = *FirstCall["from"].getAsObject();
194   EXPECT_EQ(From["name"], "caller1");
195 }
196 
TEST_F(LSPTest,CDBConfigIntegration)197 TEST_F(LSPTest, CDBConfigIntegration) {
198   auto CfgProvider =
199       config::Provider::fromAncestorRelativeYAMLFiles(".clangd", FS);
200   Opts.ConfigProvider = CfgProvider.get();
201 
202   // Map bar.cpp to a different compilation database which defines FOO->BAR.
203   FS.Files[".clangd"] = R"yaml(
204 If:
205   PathMatch: bar.cpp
206 CompileFlags:
207   CompilationDatabase: bar
208 )yaml";
209   FS.Files["bar/compile_flags.txt"] = "-DFOO=BAR";
210 
211   auto &Client = start();
212   // foo.cpp gets parsed as normal.
213   Client.didOpen("foo.cpp", "int x = FOO;");
214   EXPECT_THAT(Client.diagnostics("foo.cpp"),
215               llvm::ValueIs(testing::ElementsAre(
216                   DiagMessage("Use of undeclared identifier 'FOO'"))));
217   // bar.cpp shows the configured compile command.
218   Client.didOpen("bar.cpp", "int x = FOO;");
219   EXPECT_THAT(Client.diagnostics("bar.cpp"),
220               llvm::ValueIs(testing::ElementsAre(
221                   DiagMessage("Use of undeclared identifier 'BAR'"))));
222 }
223 
224 } // namespace
225 } // namespace clangd
226 } // namespace clang
227