1 | //===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===// |
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 "ClangdServer.h" |
11 | #include "CodeComplete.h" |
12 | #include "CompileCommands.h" |
13 | #include "ConfigFragment.h" |
14 | #include "GlobalCompilationDatabase.h" |
15 | #include "Matchers.h" |
16 | #include "SyncAPI.h" |
17 | #include "TestFS.h" |
18 | #include "TestTU.h" |
19 | #include "TidyProvider.h" |
20 | #include "refactor/Tweak.h" |
21 | #include "support/MemoryTree.h" |
22 | #include "support/Path.h" |
23 | #include "support/Threading.h" |
24 | #include "clang/Config/config.h" |
25 | #include "clang/Sema/CodeCompleteConsumer.h" |
26 | #include "clang/Tooling/ArgumentsAdjusters.h" |
27 | #include "clang/Tooling/Core/Replacement.h" |
28 | #include "llvm/ADT/ArrayRef.h" |
29 | #include "llvm/ADT/SmallVector.h" |
30 | #include "llvm/ADT/StringMap.h" |
31 | #include "llvm/ADT/StringRef.h" |
32 | #include "llvm/Support/Allocator.h" |
33 | #include "llvm/Support/Error.h" |
34 | #include "llvm/Support/Path.h" |
35 | #include "llvm/Support/Regex.h" |
36 | #include "llvm/Support/VirtualFileSystem.h" |
37 | #include "llvm/Testing/Support/Error.h" |
38 | #include "gmock/gmock.h" |
39 | #include "gtest/gtest.h" |
40 | #include <algorithm> |
41 | #include <chrono> |
42 | #include <iostream> |
43 | #include <optional> |
44 | #include <random> |
45 | #include <string> |
46 | #include <thread> |
47 | #include <vector> |
48 | |
49 | namespace clang { |
50 | namespace clangd { |
51 | |
52 | namespace { |
53 | |
54 | using ::testing::AllOf; |
55 | using ::testing::ElementsAre; |
56 | using ::testing::Field; |
57 | using ::testing::IsEmpty; |
58 | using ::testing::Pair; |
59 | using ::testing::SizeIs; |
60 | using ::testing::UnorderedElementsAre; |
61 | |
62 | MATCHER_P2(DeclAt, File, Range, "" ) { |
63 | return arg.PreferredDeclaration == |
64 | Location{URIForFile::canonicalize(AbsPath: File, TUPath: testRoot()), Range}; |
65 | } |
66 | |
67 | bool diagsContainErrors(const std::vector<Diag> &Diagnostics) { |
68 | for (auto D : Diagnostics) { |
69 | if (D.Severity == DiagnosticsEngine::Error || |
70 | D.Severity == DiagnosticsEngine::Fatal) |
71 | return true; |
72 | } |
73 | return false; |
74 | } |
75 | |
76 | class ErrorCheckingCallbacks : public ClangdServer::Callbacks { |
77 | public: |
78 | void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
79 | llvm::ArrayRef<Diag> Diagnostics) override { |
80 | bool HadError = diagsContainErrors(Diagnostics); |
81 | std::lock_guard<std::mutex> Lock(Mutex); |
82 | HadErrorInLastDiags = HadError; |
83 | } |
84 | |
85 | bool hadErrorInLastDiags() { |
86 | std::lock_guard<std::mutex> Lock(Mutex); |
87 | return HadErrorInLastDiags; |
88 | } |
89 | |
90 | private: |
91 | std::mutex Mutex; |
92 | bool HadErrorInLastDiags = false; |
93 | }; |
94 | |
95 | /// For each file, record whether the last published diagnostics contained at |
96 | /// least one error. |
97 | class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks { |
98 | public: |
99 | void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
100 | llvm::ArrayRef<Diag> Diagnostics) override { |
101 | bool HadError = diagsContainErrors(Diagnostics); |
102 | |
103 | std::lock_guard<std::mutex> Lock(Mutex); |
104 | LastDiagsHadError[File] = HadError; |
105 | } |
106 | |
107 | /// Exposes all files consumed by onDiagnosticsReady in an unspecified order. |
108 | /// For each file, a bool value indicates whether the last diagnostics |
109 | /// contained an error. |
110 | std::vector<std::pair<Path, bool>> filesWithDiags() const { |
111 | std::vector<std::pair<Path, bool>> Result; |
112 | std::lock_guard<std::mutex> Lock(Mutex); |
113 | for (const auto &It : LastDiagsHadError) |
114 | Result.emplace_back(args: std::string(It.first()), args: It.second); |
115 | return Result; |
116 | } |
117 | |
118 | void clear() { |
119 | std::lock_guard<std::mutex> Lock(Mutex); |
120 | LastDiagsHadError.clear(); |
121 | } |
122 | |
123 | private: |
124 | mutable std::mutex Mutex; |
125 | llvm::StringMap<bool> LastDiagsHadError; |
126 | }; |
127 | |
128 | /// Replaces all patterns of the form 0x123abc with spaces |
129 | std::string replacePtrsInDump(std::string const &Dump) { |
130 | llvm::Regex RE("0x[0-9a-fA-F]+" ); |
131 | llvm::SmallVector<llvm::StringRef, 1> Matches; |
132 | llvm::StringRef Pending = Dump; |
133 | |
134 | std::string Result; |
135 | while (RE.match(String: Pending, Matches: &Matches)) { |
136 | assert(Matches.size() == 1 && "Exactly one match expected" ); |
137 | auto MatchPos = Matches[0].data() - Pending.data(); |
138 | |
139 | Result += Pending.take_front(N: MatchPos); |
140 | Pending = Pending.drop_front(N: MatchPos + Matches[0].size()); |
141 | } |
142 | Result += Pending; |
143 | |
144 | return Result; |
145 | } |
146 | |
147 | std::string dumpAST(ClangdServer &Server, PathRef File) { |
148 | std::string Result; |
149 | Notification Done; |
150 | Server.customAction(File, Name: "DumpAST" , Action: [&](llvm::Expected<InputsAndAST> AST) { |
151 | if (AST) { |
152 | llvm::raw_string_ostream ResultOS(Result); |
153 | AST->AST.getASTContext().getTranslationUnitDecl()->dump(ResultOS, true); |
154 | } else { |
155 | llvm::consumeError(Err: AST.takeError()); |
156 | Result = "<no-ast>" ; |
157 | } |
158 | Done.notify(); |
159 | }); |
160 | Done.wait(); |
161 | return Result; |
162 | } |
163 | |
164 | std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) { |
165 | return replacePtrsInDump(Dump: dumpAST(Server, File)); |
166 | } |
167 | |
168 | std::string parseSourceAndDumpAST( |
169 | PathRef SourceFileRelPath, llvm::StringRef SourceContents, |
170 | std::vector<std::pair<PathRef, llvm::StringRef>> = {}, |
171 | bool ExpectErrors = false) { |
172 | MockFS FS; |
173 | ErrorCheckingCallbacks DiagConsumer; |
174 | MockCompilationDatabase CDB; |
175 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
176 | for (const auto &FileWithContents : ExtraFiles) |
177 | FS.Files[testPath(File: FileWithContents.first)] = |
178 | std::string(FileWithContents.second); |
179 | |
180 | auto SourceFilename = testPath(File: SourceFileRelPath); |
181 | Server.addDocument(File: SourceFilename, Contents: SourceContents); |
182 | auto Result = dumpASTWithoutMemoryLocs(Server, File: SourceFilename); |
183 | EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
184 | EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags()); |
185 | return Result; |
186 | } |
187 | |
188 | TEST(ClangdServerTest, Parse) { |
189 | // FIXME: figure out a stable format for AST dumps, so that we can check the |
190 | // output of the dump itself is equal to the expected one, not just that it's |
191 | // different. |
192 | auto Empty = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "" ); |
193 | auto OneDecl = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "int a;" ); |
194 | auto SomeDecls = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "int a; int b; int c;" ); |
195 | EXPECT_NE(Empty, OneDecl); |
196 | EXPECT_NE(Empty, SomeDecls); |
197 | EXPECT_NE(SomeDecls, OneDecl); |
198 | |
199 | auto Empty2 = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "" ); |
200 | auto OneDecl2 = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "int a;" ); |
201 | auto SomeDecls2 = parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "int a; int b; int c;" ); |
202 | EXPECT_EQ(Empty, Empty2); |
203 | EXPECT_EQ(OneDecl, OneDecl2); |
204 | EXPECT_EQ(SomeDecls, SomeDecls2); |
205 | } |
206 | |
207 | TEST(ClangdServerTest, ParseWithHeader) { |
208 | parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "#include \"foo.h\"" , ExtraFiles: {}, |
209 | /*ExpectErrors=*/true); |
210 | parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents: "#include \"foo.h\"" , ExtraFiles: {{"foo.h" , "" }}, |
211 | /*ExpectErrors=*/false); |
212 | |
213 | const auto *SourceContents = R"cpp( |
214 | #include "foo.h" |
215 | int b = a; |
216 | )cpp" ; |
217 | parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents, ExtraFiles: {{"foo.h" , "" }}, |
218 | /*ExpectErrors=*/true); |
219 | parseSourceAndDumpAST(SourceFileRelPath: "foo.cpp" , SourceContents, ExtraFiles: {{"foo.h" , "int a;" }}, |
220 | /*ExpectErrors=*/false); |
221 | } |
222 | |
223 | TEST(ClangdServerTest, Reparse) { |
224 | MockFS FS; |
225 | ErrorCheckingCallbacks DiagConsumer; |
226 | MockCompilationDatabase CDB; |
227 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
228 | |
229 | const auto *SourceContents = R"cpp( |
230 | #include "foo.h" |
231 | int b = a; |
232 | )cpp" ; |
233 | |
234 | auto FooCpp = testPath(File: "foo.cpp" ); |
235 | |
236 | FS.Files[testPath(File: "foo.h" )] = "int a;" ; |
237 | FS.Files[FooCpp] = SourceContents; |
238 | |
239 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
240 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
241 | auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, File: FooCpp); |
242 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
243 | |
244 | Server.addDocument(File: FooCpp, Contents: "" ); |
245 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
246 | auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, File: FooCpp); |
247 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
248 | |
249 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
250 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
251 | auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, File: FooCpp); |
252 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
253 | |
254 | EXPECT_EQ(DumpParse1, DumpParse2); |
255 | EXPECT_NE(DumpParse1, DumpParseEmpty); |
256 | } |
257 | |
258 | TEST(ClangdServerTest, ReparseOnHeaderChange) { |
259 | MockFS FS; |
260 | ErrorCheckingCallbacks DiagConsumer; |
261 | MockCompilationDatabase CDB; |
262 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
263 | |
264 | const auto *SourceContents = R"cpp( |
265 | #include "foo.h" |
266 | int b = a; |
267 | )cpp" ; |
268 | |
269 | auto FooCpp = testPath(File: "foo.cpp" ); |
270 | auto FooH = testPath(File: "foo.h" ); |
271 | |
272 | FS.Files[FooH] = "int a;" ; |
273 | FS.Files[FooCpp] = SourceContents; |
274 | |
275 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
276 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
277 | auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, File: FooCpp); |
278 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
279 | |
280 | FS.Files[FooH] = "" ; |
281 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
282 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
283 | auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, File: FooCpp); |
284 | EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); |
285 | |
286 | FS.Files[FooH] = "int a;" ; |
287 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
288 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
289 | auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, File: FooCpp); |
290 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
291 | |
292 | EXPECT_EQ(DumpParse1, DumpParse2); |
293 | EXPECT_NE(DumpParse1, DumpParseDifferent); |
294 | } |
295 | |
296 | TEST(ClangdServerTest, PropagatesContexts) { |
297 | static Key<int> Secret; |
298 | struct ContextReadingFS : public ThreadsafeFS { |
299 | mutable int Got; |
300 | |
301 | private: |
302 | IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override { |
303 | Got = Context::current().getExisting(Key: Secret); |
304 | return buildTestFS(Files: {}); |
305 | } |
306 | } FS; |
307 | struct Callbacks : public ClangdServer::Callbacks { |
308 | void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
309 | llvm::ArrayRef<Diag> Diagnostics) override { |
310 | Got = Context::current().getExisting(Key: Secret); |
311 | } |
312 | int Got; |
313 | } Callbacks; |
314 | MockCompilationDatabase CDB; |
315 | |
316 | // Verify that the context is plumbed to the FS provider and diagnostics. |
317 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks); |
318 | { |
319 | WithContextValue Entrypoint(Secret, 42); |
320 | Server.addDocument(File: testPath(File: "foo.cpp" ), Contents: "void main(){}" ); |
321 | } |
322 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
323 | EXPECT_EQ(FS.Got, 42); |
324 | EXPECT_EQ(Callbacks.Got, 42); |
325 | } |
326 | |
327 | TEST(ClangdServerTest, RespectsConfig) { |
328 | // Go-to-definition will resolve as marked if FOO is defined. |
329 | Annotations Example(R"cpp( |
330 | #ifdef FOO |
331 | int [[x]]; |
332 | #else |
333 | int x; |
334 | #endif |
335 | int y = ^x; |
336 | )cpp" ); |
337 | // Provide conditional config that defines FOO for foo.cc. |
338 | class ConfigProvider : public config::Provider { |
339 | std::vector<config::CompiledFragment> |
340 | getFragments(const config::Params &, |
341 | config::DiagnosticCallback DC) const override { |
342 | config::Fragment F; |
343 | F.If.PathMatch.emplace_back(args: ".*foo.cc" ); |
344 | F.CompileFlags.Add.emplace_back(args: "-DFOO=1" ); |
345 | return {std::move(F).compile(DC)}; |
346 | } |
347 | } CfgProvider; |
348 | |
349 | auto Opts = ClangdServer::optsForTest(); |
350 | Opts.ContextProvider = |
351 | ClangdServer::createConfiguredContextProvider(Provider: &CfgProvider, nullptr); |
352 | OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{}, |
353 | CommandMangler::forTests()); |
354 | MockFS FS; |
355 | ClangdServer Server(CDB, FS, Opts); |
356 | // foo.cc sees the expected definition, as FOO is defined. |
357 | Server.addDocument(File: testPath(File: "foo.cc" ), Contents: Example.code()); |
358 | auto Result = runLocateSymbolAt(Server, File: testPath(File: "foo.cc" ), Pos: Example.point()); |
359 | ASSERT_TRUE(bool(Result)) << Result.takeError(); |
360 | ASSERT_THAT(*Result, SizeIs(1)); |
361 | EXPECT_EQ(Result->front().PreferredDeclaration.range, Example.range()); |
362 | // bar.cc gets a different result, as FOO is not defined. |
363 | Server.addDocument(File: testPath(File: "bar.cc" ), Contents: Example.code()); |
364 | Result = runLocateSymbolAt(Server, File: testPath(File: "bar.cc" ), Pos: Example.point()); |
365 | ASSERT_TRUE(bool(Result)) << Result.takeError(); |
366 | ASSERT_THAT(*Result, SizeIs(1)); |
367 | EXPECT_NE(Result->front().PreferredDeclaration.range, Example.range()); |
368 | } |
369 | |
370 | TEST(ClangdServerTest, PropagatesVersion) { |
371 | MockCompilationDatabase CDB; |
372 | MockFS FS; |
373 | struct Callbacks : public ClangdServer::Callbacks { |
374 | void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
375 | llvm::ArrayRef<Diag> Diagnostics) override { |
376 | Got = Version.str(); |
377 | } |
378 | std::string Got = "" ; |
379 | } Callbacks; |
380 | |
381 | // Verify that the version is plumbed to diagnostics. |
382 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks); |
383 | runAddDocument(Server, File: testPath(File: "foo.cpp" ), Contents: "void main(){}" , Version: "42" ); |
384 | EXPECT_EQ(Callbacks.Got, "42" ); |
385 | } |
386 | |
387 | // Only enable this test on Unix |
388 | #ifdef LLVM_ON_UNIX |
389 | TEST(ClangdServerTest, SearchLibDir) { |
390 | // Checks that searches for GCC installation is done through vfs. |
391 | MockFS FS; |
392 | ErrorCheckingCallbacks DiagConsumer; |
393 | MockCompilationDatabase CDB; |
394 | CDB.ExtraClangFlags.insert(position: CDB.ExtraClangFlags.end(), |
395 | l: {"-xc++" , "-target" , "x86_64-linux-unknown" , |
396 | "-m64" , "--gcc-toolchain=/randomusr" , |
397 | "-stdlib=libstdc++" }); |
398 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
399 | |
400 | // Just a random gcc version string |
401 | SmallString<8> Version("4.9.3" ); |
402 | |
403 | // A lib dir for gcc installation |
404 | SmallString<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu" ); |
405 | llvm::sys::path::append(path&: LibDir, a: Version); |
406 | |
407 | // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc |
408 | // installation there. |
409 | SmallString<64> MockLibFile; |
410 | llvm::sys::path::append(path&: MockLibFile, a: LibDir, b: "64" , c: "crtbegin.o" ); |
411 | FS.Files[MockLibFile] = "" ; |
412 | |
413 | SmallString<64> IncludeDir("/randomusr/include/c++" ); |
414 | llvm::sys::path::append(path&: IncludeDir, a: Version); |
415 | |
416 | SmallString<64> StringPath; |
417 | llvm::sys::path::append(path&: StringPath, a: IncludeDir, b: "string" ); |
418 | FS.Files[StringPath] = "class mock_string {};" ; |
419 | |
420 | auto FooCpp = testPath(File: "foo.cpp" ); |
421 | const auto *SourceContents = R"cpp( |
422 | #include <string> |
423 | mock_string x; |
424 | )cpp" ; |
425 | FS.Files[FooCpp] = SourceContents; |
426 | |
427 | runAddDocument(Server, File: FooCpp, Contents: SourceContents); |
428 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
429 | |
430 | const auto *SourceContentsWithError = R"cpp( |
431 | #include <string> |
432 | std::string x; |
433 | )cpp" ; |
434 | runAddDocument(Server, File: FooCpp, Contents: SourceContentsWithError); |
435 | EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); |
436 | } |
437 | #endif // LLVM_ON_UNIX |
438 | |
439 | TEST(ClangdServerTest, ForceReparseCompileCommand) { |
440 | MockFS FS; |
441 | ErrorCheckingCallbacks DiagConsumer; |
442 | MockCompilationDatabase CDB; |
443 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
444 | |
445 | auto FooCpp = testPath(File: "foo.cpp" ); |
446 | const auto *SourceContents1 = R"cpp( |
447 | template <class T> |
448 | struct foo { T x; }; |
449 | )cpp" ; |
450 | const auto *SourceContents2 = R"cpp( |
451 | template <class T> |
452 | struct bar { T x; }; |
453 | )cpp" ; |
454 | |
455 | FS.Files[FooCpp] = "" ; |
456 | |
457 | // First parse files in C mode and check they produce errors. |
458 | CDB.ExtraClangFlags = {"-xc" }; |
459 | runAddDocument(Server, File: FooCpp, Contents: SourceContents1); |
460 | EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); |
461 | runAddDocument(Server, File: FooCpp, Contents: SourceContents2); |
462 | EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); |
463 | |
464 | // Now switch to C++ mode. |
465 | CDB.ExtraClangFlags = {"-xc++" }; |
466 | runAddDocument(Server, File: FooCpp, Contents: SourceContents2); |
467 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
468 | // Subsequent addDocument calls should finish without errors too. |
469 | runAddDocument(Server, File: FooCpp, Contents: SourceContents1); |
470 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
471 | runAddDocument(Server, File: FooCpp, Contents: SourceContents2); |
472 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
473 | } |
474 | |
475 | TEST(ClangdServerTest, ForceReparseCompileCommandDefines) { |
476 | MockFS FS; |
477 | ErrorCheckingCallbacks DiagConsumer; |
478 | MockCompilationDatabase CDB; |
479 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
480 | |
481 | auto FooCpp = testPath(File: "foo.cpp" ); |
482 | const auto *SourceContents = R"cpp( |
483 | #ifdef WITH_ERROR |
484 | this |
485 | #endif |
486 | |
487 | int main() { return 0; } |
488 | )cpp" ; |
489 | FS.Files[FooCpp] = "" ; |
490 | |
491 | // Parse with define, we expect to see the errors. |
492 | CDB.ExtraClangFlags = {"-DWITH_ERROR" }; |
493 | runAddDocument(Server, File: FooCpp, Contents: SourceContents); |
494 | EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); |
495 | |
496 | // Parse without the define, no errors should be produced. |
497 | CDB.ExtraClangFlags = {}; |
498 | runAddDocument(Server, File: FooCpp, Contents: SourceContents); |
499 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
500 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
501 | // Subsequent addDocument call should finish without errors too. |
502 | runAddDocument(Server, File: FooCpp, Contents: SourceContents); |
503 | EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); |
504 | } |
505 | |
506 | // Test ClangdServer.reparseOpenedFiles. |
507 | TEST(ClangdServerTest, ReparseOpenedFiles) { |
508 | Annotations FooSource(R"cpp( |
509 | #ifdef MACRO |
510 | static void $one[[bob]]() {} |
511 | #else |
512 | static void $two[[bob]]() {} |
513 | #endif |
514 | |
515 | int main () { bo^b (); return 0; } |
516 | )cpp" ); |
517 | |
518 | Annotations BarSource(R"cpp( |
519 | #ifdef MACRO |
520 | this is an error |
521 | #endif |
522 | )cpp" ); |
523 | |
524 | Annotations BazSource(R"cpp( |
525 | int hello; |
526 | )cpp" ); |
527 | |
528 | MockFS FS; |
529 | MockCompilationDatabase CDB; |
530 | MultipleErrorCheckingCallbacks DiagConsumer; |
531 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
532 | |
533 | auto FooCpp = testPath(File: "foo.cpp" ); |
534 | auto BarCpp = testPath(File: "bar.cpp" ); |
535 | auto BazCpp = testPath(File: "baz.cpp" ); |
536 | |
537 | FS.Files[FooCpp] = "" ; |
538 | FS.Files[BarCpp] = "" ; |
539 | FS.Files[BazCpp] = "" ; |
540 | |
541 | CDB.ExtraClangFlags = {"-DMACRO=1" }; |
542 | Server.addDocument(File: FooCpp, Contents: FooSource.code()); |
543 | Server.addDocument(File: BarCpp, Contents: BarSource.code()); |
544 | Server.addDocument(File: BazCpp, Contents: BazSource.code()); |
545 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
546 | |
547 | EXPECT_THAT(DiagConsumer.filesWithDiags(), |
548 | UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, true), |
549 | Pair(BazCpp, false))); |
550 | |
551 | auto Locations = runLocateSymbolAt(Server, File: FooCpp, Pos: FooSource.point()); |
552 | EXPECT_TRUE(bool(Locations)); |
553 | EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("one" )))); |
554 | |
555 | // Undefine MACRO, close baz.cpp. |
556 | CDB.ExtraClangFlags.clear(); |
557 | DiagConsumer.clear(); |
558 | Server.removeDocument(File: BazCpp); |
559 | Server.addDocument(File: FooCpp, Contents: FooSource.code()); |
560 | Server.addDocument(File: BarCpp, Contents: BarSource.code()); |
561 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
562 | |
563 | EXPECT_THAT(DiagConsumer.filesWithDiags(), |
564 | UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, false))); |
565 | |
566 | Locations = runLocateSymbolAt(Server, File: FooCpp, Pos: FooSource.point()); |
567 | EXPECT_TRUE(bool(Locations)); |
568 | EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two" )))); |
569 | } |
570 | |
571 | MATCHER_P4(Stats, Name, UsesMemory, PreambleBuilds, ASTBuilds, "" ) { |
572 | return arg.first() == Name && |
573 | (arg.second.UsedBytesAST + arg.second.UsedBytesPreamble != 0) == |
574 | UsesMemory && |
575 | std::tie(arg.second.PreambleBuilds, ASTBuilds) == |
576 | std::tie(PreambleBuilds, ASTBuilds); |
577 | } |
578 | |
579 | TEST(ClangdServerTest, FileStats) { |
580 | MockFS FS; |
581 | ErrorCheckingCallbacks DiagConsumer; |
582 | MockCompilationDatabase CDB; |
583 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
584 | |
585 | Path FooCpp = testPath(File: "foo.cpp" ); |
586 | const auto *SourceContents = R"cpp( |
587 | struct Something { |
588 | int method(); |
589 | }; |
590 | )cpp" ; |
591 | Path BarCpp = testPath(File: "bar.cpp" ); |
592 | |
593 | FS.Files[FooCpp] = "" ; |
594 | FS.Files[BarCpp] = "" ; |
595 | |
596 | EXPECT_THAT(Server.fileStats(), IsEmpty()); |
597 | |
598 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
599 | Server.addDocument(File: BarCpp, Contents: SourceContents); |
600 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
601 | |
602 | EXPECT_THAT(Server.fileStats(), |
603 | UnorderedElementsAre(Stats(FooCpp, true, 1, 1), |
604 | Stats(BarCpp, true, 1, 1))); |
605 | |
606 | Server.removeDocument(File: FooCpp); |
607 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
608 | EXPECT_THAT(Server.fileStats(), ElementsAre(Stats(BarCpp, true, 1, 1))); |
609 | |
610 | Server.removeDocument(File: BarCpp); |
611 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
612 | EXPECT_THAT(Server.fileStats(), IsEmpty()); |
613 | } |
614 | |
615 | TEST(ClangdServerTest, InvalidCompileCommand) { |
616 | MockFS FS; |
617 | ErrorCheckingCallbacks DiagConsumer; |
618 | MockCompilationDatabase CDB; |
619 | |
620 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
621 | |
622 | auto FooCpp = testPath(File: "foo.cpp" ); |
623 | // clang cannot create CompilerInvocation in this case. |
624 | CDB.ExtraClangFlags.push_back(x: "-###" ); |
625 | |
626 | // Clang can't parse command args in that case, but we shouldn't crash. |
627 | runAddDocument(Server, File: FooCpp, Contents: "int main() {}" ); |
628 | |
629 | EXPECT_EQ(dumpAST(Server, FooCpp), "<no-ast>" ); |
630 | EXPECT_ERROR(runLocateSymbolAt(Server, FooCpp, Position())); |
631 | EXPECT_ERROR(runFindDocumentHighlights(Server, FooCpp, Position())); |
632 | EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name" , |
633 | clangd::RenameOptions())); |
634 | EXPECT_ERROR( |
635 | runSignatureHelp(Server, FooCpp, Position(), MarkupKind::PlainText)); |
636 | // Identifier-based fallback completion. |
637 | EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(), |
638 | clangd::CodeCompleteOptions())) |
639 | .Completions, |
640 | ElementsAre(Field(&CodeCompletion::Name, "int" ), |
641 | Field(&CodeCompletion::Name, "main" ))); |
642 | } |
643 | |
644 | TEST(ClangdThreadingTest, StressTest) { |
645 | // Without 'static' clang gives an error for a usage inside TestDiagConsumer. |
646 | static const unsigned FilesCount = 5; |
647 | const unsigned RequestsCount = 500; |
648 | // Blocking requests wait for the parsing to complete, they slow down the test |
649 | // dramatically, so they are issued rarely. Each |
650 | // BlockingRequestInterval-request will be a blocking one. |
651 | const unsigned BlockingRequestInterval = 40; |
652 | |
653 | const auto *SourceContentsWithoutErrors = R"cpp( |
654 | int a; |
655 | int b; |
656 | int c; |
657 | int d; |
658 | )cpp" ; |
659 | |
660 | const auto *SourceContentsWithErrors = R"cpp( |
661 | int a = x; |
662 | int b; |
663 | int c; |
664 | int d; |
665 | )cpp" ; |
666 | |
667 | // Giving invalid line and column number should not crash ClangdServer, but |
668 | // just to make sure we're sometimes hitting the bounds inside the file we |
669 | // limit the intervals of line and column number that are generated. |
670 | unsigned MaxLineForFileRequests = 7; |
671 | unsigned MaxColumnForFileRequests = 10; |
672 | |
673 | std::vector<std::string> FilePaths; |
674 | MockFS FS; |
675 | for (unsigned I = 0; I < FilesCount; ++I) { |
676 | std::string Name = std::string("Foo" ) + std::to_string(val: I) + ".cpp" ; |
677 | FS.Files[Name] = "" ; |
678 | FilePaths.push_back(x: testPath(File: Name)); |
679 | } |
680 | |
681 | struct FileStat { |
682 | unsigned HitsWithoutErrors = 0; |
683 | unsigned HitsWithErrors = 0; |
684 | bool HadErrorsInLastDiags = false; |
685 | }; |
686 | |
687 | class TestDiagConsumer : public ClangdServer::Callbacks { |
688 | public: |
689 | TestDiagConsumer() : Stats(FilesCount, FileStat()) {} |
690 | |
691 | void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
692 | llvm::ArrayRef<Diag> Diagnostics) override { |
693 | StringRef FileIndexStr = llvm::sys::path::stem(path: File); |
694 | ASSERT_TRUE(FileIndexStr.consume_front("Foo" )); |
695 | |
696 | unsigned long FileIndex = std::stoul(str: FileIndexStr.str()); |
697 | |
698 | bool HadError = diagsContainErrors(Diagnostics); |
699 | |
700 | std::lock_guard<std::mutex> Lock(Mutex); |
701 | if (HadError) |
702 | Stats[FileIndex].HitsWithErrors++; |
703 | else |
704 | Stats[FileIndex].HitsWithoutErrors++; |
705 | Stats[FileIndex].HadErrorsInLastDiags = HadError; |
706 | } |
707 | |
708 | std::vector<FileStat> takeFileStats() { |
709 | std::lock_guard<std::mutex> Lock(Mutex); |
710 | return std::move(Stats); |
711 | } |
712 | |
713 | private: |
714 | std::mutex Mutex; |
715 | std::vector<FileStat> Stats; |
716 | }; |
717 | |
718 | struct RequestStats { |
719 | unsigned RequestsWithoutErrors = 0; |
720 | unsigned RequestsWithErrors = 0; |
721 | bool LastContentsHadErrors = false; |
722 | bool FileIsRemoved = true; |
723 | }; |
724 | |
725 | std::vector<RequestStats> ReqStats; |
726 | ReqStats.reserve(n: FilesCount); |
727 | for (unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex) |
728 | ReqStats.emplace_back(); |
729 | |
730 | TestDiagConsumer DiagConsumer; |
731 | { |
732 | MockCompilationDatabase CDB; |
733 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
734 | |
735 | // Prepare some random distributions for the test. |
736 | std::random_device RandGen; |
737 | |
738 | std::uniform_int_distribution<unsigned> FileIndexDist(0, FilesCount - 1); |
739 | // Pass a text that contains compiler errors to addDocument in about 20% of |
740 | // all requests. |
741 | std::bernoulli_distribution ShouldHaveErrorsDist(0.2); |
742 | // Line and Column numbers for requests that need them. |
743 | std::uniform_int_distribution<int> LineDist(0, MaxLineForFileRequests); |
744 | std::uniform_int_distribution<int> ColumnDist(0, MaxColumnForFileRequests); |
745 | |
746 | // Some helpers. |
747 | auto UpdateStatsOnAddDocument = [&](unsigned FileIndex, bool HadErrors) { |
748 | auto &Stats = ReqStats[FileIndex]; |
749 | |
750 | if (HadErrors) |
751 | ++Stats.RequestsWithErrors; |
752 | else |
753 | ++Stats.RequestsWithoutErrors; |
754 | Stats.LastContentsHadErrors = HadErrors; |
755 | Stats.FileIsRemoved = false; |
756 | }; |
757 | |
758 | auto UpdateStatsOnRemoveDocument = [&](unsigned FileIndex) { |
759 | auto &Stats = ReqStats[FileIndex]; |
760 | |
761 | Stats.FileIsRemoved = true; |
762 | }; |
763 | |
764 | auto AddDocument = [&](unsigned FileIndex, bool SkipCache) { |
765 | bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen); |
766 | Server.addDocument(File: FilePaths[FileIndex], |
767 | Contents: ShouldHaveErrors ? SourceContentsWithErrors |
768 | : SourceContentsWithoutErrors); |
769 | UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors); |
770 | }; |
771 | |
772 | // Various requests that we would randomly run. |
773 | auto AddDocumentRequest = [&]() { |
774 | unsigned FileIndex = FileIndexDist(RandGen); |
775 | AddDocument(FileIndex, /*SkipCache=*/false); |
776 | }; |
777 | |
778 | auto ForceReparseRequest = [&]() { |
779 | unsigned FileIndex = FileIndexDist(RandGen); |
780 | AddDocument(FileIndex, /*SkipCache=*/true); |
781 | }; |
782 | |
783 | auto RemoveDocumentRequest = [&]() { |
784 | unsigned FileIndex = FileIndexDist(RandGen); |
785 | // Make sure we don't violate the ClangdServer's contract. |
786 | if (ReqStats[FileIndex].FileIsRemoved) |
787 | AddDocument(FileIndex, /*SkipCache=*/false); |
788 | |
789 | Server.removeDocument(File: FilePaths[FileIndex]); |
790 | UpdateStatsOnRemoveDocument(FileIndex); |
791 | }; |
792 | |
793 | auto CodeCompletionRequest = [&]() { |
794 | unsigned FileIndex = FileIndexDist(RandGen); |
795 | // Make sure we don't violate the ClangdServer's contract. |
796 | if (ReqStats[FileIndex].FileIsRemoved) |
797 | AddDocument(FileIndex, /*SkipCache=*/false); |
798 | |
799 | Position Pos; |
800 | Pos.line = LineDist(RandGen); |
801 | Pos.character = ColumnDist(RandGen); |
802 | // FIXME(ibiryukov): Also test async completion requests. |
803 | // Simply putting CodeCompletion into async requests now would make |
804 | // tests slow, since there's no way to cancel previous completion |
805 | // requests as opposed to AddDocument/RemoveDocument, which are implicitly |
806 | // cancelled by any subsequent AddDocument/RemoveDocument request to the |
807 | // same file. |
808 | cantFail(ValOrErr: runCodeComplete(Server, File: FilePaths[FileIndex], Pos, |
809 | Opts: clangd::CodeCompleteOptions())); |
810 | }; |
811 | |
812 | auto LocateSymbolRequest = [&]() { |
813 | unsigned FileIndex = FileIndexDist(RandGen); |
814 | // Make sure we don't violate the ClangdServer's contract. |
815 | if (ReqStats[FileIndex].FileIsRemoved) |
816 | AddDocument(FileIndex, /*SkipCache=*/false); |
817 | |
818 | Position Pos; |
819 | Pos.line = LineDist(RandGen); |
820 | Pos.character = ColumnDist(RandGen); |
821 | |
822 | ASSERT_TRUE(!!runLocateSymbolAt(Server, FilePaths[FileIndex], Pos)); |
823 | }; |
824 | |
825 | std::vector<std::function<void()>> AsyncRequests = { |
826 | AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest}; |
827 | std::vector<std::function<void()>> BlockingRequests = { |
828 | CodeCompletionRequest, LocateSymbolRequest}; |
829 | |
830 | // Bash requests to ClangdServer in a loop. |
831 | std::uniform_int_distribution<int> AsyncRequestIndexDist( |
832 | 0, AsyncRequests.size() - 1); |
833 | std::uniform_int_distribution<int> BlockingRequestIndexDist( |
834 | 0, BlockingRequests.size() - 1); |
835 | for (unsigned I = 1; I <= RequestsCount; ++I) { |
836 | if (I % BlockingRequestInterval != 0) { |
837 | // Issue an async request most of the time. It should be fast. |
838 | unsigned RequestIndex = AsyncRequestIndexDist(RandGen); |
839 | AsyncRequests[RequestIndex](); |
840 | } else { |
841 | // Issue a blocking request once in a while. |
842 | auto RequestIndex = BlockingRequestIndexDist(RandGen); |
843 | BlockingRequests[RequestIndex](); |
844 | } |
845 | } |
846 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
847 | } |
848 | |
849 | // Check some invariants about the state of the program. |
850 | std::vector<FileStat> Stats = DiagConsumer.takeFileStats(); |
851 | for (unsigned I = 0; I < FilesCount; ++I) { |
852 | if (!ReqStats[I].FileIsRemoved) { |
853 | ASSERT_EQ(Stats[I].HadErrorsInLastDiags, |
854 | ReqStats[I].LastContentsHadErrors); |
855 | } |
856 | |
857 | ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors); |
858 | ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors); |
859 | } |
860 | } |
861 | |
862 | TEST(ClangdThreadingTest, NoConcurrentDiagnostics) { |
863 | class NoConcurrentAccessDiagConsumer : public ClangdServer::Callbacks { |
864 | public: |
865 | std::atomic<int> Count = {0}; |
866 | |
867 | NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse) |
868 | : StartSecondReparse(std::move(StartSecondReparse)) {} |
869 | |
870 | void onDiagnosticsReady(PathRef, llvm::StringRef, |
871 | llvm::ArrayRef<Diag>) override { |
872 | ++Count; |
873 | std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t()); |
874 | ASSERT_TRUE(Lock.owns_lock()) |
875 | << "Detected concurrent onDiagnosticsReady calls for the same file." ; |
876 | |
877 | // If we started the second parse immediately, it might cancel the first. |
878 | // So we don't allow it to start until the first has delivered diags... |
879 | if (FirstRequest) { |
880 | FirstRequest = false; |
881 | StartSecondReparse.set_value(); |
882 | // ... but then we wait long enough that the callbacks would overlap. |
883 | std::this_thread::sleep_for(rtime: std::chrono::milliseconds(50)); |
884 | } |
885 | } |
886 | |
887 | private: |
888 | std::mutex Mutex; |
889 | bool FirstRequest = true; |
890 | std::promise<void> StartSecondReparse; |
891 | }; |
892 | |
893 | const auto *SourceContentsWithoutErrors = R"cpp( |
894 | int a; |
895 | int b; |
896 | int c; |
897 | int d; |
898 | )cpp" ; |
899 | |
900 | const auto *SourceContentsWithErrors = R"cpp( |
901 | int a = x; |
902 | int b; |
903 | int c; |
904 | int d; |
905 | )cpp" ; |
906 | |
907 | auto FooCpp = testPath(File: "foo.cpp" ); |
908 | MockFS FS; |
909 | FS.Files[FooCpp] = "" ; |
910 | |
911 | std::promise<void> StartSecondPromise; |
912 | std::future<void> StartSecond = StartSecondPromise.get_future(); |
913 | |
914 | NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise)); |
915 | MockCompilationDatabase CDB; |
916 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
917 | Server.addDocument(File: FooCpp, Contents: SourceContentsWithErrors); |
918 | StartSecond.wait(); |
919 | Server.addDocument(File: FooCpp, Contents: SourceContentsWithoutErrors); |
920 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
921 | ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both? |
922 | } |
923 | |
924 | TEST(ClangdServerTest, FormatCode) { |
925 | MockFS FS; |
926 | ErrorCheckingCallbacks DiagConsumer; |
927 | MockCompilationDatabase CDB; |
928 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
929 | |
930 | auto Path = testPath(File: "foo.cpp" ); |
931 | std::string Code = R"cpp( |
932 | #include "x.h" |
933 | #include "y.h" |
934 | |
935 | void f( ) {} |
936 | )cpp" ; |
937 | std::string Expected = R"cpp( |
938 | #include "x.h" |
939 | #include "y.h" |
940 | |
941 | void f() {} |
942 | )cpp" ; |
943 | FS.Files[Path] = Code; |
944 | runAddDocument(Server, File: Path, Contents: Code); |
945 | |
946 | auto Replaces = runFormatFile(Server, File: Path, /*Rng=*/std::nullopt); |
947 | EXPECT_TRUE(static_cast<bool>(Replaces)); |
948 | auto Changed = tooling::applyAllReplacements(Code, Replaces: *Replaces); |
949 | EXPECT_TRUE(static_cast<bool>(Changed)); |
950 | EXPECT_EQ(Expected, *Changed); |
951 | } |
952 | |
953 | TEST(ClangdServerTest, ChangedHeaderFromISystem) { |
954 | MockFS FS; |
955 | ErrorCheckingCallbacks DiagConsumer; |
956 | MockCompilationDatabase CDB; |
957 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
958 | |
959 | auto SourcePath = testPath(File: "source/foo.cpp" ); |
960 | auto = testPath(File: "headers/foo.h" ); |
961 | FS.Files[HeaderPath] = "struct X { int bar; };" ; |
962 | Annotations Code(R"cpp( |
963 | #include "foo.h" |
964 | |
965 | int main() { |
966 | X().ba^ |
967 | })cpp" ); |
968 | CDB.ExtraClangFlags.push_back(x: "-xc++" ); |
969 | CDB.ExtraClangFlags.push_back(x: "-isystem" + testPath(File: "headers" )); |
970 | |
971 | runAddDocument(Server, File: SourcePath, Contents: Code.code()); |
972 | auto Completions = cantFail(ValOrErr: runCodeComplete(Server, File: SourcePath, Pos: Code.point(), |
973 | Opts: clangd::CodeCompleteOptions())) |
974 | .Completions; |
975 | EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar" ))); |
976 | // Update the header and rerun addDocument to make sure we get the updated |
977 | // files. |
978 | FS.Files[HeaderPath] = "struct X { int bar; int baz; };" ; |
979 | runAddDocument(Server, File: SourcePath, Contents: Code.code()); |
980 | Completions = cantFail(ValOrErr: runCodeComplete(Server, File: SourcePath, Pos: Code.point(), |
981 | Opts: clangd::CodeCompleteOptions())) |
982 | .Completions; |
983 | // We want to make sure we see the updated version. |
984 | EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar" ), |
985 | Field(&CodeCompletion::Name, "baz" ))); |
986 | } |
987 | |
988 | // FIXME(ioeric): make this work for windows again. |
989 | #ifndef _WIN32 |
990 | // Check that running code completion doesn't stat() a bunch of files from the |
991 | // preamble again. (They should be using the preamble's stat-cache) |
992 | TEST(ClangdTests, PreambleVFSStatCache) { |
993 | class StatRecordingFS : public ThreadsafeFS { |
994 | llvm::StringMap<unsigned> &CountStats; |
995 | |
996 | public: |
997 | // If relative paths are used, they are resolved with testPath(). |
998 | llvm::StringMap<std::string> Files; |
999 | |
1000 | StatRecordingFS(llvm::StringMap<unsigned> &CountStats) |
1001 | : CountStats(CountStats) {} |
1002 | |
1003 | private: |
1004 | IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override { |
1005 | class StatRecordingVFS : public llvm::vfs::ProxyFileSystem { |
1006 | public: |
1007 | StatRecordingVFS(IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, |
1008 | llvm::StringMap<unsigned> &CountStats) |
1009 | : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {} |
1010 | |
1011 | llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> |
1012 | openFileForRead(const Twine &Path) override { |
1013 | ++CountStats[llvm::sys::path::filename(path: Path.str())]; |
1014 | return ProxyFileSystem::openFileForRead(Path); |
1015 | } |
1016 | llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override { |
1017 | ++CountStats[llvm::sys::path::filename(path: Path.str())]; |
1018 | return ProxyFileSystem::status(Path); |
1019 | } |
1020 | |
1021 | private: |
1022 | llvm::StringMap<unsigned> &CountStats; |
1023 | }; |
1024 | |
1025 | return IntrusiveRefCntPtr<StatRecordingVFS>( |
1026 | new StatRecordingVFS(buildTestFS(Files), CountStats)); |
1027 | } |
1028 | }; |
1029 | |
1030 | llvm::StringMap<unsigned> CountStats; |
1031 | StatRecordingFS FS(CountStats); |
1032 | ErrorCheckingCallbacks DiagConsumer; |
1033 | MockCompilationDatabase CDB; |
1034 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
1035 | |
1036 | auto SourcePath = testPath(File: "foo.cpp" ); |
1037 | auto = testPath(File: "foo.h" ); |
1038 | FS.Files[HeaderPath] = "struct TestSym {};" ; |
1039 | Annotations Code(R"cpp( |
1040 | #include "foo.h" |
1041 | |
1042 | int main() { |
1043 | TestSy^ |
1044 | })cpp" ); |
1045 | |
1046 | runAddDocument(Server, File: SourcePath, Contents: Code.code()); |
1047 | |
1048 | unsigned Before = CountStats["foo.h" ]; |
1049 | EXPECT_GT(Before, 0u); |
1050 | auto Completions = cantFail(ValOrErr: runCodeComplete(Server, File: SourcePath, Pos: Code.point(), |
1051 | Opts: clangd::CodeCompleteOptions())) |
1052 | .Completions; |
1053 | EXPECT_EQ(CountStats["foo.h" ], Before); |
1054 | EXPECT_THAT(Completions, |
1055 | ElementsAre(Field(&CodeCompletion::Name, "TestSym" ))); |
1056 | } |
1057 | #endif |
1058 | |
1059 | TEST(ClangdServerTest, FallbackWhenPreambleIsNotReady) { |
1060 | MockFS FS; |
1061 | ErrorCheckingCallbacks DiagConsumer; |
1062 | MockCompilationDatabase CDB; |
1063 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
1064 | |
1065 | auto FooCpp = testPath(File: "foo.cpp" ); |
1066 | Annotations Code(R"cpp( |
1067 | namespace ns { int xyz; } |
1068 | using namespace ns; |
1069 | int main() { |
1070 | xy^ |
1071 | })cpp" ); |
1072 | FS.Files[FooCpp] = FooCpp; |
1073 | |
1074 | auto Opts = clangd::CodeCompleteOptions(); |
1075 | Opts.RunParser = CodeCompleteOptions::ParseIfReady; |
1076 | |
1077 | // This will make compile command broken and preamble absent. |
1078 | CDB.ExtraClangFlags = {"-###" }; |
1079 | Server.addDocument(File: FooCpp, Contents: Code.code()); |
1080 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1081 | auto Res = cantFail(ValOrErr: runCodeComplete(Server, File: FooCpp, Pos: Code.point(), Opts)); |
1082 | EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); |
1083 | // Identifier-based fallback completion doesn't know about "symbol" scope. |
1084 | EXPECT_THAT(Res.Completions, |
1085 | ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz" ), |
1086 | Field(&CodeCompletion::Scope, "" )))); |
1087 | |
1088 | // Make the compile command work again. |
1089 | CDB.ExtraClangFlags = {"-std=c++11" }; |
1090 | Server.addDocument(File: FooCpp, Contents: Code.code()); |
1091 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1092 | EXPECT_THAT( |
1093 | cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions, |
1094 | ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz" ), |
1095 | Field(&CodeCompletion::Scope, "ns::" )))); |
1096 | |
1097 | // Now force identifier-based completion. |
1098 | Opts.RunParser = CodeCompleteOptions::NeverParse; |
1099 | EXPECT_THAT( |
1100 | cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)).Completions, |
1101 | ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz" ), |
1102 | Field(&CodeCompletion::Scope, "" )))); |
1103 | } |
1104 | |
1105 | TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) { |
1106 | MockFS FS; |
1107 | ErrorCheckingCallbacks DiagConsumer; |
1108 | // Returns compile command only when notified. |
1109 | class DelayedCompilationDatabase : public GlobalCompilationDatabase { |
1110 | public: |
1111 | DelayedCompilationDatabase(Notification &CanReturnCommand) |
1112 | : CanReturnCommand(CanReturnCommand) {} |
1113 | |
1114 | std::optional<tooling::CompileCommand> |
1115 | getCompileCommand(PathRef File) const override { |
1116 | // FIXME: make this timeout and fail instead of waiting forever in case |
1117 | // something goes wrong. |
1118 | CanReturnCommand.wait(); |
1119 | auto FileName = llvm::sys::path::filename(path: File); |
1120 | std::vector<std::string> CommandLine = {"clangd" , "-ffreestanding" , |
1121 | std::string(File)}; |
1122 | return {tooling::CompileCommand(llvm::sys::path::parent_path(path: File), |
1123 | FileName, std::move(CommandLine), "" )}; |
1124 | } |
1125 | |
1126 | std::vector<std::string> ; |
1127 | |
1128 | private: |
1129 | Notification &CanReturnCommand; |
1130 | }; |
1131 | |
1132 | Notification CanReturnCommand; |
1133 | DelayedCompilationDatabase CDB(CanReturnCommand); |
1134 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
1135 | |
1136 | auto FooCpp = testPath(File: "foo.cpp" ); |
1137 | Annotations Code(R"cpp( |
1138 | namespace ns { int xyz; } |
1139 | using namespace ns; |
1140 | int main() { |
1141 | xy^ |
1142 | })cpp" ); |
1143 | FS.Files[FooCpp] = FooCpp; |
1144 | Server.addDocument(File: FooCpp, Contents: Code.code()); |
1145 | |
1146 | // Sleep for some time to make sure code completion is not run because update |
1147 | // hasn't been scheduled. |
1148 | std::this_thread::sleep_for(rtime: std::chrono::milliseconds(10)); |
1149 | auto Opts = clangd::CodeCompleteOptions(); |
1150 | Opts.RunParser = CodeCompleteOptions::ParseIfReady; |
1151 | |
1152 | auto Res = cantFail(ValOrErr: runCodeComplete(Server, File: FooCpp, Pos: Code.point(), Opts)); |
1153 | EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); |
1154 | |
1155 | CanReturnCommand.notify(); |
1156 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1157 | EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(), |
1158 | clangd::CodeCompleteOptions())) |
1159 | .Completions, |
1160 | ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz" ), |
1161 | Field(&CodeCompletion::Scope, "ns::" )))); |
1162 | } |
1163 | |
1164 | TEST(ClangdServerTest, CustomAction) { |
1165 | OverlayCDB CDB(/*Base=*/nullptr); |
1166 | MockFS FS; |
1167 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest()); |
1168 | |
1169 | Server.addDocument(File: testPath(File: "foo.cc" ), Contents: "void x();" ); |
1170 | Decl::Kind XKind = Decl::TranslationUnit; |
1171 | EXPECT_THAT_ERROR(runCustomAction(Server, testPath("foo.cc" ), |
1172 | [&](InputsAndAST AST) { |
1173 | XKind = findDecl(AST.AST, "x" ).getKind(); |
1174 | }), |
1175 | llvm::Succeeded()); |
1176 | EXPECT_EQ(XKind, Decl::Function); |
1177 | } |
1178 | |
1179 | // Tests fails when built with asan due to stack overflow. So skip running the |
1180 | // test as a workaround. |
1181 | #if !defined(__has_feature) || !__has_feature(address_sanitizer) |
1182 | TEST(ClangdServerTest, TestStackOverflow) { |
1183 | MockFS FS; |
1184 | ErrorCheckingCallbacks DiagConsumer; |
1185 | MockCompilationDatabase CDB; |
1186 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &DiagConsumer); |
1187 | |
1188 | const char *SourceContents = R"cpp( |
1189 | constexpr int foo() { return foo(); } |
1190 | static_assert(foo()); |
1191 | )cpp" ; |
1192 | |
1193 | auto FooCpp = testPath(File: "foo.cpp" ); |
1194 | FS.Files[FooCpp] = SourceContents; |
1195 | |
1196 | Server.addDocument(File: FooCpp, Contents: SourceContents); |
1197 | ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics" ; |
1198 | // check that we got a constexpr depth error, and not crashed by stack |
1199 | // overflow |
1200 | EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); |
1201 | } |
1202 | #endif |
1203 | |
1204 | TEST(ClangdServer, TidyOverrideTest) { |
1205 | struct DiagsCheckingCallback : public ClangdServer::Callbacks { |
1206 | public: |
1207 | void onDiagnosticsReady(PathRef File, llvm::StringRef Version, |
1208 | llvm::ArrayRef<Diag> Diagnostics) override { |
1209 | std::lock_guard<std::mutex> Lock(Mutex); |
1210 | HadDiagsInLastCallback = !Diagnostics.empty(); |
1211 | } |
1212 | |
1213 | std::mutex Mutex; |
1214 | bool HadDiagsInLastCallback = false; |
1215 | } DiagConsumer; |
1216 | |
1217 | MockFS FS; |
1218 | // These checks don't work well in clangd, even if configured they shouldn't |
1219 | // run. |
1220 | FS.Files[testPath(File: ".clang-tidy" )] = R"( |
1221 | Checks: -*,bugprone-use-after-move,llvm-header-guard |
1222 | )" ; |
1223 | MockCompilationDatabase CDB; |
1224 | std::vector<TidyProvider> Stack; |
1225 | Stack.push_back(x: provideClangTidyFiles(FS)); |
1226 | Stack.push_back(x: disableUnusableChecks()); |
1227 | TidyProvider Provider = combine(Providers: std::move(Stack)); |
1228 | CDB.ExtraClangFlags = {"-xc++" }; |
1229 | auto Opts = ClangdServer::optsForTest(); |
1230 | Opts.ClangTidyProvider = Provider; |
1231 | ClangdServer Server(CDB, FS, Opts, &DiagConsumer); |
1232 | const char *SourceContents = R"cpp( |
1233 | struct Foo { Foo(); Foo(Foo&); Foo(Foo&&); }; |
1234 | namespace std { Foo&& move(Foo&); } |
1235 | void foo() { |
1236 | Foo x; |
1237 | Foo y = std::move(x); |
1238 | Foo z = x; |
1239 | })cpp" ; |
1240 | Server.addDocument(File: testPath(File: "foo.h" ), Contents: SourceContents); |
1241 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1242 | EXPECT_FALSE(DiagConsumer.HadDiagsInLastCallback); |
1243 | } |
1244 | |
1245 | TEST(ClangdServer, MemoryUsageTest) { |
1246 | MockFS FS; |
1247 | MockCompilationDatabase CDB; |
1248 | ClangdServer Server(CDB, FS, ClangdServer::optsForTest()); |
1249 | |
1250 | auto FooCpp = testPath(File: "foo.cpp" ); |
1251 | Server.addDocument(File: FooCpp, Contents: "" ); |
1252 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1253 | |
1254 | llvm::BumpPtrAllocator Alloc; |
1255 | MemoryTree MT(&Alloc); |
1256 | Server.profile(MT); |
1257 | ASSERT_TRUE(MT.children().count("tuscheduler" )); |
1258 | EXPECT_TRUE(MT.child("tuscheduler" ).children().count(FooCpp)); |
1259 | } |
1260 | |
1261 | TEST(ClangdServer, RespectsTweakFormatting) { |
1262 | static constexpr const char *TweakID = "ModuleTweak" ; |
1263 | static constexpr const char *NewContents = "{not;\nformatted;}" ; |
1264 | |
1265 | // Contributes a tweak that generates a non-formatted insertion and disables |
1266 | // formatting. |
1267 | struct TweakContributingModule final : public FeatureModule { |
1268 | struct ModuleTweak final : public Tweak { |
1269 | const char *id() const override { return TweakID; } |
1270 | bool prepare(const Selection &Sel) override { return true; } |
1271 | Expected<Effect> apply(const Selection &Sel) override { |
1272 | auto &SM = Sel.AST->getSourceManager(); |
1273 | llvm::StringRef FilePath = SM.getFilename(SpellingLoc: Sel.Cursor); |
1274 | tooling::Replacements Reps; |
1275 | llvm::cantFail( |
1276 | Err: Reps.add(R: tooling::Replacement(FilePath, 0, 0, NewContents))); |
1277 | auto E = llvm::cantFail(ValOrErr: Effect::mainFileEdit(SM, Replacements: std::move(Reps))); |
1278 | E.FormatEdits = false; |
1279 | return E; |
1280 | } |
1281 | std::string title() const override { return id(); } |
1282 | llvm::StringLiteral kind() const override { |
1283 | return llvm::StringLiteral("" ); |
1284 | }; |
1285 | }; |
1286 | |
1287 | void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override { |
1288 | Out.emplace_back(args: new ModuleTweak); |
1289 | } |
1290 | }; |
1291 | |
1292 | MockFS FS; |
1293 | MockCompilationDatabase CDB; |
1294 | auto Opts = ClangdServer::optsForTest(); |
1295 | FeatureModuleSet Set; |
1296 | Set.add(M: std::make_unique<TweakContributingModule>()); |
1297 | Opts.FeatureModules = &Set; |
1298 | ClangdServer Server(CDB, FS, Opts); |
1299 | |
1300 | auto FooCpp = testPath(File: "foo.cpp" ); |
1301 | Server.addDocument(File: FooCpp, Contents: "" ); |
1302 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1303 | |
1304 | // Ensure that disabled formatting is respected. |
1305 | Notification N; |
1306 | Server.applyTweak(File: FooCpp, Sel: {}, ID: TweakID, CB: [&](llvm::Expected<Tweak::Effect> E) { |
1307 | ASSERT_TRUE(static_cast<bool>(E)); |
1308 | EXPECT_THAT(llvm::cantFail(E->ApplyEdits.lookup(FooCpp).apply()), |
1309 | NewContents); |
1310 | N.notify(); |
1311 | }); |
1312 | N.wait(); |
1313 | } |
1314 | |
1315 | TEST(ClangdServer, InactiveRegions) { |
1316 | struct InactiveRegionsCallback : ClangdServer::Callbacks { |
1317 | std::vector<std::vector<Range>> FoundInactiveRegions; |
1318 | |
1319 | void onInactiveRegionsReady(PathRef FIle, |
1320 | std::vector<Range> InactiveRegions) override { |
1321 | FoundInactiveRegions.push_back(x: std::move(InactiveRegions)); |
1322 | } |
1323 | }; |
1324 | |
1325 | MockFS FS; |
1326 | MockCompilationDatabase CDB; |
1327 | CDB.ExtraClangFlags.push_back(x: "-DCMDMACRO" ); |
1328 | auto Opts = ClangdServer::optsForTest(); |
1329 | Opts.PublishInactiveRegions = true; |
1330 | InactiveRegionsCallback Callback; |
1331 | ClangdServer Server(CDB, FS, Opts, &Callback); |
1332 | Annotations Source(R"cpp( |
1333 | #define PREAMBLEMACRO 42 |
1334 | #if PREAMBLEMACRO > 40 |
1335 | #define ACTIVE |
1336 | #else |
1337 | $inactive1[[ #define INACTIVE]] |
1338 | #endif |
1339 | int endPreamble; |
1340 | #ifndef CMDMACRO |
1341 | $inactive2[[ int inactiveInt;]] |
1342 | #endif |
1343 | #undef CMDMACRO |
1344 | #ifdef CMDMACRO |
1345 | $inactive3[[ int inactiveInt2;]] |
1346 | #elif PREAMBLEMACRO > 0 |
1347 | int activeInt1; |
1348 | int activeInt2; |
1349 | #else |
1350 | $inactive4[[ int inactiveInt3;]] |
1351 | #endif |
1352 | #ifdef CMDMACRO |
1353 | #endif // empty inactive range, gets dropped |
1354 | )cpp" ); |
1355 | Server.addDocument(File: testPath(File: "foo.cpp" ), Contents: Source.code()); |
1356 | ASSERT_TRUE(Server.blockUntilIdleForTest()); |
1357 | EXPECT_THAT(Callback.FoundInactiveRegions, |
1358 | ElementsAre(ElementsAre( |
1359 | Source.range("inactive1" ), Source.range("inactive2" ), |
1360 | Source.range("inactive3" ), Source.range("inactive4" )))); |
1361 | } |
1362 | |
1363 | } // namespace |
1364 | } // namespace clangd |
1365 | } // namespace clang |
1366 | |