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"
19#include "TestTU.h"
20#include "support/Path.h"
22#include "llvm/ADT/StringRef.h"
23#include "llvm/Support/FileSystem.h"
24#include "llvm/Support/raw_ostream.h"
25#include "gmock/gmock.h"
26#include "gtest/gtest.h"
27
28namespace clang::clangd {
29namespace {
30
31class GlobalScanningCounterProjectModules : public ProjectModules {
32public:
33 GlobalScanningCounterProjectModules(
34 std::unique_ptr<ProjectModules> Underlying, std::atomic<unsigned> &Count)
35 : Underlying(std::move(Underlying)), Count(Count) {}
36
37 std::vector<std::string> getRequiredModules(PathRef File) override {
38 return Underlying->getRequiredModules(File);
39 }
40
41 std::string getModuleNameForSource(PathRef File) override {
42 return Underlying->getModuleNameForSource(File);
43 }
44
45 void setCommandMangler(CommandMangler Mangler) override {
46 Underlying->setCommandMangler(std::move(Mangler));
47 }
48
49 std::string getSourceForModuleName(llvm::StringRef ModuleName,
50 PathRef RequiredSrcFile) override {
51 Count++;
52 return Underlying->getSourceForModuleName(ModuleName, RequiredSrcFile);
53 }
54
55private:
56 std::unique_ptr<ProjectModules> Underlying;
57 std::atomic<unsigned> &Count;
58};
59
60class MockDirectoryCompilationDatabase : public MockCompilationDatabase {
61public:
62 MockDirectoryCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
63 : MockCompilationDatabase(TestDir),
64 MockedCDBPtr(std::make_shared<MockClangCompilationDatabase>(*this)),
65 TFS(TFS), GlobalScanningCount(0) {
66 this->ExtraClangFlags.push_back("-std=c++20");
67 this->ExtraClangFlags.push_back("-c");
68 }
69
70 void addFile(llvm::StringRef Path, llvm::StringRef Contents);
71
72 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
73 return std::make_unique<GlobalScanningCounterProjectModules>(
74 scanningProjectModules(MockedCDBPtr, TFS), GlobalScanningCount);
75 }
76
77 unsigned getGlobalScanningCount() const { return GlobalScanningCount; }
78
79private:
80 class MockClangCompilationDatabase : public tooling::CompilationDatabase {
81 public:
82 MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB)
83 : MCDB(MCDB) {}
84
85 std::vector<tooling::CompileCommand>
86 getCompileCommands(StringRef FilePath) const override {
87 std::optional<tooling::CompileCommand> Cmd =
88 MCDB.getCompileCommand(FilePath);
89 EXPECT_TRUE(Cmd);
90 return {*Cmd};
91 }
92
93 std::vector<std::string> getAllFiles() const override { return Files; }
94
95 void AddFile(StringRef File) { Files.push_back(File.str()); }
96
97 private:
98 MockDirectoryCompilationDatabase &MCDB;
99 std::vector<std::string> Files;
100 };
101
102 std::shared_ptr<MockClangCompilationDatabase> MockedCDBPtr;
103 const ThreadsafeFS &TFS;
104
105 mutable std::atomic<unsigned> GlobalScanningCount;
106};
107
108// Add files to the working testing directory and the compilation database.
109void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path,
110 llvm::StringRef Contents) {
111 ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
112
113 SmallString<256> AbsPath(Directory);
114 llvm::sys::path::append(AbsPath, Path);
115
116 ASSERT_FALSE(
117 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
118
119 std::error_code EC;
120 llvm::raw_fd_ostream OS(AbsPath, EC);
121 ASSERT_FALSE(EC);
122 OS << Contents;
123
124 MockedCDBPtr->AddFile(Path);
125}
126
127class PrerequisiteModulesTests : public ::testing::Test {
128protected:
129 void SetUp() override {
130 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir));
131 }
132
133 void TearDown() override {
134 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir));
135 }
136
137public:
138 // Get the absolute path for file specified by Path under testing working
139 // directory.
140 std::string getFullPath(llvm::StringRef Path) {
141 SmallString<128> Result(TestDir);
142 llvm::sys::path::append(Result, Path);
143 EXPECT_TRUE(llvm::sys::fs::exists(Result.str()));
144 return Result.str().str();
145 }
146
147 ParseInputs getInputs(llvm::StringRef FileName,
148 const GlobalCompilationDatabase &CDB) {
149 std::string FullPathName = getFullPath(FileName);
150
151 ParseInputs Inputs;
152 std::optional<tooling::CompileCommand> Cmd =
153 CDB.getCompileCommand(FullPathName);
154 EXPECT_TRUE(Cmd);
155 Inputs.CompileCommand = std::move(*Cmd);
156 Inputs.TFS = &FS;
157
158 if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName))
159 Inputs.Contents = Contents->get()->getBuffer().str();
160
161 return Inputs;
162 }
163
164 SmallString<256> TestDir;
165 // FIXME: It will be better to use the MockFS if the scanning process and
166 // build module process doesn't depend on reading real IO.
167 RealThreadsafeFS FS;
168
169 DiagnosticConsumer DiagConsumer;
170};
171
172TEST_F(PrerequisiteModulesTests, NonModularTest) {
173 MockDirectoryCompilationDatabase CDB(TestDir, FS);
174
175 CDB.addFile("foo.h", R"cpp(
176inline void foo() {}
177 )cpp");
178
179 CDB.addFile("NonModular.cpp", R"cpp(
180#include "foo.h"
181void use() {
182 foo();
183}
184 )cpp");
185
186 ModulesBuilder Builder(CDB);
187
188 // NonModular.cpp is not related to modules. So nothing should be built.
189 auto NonModularInfo =
190 Builder.buildPrerequisiteModulesFor(getFullPath("NonModular.cpp"), FS);
191 EXPECT_TRUE(NonModularInfo);
192
193 HeaderSearchOptions HSOpts;
194 NonModularInfo->adjustHeaderSearchOptions(HSOpts);
195 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
196
197 auto Invocation =
198 buildCompilerInvocation(getInputs("NonModular.cpp", CDB), DiagConsumer);
199 EXPECT_TRUE(NonModularInfo->canReuse(*Invocation, FS.view(TestDir)));
200}
201
202TEST_F(PrerequisiteModulesTests, ModuleWithoutDepTest) {
203 MockDirectoryCompilationDatabase CDB(TestDir, FS);
204
205 CDB.addFile("foo.h", R"cpp(
206inline void foo() {}
207 )cpp");
208
209 CDB.addFile("M.cppm", R"cpp(
210module;
211#include "foo.h"
212export module M;
213 )cpp");
214
215 ModulesBuilder Builder(CDB);
216
217 auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS);
218 EXPECT_TRUE(MInfo);
219
220 // Nothing should be built since M doesn't dependent on anything.
221 HeaderSearchOptions HSOpts;
222 MInfo->adjustHeaderSearchOptions(HSOpts);
223 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
224
225 auto Invocation =
226 buildCompilerInvocation(getInputs("M.cppm", CDB), DiagConsumer);
227 EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir)));
228}
229
230TEST_F(PrerequisiteModulesTests, ModuleWithArgumentPatch) {
231 MockDirectoryCompilationDatabase CDB(TestDir, FS);
232
233 CDB.ExtraClangFlags.push_back("-invalid-unknown-flag");
234
235 CDB.addFile("Dep.cppm", R"cpp(
236export module Dep;
237 )cpp");
238
239 CDB.addFile("M.cppm", R"cpp(
240export module M;
241import Dep;
242 )cpp");
243
244 // An invalid flag will break the module compilation and the
245 // getRequiredModules would return an empty array
246 auto ProjectModules = CDB.getProjectModules(getFullPath("M.cppm"));
247 EXPECT_TRUE(
248 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
249
250 // Set the mangler to filter out the invalid flag
251 ProjectModules->setCommandMangler([](tooling::CompileCommand &Command,
252 PathRef) {
253 auto const It = llvm::find(Command.CommandLine, "-invalid-unknown-flag");
254 Command.CommandLine.erase(It);
255 });
256
257 // And now it returns a non-empty list of required modules since the
258 // compilation succeeded
259 EXPECT_FALSE(
260 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty());
261}
262
263TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) {
264 MockDirectoryCompilationDatabase CDB(TestDir, FS);
265
266 CDB.addFile("foo.h", R"cpp(
267inline void foo() {}
268 )cpp");
269
270 CDB.addFile("M.cppm", R"cpp(
271module;
272#include "foo.h"
273export module M;
274 )cpp");
275
276 CDB.addFile("N.cppm", R"cpp(
277export module N;
278import :Part;
279import M;
280 )cpp");
281
282 CDB.addFile("N-part.cppm", R"cpp(
283// Different module name with filename intentionally.
284export module N:Part;
285 )cpp");
286
287 ModulesBuilder Builder(CDB);
288
289 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
290 EXPECT_TRUE(NInfo);
291
292 ParseInputs NInput = getInputs("N.cppm", CDB);
293 std::unique_ptr<CompilerInvocation> Invocation =
294 buildCompilerInvocation(NInput, DiagConsumer);
295 // Test that `PrerequisiteModules::canReuse` works basically.
296 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
297
298 {
299 // Check that
300 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
301 // can appending HeaderSearchOptions correctly.
302 HeaderSearchOptions HSOpts;
303 NInfo->adjustHeaderSearchOptions(HSOpts);
304
305 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
306 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N:Part"));
307 }
308
309 {
310 // Check that
311 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
312 // can replace HeaderSearchOptions correctly.
313 HeaderSearchOptions HSOpts;
314 HSOpts.PrebuiltModuleFiles["M"] = "incorrect_path";
315 HSOpts.PrebuiltModuleFiles["N:Part"] = "incorrect_path";
316 NInfo->adjustHeaderSearchOptions(HSOpts);
317
318 EXPECT_TRUE(StringRef(HSOpts.PrebuiltModuleFiles["M"]).ends_with(".pcm"));
319 EXPECT_TRUE(
320 StringRef(HSOpts.PrebuiltModuleFiles["N:Part"]).ends_with(".pcm"));
321 }
322}
323
324TEST_F(PrerequisiteModulesTests, ReusabilityTest) {
325 MockDirectoryCompilationDatabase CDB(TestDir, FS);
326
327 CDB.addFile("foo.h", R"cpp(
328inline void foo() {}
329 )cpp");
330
331 CDB.addFile("M.cppm", R"cpp(
332module;
333#include "foo.h"
334export module M;
335 )cpp");
336
337 CDB.addFile("N.cppm", R"cpp(
338export module N;
339import :Part;
340import M;
341 )cpp");
342
343 CDB.addFile("N-part.cppm", R"cpp(
344// Different module name with filename intentionally.
345export module N:Part;
346 )cpp");
347
348 ModulesBuilder Builder(CDB);
349
350 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
351 EXPECT_TRUE(NInfo);
352 EXPECT_TRUE(NInfo);
353
354 ParseInputs NInput = getInputs("N.cppm", CDB);
355 std::unique_ptr<CompilerInvocation> Invocation =
356 buildCompilerInvocation(NInput, DiagConsumer);
357 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
358
359 // Test that we can still reuse the NInfo after we touch a unrelated file.
360 {
361 CDB.addFile("L.cppm", R"cpp(
362module;
363#include "foo.h"
364export module L;
365export int ll = 43;
366 )cpp");
367 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
368
369 CDB.addFile("bar.h", R"cpp(
370inline void bar() {}
371inline void bar(int) {}
372 )cpp");
373 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
374 }
375
376 // Test that we can't reuse the NInfo after we touch a related file.
377 {
378 CDB.addFile("M.cppm", R"cpp(
379module;
380#include "foo.h"
381export module M;
382export int mm = 44;
383 )cpp");
384 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
385
386 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
387 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
388
389 CDB.addFile("foo.h", R"cpp(
390inline void foo() {}
391inline void foo(int) {}
392 )cpp");
393 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
394
395 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
396 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
397 }
398
399 CDB.addFile("N-part.cppm", R"cpp(
400export module N:Part;
401// Intentioned to make it uncompilable.
402export int NPart = 4LIdjwldijaw
403 )cpp");
404 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
405 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
406 EXPECT_TRUE(NInfo);
407 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
408
409 CDB.addFile("N-part.cppm", R"cpp(
410export module N:Part;
411export int NPart = 43;
412 )cpp");
413 EXPECT_TRUE(NInfo);
414 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
415 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
416 EXPECT_TRUE(NInfo);
417 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
418
419 // Test that if we changed the modification time of the file, the module files
420 // info is still reusable if its content doesn't change.
421 CDB.addFile("N-part.cppm", R"cpp(
422export module N:Part;
423export int NPart = 43;
424 )cpp");
425 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
426
427 CDB.addFile("N.cppm", R"cpp(
428export module N;
429import :Part;
430import M;
431
432export int nn = 43;
433 )cpp");
434 // NInfo should be reusable after we change its content.
435 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
436}
437
438// An End-to-End test for modules.
439TEST_F(PrerequisiteModulesTests, ParsedASTTest) {
440 MockDirectoryCompilationDatabase CDB(TestDir, FS);
441
442 CDB.addFile("A.cppm", R"cpp(
443export module A;
444export void printA();
445 )cpp");
446
447 CDB.addFile("Use.cpp", R"cpp(
448import A;
449)cpp");
450
451 ModulesBuilder Builder(CDB);
452
453 ParseInputs Use = getInputs("Use.cpp", CDB);
454 Use.ModulesManager = &Builder;
455
456 std::unique_ptr<CompilerInvocation> CI =
457 buildCompilerInvocation(Use, DiagConsumer);
458 EXPECT_TRUE(CI);
459
460 auto Preamble =
461 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
462 /*Callback=*/nullptr);
463 EXPECT_TRUE(Preamble);
464 EXPECT_TRUE(Preamble->RequiredModules);
465
466 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Use, std::move(CI), {},
467 Preamble);
468 EXPECT_TRUE(AST);
469
470 const NamedDecl &D = findDecl(*AST, "printA");
471 EXPECT_TRUE(D.isFromASTFile());
472}
473
474// An end to end test for code complete in modules
475TEST_F(PrerequisiteModulesTests, CodeCompleteTest) {
476 MockDirectoryCompilationDatabase CDB(TestDir, FS);
477
478 CDB.addFile("A.cppm", R"cpp(
479export module A;
480export void printA();
481 )cpp");
482
483 llvm::StringLiteral UserContents = R"cpp(
484import A;
485void func() {
486 print^
487}
488)cpp";
489
490 CDB.addFile("Use.cpp", UserContents);
491 Annotations Test(UserContents);
492
493 ModulesBuilder Builder(CDB);
494
495 ParseInputs Use = getInputs("Use.cpp", CDB);
496 Use.ModulesManager = &Builder;
497
498 std::unique_ptr<CompilerInvocation> CI =
499 buildCompilerInvocation(Use, DiagConsumer);
500 EXPECT_TRUE(CI);
501
502 auto Preamble =
503 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
504 /*Callback=*/nullptr);
505 EXPECT_TRUE(Preamble);
506 EXPECT_TRUE(Preamble->RequiredModules);
507
508 auto Result = codeComplete(getFullPath("Use.cpp"), Test.point(),
509 Preamble.get(), Use, {});
510 EXPECT_FALSE(Result.Completions.empty());
511 EXPECT_EQ(Result.Completions[0].Name, "printA");
512}
513
514TEST_F(PrerequisiteModulesTests, SignatureHelpTest) {
515 MockDirectoryCompilationDatabase CDB(TestDir, FS);
516
517 CDB.addFile("A.cppm", R"cpp(
518export module A;
519export void printA(int a);
520 )cpp");
521
522 llvm::StringLiteral UserContents = R"cpp(
523import A;
524void func() {
525 printA(^);
526}
527)cpp";
528
529 CDB.addFile("Use.cpp", UserContents);
530 Annotations Test(UserContents);
531
532 ModulesBuilder Builder(CDB);
533
534 ParseInputs Use = getInputs("Use.cpp", CDB);
535 Use.ModulesManager = &Builder;
536
537 std::unique_ptr<CompilerInvocation> CI =
538 buildCompilerInvocation(Use, DiagConsumer);
539 EXPECT_TRUE(CI);
540
541 auto Preamble =
542 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
543 /*Callback=*/nullptr);
544 EXPECT_TRUE(Preamble);
545 EXPECT_TRUE(Preamble->RequiredModules);
546
547 auto Result = signatureHelp(getFullPath("Use.cpp"), Test.point(), *Preamble,
549 EXPECT_FALSE(Result.signatures.empty());
550 EXPECT_EQ(Result.signatures[0].label, "printA(int a) -> void");
551 EXPECT_EQ(Result.signatures[0].parameters[0].labelString, "int a");
552}
553
554TEST_F(PrerequisiteModulesTests, ReusablePrerequisiteModulesTest) {
555 MockDirectoryCompilationDatabase CDB(TestDir, FS);
556
557 CDB.addFile("M.cppm", R"cpp(
558export module M;
559export int M = 43;
560 )cpp");
561 CDB.addFile("A.cppm", R"cpp(
562export module A;
563import M;
564export int A = 43 + M;
565 )cpp");
566 CDB.addFile("B.cppm", R"cpp(
567export module B;
568import M;
569export int B = 44 + M;
570 )cpp");
571
572 ModulesBuilder Builder(CDB);
573
574 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
575 EXPECT_TRUE(AInfo);
576 auto BInfo = Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
577 EXPECT_TRUE(BInfo);
578 HeaderSearchOptions HSOptsA(TestDir);
579 HeaderSearchOptions HSOptsB(TestDir);
580 AInfo->adjustHeaderSearchOptions(HSOptsA);
581 BInfo->adjustHeaderSearchOptions(HSOptsB);
582
583 EXPECT_FALSE(HSOptsA.PrebuiltModuleFiles.empty());
584 EXPECT_FALSE(HSOptsB.PrebuiltModuleFiles.empty());
585
586 // Check that we're reusing the module files.
587 EXPECT_EQ(HSOptsA.PrebuiltModuleFiles, HSOptsB.PrebuiltModuleFiles);
588
589 // Update M.cppm to check if the modules builder can update correctly.
590 CDB.addFile("M.cppm", R"cpp(
591export module M;
592export constexpr int M = 43;
593 )cpp");
594
595 ParseInputs AUse = getInputs("A.cppm", CDB);
596 AUse.ModulesManager = &Builder;
597 std::unique_ptr<CompilerInvocation> AInvocation =
598 buildCompilerInvocation(AUse, DiagConsumer);
599 EXPECT_FALSE(AInfo->canReuse(*AInvocation, FS.view(TestDir)));
600
601 ParseInputs BUse = getInputs("B.cppm", CDB);
602 AUse.ModulesManager = &Builder;
603 std::unique_ptr<CompilerInvocation> BInvocation =
604 buildCompilerInvocation(BUse, DiagConsumer);
605 EXPECT_FALSE(BInfo->canReuse(*BInvocation, FS.view(TestDir)));
606
607 auto NewAInfo =
608 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
609 auto NewBInfo =
610 Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
611 EXPECT_TRUE(NewAInfo);
612 EXPECT_TRUE(NewBInfo);
613 HeaderSearchOptions NewHSOptsA(TestDir);
614 HeaderSearchOptions NewHSOptsB(TestDir);
615 NewAInfo->adjustHeaderSearchOptions(NewHSOptsA);
616 NewBInfo->adjustHeaderSearchOptions(NewHSOptsB);
617
618 EXPECT_FALSE(NewHSOptsA.PrebuiltModuleFiles.empty());
619 EXPECT_FALSE(NewHSOptsB.PrebuiltModuleFiles.empty());
620
621 EXPECT_EQ(NewHSOptsA.PrebuiltModuleFiles, NewHSOptsB.PrebuiltModuleFiles);
622 // Check that we didn't reuse the old and stale module files.
623 EXPECT_NE(NewHSOptsA.PrebuiltModuleFiles, HSOptsA.PrebuiltModuleFiles);
624}
625
626TEST_F(PrerequisiteModulesTests, ScanningCacheTest) {
627 MockDirectoryCompilationDatabase CDB(TestDir, FS);
628
629 CDB.addFile("M.cppm", R"cpp(
630export module M;
631 )cpp");
632 CDB.addFile("A.cppm", R"cpp(
633export module A;
634import M;
635 )cpp");
636 CDB.addFile("B.cppm", R"cpp(
637export module B;
638import M;
639 )cpp");
640
641 ModulesBuilder Builder(CDB);
642
643 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
644 Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
645 EXPECT_EQ(CDB.getGlobalScanningCount(), 1u);
646}
647
648// Test that canReuse detects changes to headers included in module units.
649// This verifies that the ASTReader correctly tracks header file dependencies
650// in BMI files and that IsModuleFileUpToDate correctly validates them.
651TEST_F(PrerequisiteModulesTests, CanReuseWithHeadersInModuleUnit) {
652 MockDirectoryCompilationDatabase CDB(TestDir, FS);
653
654 // Create a header file that will be included in a module unit
655 CDB.addFile("header1.h", R"cpp(
656inline int getValue() { return 42; }
657 )cpp");
658
659 // Module M includes header1.h in the global module fragment
660 CDB.addFile("M.cppm", R"cpp(
661module;
662#include "header1.h"
663export module M;
664export int m_value = getValue();
665 )cpp");
666
667 // Module N imports M (similar structure to ReusabilityTest)
668 CDB.addFile("N.cppm", R"cpp(
669export module N;
670import :Part;
671import M;
672 )cpp");
673
674 // Add a module partition (similar to ReusabilityTest)
675 CDB.addFile("N-part.cppm", R"cpp(
676export module N:Part;
677 )cpp");
678
679 ModulesBuilder Builder(CDB);
680
681 // Build prerequisite modules for N (which depends on M)
682 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
683 ASSERT_TRUE(NInfo);
684
685 ParseInputs NInput = getInputs("N.cppm", CDB);
686 std::unique_ptr<CompilerInvocation> Invocation =
687 buildCompilerInvocation(NInput, DiagConsumer);
688
689 // Initially, canReuse should return true
690 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
691
692 // Test 1: Modify header1.h (included by M)
693 // canReuse should detect this change since M's BMI records header1.h as input
694 CDB.addFile("header1.h", R"cpp(
695inline int getValue() { return 43; }
696 )cpp");
697 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
698
699 // Rebuild and verify canReuse returns true again
700 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
701 ASSERT_TRUE(NInfo);
702 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
703
704 // Test 2: Modify the module source file itself
705 CDB.addFile("M.cppm", R"cpp(
706module;
707#include "header1.h"
708export module M;
709export int m_value = getValue();
710export int m_new_value = 10;
711 )cpp");
712 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
713
714 // Rebuild after module source change
715 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
716 ASSERT_TRUE(NInfo);
717 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
718}
719
720TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileTest) {
721 MockDirectoryCompilationDatabase CDB(TestDir, FS);
722
723 CDB.addFile("M.cppm", R"cpp(
724export module M;
725 )cpp");
726
727 CDB.addFile("U.cpp", R"cpp(
728import M;
729 )cpp");
730
731 // Use ModulesBuilder to produce the prebuilt module file.
732 ModulesBuilder Builder(CDB);
733 auto ModuleInfo =
734 Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
735 HeaderSearchOptions HS(TestDir);
736 ModuleInfo->adjustHeaderSearchOptions(HS);
737
738 CDB.ExtraClangFlags.push_back("-fmodule-file=M=" +
739 HS.PrebuiltModuleFiles["M"]);
740 ModulesBuilder Builder2(CDB);
741 auto ModuleInfo2 =
742 Builder2.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
743 HeaderSearchOptions HS2(TestDir);
744 ModuleInfo2->adjustHeaderSearchOptions(HS2);
745
746 EXPECT_EQ(HS.PrebuiltModuleFiles, HS2.PrebuiltModuleFiles);
747}
748
749// Test that prebuilt module files with relative paths are correctly resolved.
750// This tests the fix for the issue where clangd couldn't find BMI files when
751// the compilation database contained relative paths in -fmodule-file=
752// arguments.
753TEST_F(PrerequisiteModulesTests, PrebuiltModuleFileWithRelativePath) {
754 MockDirectoryCompilationDatabase CDB(TestDir, FS);
755
756 CDB.addFile("M.cppm", R"cpp(
757export module M;
758export int m_value = 42;
759 )cpp");
760
761 CDB.addFile("U.cpp", R"cpp(
762import M;
763int use() { return m_value; }
764 )cpp");
765
766 // Step 1: Build the module file using ModulesBuilder
767 ModulesBuilder Builder(CDB);
768 auto ModuleInfo =
769 Builder.buildPrerequisiteModulesFor(getFullPath("U.cpp"), FS);
770 ASSERT_TRUE(ModuleInfo);
771
772 HeaderSearchOptions HS(TestDir);
773 ModuleInfo->adjustHeaderSearchOptions(HS);
774 ASSERT_EQ(HS.PrebuiltModuleFiles.count("M"), 1u);
775
776 // Get the absolute path of the built module file
777 std::string OriginalBMPath = HS.PrebuiltModuleFiles["M"];
778 ASSERT_TRUE(llvm::sys::path::is_absolute(OriginalBMPath));
779 ASSERT_TRUE(llvm::sys::fs::exists(OriginalBMPath));
780
781 // Step 2: Create a subdirectory in TestDir and copy the BMI there
782 SmallString<256> BMSubDir(TestDir);
783 llvm::sys::path::append(BMSubDir, "prebuilt_modules");
784 ASSERT_FALSE(llvm::sys::fs::create_directories(BMSubDir));
785
786 SmallString<256> NewBMPath(BMSubDir);
787 llvm::sys::path::append(NewBMPath, "M.pcm");
788
789 // Copy the BMI file to the new location
790 ASSERT_FALSE(llvm::sys::fs::copy_file(OriginalBMPath, NewBMPath));
791 ASSERT_TRUE(llvm::sys::fs::exists(NewBMPath));
792
793 // Step 3: Create a relative path from the new absolute path
794 std::string RelativeBMPath =
795 llvm::StringRef(NewBMPath).drop_front(TestDir.size() + 1).str();
796 ASSERT_FALSE(RelativeBMPath.empty());
797 ASSERT_TRUE(llvm::sys::path::is_relative(RelativeBMPath));
798
799 // Step 4: Create a new CDB with relative path in -fmodule-file=
800 MockDirectoryCompilationDatabase CDBWithRelativePath(TestDir, FS);
801
802 CDBWithRelativePath.addFile("M.cppm", R"cpp(
803export module M;
804export int m_value = 42;
805 )cpp");
806
807 CDBWithRelativePath.addFile("U.cpp", R"cpp(
808import M;
809int use() { return m_value; }
810 )cpp");
811
812 // Use relative path in -fmodule-file= argument
813 CDBWithRelativePath.ExtraClangFlags.push_back("-fmodule-file=M=" +
814 RelativeBMPath);
815
816 // Step 5: Verify that clangd can find and reuse the prebuilt module file
817 ModulesBuilder BuilderWithRelativePath(CDBWithRelativePath);
818 auto ModuleInfo2 = BuilderWithRelativePath.buildPrerequisiteModulesFor(
819 getFullPath("U.cpp"), FS);
820 ASSERT_TRUE(ModuleInfo2);
821
822 HeaderSearchOptions HS2(TestDir);
823 ModuleInfo2->adjustHeaderSearchOptions(HS2);
824
825 // The module file should be found and the paths should match
826 ASSERT_EQ(HS2.PrebuiltModuleFiles.count("M"), 1u);
827 EXPECT_EQ(HS2.PrebuiltModuleFiles["M"], std::string(NewBMPath))
828 << "Expected absolute path: " << NewBMPath
829 << "\nGot: " << HS2.PrebuiltModuleFiles["M"]
830 << "\nRelative path used: " << RelativeBMPath;
831}
832
833} // namespace
834} // namespace clang::clangd
835
836#endif
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
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::unique_ptr< ProjectModules > scanningProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Providing modules information for the project by scanning every file.
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
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:571
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.