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