22#include "clang/Basic/Diagnostic.h"
23#include "clang/Basic/LLVM.h"
24#include "llvm/ADT/FunctionExtras.h"
25#include "llvm/ADT/StringRef.h"
26#include "llvm/Support/Error.h"
27#include "llvm/Support/FormatVariadic.h"
28#include "llvm/Support/JSON.h"
29#include "llvm/Support/raw_ostream.h"
30#include "llvm/Testing/Support/Error.h"
31#include "llvm/Testing/Support/SupportHelpers.h"
32#include "gmock/gmock.h"
33#include "gtest/gtest.h"
35#include <condition_variable>
47using testing::ElementsAre;
50 if (
const auto *O = arg.getAsObject()) {
51 if (
const auto Msg = O->getString(
"message"))
57class LSPTest :
public ::testing::Test {
59 LSPTest() : LogSession(L) {
60 ClangdServer::Options &Base = Opts;
63 Base.BuildDynamicSymbolIndex =
true;
68 EXPECT_FALSE(Server) <<
"Already initialized";
69 Server.emplace(Client.transport(), FS, Opts);
70 ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
71 Client.call(
"initialize", llvm::json::Object{});
77 Client.call(
"shutdown",
nullptr);
78 Client.notify(
"exit",
nullptr);
91 ClangdLSPServer::Options Opts;
97 void log(Level L,
const char *Fmt,
98 const llvm::formatv_object_base &Message)
override {
99 raw_ostream::Colors
Color;
102 Color = raw_ostream::BLUE;
105 Color = raw_ostream::RED;
108 Color = raw_ostream::YELLOW;
111 std::lock_guard<std::mutex> Lock(LogMu);
112 (llvm::outs().changeColor(
Color) << Message <<
"\n").resetColor();
118 LoggingSession LogSession;
119 std::optional<ClangdLSPServer> Server;
120 std::optional<std::thread> ServerThread;
124TEST_F(LSPTest, GoToDefinition) {
125 Annotations
Code(R
"cpp(
127 return n >= 2 ? ^fib(n - 1) + fib(n - 2) : 1;
130 auto &Client = start();
132 auto &Def = Client.
call(
"textDocument/definition",
134 {
"textDocument", Client.
documentID(
"foo.cpp")},
135 {
"position",
Code.point()},
137 llvm::json::Value Want = llvm::json::Array{llvm::json::Object{
138 {
"uri", Client.
uri(
"foo.cpp")}, {
"range",
Code.range()}}};
139 EXPECT_EQ(Def.takeValue(), Want);
143 auto &Client = start();
144 Client.
didOpen(
"foo.cpp",
"void main(int, char**);");
146 llvm::ValueIs(testing::ElementsAre(
147 diagMessage(
"'main' must return 'int' (fix available)"))));
149 Client.
didChange(
"foo.cpp",
"int x = \"42\";");
151 llvm::ValueIs(testing::ElementsAre(
152 diagMessage(
"Cannot initialize a variable of type 'int' with "
153 "an lvalue of type 'const char[3]'"))));
156 EXPECT_THAT(Client.
diagnostics(
"foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
159TEST_F(LSPTest, DiagnosticsHeaderSaved) {
160 auto &Client = start();
161 Client.
didOpen(
"foo.cpp", R
"cpp(
166 llvm::ValueIs(testing::ElementsAre(
167 diagMessage(
"'foo.h' file not found"),
168 diagMessage(
"Use of undeclared identifier 'VAR'"))));
170 FS.Files[
"foo.h"] =
"#define VAR original";
172 "textDocument/didSave",
173 llvm::json::Object{{
"textDocument", Client.
documentID(
"foo.h")}});
175 llvm::ValueIs(testing::ElementsAre(
176 diagMessage(
"Use of undeclared identifier 'original'"))));
178 FS.Files[
"foo.h"] =
"#define VAR changed";
180 "textDocument/didSave",
181 llvm::json::Object{{
"textDocument", Client.
documentID(
"foo.h")}});
184 llvm::ValueIs(testing::ElementsAre(
185 diagMessage(
"Use of undeclared identifier 'changed'"))));
188TEST_F(LSPTest, RecordsLatencies) {
189 trace::TestTracer Tracer;
190 auto &Client = start();
191 llvm::StringLiteral MethodName =
"method_name";
192 EXPECT_THAT(Tracer.takeMetric(
"lsp_latency", MethodName), testing::SizeIs(0));
193 llvm::consumeError(Client.
call(MethodName, {}).
take().takeError());
195 EXPECT_THAT(Tracer.takeMetric(
"lsp_latency", MethodName), testing::SizeIs(1));
198TEST_F(LSPTest, IncomingCalls) {
199 Annotations
Code(R
"cpp(
205 auto &Client = start();
208 .
call(
"textDocument/prepareCallHierarchy",
210 {
"textDocument", Client.
documentID(
"foo.cpp")},
211 {
"position",
Code.point()}})
213 auto FirstItem = (*Items.getAsArray())[0];
215 .
call(
"callHierarchy/incomingCalls",
216 llvm::json::Object{{
"item", FirstItem}})
218 auto FirstCall = *(*Calls.getAsArray())[0].getAsObject();
219 EXPECT_EQ(FirstCall[
"fromRanges"], llvm::json::Value{
Code.range()});
220 auto From = *FirstCall[
"from"].getAsObject();
221 EXPECT_EQ(From[
"name"],
"caller1");
224TEST_F(LSPTest, CDBConfigIntegration) {
227 Opts.ConfigProvider = CfgProvider.get();
230 FS.Files[
".clangd"] = R
"yaml(
234 CompilationDatabase: bar
236 FS.Files["bar/compile_flags.txt"] =
"-DFOO=BAR";
238 auto &Client = start();
240 Client.
didOpen(
"foo.cpp",
"int x = FOO;");
242 llvm::ValueIs(testing::ElementsAre(
243 diagMessage(
"Use of undeclared identifier 'FOO'"))));
245 Client.
didOpen(
"bar.cpp",
"int x = FOO;");
247 llvm::ValueIs(testing::ElementsAre(
248 diagMessage(
"Use of undeclared identifier 'BAR'"))));
251TEST_F(LSPTest, ModulesTest) {
252 class MathModule final :
public FeatureModule {
253 OutgoingNotification<int>
Changed;
254 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
255 llvm::json::Object &ServerCaps)
override {
256 Bind.notification(
"add",
this, &MathModule::add);
257 Bind.method(
"get",
this, &MathModule::get);
258 Changed = Bind.outgoingNotification(
"changed");
263 void add(
const int &
X) {
267 void get(
const std::nullptr_t &, Callback<int> Reply) {
268 scheduler().runQuick(
270 [Reply(std::move(Reply)), Value(Value)]()
mutable { Reply(Value); });
275 auto &Client = start();
280 ElementsAre(llvm::json::Value(2), llvm::json::Value(10)));
286llvm::unique_function<void(llvm::Expected<T>)>
287capture(std::optional<llvm::Expected<T>> &
Out) {
289 return [&
Out](llvm::Expected<T> V) {
Out.emplace(std::move(V)); };
292TEST_F(LSPTest, FeatureModulesThreadingTest) {
295 class AsyncCounter final :
public FeatureModule {
296 bool ShouldStop =
false;
298 std::deque<Callback<int>> Queue;
299 std::condition_variable CV;
304 std::unique_lock<std::mutex> Lock(Mu);
306 CV.wait(Lock, [&] {
return ShouldStop || !Queue.empty(); });
312 Callback<int> &Task = Queue.front();
322 bool blockUntilIdle(Deadline D)
override {
323 std::unique_lock<std::mutex> Lock(Mu);
324 return clangd::wait(Lock, CV, D, [
this] {
return Queue.empty(); });
327 void stop()
override {
329 std::lock_guard<std::mutex> Lock(Mu);
336 AsyncCounter() : Thread([this] {
run(); }) {}
344 std::lock_guard<std::mutex> Lock(Mu);
345 EXPECT_TRUE(ShouldStop) <<
"ClangdServer should request shutdown";
346 EXPECT_EQ(Queue.size(), 0u) <<
"ClangdServer should block until idle";
351 void initializeLSP(LSPBinder &Bind,
const llvm::json::Object &ClientCaps,
352 llvm::json::Object &ServerCaps)
override {
353 Bind.notification(
"increment",
this, &AsyncCounter::increment);
359 std::lock_guard<std::mutex> Lock(Mu);
364 void increment(
const std::nullptr_t &) {
366 std::lock_guard<std::mutex> Lock(Mu);
367 Queue.push_back(
nullptr);
374 auto &Client = start();
376 Client.
notify(
"increment",
nullptr);
377 Client.
notify(
"increment",
nullptr);
378 Client.
notify(
"increment",
nullptr);
382 Client.
notify(
"increment",
nullptr);
383 Client.
notify(
"increment",
nullptr);
384 Client.
notify(
"increment",
nullptr);
388TEST_F(LSPTest, DiagModuleTest) {
389 static constexpr llvm::StringLiteral DiagMsg =
"DiagMsg";
390 class DiagModule final :
public FeatureModule {
391 struct DiagHooks :
public ASTListener {
392 void sawDiagnostic(
const clang::Diagnostic &, clangd::Diag &D)
override {
393 D.Message = DiagMsg.str();
398 std::unique_ptr<ASTListener> astListeners()
override {
399 return std::make_unique<DiagHooks>();
404 auto &Client = start();
405 Client.
didOpen(
"foo.cpp",
"test;");
407 llvm::ValueIs(testing::ElementsAre(diagMessage(DiagMsg))));
FeatureModuleSet FeatureModules
CompiledFragmentImpl & Out
const google::protobuf::Message & M
WantDiagnostics Diagnostics
static Options optsForTest()
bool add(std::unique_ptr< Mod > M)
llvm::Expected< llvm::json::Value > take()
llvm::json::Value takeValue()
CallResult & call(llvm::StringRef Method, llvm::json::Value Params)
std::vector< llvm::json::Value > takeNotifications(llvm::StringRef Method)
static llvm::json::Value uri(llvm::StringRef Path)
static llvm::json::Value documentID(llvm::StringRef Path)
void didClose(llvm::StringRef Path)
void notify(llvm::StringRef Method, llvm::json::Value Params)
void didOpen(llvm::StringRef Path, llvm::StringRef Content)
void didChange(llvm::StringRef Path, llvm::StringRef Content)
std::optional< std::vector< llvm::json::Value > > diagnostics(llvm::StringRef Path)
Interface to allow custom logging in clangd.
static std::unique_ptr< Provider > fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath, const ThreadsafeFS &, bool Trusted=false)
@ Changed
The file got changed.
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
void log(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//