clang-tools 23.0.0git
PrerequisiteModulesTest.cpp
Go to the documentation of this file.
1//===--------------- PrerequisiteModulesTests.cpp -------------------*- C++
2//-*-===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9
10/// FIXME: Skip testing on windows temporarily due to the different escaping
11/// code mode.
12#ifndef _WIN32
13
14#include "Annotations.h"
15#include "CodeComplete.h"
16#include "Compiler.h"
17#include "ModulesBuilder.h"
18#include "ProjectModules.h"
19#include "TestTU.h"
20#include "support/Path.h"
22#include "clang/Tooling/Tooling.h"
23#include "llvm/ADT/ScopeExit.h"
24#include "llvm/ADT/StringRef.h"
25#include "llvm/Support/Chrono.h"
26#include "llvm/Support/CommandLine.h"
27#include "llvm/Support/FileSystem.h"
28#include "llvm/Support/Process.h"
29#include "llvm/Support/VirtualFileSystem.h"
30#include "llvm/Support/raw_ostream.h"
31#include "llvm/TargetParser/Host.h"
32#include "gmock/gmock.h"
33#include "gtest/gtest.h"
34
35namespace clang::clangd {
36namespace {
37
38MATCHER_P(named, Name, "") { return arg.Name == Name; }
39
40class GlobalScanningCounterProjectModules : public ProjectModules {
41public:
42 GlobalScanningCounterProjectModules(
43 std::unique_ptr<ProjectModules> Underlying, std::atomic<unsigned> &Count)
44 : Underlying(std::move(Underlying)), Count(Count) {}
45
46 std::vector<std::string> getRequiredModules(PathRef File) override {
47 return Underlying->getRequiredModules(File);
48 }
49
50 std::string getModuleNameForSource(PathRef File) override {
51 return Underlying->getModuleNameForSource(File);
52 }
53
54 void setCommandMangler(CommandMangler Mangler) override {
55 Underlying->setCommandMangler(std::move(Mangler));
56 }
57
58 std::string getSourceForModuleName(llvm::StringRef ModuleName,
59 PathRef RequiredSrcFile) override {
60 Count++;
61 return Underlying->getSourceForModuleName(ModuleName, RequiredSrcFile);
62 }
63
64 ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override {
65 return Underlying->getModuleNameState(ModuleName);
66 }
67
68private:
69 std::unique_ptr<ProjectModules> Underlying;
70 std::atomic<unsigned> &Count;
71};
72
73class PerFileModulesCompilationDatabase : public GlobalCompilationDatabase {
74public:
75 PerFileModulesCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
76 : Directory(TestDir), TFS(TFS),
77 ToolingCDB(std::make_shared<IndexedCompilationDatabase>(*this)) {}
78
79 void addFile(llvm::StringRef Path, llvm::StringRef Contents,
80 std::vector<std::string> ExtraFlags = {}) {
81 ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
82
83 SmallString<256> AbsPath(Directory);
84 llvm::sys::path::append(AbsPath, Path);
85
86 ASSERT_FALSE(llvm::sys::fs::create_directories(
87 llvm::sys::path::parent_path(AbsPath)));
88
89 std::error_code EC;
90 llvm::raw_fd_ostream OS(AbsPath, EC);
91 ASSERT_FALSE(EC);
92 OS << Contents;
93
94 std::vector<std::string> CommandLine = {"clang", "-std=c++20", "-c"};
95 CommandLine.insert(CommandLine.end(), ExtraFlags.begin(), ExtraFlags.end());
96 CommandLine.push_back(std::string(AbsPath));
97
98 Commands[maybeCaseFoldPath(AbsPath)] = tooling::CompileCommand(
99 Directory, std::string(AbsPath), std::move(CommandLine), "");
100 Files.push_back(std::string(AbsPath));
101 }
102
103 std::optional<tooling::CompileCommand>
104 getCompileCommand(PathRef File) const override {
105 auto It = Commands.find(maybeCaseFoldPath(File));
106 if (It == Commands.end())
107 return std::nullopt;
108 tooling::CompileCommand Cmd = It->second;
109 if (llvm::any_of(Cmd.CommandLine, [](llvm::StringRef Arg) {
110 return Arg.starts_with("@");
111 })) {
112 auto FS = llvm::vfs::getRealFileSystem();
113 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
114 ? llvm::cl::TokenizeWindowsCommandLine
115 : llvm::cl::TokenizeGNUCommandLine;
116 tooling::addExpandedResponseFiles(Cmd.CommandLine, Cmd.Directory,
117 Tokenizer, *FS);
118 }
119 return Cmd;
120 }
121
122 std::optional<ProjectInfo> getProjectInfo(PathRef) const override {
123 return ProjectInfo{std::string(Directory)};
124 }
125
126 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
127 return clang::clangd::getProjectModules(ToolingCDB, TFS);
128 }
129
130private:
131 class IndexedCompilationDatabase : public tooling::CompilationDatabase {
132 public:
133 IndexedCompilationDatabase(const PerFileModulesCompilationDatabase &CDB)
134 : CDB(CDB) {}
135
136 std::vector<tooling::CompileCommand>
137 getCompileCommands(StringRef FilePath) const override {
138 if (auto Cmd = CDB.getCompileCommand(FilePath))
139 return {*Cmd};
140 return {};
141 }
142
143 std::vector<std::string> getAllFiles() const override { return CDB.Files; }
144
145 private:
146 const PerFileModulesCompilationDatabase &CDB;
147 };
148
149 std::string Directory;
150 const ThreadsafeFS &TFS;
151 llvm::StringMap<tooling::CompileCommand> Commands;
152 std::vector<std::string> Files;
153 std::shared_ptr<IndexedCompilationDatabase> ToolingCDB;
154};
155
156class ModuleUnitRootCompilationDatabase
157 : public PerFileModulesCompilationDatabase {
158public:
159 ModuleUnitRootCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
160 : PerFileModulesCompilationDatabase(TestDir, TFS) {}
161
162 std::optional<ProjectInfo> getProjectInfo(PathRef File) const override {
163 // Treat each module-unit directory as its own project root so tests can
164 // verify that the persistent cache follows the providing module unit.
165 llvm::SmallString<256> Root(File);
166 llvm::sys::path::remove_filename(Root);
167 return ProjectInfo{std::string(Root)};
168 }
169};
170
171class MockDirectoryCompilationDatabase : public MockCompilationDatabase {
172public:
173 MockDirectoryCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
174 : MockCompilationDatabase(TestDir),
175 MockedCDBPtr(std::make_shared<MockClangCompilationDatabase>(*this)),
176 TFS(TFS), GlobalScanningCount(0) {
177 this->ExtraClangFlags.push_back("-std=c++20");
178 this->ExtraClangFlags.push_back("-c");
179 }
180
181 void addFile(llvm::StringRef Path, llvm::StringRef Contents);
182
183 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
184 return std::make_unique<GlobalScanningCounterProjectModules>(
185 clang::clangd::getProjectModules(MockedCDBPtr, TFS),
186 GlobalScanningCount);
187 }
188
189 unsigned getGlobalScanningCount() const { return GlobalScanningCount; }
190
191private:
192 class MockClangCompilationDatabase : public tooling::CompilationDatabase {
193 public:
194 MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB)
195 : MCDB(MCDB) {}
196
197 std::vector<tooling::CompileCommand>
198 getCompileCommands(StringRef FilePath) const override {
199 std::optional<tooling::CompileCommand> Cmd =
200 MCDB.getCompileCommand(FilePath);
201 EXPECT_TRUE(Cmd);
202 return {*Cmd};
203 }
204
205 std::vector<std::string> getAllFiles() const override { return Files; }
206
207 void AddFile(StringRef File) { Files.push_back(File.str()); }
208
209 private:
210 MockDirectoryCompilationDatabase &MCDB;
211 std::vector<std::string> Files;
212 };
213
214 std::shared_ptr<MockClangCompilationDatabase> MockedCDBPtr;
215 const ThreadsafeFS &TFS;
216
217 mutable std::atomic<unsigned> GlobalScanningCount;
218};
219
220// Add files to the working testing directory and the compilation database.
221void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path,
222 llvm::StringRef Contents) {
223 ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
224
225 SmallString<256> AbsPath(Directory);
226 llvm::sys::path::append(AbsPath, Path);
227
228 ASSERT_FALSE(
229 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
230
231 std::error_code EC;
232 llvm::raw_fd_ostream OS(AbsPath, EC);
233 ASSERT_FALSE(EC);
234 OS << Contents;
235
236 MockedCDBPtr->AddFile(Path);
237}
238
239class PrerequisiteModulesTests : public ::testing::Test {
240protected:
241 void SetUp() override {
242 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir));
243 }
244
245 void TearDown() override {
246 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir));
247 }
248
249public:
250 // Get the absolute path for file specified by Path under testing working
251 // directory.
252 std::string getFullPath(llvm::StringRef Path) {
253 SmallString<128> Result(TestDir);
254 llvm::sys::path::append(Result, Path);
255 EXPECT_TRUE(llvm::sys::fs::exists(Result.str()));
256 return Result.str().str();
257 }
258
259 ParseInputs getInputs(llvm::StringRef FileName,
260 const GlobalCompilationDatabase &CDB) {
261 std::string FullPathName = getFullPath(FileName);
262
263 ParseInputs Inputs;
264 std::optional<tooling::CompileCommand> Cmd =
265 CDB.getCompileCommand(FullPathName);
266 EXPECT_TRUE(Cmd);
267 Inputs.CompileCommand = std::move(*Cmd);
268 Inputs.TFS = &FS;
269
270 if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName))
271 Inputs.Contents = Contents->get()->getBuffer().str();
272
273 return Inputs;
274 }
275
276 SmallString<256> TestDir;
277 // FIXME: It will be better to use the MockFS if the scanning process and
278 // build module process doesn't depend on reading real IO.
279 RealThreadsafeFS FS;
280
281 DiagnosticConsumer DiagConsumer;
282};
283
284TEST_F(PrerequisiteModulesTests, NonModularTest) {
285 MockDirectoryCompilationDatabase CDB(TestDir, FS);
286
287 CDB.addFile("foo.h", R"cpp(
288inline void foo() {}
289 )cpp");
290
291 CDB.addFile("NonModular.cpp", R"cpp(
292#include "foo.h"
293void use() {
294 foo();
295}
296 )cpp");
297
298 ModulesBuilder Builder(CDB);
299
300 // NonModular.cpp is not related to modules. So nothing should be built.
301 auto NonModularInfo =
302 Builder.buildPrerequisiteModulesFor(getFullPath("NonModular.cpp"), FS);
303 EXPECT_TRUE(NonModularInfo);
304
305 HeaderSearchOptions HSOpts;
306 NonModularInfo->adjustHeaderSearchOptions(HSOpts);
307 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
308
309 auto Invocation =
310 buildCompilerInvocation(getInputs("NonModular.cpp", CDB), DiagConsumer);
311 EXPECT_TRUE(NonModularInfo->canReuse(*Invocation, FS.view(TestDir)));
312}
313
314TEST_F(PrerequisiteModulesTests, ModuleWithoutDepTest) {
315 MockDirectoryCompilationDatabase CDB(TestDir, FS);
316
317 CDB.addFile("foo.h", R"cpp(
318inline void foo() {}
319 )cpp");
320
321 CDB.addFile("M.cppm", R"cpp(
322module;
323#include "foo.h"
324export module M;
325 )cpp");
326
327 ModulesBuilder Builder(CDB);
328
329 auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS);
330 EXPECT_TRUE(MInfo);
331
332 // Nothing should be built since M doesn't dependent on anything.
333 HeaderSearchOptions HSOpts;
334 MInfo->adjustHeaderSearchOptions(HSOpts);
335 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
336
337 auto Invocation =
338 buildCompilerInvocation(getInputs("M.cppm", CDB), DiagConsumer);
339 EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir)));
340}
341
342TEST_F(PrerequisiteModulesTests, ModuleWithArgumentPatch) {
343 MockDirectoryCompilationDatabase CDB(TestDir, FS);
344
345 CDB.ExtraClangFlags.push_back("-invalid-unknown-flag");
346
347 CDB.addFile("Dep.cppm", R"cpp(
348export module Dep;
349 )cpp");
350
351 CDB.addFile("M.cppm", R"cpp(
352export module M;
353import Dep;
354 )cpp");
355
356 // An invalid flag will break the module compilation and the
357 // getRequiredModules would return an empty array
358 auto ProjectModules = CDB.getProjectModules(getFullPath("M.cppm"));
359 EXPECT_TRUE(
360 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
361
362 // Set the mangler to filter out the invalid flag
363 ProjectModules->setCommandMangler([](tooling::CompileCommand &Command,
364 PathRef) {
365 auto const It = llvm::find(Command.CommandLine, "-invalid-unknown-flag");
366 Command.CommandLine.erase(It);
367 });
368
369 // And now it returns a non-empty list of required modules since the
370 // compilation succeeded
371 EXPECT_FALSE(
372 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
373}
374
375TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) {
376 MockDirectoryCompilationDatabase CDB(TestDir, FS);
377
378 CDB.addFile("foo.h", R"cpp(
379inline void foo() {}
380 )cpp");
381
382 CDB.addFile("M.cppm", R"cpp(
383module;
384#include "foo.h"
385export module M;
386 )cpp");
387
388 CDB.addFile("N.cppm", R"cpp(
389export module N;
390import :Part;
391import M;
392 )cpp");
393
394 CDB.addFile("N-part.cppm", R"cpp(
395// Different module name with filename intentionally.
396export module N:Part;
397 )cpp");
398
399 ModulesBuilder Builder(CDB);
400
401 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
402 EXPECT_TRUE(NInfo);
403
404 ParseInputs NInput = getInputs("N.cppm", CDB);
405 std::unique_ptr<CompilerInvocation> Invocation =
406 buildCompilerInvocation(NInput, DiagConsumer);
407 // Test that `PrerequisiteModules::canReuse` works basically.
408 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
409
410 {
411 // Check that
412 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
413 // can appending HeaderSearchOptions correctly.
414 HeaderSearchOptions HSOpts;
415 NInfo->adjustHeaderSearchOptions(HSOpts);
416
417 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
418 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N:Part"));
419 }
420
421 {
422 // Check that
423 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
424 // can replace HeaderSearchOptions correctly.
425 HeaderSearchOptions HSOpts;
426 HSOpts.PrebuiltModuleFiles["M"] = "incorrect_path";
427 HSOpts.PrebuiltModuleFiles["N:Part"] = "incorrect_path";
428 NInfo->adjustHeaderSearchOptions(HSOpts);
429
430 EXPECT_TRUE(StringRef(HSOpts.PrebuiltModuleFiles["M"]).ends_with(".pcm"));
431 EXPECT_TRUE(
432 StringRef(HSOpts.PrebuiltModuleFiles["N:Part"]).ends_with(".pcm"));
433 }
434}
435
436TEST_F(PrerequisiteModulesTests, ReusabilityTest) {
437 MockDirectoryCompilationDatabase CDB(TestDir, FS);
438
439 CDB.addFile("foo.h", R"cpp(
440inline void foo() {}
441 )cpp");
442
443 CDB.addFile("M.cppm", R"cpp(
444module;
445#include "foo.h"
446export module M;
447 )cpp");
448
449 CDB.addFile("N.cppm", R"cpp(
450export module N;
451import :Part;
452import M;
453 )cpp");
454
455 CDB.addFile("N-part.cppm", R"cpp(
456// Different module name with filename intentionally.
457export module N:Part;
458 )cpp");
459
460 ModulesBuilder Builder(CDB);
461
462 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
463 EXPECT_TRUE(NInfo);
464 EXPECT_TRUE(NInfo);
465
466 ParseInputs NInput = getInputs("N.cppm", CDB);
467 std::unique_ptr<CompilerInvocation> Invocation =
468 buildCompilerInvocation(NInput, DiagConsumer);
469 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
470
471 // Test that we can still reuse the NInfo after we touch a unrelated file.
472 {
473 CDB.addFile("L.cppm", R"cpp(
474module;
475#include "foo.h"
476export module L;
477export int ll = 43;
478 )cpp");
479 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
480
481 CDB.addFile("bar.h", R"cpp(
482inline void bar() {}
483inline void bar(int) {}
484 )cpp");
485 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
486 }
487
488 // Test that we can't reuse the NInfo after we touch a related file.
489 {
490 CDB.addFile("M.cppm", R"cpp(
491module;
492#include "foo.h"
493export module M;
494export int mm = 44;
495 )cpp");
496 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
497
498 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
499 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
500
501 CDB.addFile("foo.h", R"cpp(
502inline void foo() {}
503inline void foo(int) {}
504 )cpp");
505 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
506
507 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
508 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
509 }
510
511 CDB.addFile("N-part.cppm", R"cpp(
512export module N:Part;
513// Intentioned to make it uncompilable.
514export int NPart = 4LIdjwldijaw
515 )cpp");
516 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
517 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
518 EXPECT_TRUE(NInfo);
519 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
520
521 CDB.addFile("N-part.cppm", R"cpp(
522export module N:Part;
523export int NPart = 43;
524 )cpp");
525 EXPECT_TRUE(NInfo);
526 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
527 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
528 EXPECT_TRUE(NInfo);
529 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
530
531 // Test that if we changed the modification time of the file, the module files
532 // info is still reusable if its content doesn't change.
533 CDB.addFile("N-part.cppm", R"cpp(
534export module N:Part;
535export int NPart = 43;
536 )cpp");
537 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
538
539 CDB.addFile("N.cppm", R"cpp(
540export module N;
541import :Part;
542import M;
543
544export int nn = 43;
545 )cpp");
546 // NInfo should be reusable after we change its content.
547 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
548}
549
550TEST_F(PrerequisiteModulesTests, CanReuseWithTransitiveNamedModuleImports) {
551 MockDirectoryCompilationDatabase CDB(TestDir, FS);
552
553 CDB.addFile("N.cppm", R"cpp(
554export module N;
555export inline constexpr int n = 1;
556 )cpp");
557
558 CDB.addFile("M.cppm", R"cpp(
559export module M;
560import N;
561export inline constexpr int m = n + 1;
562 )cpp");
563
564 CDB.addFile("A.cppm", R"cpp(
565export module A;
566import M;
567export inline constexpr int a = m + 1;
568 )cpp");
569
570 CDB.addFile("Use.cpp", R"cpp(
571import A;
572int use() { return a; }
573 )cpp");
574
575 ModulesBuilder Builder(CDB);
576
577 auto UseInfo = Builder.buildPrerequisiteModulesFor(getFullPath("Use.cpp"), FS);
578 ASSERT_TRUE(UseInfo);
579
580 HeaderSearchOptions HSOpts;
581 UseInfo->adjustHeaderSearchOptions(HSOpts);
582 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("A"));
583 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
584 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N"));
585
586 auto Invocation =
587 buildCompilerInvocation(getInputs("Use.cpp", CDB), DiagConsumer);
588 ASSERT_TRUE(Invocation);
589
590 EXPECT_TRUE(UseInfo->canReuse(*Invocation, FS.view(TestDir)));
591}
592
593// An End-to-End test for modules.
594TEST_F(PrerequisiteModulesTests, ParsedASTTest) {
595 MockDirectoryCompilationDatabase CDB(TestDir, FS);
596
597 CDB.addFile("A.cppm", R"cpp(
598export module A;
599export void printA();
600 )cpp");
601
602 CDB.addFile("Use.cpp", R"cpp(
603import A;
604)cpp");
605
606 ModulesBuilder Builder(CDB);
607
608 ParseInputs Use = getInputs("Use.cpp", CDB);
609 Use.ModulesManager = &Builder;
610
611 std::unique_ptr<CompilerInvocation> CI =
612 buildCompilerInvocation(Use, DiagConsumer);
613 EXPECT_TRUE(CI);
614
615 auto Preamble =
616 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
617 /*Callback=*/nullptr);
618 EXPECT_TRUE(Preamble);
619 EXPECT_TRUE(Preamble->RequiredModules);
620
621 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Use, std::move(CI), {},
622 Preamble);
623 EXPECT_TRUE(AST);
624
625 const NamedDecl &D = findDecl(*AST, "printA");
626 EXPECT_TRUE(D.isFromASTFile());
627}
628
629// An end to end test for code complete in modules
630TEST_F(PrerequisiteModulesTests, CodeCompleteTest) {
631 MockDirectoryCompilationDatabase CDB(TestDir, FS);
632
633 CDB.addFile("A.cppm", R"cpp(
634export module A;
635export void printA();
636 )cpp");
637
638 llvm::StringLiteral UserContents = R"cpp(
639import A;
640void func() {
641 print^
642}
643)cpp";
644
645 CDB.addFile("Use.cpp", UserContents);
646 Annotations Test(UserContents);
647
648 ModulesBuilder Builder(CDB);
649
650 ParseInputs Use = getInputs("Use.cpp", CDB);
651 Use.ModulesManager = &Builder;
652
653 std::unique_ptr<CompilerInvocation> CI =
654 buildCompilerInvocation(Use, DiagConsumer);
655 EXPECT_TRUE(CI);
656
657 auto Preamble =
658 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
659 /*Callback=*/nullptr);
660 EXPECT_TRUE(Preamble);
661 EXPECT_TRUE(Preamble->RequiredModules);
662
663 auto Result = codeComplete(getFullPath("Use.cpp"), Test.point(),
664 Preamble.get(), Use, {});
665 EXPECT_FALSE(Result.Completions.empty());
666 EXPECT_EQ(Result.Completions[0].Name, "printA");
667}
668
669TEST_F(PrerequisiteModulesTests, SignatureHelpTest) {
670 MockDirectoryCompilationDatabase CDB(TestDir, FS);
671
672 CDB.addFile("A.cppm", R"cpp(
673export module A;
674export void printA(int a);
675 )cpp");
676
677 llvm::StringLiteral UserContents = R"cpp(
678import A;
679void func() {
680 printA(^);
681}
682)cpp";
683
684 CDB.addFile("Use.cpp", UserContents);
685 Annotations Test(UserContents);
686
687 ModulesBuilder Builder(CDB);
688
689 ParseInputs Use = getInputs("Use.cpp", CDB);
690 Use.ModulesManager = &Builder;
691
692 std::unique_ptr<CompilerInvocation> CI =
693 buildCompilerInvocation(Use, DiagConsumer);
694 EXPECT_TRUE(CI);
695
696 auto Preamble =
697 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
698 /*Callback=*/nullptr);
699 EXPECT_TRUE(Preamble);
700 EXPECT_TRUE(Preamble->RequiredModules);
701
702 auto Result = signatureHelp(getFullPath("Use.cpp"), Test.point(), *Preamble,
704 EXPECT_FALSE(Result.signatures.empty());
705 EXPECT_EQ(Result.signatures[0].label, "printA(int a) -> void");
706 EXPECT_EQ(Result.signatures[0].parameters[0].labelString, "int a");
707}
708
709TEST_F(PrerequisiteModulesTests, ReusablePrerequisiteModulesTest) {
710 MockDirectoryCompilationDatabase CDB(TestDir, FS);
711
712 CDB.addFile("M.cppm", R"cpp(
713export module M;
714export int M = 43;
715 )cpp");
716 CDB.addFile("A.cppm", R"cpp(
717export module A;
718import M;
719export int A = 43 + M;
720 )cpp");
721 CDB.addFile("B.cppm", R"cpp(
722export module B;
723import M;
724export int B = 44 + M;
725 )cpp");
726
727 ModulesBuilder Builder(CDB);
728
729 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
730 EXPECT_TRUE(AInfo);
731 auto BInfo = Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
732 EXPECT_TRUE(BInfo);
733 HeaderSearchOptions HSOptsA(TestDir);
734 HeaderSearchOptions HSOptsB(TestDir);
735 AInfo->adjustHeaderSearchOptions(HSOptsA);
736 BInfo->adjustHeaderSearchOptions(HSOptsB);
737
738 EXPECT_FALSE(HSOptsA.PrebuiltModuleFiles.empty());
739 EXPECT_FALSE(HSOptsB.PrebuiltModuleFiles.empty());
740
741 // Check that we're reusing the module files.
742 EXPECT_EQ(HSOptsA.PrebuiltModuleFiles, HSOptsB.PrebuiltModuleFiles);
743
744 // Update M.cppm to check if the modules builder can update correctly.
745 CDB.addFile("M.cppm", R"cpp(
746export module M;
747export constexpr int M = 43;
748 )cpp");
749
750 ParseInputs AUse = getInputs("A.cppm", CDB);
751 AUse.ModulesManager = &Builder;
752 std::unique_ptr<CompilerInvocation> AInvocation =
753 buildCompilerInvocation(AUse, DiagConsumer);
754 EXPECT_FALSE(AInfo->canReuse(*AInvocation, FS.view(TestDir)));
755
756 ParseInputs BUse = getInputs("B.cppm", CDB);
757 AUse.ModulesManager = &Builder;
758 std::unique_ptr<CompilerInvocation> BInvocation =
759 buildCompilerInvocation(BUse, DiagConsumer);
760 EXPECT_FALSE(BInfo->canReuse(*BInvocation, FS.view(TestDir)));
761
762 auto NewAInfo =
763 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
764 auto NewBInfo =
765 Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
766 EXPECT_TRUE(NewAInfo);
767 EXPECT_TRUE(NewBInfo);
768 HeaderSearchOptions NewHSOptsA(TestDir);
769 HeaderSearchOptions NewHSOptsB(TestDir);
770 NewAInfo->adjustHeaderSearchOptions(NewHSOptsA);
771 NewBInfo->adjustHeaderSearchOptions(NewHSOptsB);
772
773 EXPECT_FALSE(NewHSOptsA.PrebuiltModuleFiles.empty());
774 EXPECT_FALSE(NewHSOptsB.PrebuiltModuleFiles.empty());
775
776 EXPECT_EQ(NewHSOptsA.PrebuiltModuleFiles, NewHSOptsB.PrebuiltModuleFiles);
777 // Persistent cache keeps the published BMI path stable, so verify the new
778 // module graph by reuse semantics instead of expecting a different path.
779 EXPECT_TRUE(NewAInfo->canReuse(*AInvocation, FS.view(TestDir)));
780 EXPECT_TRUE(NewBInfo->canReuse(*BInvocation, FS.view(TestDir)));
781}
782
783TEST_F(PrerequisiteModulesTests, ScanningCacheTest) {
784 MockDirectoryCompilationDatabase CDB(TestDir, FS);
785
786 CDB.addFile("M.cppm", R"cpp(
787export module M;
788 )cpp");
789 CDB.addFile("A.cppm", R"cpp(
790export module A;
791import M;
792 )cpp");
793 CDB.addFile("B.cppm", R"cpp(
794export module B;
795import M;
796 )cpp");
797
798 ModulesBuilder Builder(CDB);
799
800 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
801 Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
802 EXPECT_EQ(CDB.getGlobalScanningCount(), 1u);
803}
804
805// Test that canReuse detects changes to headers included in module units.
806// This verifies that the ASTReader correctly tracks header file dependencies
807// in BMI files and that IsModuleFileUpToDate correctly validates them.
808TEST_F(PrerequisiteModulesTests, CanReuseWithHeadersInModuleUnit) {
809 MockDirectoryCompilationDatabase CDB(TestDir, FS);
810
811 // Create a header file that will be included in a module unit
812 CDB.addFile("header1.h", R"cpp(
813inline int getValue() { return 42; }
814 )cpp");
815
816 // Module M includes header1.h in the global module fragment
817 CDB.addFile("M.cppm", R"cpp(
818module;
819#include "header1.h"
820export module M;
821export int m_value = getValue();
822 )cpp");
823
824 // Module N imports M (similar structure to ReusabilityTest)
825 CDB.addFile("N.cppm", R"cpp(
826export module N;
827import :Part;
828import M;
829 )cpp");
830
831 // Add a module partition (similar to ReusabilityTest)
832 CDB.addFile("N-part.cppm", R"cpp(
833export module N:Part;
834 )cpp");
835
836 ModulesBuilder Builder(CDB);
837
838 // Build prerequisite modules for N (which depends on M)
839 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
840 ASSERT_TRUE(NInfo);
841
842 ParseInputs NInput = getInputs("N.cppm", CDB);
843 std::unique_ptr<CompilerInvocation> Invocation =
844 buildCompilerInvocation(NInput, DiagConsumer);
845
846 // Initially, canReuse should return true
847 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
848
849 // Test 1: Modify header1.h (included by M)
850 // canReuse should detect this change since M's BMI records header1.h as input
851 CDB.addFile("header1.h", R"cpp(
852inline int getValue() { return 43; }
853 )cpp");
854 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
855
856 // Rebuild and verify canReuse returns true again
857 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
858 ASSERT_TRUE(NInfo);
859 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
860
861 // Test 2: Modify the module source file itself
862 CDB.addFile("M.cppm", R"cpp(
863module;
864#include "header1.h"
865export module M;
866export int m_value = getValue();
867export int m_new_value = 10;
868 )cpp");
869 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
870
871 // Rebuild after module source change
872 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
873 ASSERT_TRUE(NInfo);
874 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
875}
876
877TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileTest) {
878 MockDirectoryCompilationDatabase CDB(TestDir, FS);
879
880 CDB.addFile("M.cppm", R"cpp(
881export module M;
882 )cpp");
883
884 CDB.addFile("U.cpp", R"cpp(
885import M;
886 )cpp");
887
888 // Use ModulesBuilder to produce the prebuilt module file.
889 ModulesBuilder Builder(CDB);
890 auto ModuleInfo =
891 Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
892 HeaderSearchOptions HS(TestDir);
893 ModuleInfo->adjustHeaderSearchOptions(HS);
894
895 CDB.ExtraClangFlags.push_back("-fmodule-file=M=" +
896 HS.PrebuiltModuleFiles["M"]);
897 ModulesBuilder Builder2(CDB);
898 auto ModuleInfo2 =
899 Builder2.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
900 HeaderSearchOptions HS2(TestDir);
901 ModuleInfo2->adjustHeaderSearchOptions(HS2);
902
903 EXPECT_EQ(HS.PrebuiltModuleFiles, HS2.PrebuiltModuleFiles);
904}
905
906// Test that prebuilt module files with relative paths are correctly resolved.
907// This tests the fix for the issue where clangd couldn't find BMI files when
908// the compilation database contained relative paths in -fmodule-file=
909// arguments.
910TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileWithRelativePath) {
911 MockDirectoryCompilationDatabase CDB(TestDir, FS);
912
913 CDB.addFile("M.cppm", R"cpp(
914export module M;
915export int m_value = 42;
916 )cpp");
917
918 CDB.addFile("U.cpp", R"cpp(
919import M;
920int use() { return m_value; }
921 )cpp");
922
923 // Step 1: Build the module file using ModulesBuilder
924 ModulesBuilder Builder(CDB);
925 auto ModuleInfo =
926 Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
927 ASSERT_TRUE(ModuleInfo);
928
929 HeaderSearchOptions HS(TestDir);
930 ModuleInfo->adjustHeaderSearchOptions(HS);
931 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
932
933 // Get the absolute path of the built module file
934 std::string OriginalBMPath = HS.PrebuiltModuleFiles["M"];
935 ASSERT_TRUE(llvm::sys::path::is_absolute(OriginalBMPath));
936 ASSERT_TRUE(llvm::sys::fs::exists(OriginalBMPath));
937
938 // Step 2: Create a subdirectory in TestDir and copy the BMI there
939 SmallString<256> BMSubDir(TestDir);
940 llvm::sys::path::append(BMSubDir, "prebuilt_modules");
941 ASSERT_FALSE(llvm::sys::fs::create_directories(BMSubDir));
942
943 SmallString<256> NewBMPath(BMSubDir);
944 llvm::sys::path::append(NewBMPath, "M.pcm");
945
946 // Copy the BMI file to the new location
947 ASSERT_FALSE(llvm::sys::fs::copy_file(OriginalBMPath, NewBMPath));
948 ASSERT_TRUE(llvm::sys::fs::exists(NewBMPath));
949
950 // Step 3: Create a relative path from the new absolute path
951 std::string RelativeBMPath =
952 llvm::StringRef(NewBMPath).drop_front(TestDir.size() + 1).str();
953 ASSERT_FALSE(RelativeBMPath.empty());
954 ASSERT_TRUE(llvm::sys::path::is_relative(RelativeBMPath));
955
956 // Step 4: Create a new CDB with relative path in -fmodule-file=
957 MockDirectoryCompilationDatabase CDBWithRelativePath(TestDir, FS);
958
959 CDBWithRelativePath.addFile("M.cppm", R"cpp(
960export module M;
961export int m_value = 42;
962 )cpp");
963
964 CDBWithRelativePath.addFile("U.cpp", R"cpp(
965import M;
966int use() { return m_value; }
967 )cpp");
968
969 // Use relative path in -fmodule-file= argument
970 CDBWithRelativePath.ExtraClangFlags.push_back("-fmodule-file=M=" +
971 RelativeBMPath);
972
973 // Step 5: Verify that clangd can find and reuse the prebuilt module file
974 ModulesBuilder BuilderWithRelativePath(CDBWithRelativePath);
975 auto ModuleInfo2 = BuilderWithRelativePath.buildPrerequisiteModulesFor(
976 getFullPath("U.cpp"), FS);
977 ASSERT_TRUE(ModuleInfo2);
978
979 HeaderSearchOptions HS2(TestDir);
980 ModuleInfo2->adjustHeaderSearchOptions(HS2);
981
982 // The module file should be found and the paths should match
983 ASSERT_EQ(HS2.PrebuiltModuleFiles.count("M"), 1u);
984 EXPECT_EQ(HS2.PrebuiltModuleFiles["M"], std::string(NewBMPath))
985 << "Expected absolute path: " << NewBMPath
986 << "\nGot: " << HS2.PrebuiltModuleFiles["M"]
987 << "\nRelative path used: " << RelativeBMPath;
988}
989
990TEST_F(PrerequisiteModulesTests,
991 UniqueModuleNameStateResolvedFromCompileCommands) {
992 PerFileModulesCompilationDatabase CDB(TestDir, FS);
993
994 SmallString<256> MPcm(TestDir);
995 llvm::sys::path::append(MPcm, "build", "M.pcm");
996
997 CDB.addFile("M.cppm", R"cpp(
998export module M;
999export int value = 1;
1000 )cpp",
1001 {"--precompile", "-o", std::string(MPcm)});
1002 CDB.addFile("A.cpp", R"cpp(
1003import M;
1004int useA() { return value; }
1005 )cpp",
1006 {"-fmodule-file=M=" + std::string(MPcm)});
1007 CDB.addFile("B.cpp", R"cpp(
1008import M;
1009int useB() { return value; }
1010 )cpp",
1011 {"-fmodule-file=M=" + std::string(MPcm)});
1012
1013 auto ProjectModules = CDB.getProjectModules(getFullPath("A.cpp"));
1014 ASSERT_TRUE(ProjectModules);
1015
1016 EXPECT_EQ(ProjectModules->getModuleNameState("M"),
1018}
1019
1020TEST_F(PrerequisiteModulesTests,
1021 DuplicateModuleNamesResolvedFromCompileCommands) {
1022 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1023
1024 SmallString<256> APcm(TestDir);
1025 llvm::sys::path::append(APcm, "build", "a", "M.pcm");
1026 SmallString<256> BPcm(TestDir);
1027 llvm::sys::path::append(BPcm, "build", "b", "M.pcm");
1028
1029 CDB.addFile("a/M.cppm", R"cpp(
1030export module M;
1031export int onlyA = 1;
1032 )cpp",
1033 {"--precompile", "-o", std::string(APcm)});
1034 CDB.addFile("b/M.cppm", R"cpp(
1035export module M;
1036export int onlyB = 2;
1037 )cpp",
1038 {"--precompile", "-o", std::string(BPcm)});
1039 CDB.addFile("a/Use.cpp", R"cpp(
1040import M;
1041int useA() { return onlyA; }
1042 )cpp",
1043 {"-fmodule-file=M=" + std::string(APcm)});
1044 CDB.addFile("b/Use.cpp", R"cpp(
1045import M;
1046int useB() { return onlyB; }
1047 )cpp",
1048 {"-fmodule-file=M=" + std::string(BPcm)});
1049
1050 auto ProjectModules = CDB.getProjectModules(getFullPath("a/Use.cpp"));
1051 ASSERT_TRUE(ProjectModules);
1052 EXPECT_EQ(ProjectModules->getModuleNameState("M"),
1054
1055 EXPECT_EQ(
1056 ProjectModules->getSourceForModuleName("M", getFullPath("a/Use.cpp")),
1057 getFullPath("a/M.cppm"));
1058 EXPECT_EQ(
1059 ProjectModules->getSourceForModuleName("M", getFullPath("b/Use.cpp")),
1060 getFullPath("b/M.cppm"));
1061}
1062
1063TEST_F(PrerequisiteModulesTests,
1064 DuplicateModuleNamesResolvedFromResponseFiles) {
1065 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1066
1067 SmallString<256> APcm(TestDir);
1068 llvm::sys::path::append(APcm, "build", "a", "M.pcm");
1069 SmallString<256> BPcm(TestDir);
1070 llvm::sys::path::append(BPcm, "build", "b", "M.pcm");
1071
1072 SmallString<256> RspDir(TestDir);
1073 llvm::sys::path::append(RspDir, "build", "rsp");
1074 ASSERT_FALSE(llvm::sys::fs::create_directories(RspDir));
1075
1076 SmallString<256> AMRsp(RspDir);
1077 llvm::sys::path::append(AMRsp, "a-m.rsp");
1078 {
1079 std::error_code EC;
1080 llvm::raw_fd_ostream OS(AMRsp, EC);
1081 ASSERT_FALSE(EC);
1082 OS << "-x c++-module -fmodule-output=" << APcm;
1083 OS.close();
1084 }
1085
1086 SmallString<256> BMRsp(RspDir);
1087 llvm::sys::path::append(BMRsp, "b-m.rsp");
1088 {
1089 std::error_code EC;
1090 llvm::raw_fd_ostream OS(BMRsp, EC);
1091 ASSERT_FALSE(EC);
1092 OS << "-x c++-module -fmodule-output=" << BPcm;
1093 OS.close();
1094 }
1095
1096 SmallString<256> AUseRsp(RspDir);
1097 llvm::sys::path::append(AUseRsp, "a-use.rsp");
1098 {
1099 std::error_code EC;
1100 llvm::raw_fd_ostream OS(AUseRsp, EC);
1101 ASSERT_FALSE(EC);
1102 OS << "-fmodule-file=M=" << APcm;
1103 OS.close();
1104 }
1105
1106 SmallString<256> BUseRsp(RspDir);
1107 llvm::sys::path::append(BUseRsp, "b-use.rsp");
1108 {
1109 std::error_code EC;
1110 llvm::raw_fd_ostream OS(BUseRsp, EC);
1111 ASSERT_FALSE(EC);
1112 OS << "-fmodule-file=M=" << BPcm;
1113 OS.close();
1114 }
1115
1116 CDB.addFile("a/M.cppm", R"cpp(
1117export module M;
1118export int onlyA = 1;
1119 )cpp",
1120 {"@" + std::string(AMRsp)});
1121 CDB.addFile("b/M.cppm", R"cpp(
1122export module M;
1123export int onlyB = 2;
1124 )cpp",
1125 {"@" + std::string(BMRsp)});
1126 CDB.addFile("a/Use.cpp", R"cpp(
1127import M;
1128int useA() { return onlyA; }
1129 )cpp",
1130 {"@" + std::string(AUseRsp)});
1131 CDB.addFile("b/Use.cpp", R"cpp(
1132import M;
1133int useB() { return onlyB; }
1134 )cpp",
1135 {"@" + std::string(BUseRsp)});
1136
1137 auto ProjectModules = CDB.getProjectModules(getFullPath("a/Use.cpp"));
1138 ASSERT_TRUE(ProjectModules);
1139 EXPECT_EQ(ProjectModules->getModuleNameState("M"),
1141
1142 EXPECT_EQ(
1143 ProjectModules->getSourceForModuleName("M", getFullPath("a/Use.cpp")),
1144 getFullPath("a/M.cppm"));
1145 EXPECT_EQ(
1146 ProjectModules->getSourceForModuleName("M", getFullPath("b/Use.cpp")),
1147 getFullPath("b/M.cppm"));
1148}
1149
1150TEST_F(PrerequisiteModulesTests, DuplicateModuleNamesKeepSeparateBMICache) {
1151 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1152
1153 SmallString<256> APcm(TestDir);
1154 llvm::sys::path::append(APcm, "build", "a", "M.pcm");
1155 SmallString<256> BPcm(TestDir);
1156 llvm::sys::path::append(BPcm, "build", "b", "M.pcm");
1157
1158 CDB.addFile("a/M.cppm", R"cpp(
1159export module M;
1160export int onlyA = 1;
1161 )cpp",
1162 {"--precompile", "-o", std::string(APcm)});
1163 CDB.addFile("b/M.cppm", R"cpp(
1164export module M;
1165export int onlyB = 2;
1166 )cpp",
1167 {"--precompile", "-o", std::string(BPcm)});
1168 CDB.addFile("a/Use.cpp", R"cpp(
1169import M;
1170int useA() { return onlyA; }
1171 )cpp",
1172 {"-fmodule-file=M=" + std::string(APcm)});
1173 CDB.addFile("b/Use.cpp", R"cpp(
1174import M;
1175int useB() { return onlyB; }
1176 )cpp",
1177 {"-fmodule-file=M=" + std::string(BPcm)});
1178
1179 ModulesBuilder Builder(CDB);
1180
1181 auto AInfo =
1182 Builder.buildPrerequisiteModulesFor(getFullPath("a/Use.cpp"), FS);
1183 auto BInfo =
1184 Builder.buildPrerequisiteModulesFor(getFullPath("b/Use.cpp"), FS);
1185 ASSERT_TRUE(AInfo);
1186 ASSERT_TRUE(BInfo);
1187
1188 HeaderSearchOptions HSA(TestDir);
1189 HeaderSearchOptions HSB(TestDir);
1190 AInfo->adjustHeaderSearchOptions(HSA);
1191 BInfo->adjustHeaderSearchOptions(HSB);
1192 ASSERT_EQ(HSA.PrebuiltModuleFiles.count("M"), 1u);
1193 ASSERT_EQ(HSB.PrebuiltModuleFiles.count("M"), 1u);
1194 EXPECT_NE(HSA.PrebuiltModuleFiles["M"], HSB.PrebuiltModuleFiles["M"]);
1195
1196 auto UseA = getInputs("a/Use.cpp", CDB);
1197 UseA.ModulesManager = &Builder;
1198 auto CIA = buildCompilerInvocation(UseA, DiagConsumer);
1199 ASSERT_TRUE(CIA);
1200 auto PreambleA = buildPreamble(getFullPath("a/Use.cpp"), *CIA, UseA,
1201 /*InMemory=*/true, /*Callback=*/nullptr);
1202 ASSERT_TRUE(PreambleA);
1203 auto ASTA = ParsedAST::build(getFullPath("a/Use.cpp"), UseA, std::move(CIA),
1204 {}, PreambleA);
1205 ASSERT_TRUE(ASTA);
1206 EXPECT_TRUE(findDecl(*ASTA, "onlyA").isFromASTFile());
1207
1208 auto UseB = getInputs("b/Use.cpp", CDB);
1209 UseB.ModulesManager = &Builder;
1210 auto CIB = buildCompilerInvocation(UseB, DiagConsumer);
1211 ASSERT_TRUE(CIB);
1212 auto PreambleB = buildPreamble(getFullPath("b/Use.cpp"), *CIB, UseB,
1213 /*InMemory=*/true, /*Callback=*/nullptr);
1214 ASSERT_TRUE(PreambleB);
1215 auto ASTB = ParsedAST::build(getFullPath("b/Use.cpp"), UseB, std::move(CIB),
1216 {}, PreambleB);
1217 ASSERT_TRUE(ASTB);
1218 EXPECT_TRUE(findDecl(*ASTB, "onlyB").isFromASTFile());
1219}
1220
1221TEST_F(PrerequisiteModulesTests, PersistentModuleCacheReusedAcrossBuilders) {
1222 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1223
1224 CDB.addFile("M.cppm", R"cpp(
1225export module M;
1226export int MValue = 43;
1227 )cpp");
1228 CDB.addFile("A.cppm", R"cpp(
1229export module A;
1230import M;
1231export int AValue = MValue;
1232 )cpp");
1233
1234 std::string FirstPCMPath;
1235 {
1236 ModulesBuilder Builder(CDB);
1237 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1238 ASSERT_TRUE(AInfo);
1239 HeaderSearchOptions HS(TestDir);
1240 AInfo->adjustHeaderSearchOptions(HS);
1241 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1242 FirstPCMPath = HS.PrebuiltModuleFiles["M"];
1243 EXPECT_TRUE(llvm::sys::fs::exists(FirstPCMPath));
1244 EXPECT_TRUE(StringRef(FirstPCMPath).contains(".cache/clangd/modules"));
1245 }
1246
1247 EXPECT_FALSE(llvm::sys::fs::exists(FirstPCMPath));
1248
1249 // A fresh builder should reuse the persistent BMI published by the first one
1250 // instead of rebuilding its stable cache entry.
1251 ModulesBuilder Builder(CDB);
1252 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1253 ASSERT_TRUE(AInfo);
1254 HeaderSearchOptions HS(TestDir);
1255 AInfo->adjustHeaderSearchOptions(HS);
1256 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1257 EXPECT_TRUE(llvm::sys::fs::exists(HS.PrebuiltModuleFiles["M"]));
1258 EXPECT_TRUE(StringRef(HS.PrebuiltModuleFiles["M"]).contains("M-"));
1259
1260 ParseInputs AUse = getInputs("A.cppm", CDB);
1261 AUse.ModulesManager = &Builder;
1262 auto Invocation = buildCompilerInvocation(AUse, DiagConsumer);
1263 ASSERT_TRUE(Invocation);
1264 EXPECT_TRUE(AInfo->canReuse(*Invocation, FS.view(TestDir)));
1265}
1266
1267TEST_F(PrerequisiteModulesTests,
1268 PersistentModuleCacheRebuildsAfterDeletingStalePCM) {
1269 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1270
1271 CDB.addFile("M.cppm", R"cpp(
1272export module M;
1273export int MValue = 43;
1274 )cpp");
1275 CDB.addFile("A.cppm", R"cpp(
1276export module A;
1277import M;
1278export int AValue = MValue;
1279 )cpp");
1280
1281 ModulesBuilder Builder(CDB);
1282 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1283 ASSERT_TRUE(AInfo);
1284 HeaderSearchOptions HS(TestDir);
1285 AInfo->adjustHeaderSearchOptions(HS);
1286 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1287 std::string PCMPath = HS.PrebuiltModuleFiles["M"];
1288
1289 std::error_code EC;
1290 llvm::raw_fd_ostream OS(PCMPath, EC);
1291 ASSERT_FALSE(EC);
1292 OS << "broken";
1293 OS.close();
1294
1295 // Corrupt the handed-out BMI and ensure clangd rebuilds a usable replacement.
1296 auto NewAInfo =
1297 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1298 ASSERT_TRUE(NewAInfo);
1299 HeaderSearchOptions NewHS(TestDir);
1300 NewAInfo->adjustHeaderSearchOptions(NewHS);
1301 ASSERT_EQ(NewHS.PrebuiltModuleFiles.count("M"), 1u);
1302 EXPECT_TRUE(llvm::sys::fs::exists(NewHS.PrebuiltModuleFiles["M"]));
1303 EXPECT_TRUE(StringRef(NewHS.PrebuiltModuleFiles["M"]).contains("M-"));
1304
1305 ParseInputs AUse = getInputs("A.cppm", CDB);
1306 AUse.ModulesManager = &Builder;
1307 auto Invocation = buildCompilerInvocation(AUse, DiagConsumer);
1308 ASSERT_TRUE(Invocation);
1309 EXPECT_TRUE(NewAInfo->canReuse(*Invocation, FS.view(TestDir)));
1310}
1311
1312TEST_F(PrerequisiteModulesTests, PersistentModuleCacheCreatesSourceHashLock) {
1313 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1314
1315 CDB.addFile("M.cppm", R"cpp(
1316export module M;
1317export int MValue = 43;
1318 )cpp");
1319 CDB.addFile("A.cppm", R"cpp(
1320export module A;
1321import M;
1322export int AValue = MValue;
1323 )cpp");
1324
1325 ModulesBuilder Builder(CDB);
1326 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1327 ASSERT_TRUE(AInfo);
1328
1329 HeaderSearchOptions HS(TestDir);
1330 AInfo->adjustHeaderSearchOptions(HS);
1331 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1332
1333 llvm::SmallString<256> PCMPath(HS.PrebuiltModuleFiles["M"]);
1334 llvm::sys::path::remove_filename(PCMPath);
1335 llvm::SmallString<256> SourceHashDir(PCMPath);
1336 llvm::sys::path::remove_filename(SourceHashDir);
1337 llvm::SmallString<256> CacheRoot(SourceHashDir);
1338 llvm::sys::path::remove_filename(CacheRoot);
1339
1340 // Locks live next to the persistent cache and are keyed by source-hash so
1341 // builders publishing the same module unit serialize with each other.
1342 llvm::StringRef SourceDirectoryName =
1343 llvm::sys::path::filename(SourceHashDir);
1344 // Split from the right because the readable basename may also contain '-'.
1345 llvm::StringRef SourceHash = SourceDirectoryName.rsplit('-').second;
1346 llvm::SmallString<256> LockPath(CacheRoot);
1347 llvm::sys::path::append(LockPath, ".locks", SourceHash);
1348
1349 EXPECT_TRUE(llvm::sys::fs::exists(LockPath));
1350}
1351
1352TEST_F(PrerequisiteModulesTests,
1353 PersistentModuleCacheGCRemovesOldStablePublishedModule) {
1354 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1355
1356 CDB.addFile("M.cppm", R"cpp(
1357export module M;
1358export int MValue = 43;
1359 )cpp");
1360 CDB.addFile("A.cppm", R"cpp(
1361export module A;
1362import M;
1363export int AValue = MValue;
1364 )cpp");
1365
1366 llvm::SmallString<256> OrphanPCMPath;
1367 {
1368 ModulesBuilder Builder(CDB);
1369 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1370 ASSERT_TRUE(AInfo);
1371 HeaderSearchOptions HS(TestDir);
1372 AInfo->adjustHeaderSearchOptions(HS);
1373 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1374
1375 OrphanPCMPath = HS.PrebuiltModuleFiles["M"];
1376 llvm::sys::path::remove_filename(OrphanPCMPath);
1377 llvm::sys::path::append(OrphanPCMPath, "Orphan.pcm");
1378
1379 std::error_code EC;
1380 llvm::raw_fd_ostream OS(OrphanPCMPath, EC);
1381 ASSERT_FALSE(EC);
1382 OS << "orphan";
1383 OS.close();
1384 EXPECT_TRUE(llvm::sys::fs::exists(OrphanPCMPath));
1385
1386 int FD = -1;
1387 ASSERT_FALSE(llvm::sys::fs::openFileForWrite(OrphanPCMPath, FD,
1388 llvm::sys::fs::CD_OpenExisting,
1389 llvm::sys::fs::OF_None));
1390 auto CloseFD = llvm::scope_exit(
1391 [&] { llvm::sys::Process::SafelyCloseFileDescriptor(FD); });
1392 llvm::sys::TimePoint<> OldTime =
1393 std::chrono::system_clock::now() - std::chrono::hours(24 * 5);
1394 ASSERT_FALSE(llvm::sys::fs::setLastAccessAndModificationTime(FD, OldTime));
1395 }
1396
1397 ModulesBuilder Builder(CDB);
1398 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1399 ASSERT_TRUE(AInfo);
1400 EXPECT_FALSE(llvm::sys::fs::exists(OrphanPCMPath));
1401}
1402
1403TEST_F(PrerequisiteModulesTests,
1404 PersistentModuleCacheGCKeepsRecentStablePublishedModule) {
1405 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1406
1407 CDB.addFile("M.cppm", R"cpp(
1408export module M;
1409export int MValue = 43;
1410 )cpp");
1411 CDB.addFile("A.cppm", R"cpp(
1412export module A;
1413import M;
1414export int AValue = MValue;
1415 )cpp");
1416
1417 llvm::SmallString<256> OrphanPCMPath;
1418 {
1419 ModulesBuilder Builder(CDB);
1420 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1421 ASSERT_TRUE(AInfo);
1422 HeaderSearchOptions HS(TestDir);
1423 AInfo->adjustHeaderSearchOptions(HS);
1424 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1425
1426 OrphanPCMPath = HS.PrebuiltModuleFiles["M"];
1427 llvm::sys::path::remove_filename(OrphanPCMPath);
1428 llvm::sys::path::append(OrphanPCMPath, "Orphan.pcm");
1429
1430 std::error_code EC;
1431 llvm::raw_fd_ostream OS(OrphanPCMPath, EC);
1432 ASSERT_FALSE(EC);
1433 OS << "orphan";
1434 OS.close();
1435 EXPECT_TRUE(llvm::sys::fs::exists(OrphanPCMPath));
1436 }
1437
1438 ModulesBuilder Builder(CDB);
1439 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1440 ASSERT_TRUE(AInfo);
1441 EXPECT_TRUE(llvm::sys::fs::exists(OrphanPCMPath));
1442}
1443
1444TEST_F(PrerequisiteModulesTests,
1445 PersistentModuleCacheGCRemovesOldVersionedModuleFile) {
1446 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1447
1448 CDB.addFile("M.cppm", R"cpp(
1449export module M;
1450export int MValue = 43;
1451 )cpp");
1452 CDB.addFile("A.cppm", R"cpp(
1453export module A;
1454import M;
1455export int AValue = MValue;
1456 )cpp");
1457
1458 llvm::SmallString<256> OldVersionedPCMPath;
1459 {
1460 ModulesBuilder Builder(CDB);
1461 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1462 ASSERT_TRUE(AInfo);
1463 HeaderSearchOptions HS(TestDir);
1464 AInfo->adjustHeaderSearchOptions(HS);
1465 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1466
1467 OldVersionedPCMPath = HS.PrebuiltModuleFiles["M"];
1468 ASSERT_TRUE(llvm::sys::fs::exists(OldVersionedPCMPath));
1469
1470 int FD = -1;
1471 ASSERT_FALSE(llvm::sys::fs::openFileForWrite(OldVersionedPCMPath, FD,
1472 llvm::sys::fs::CD_OpenExisting,
1473 llvm::sys::fs::OF_None));
1474 auto CloseFD = llvm::scope_exit(
1475 [&] { llvm::sys::Process::SafelyCloseFileDescriptor(FD); });
1476 llvm::sys::TimePoint<> OldTime =
1477 std::chrono::system_clock::now() - std::chrono::hours(24 * 5);
1478 ASSERT_FALSE(llvm::sys::fs::setLastAccessAndModificationTime(FD, OldTime));
1479 }
1480
1481 ModulesBuilder Builder(CDB);
1482 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1483 ASSERT_TRUE(AInfo);
1484 EXPECT_FALSE(llvm::sys::fs::exists(OldVersionedPCMPath));
1485}
1486
1487TEST_F(PrerequisiteModulesTests,
1488 PersistentModuleCacheGCKeepsRecentVersionedModuleFile) {
1489 PerFileModulesCompilationDatabase CDB(TestDir, FS);
1490
1491 CDB.addFile("M.cppm", R"cpp(
1492export module M;
1493export int MValue = 43;
1494 )cpp");
1495 CDB.addFile("A.cppm", R"cpp(
1496export module A;
1497import M;
1498export int AValue = MValue;
1499 )cpp");
1500
1501 auto FirstBuilder = std::make_unique<ModulesBuilder>(CDB);
1502 auto AInfo =
1503 FirstBuilder->buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1504 ASSERT_TRUE(AInfo);
1505 HeaderSearchOptions HS(TestDir);
1506 AInfo->adjustHeaderSearchOptions(HS);
1507 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
1508 llvm::StringRef CopyOnReadPCMPath = HS.PrebuiltModuleFiles["M"];
1509 ASSERT_TRUE(llvm::sys::fs::exists(CopyOnReadPCMPath));
1510
1511 ModulesBuilder SecondBuilder(CDB);
1512 auto SecondInfo =
1513 SecondBuilder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
1514 ASSERT_TRUE(SecondInfo);
1515 EXPECT_TRUE(llvm::sys::fs::exists(CopyOnReadPCMPath));
1516}
1517
1518TEST_F(PrerequisiteModulesTests,
1519 PersistentModuleCacheIgnoresRequiredSourceForOnDiskPath) {
1520 ModuleUnitRootCompilationDatabase CDB(TestDir, FS);
1521
1522 CDB.addFile("shared/M.cppm", R"cpp(
1523export module M;
1524export int MValue = 43;
1525 )cpp");
1526 CDB.addFile("projA/A.cppm", R"cpp(
1527export module A;
1528import M;
1529export int AValue = MValue;
1530 )cpp");
1531 CDB.addFile("projB/B.cppm", R"cpp(
1532export module B;
1533import M;
1534export int BValue = MValue;
1535 )cpp");
1536
1537 ModulesBuilder Builder(CDB);
1538
1539 auto AInfo =
1540 Builder.buildPrerequisiteModulesFor(getFullPath("projA/A.cppm"), FS);
1541 auto BInfo =
1542 Builder.buildPrerequisiteModulesFor(getFullPath("projB/B.cppm"), FS);
1543 ASSERT_TRUE(AInfo);
1544 ASSERT_TRUE(BInfo);
1545
1546 HeaderSearchOptions HSA(TestDir);
1547 HeaderSearchOptions HSB(TestDir);
1548 AInfo->adjustHeaderSearchOptions(HSA);
1549 BInfo->adjustHeaderSearchOptions(HSB);
1550 ASSERT_EQ(HSA.PrebuiltModuleFiles.count("M"), 1u);
1551 ASSERT_EQ(HSB.PrebuiltModuleFiles.count("M"), 1u);
1552 EXPECT_TRUE(llvm::sys::fs::exists(HSA.PrebuiltModuleFiles["M"]));
1553 EXPECT_TRUE(llvm::sys::fs::exists(HSB.PrebuiltModuleFiles["M"]));
1554
1555 llvm::SmallString<256> ExpectedRoot(getFullPath("shared/M.cppm"));
1556 llvm::sys::path::remove_filename(ExpectedRoot);
1557 llvm::sys::path::append(ExpectedRoot, ".cache", "clangd", "modules");
1558 EXPECT_TRUE(
1559 StringRef(HSA.PrebuiltModuleFiles["M"]).starts_with(ExpectedRoot));
1560 EXPECT_TRUE(StringRef(HSA.PrebuiltModuleFiles["M"]).contains("M.cppm-"));
1561 EXPECT_TRUE(
1562 StringRef(HSB.PrebuiltModuleFiles["M"]).starts_with(ExpectedRoot));
1563 EXPECT_TRUE(StringRef(HSB.PrebuiltModuleFiles["M"]).contains("M.cppm-"));
1564}
1565
1566TEST_F(PrerequisiteModulesTests, ModuleImportThroughInclude) {
1567 MockDirectoryCompilationDatabase CDB(TestDir, FS);
1568
1569 Annotations UseCpp(R"cpp(
1570#include "Header.hpp"
1571void use() {
1572 TypeFrom^Module t1;
1573 TypeFromHeader t2;
1574}
1575)cpp");
1576
1577 CDB.addFile("M.cppm", R"cpp(
1578export module M;
1579export struct TypeFromModule {};
1580)cpp");
1581
1582 CDB.addFile("Header.hpp", R"cpp(
1583import M;
1584struct TypeFromHeader {};
1585)cpp");
1586
1587 CDB.addFile("Use.cpp", UseCpp.code());
1588
1589 ModulesBuilder Builder(CDB);
1590
1591 auto Inputs = getInputs("Use.cpp", CDB);
1592 Inputs.ModulesManager = &Builder;
1593 Inputs.Opts.SkipPreambleBuild = true;
1594
1595 auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
1596 ASSERT_TRUE(CI);
1597
1598 auto Preamble =
1599 buildPreamble(getFullPath("Use.cpp"), *CI, Inputs, /*StoreInMemory=*/true,
1600 /*PeambleCallback=*/nullptr);
1601 ASSERT_TRUE(Preamble);
1602 EXPECT_EQ(Preamble->Preamble.getBounds().Size, 0u);
1603
1604 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Inputs, std::move(CI), {},
1605 Preamble);
1606 ASSERT_TRUE(AST);
1607 EXPECT_TRUE(AST->getDiagnostics().empty());
1608
1609 auto Result = codeComplete(getFullPath("Use.cpp"), UseCpp.point(),
1610 Preamble.get(), Inputs, {});
1611 EXPECT_THAT(Result.Completions,
1612 testing::UnorderedElementsAre(named("TypeFromModule"),
1613 named("TypeFromHeader")));
1614}
1615
1616} // namespace
1617} // namespace clang::clangd
1618
1619#endif
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
std::string CommandLine
Provides compilation arguments used for parsing C and C++ files.
static std::optional< ParsedAST > build(llvm::StringRef Filename, const ParseInputs &Inputs, std::unique_ptr< clang::CompilerInvocation > CI, llvm::ArrayRef< Diag > CompilerInvocationDiags, std::shared_ptr< const PreambleData > Preamble)
Attempts to run Clang and store the parsed AST.
An interface to query the modules information in the project.
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(std::nullopt_t CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
const NamedDecl & findDecl(ParsedAST &AST, llvm::StringRef QName)
Definition TestTU.cpp:220
std::string maybeCaseFoldPath(PathRef Path)
Definition Path.cpp:18
std::unique_ptr< CompilerInvocation > buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D, std::vector< std::string > *CC1Args)
Builds compiler invocation that could be used to build AST or preamble.
Definition Compiler.cpp:96
MATCHER_P(named, N, "")
std::shared_ptr< const PreambleData > buildPreamble(PathRef FileName, CompilerInvocation CI, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback, PreambleBuildStats *Stats)
Build a preamble for the new inputs unless an old one can be reused.
Definition Preamble.cpp:573
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
std::string Path
A typedef to represent a file path.
Definition Path.h:26
CodeCompleteResult codeComplete(PathRef FileName, Position Pos, const PreambleData *Preamble, const ParseInputs &ParseInput, CodeCompleteOptions Opts, SpeculativeFuzzyFind *SpecFuzzyFind)
Gets code completions at a specified Pos in FileName.
SignatureHelp signatureHelp(PathRef FileName, Position Pos, const PreambleData &Preamble, const ParseInputs &ParseInput, MarkupKind DocumentationFormat)
Get signature help at a specified Pos in FileName.
std::unique_ptr< ProjectModules > getProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Creates the project-modules facade used by clangd.