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 "CodeComplete.h"
12 #include "LSPClient.h"
13 #include "Protocol.h"
14 #include "TestFS.h"
15 #include "refactor/Rename.h"
16 #include "support/Logger.h"
17 #include "support/TestTracer.h"
18 #include "llvm/ADT/StringRef.h"
19 #include "llvm/Support/Error.h"
20 #include "llvm/Support/JSON.h"
21 #include "llvm/Testing/Support/SupportHelpers.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 
25 namespace clang {
26 namespace clangd {
27 namespace {
28 
29 MATCHER_P(DiagMessage, M, "") {
30   if (const auto *O = arg.getAsObject()) {
31     if (const auto Msg = O->getString("message"))
32       return *Msg == M;
33   }
34   return false;
35 }
36 
37 class LSPTest : public ::testing::Test, private clangd::Logger {
38 protected:
LSPTest()39   LSPTest() : LogSession(*this) {}
40 
start()41   LSPClient &start() {
42     EXPECT_FALSE(Server.hasValue()) << "Already initialized";
43     Server.emplace(Client.transport(), FS, CCOpts, RenameOpts,
44                    /*CompileCommandsDir=*/llvm::None, /*UseDirBasedCDB=*/false,
45                    /*ForcedOffsetEncoding=*/llvm::None, Opts);
46     ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
47     Client.call("initialize", llvm::json::Object{});
48     return Client;
49   }
50 
stop()51   void stop() {
52     assert(Server);
53     Client.call("shutdown", nullptr);
54     Client.notify("exit", nullptr);
55     Client.stop();
56     ServerThread->join();
57     Server.reset();
58     ServerThread.reset();
59   }
60 
~LSPTest()61   ~LSPTest() {
62     if (Server)
63       stop();
64   }
65 
66   MockFS FS;
67   CodeCompleteOptions CCOpts;
68   RenameOptions RenameOpts;
69   ClangdServer::Options Opts = ClangdServer::optsForTest();
70 
71 private:
72   // Color logs so we can distinguish them from test output.
log(Level L,const llvm::formatv_object_base & Message)73   void log(Level L, 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 } // namespace
171 } // namespace clangd
172 } // namespace clang
173