[AMDGPU][AsmParser][NFC] Translate parsed MIMG instructions to MCInsts automatically.
[llvm-project.git] / clang-tools-extra / clangd / unittests / ClangdLSPServerTests.cpp
blob4c6a238bbb261f0abaed19ad890989563693e979
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 "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 #include <optional>
25 namespace clang {
26 namespace clangd {
27 namespace {
28 using llvm::Succeeded;
29 using testing::ElementsAre;
31 MATCHER_P(diagMessage, M, "") {
32 if (const auto *O = arg.getAsObject()) {
33 if (const auto Msg = O->getString("message"))
34 return *Msg == M;
36 return false;
39 class LSPTest : public ::testing::Test {
40 protected:
41 LSPTest() : LogSession(L) {
42 ClangdServer::Options &Base = Opts;
43 Base = ClangdServer::optsForTest();
44 // This is needed to we can test index-based operations like call hierarchy.
45 Base.BuildDynamicSymbolIndex = true;
46 Base.FeatureModules = &FeatureModules;
49 LSPClient &start() {
50 EXPECT_FALSE(Server) << "Already initialized";
51 Server.emplace(Client.transport(), FS, Opts);
52 ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
53 Client.call("initialize", llvm::json::Object{});
54 return Client;
57 void stop() {
58 assert(Server);
59 Client.call("shutdown", nullptr);
60 Client.notify("exit", nullptr);
61 Client.stop();
62 ServerThread->join();
63 Server.reset();
64 ServerThread.reset();
67 ~LSPTest() {
68 if (Server)
69 stop();
72 MockFS FS;
73 ClangdLSPServer::Options Opts;
74 FeatureModuleSet FeatureModules;
76 private:
77 class Logger : public clang::clangd::Logger {
78 // Color logs so we can distinguish them from test output.
79 void log(Level L, const char *Fmt,
80 const llvm::formatv_object_base &Message) override {
81 raw_ostream::Colors Color;
82 switch (L) {
83 case Level::Verbose:
84 Color = raw_ostream::BLUE;
85 break;
86 case Level::Error:
87 Color = raw_ostream::RED;
88 break;
89 default:
90 Color = raw_ostream::YELLOW;
91 break;
93 std::lock_guard<std::mutex> Lock(LogMu);
94 (llvm::outs().changeColor(Color) << Message << "\n").resetColor();
96 std::mutex LogMu;
99 Logger L;
100 LoggingSession LogSession;
101 std::optional<ClangdLSPServer> Server;
102 std::optional<std::thread> ServerThread;
103 LSPClient Client;
106 TEST_F(LSPTest, GoToDefinition) {
107 Annotations Code(R"cpp(
108 int [[fib]](int n) {
109 return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1;
111 )cpp");
112 auto &Client = start();
113 Client.didOpen("foo.cpp", Code.code());
114 auto &Def = Client.call("textDocument/definition",
115 llvm::json::Object{
116 {"textDocument", Client.documentID("foo.cpp")},
117 {"position", Code.point()},
119 llvm::json::Value Want = llvm::json::Array{llvm::json::Object{
120 {"uri", Client.uri("foo.cpp")}, {"range", Code.range()}}};
121 EXPECT_EQ(Def.takeValue(), Want);
124 TEST_F(LSPTest, Diagnostics) {
125 auto &Client = start();
126 Client.didOpen("foo.cpp", "void main(int, char**);");
127 EXPECT_THAT(Client.diagnostics("foo.cpp"),
128 llvm::ValueIs(testing::ElementsAre(
129 diagMessage("'main' must return 'int' (fix available)"))));
131 Client.didChange("foo.cpp", "int x = \"42\";");
132 EXPECT_THAT(Client.diagnostics("foo.cpp"),
133 llvm::ValueIs(testing::ElementsAre(
134 diagMessage("Cannot initialize a variable of type 'int' with "
135 "an lvalue of type 'const char[3]'"))));
137 Client.didClose("foo.cpp");
138 EXPECT_THAT(Client.diagnostics("foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
141 TEST_F(LSPTest, DiagnosticsHeaderSaved) {
142 auto &Client = start();
143 Client.didOpen("foo.cpp", R"cpp(
144 #include "foo.h"
145 int x = VAR;
146 )cpp");
147 EXPECT_THAT(Client.diagnostics("foo.cpp"),
148 llvm::ValueIs(testing::ElementsAre(
149 diagMessage("'foo.h' file not found"),
150 diagMessage("Use of undeclared identifier 'VAR'"))));
151 // Now create the header.
152 FS.Files["foo.h"] = "#define VAR original";
153 Client.notify(
154 "textDocument/didSave",
155 llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
156 EXPECT_THAT(Client.diagnostics("foo.cpp"),
157 llvm::ValueIs(testing::ElementsAre(
158 diagMessage("Use of undeclared identifier 'original'"))));
159 // Now modify the header from within the "editor".
160 FS.Files["foo.h"] = "#define VAR changed";
161 Client.notify(
162 "textDocument/didSave",
163 llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
164 // Foo.cpp should be rebuilt with new diagnostics.
165 EXPECT_THAT(Client.diagnostics("foo.cpp"),
166 llvm::ValueIs(testing::ElementsAre(
167 diagMessage("Use of undeclared identifier 'changed'"))));
170 TEST_F(LSPTest, RecordsLatencies) {
171 trace::TestTracer Tracer;
172 auto &Client = start();
173 llvm::StringLiteral MethodName = "method_name";
174 EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(0));
175 llvm::consumeError(Client.call(MethodName, {}).take().takeError());
176 stop();
177 EXPECT_THAT(Tracer.takeMetric("lsp_latency", MethodName), testing::SizeIs(1));
180 TEST_F(LSPTest, IncomingCalls) {
181 Annotations Code(R"cpp(
182 void calle^e(int);
183 void caller1() {
184 [[callee]](42);
186 )cpp");
187 auto &Client = start();
188 Client.didOpen("foo.cpp", Code.code());
189 auto Items = Client
190 .call("textDocument/prepareCallHierarchy",
191 llvm::json::Object{
192 {"textDocument", Client.documentID("foo.cpp")},
193 {"position", Code.point()}})
194 .takeValue();
195 auto FirstItem = (*Items.getAsArray())[0];
196 auto Calls = Client
197 .call("callHierarchy/incomingCalls",
198 llvm::json::Object{{"item", FirstItem}})
199 .takeValue();
200 auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
201 EXPECT_EQ(FirstCall["fromRanges"], llvm::json::Value{Code.range()});
202 auto From = *FirstCall["from"].getAsObject();
203 EXPECT_EQ(From["name"], "caller1");
206 TEST_F(LSPTest, CDBConfigIntegration) {
207 auto CfgProvider =
208 config::Provider::fromAncestorRelativeYAMLFiles(".clangd", FS);
209 Opts.ConfigProvider = CfgProvider.get();
211 // Map bar.cpp to a different compilation database which defines FOO->BAR.
212 FS.Files[".clangd"] = R"yaml(
214 PathMatch: bar.cpp
215 CompileFlags:
216 CompilationDatabase: bar
217 )yaml";
218 FS.Files["bar/compile_flags.txt"] = "-DFOO=BAR";
220 auto &Client = start();
221 // foo.cpp gets parsed as normal.
222 Client.didOpen("foo.cpp", "int x = FOO;");
223 EXPECT_THAT(Client.diagnostics("foo.cpp"),
224 llvm::ValueIs(testing::ElementsAre(
225 diagMessage("Use of undeclared identifier 'FOO'"))));
226 // bar.cpp shows the configured compile command.
227 Client.didOpen("bar.cpp", "int x = FOO;");
228 EXPECT_THAT(Client.diagnostics("bar.cpp"),
229 llvm::ValueIs(testing::ElementsAre(
230 diagMessage("Use of undeclared identifier 'BAR'"))));
233 TEST_F(LSPTest, ModulesTest) {
234 class MathModule final : public FeatureModule {
235 OutgoingNotification<int> Changed;
236 void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps,
237 llvm::json::Object &ServerCaps) override {
238 Bind.notification("add", this, &MathModule::add);
239 Bind.method("get", this, &MathModule::get);
240 Changed = Bind.outgoingNotification("changed");
243 int Value = 0;
245 void add(const int &X) {
246 Value += X;
247 Changed(Value);
249 void get(const std::nullptr_t &, Callback<int> Reply) {
250 scheduler().runQuick(
251 "get", "",
252 [Reply(std::move(Reply)), Value(Value)]() mutable { Reply(Value); });
255 FeatureModules.add(std::make_unique<MathModule>());
257 auto &Client = start();
258 Client.notify("add", 2);
259 Client.notify("add", 8);
260 EXPECT_EQ(10, Client.call("get", nullptr).takeValue());
261 EXPECT_THAT(Client.takeNotifications("changed"),
262 ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
265 // Creates a Callback that writes its received value into an
266 // std::optional<Expected>.
267 template <typename T>
268 llvm::unique_function<void(llvm::Expected<T>)>
269 capture(std::optional<llvm::Expected<T>> &Out) {
270 Out.reset();
271 return [&Out](llvm::Expected<T> V) { Out.emplace(std::move(V)); };
274 TEST_F(LSPTest, FeatureModulesThreadingTest) {
275 // A feature module that does its work on a background thread, and so
276 // exercises the block/shutdown protocol.
277 class AsyncCounter final : public FeatureModule {
278 bool ShouldStop = false;
279 int State = 0;
280 std::deque<Callback<int>> Queue; // null = increment, non-null = read.
281 std::condition_variable CV;
282 std::mutex Mu;
283 std::thread Thread;
285 void run() {
286 std::unique_lock<std::mutex> Lock(Mu);
287 while (true) {
288 CV.wait(Lock, [&] { return ShouldStop || !Queue.empty(); });
289 if (ShouldStop) {
290 Queue.clear();
291 CV.notify_all();
292 return;
294 Callback<int> &Task = Queue.front();
295 if (Task)
296 Task(State);
297 else
298 ++State;
299 Queue.pop_front();
300 CV.notify_all();
304 bool blockUntilIdle(Deadline D) override {
305 std::unique_lock<std::mutex> Lock(Mu);
306 return clangd::wait(Lock, CV, D, [this] { return Queue.empty(); });
309 void stop() override {
311 std::lock_guard<std::mutex> Lock(Mu);
312 ShouldStop = true;
314 CV.notify_all();
317 public:
318 AsyncCounter() : Thread([this] { run(); }) {}
319 ~AsyncCounter() {
320 // Verify shutdown sequence was performed.
321 // Real modules would not do this, to be robust to no ClangdServer.
323 // We still need the lock here, as Queue might be empty when
324 // ClangdServer calls blockUntilIdle, but run() might not have returned
325 // yet.
326 std::lock_guard<std::mutex> Lock(Mu);
327 EXPECT_TRUE(ShouldStop) << "ClangdServer should request shutdown";
328 EXPECT_EQ(Queue.size(), 0u) << "ClangdServer should block until idle";
330 Thread.join();
333 void initializeLSP(LSPBinder &Bind, const llvm::json::Object &ClientCaps,
334 llvm::json::Object &ServerCaps) override {
335 Bind.notification("increment", this, &AsyncCounter::increment);
338 // Get the current value, bypassing the queue.
339 // Used to verify that sync->blockUntilIdle avoids races in tests.
340 int getSync() {
341 std::lock_guard<std::mutex> Lock(Mu);
342 return State;
345 // Increment the current value asynchronously.
346 void increment(const std::nullptr_t &) {
348 std::lock_guard<std::mutex> Lock(Mu);
349 Queue.push_back(nullptr);
351 CV.notify_all();
355 FeatureModules.add(std::make_unique<AsyncCounter>());
356 auto &Client = start();
358 Client.notify("increment", nullptr);
359 Client.notify("increment", nullptr);
360 Client.notify("increment", nullptr);
361 EXPECT_THAT_EXPECTED(Client.call("sync", nullptr).take(), Succeeded());
362 EXPECT_EQ(3, FeatureModules.get<AsyncCounter>()->getSync());
363 // Throw some work on the queue to make sure shutdown blocks on it.
364 Client.notify("increment", nullptr);
365 Client.notify("increment", nullptr);
366 Client.notify("increment", nullptr);
367 // And immediately shut down. FeatureModule destructor verifies we blocked.
370 TEST_F(LSPTest, DiagModuleTest) {
371 static constexpr llvm::StringLiteral DiagMsg = "DiagMsg";
372 class DiagModule final : public FeatureModule {
373 struct DiagHooks : public ASTListener {
374 void sawDiagnostic(const clang::Diagnostic &, clangd::Diag &D) override {
375 D.Message = DiagMsg.str();
379 public:
380 std::unique_ptr<ASTListener> astListeners() override {
381 return std::make_unique<DiagHooks>();
384 FeatureModules.add(std::make_unique<DiagModule>());
386 auto &Client = start();
387 Client.didOpen("foo.cpp", "test;");
388 EXPECT_THAT(Client.diagnostics("foo.cpp"),
389 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg))));
391 } // namespace
392 } // namespace clangd
393 } // namespace clang