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