1 //===--------------- PrerequisiteModulesTests.cpp -------------------*- C++
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
8 //===----------------------------------------------------------------------===//
10 /// FIXME: Skip testing on windows temporarily due to the different escaping
14 #include "Annotations.h"
15 #include "CodeComplete.h"
17 #include "ModulesBuilder.h"
18 #include "ScanningProjectModules.h"
20 #include "support/ThreadsafeFS.h"
21 #include "llvm/ADT/StringRef.h"
22 #include "llvm/Support/FileSystem.h"
23 #include "llvm/Support/raw_ostream.h"
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
27 namespace clang::clangd
{
30 class MockDirectoryCompilationDatabase
: public MockCompilationDatabase
{
32 MockDirectoryCompilationDatabase(StringRef TestDir
, const ThreadsafeFS
&TFS
)
33 : MockCompilationDatabase(TestDir
),
34 MockedCDBPtr(std::make_shared
<MockClangCompilationDatabase
>(*this)),
36 this->ExtraClangFlags
.push_back("-std=c++20");
37 this->ExtraClangFlags
.push_back("-c");
40 void addFile(llvm::StringRef Path
, llvm::StringRef Contents
);
42 std::unique_ptr
<ProjectModules
> getProjectModules(PathRef
) const override
{
43 return scanningProjectModules(MockedCDBPtr
, TFS
);
47 class MockClangCompilationDatabase
: public tooling::CompilationDatabase
{
49 MockClangCompilationDatabase(MockDirectoryCompilationDatabase
&MCDB
)
52 std::vector
<tooling::CompileCommand
>
53 getCompileCommands(StringRef FilePath
) const override
{
54 std::optional
<tooling::CompileCommand
> Cmd
=
55 MCDB
.getCompileCommand(FilePath
);
60 std::vector
<std::string
> getAllFiles() const override
{ return Files
; }
62 void AddFile(StringRef File
) { Files
.push_back(File
.str()); }
65 MockDirectoryCompilationDatabase
&MCDB
;
66 std::vector
<std::string
> Files
;
69 std::shared_ptr
<MockClangCompilationDatabase
> MockedCDBPtr
;
70 const ThreadsafeFS
&TFS
;
73 // Add files to the working testing directory and the compilation database.
74 void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path
,
75 llvm::StringRef Contents
) {
76 ASSERT_FALSE(llvm::sys::path::is_absolute(Path
));
78 SmallString
<256> AbsPath(Directory
);
79 llvm::sys::path::append(AbsPath
, Path
);
82 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath
)));
85 llvm::raw_fd_ostream
OS(AbsPath
, EC
);
89 MockedCDBPtr
->AddFile(Path
);
92 class PrerequisiteModulesTests
: public ::testing::Test
{
94 void SetUp() override
{
95 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir
));
98 void TearDown() override
{
99 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir
));
103 // Get the absolute path for file specified by Path under testing working
105 std::string
getFullPath(llvm::StringRef Path
) {
106 SmallString
<128> Result(TestDir
);
107 llvm::sys::path::append(Result
, Path
);
108 EXPECT_TRUE(llvm::sys::fs::exists(Result
.str()));
109 return Result
.str().str();
112 ParseInputs
getInputs(llvm::StringRef FileName
,
113 const GlobalCompilationDatabase
&CDB
) {
114 std::string FullPathName
= getFullPath(FileName
);
117 std::optional
<tooling::CompileCommand
> Cmd
=
118 CDB
.getCompileCommand(FullPathName
);
120 Inputs
.CompileCommand
= std::move(*Cmd
);
123 if (auto Contents
= FS
.view(TestDir
)->getBufferForFile(FullPathName
))
124 Inputs
.Contents
= Contents
->get()->getBuffer().str();
129 SmallString
<256> TestDir
;
130 // FIXME: It will be better to use the MockFS if the scanning process and
131 // build module process doesn't depend on reading real IO.
134 DiagnosticConsumer DiagConsumer
;
137 TEST_F(PrerequisiteModulesTests
, NonModularTest
) {
138 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
140 CDB
.addFile("foo.h", R
"cpp(
144 CDB
.addFile("NonModular.cpp", R
"cpp(
151 ModulesBuilder
Builder(CDB
);
153 // NonModular.cpp is not related to modules. So nothing should be built.
154 auto NonModularInfo
=
155 Builder
.buildPrerequisiteModulesFor(getFullPath("NonModular.cpp"), FS
);
156 EXPECT_TRUE(NonModularInfo
);
158 HeaderSearchOptions HSOpts
;
159 NonModularInfo
->adjustHeaderSearchOptions(HSOpts
);
160 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.empty());
163 buildCompilerInvocation(getInputs("NonModular.cpp", CDB
), DiagConsumer
);
164 EXPECT_TRUE(NonModularInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
167 TEST_F(PrerequisiteModulesTests
, ModuleWithoutDepTest
) {
168 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
170 CDB
.addFile("foo.h", R
"cpp(
174 CDB
.addFile("M.cppm", R
"cpp(
180 ModulesBuilder
Builder(CDB
);
182 auto MInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS
);
185 // Nothing should be built since M doesn't dependent on anything.
186 HeaderSearchOptions HSOpts
;
187 MInfo
->adjustHeaderSearchOptions(HSOpts
);
188 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.empty());
191 buildCompilerInvocation(getInputs("M.cppm", CDB
), DiagConsumer
);
192 EXPECT_TRUE(MInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
195 TEST_F(PrerequisiteModulesTests
, ModuleWithArgumentPatch
) {
196 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
198 CDB
.ExtraClangFlags
.push_back("-invalid-unknown-flag");
200 CDB
.addFile("Dep.cppm", R
"cpp(
204 CDB
.addFile("M.cppm", R
"cpp(
209 // An invalid flag will break the module compilation and the
210 // getRequiredModules would return an empty array
211 auto ProjectModules
= CDB
.getProjectModules(getFullPath("M.cppm"));
213 ProjectModules
->getRequiredModules(getFullPath("M.cppm")).empty());
215 // Set the mangler to filter out the invalid flag
216 ProjectModules
->setCommandMangler(
217 [](tooling::CompileCommand
&Command
, PathRef
) {
219 std::find(Command
.CommandLine
.begin(), Command
.CommandLine
.end(),
220 "-invalid-unknown-flag");
221 Command
.CommandLine
.erase(It
);
224 // And now it returns a non-empty list of required modules since the
225 // compilation succeeded
227 ProjectModules
->getRequiredModules(getFullPath("M.cppm")).empty());
230 TEST_F(PrerequisiteModulesTests
, ModuleWithDepTest
) {
231 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
233 CDB
.addFile("foo.h", R
"cpp(
237 CDB
.addFile("M.cppm", R
"cpp(
243 CDB
.addFile("N.cppm", R
"cpp(
249 CDB
.addFile("N-part.cppm", R
"cpp(
250 // Different module name with filename intentionally.
251 export module N:Part;
254 ModulesBuilder
Builder(CDB
);
256 auto NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
259 ParseInputs NInput
= getInputs("N.cppm", CDB
);
260 std::unique_ptr
<CompilerInvocation
> Invocation
=
261 buildCompilerInvocation(NInput
, DiagConsumer
);
262 // Test that `PrerequisiteModules::canReuse` works basically.
263 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
267 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
268 // can appending HeaderSearchOptions correctly.
269 HeaderSearchOptions HSOpts
;
270 NInfo
->adjustHeaderSearchOptions(HSOpts
);
272 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.count("M"));
273 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.count("N:Part"));
278 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
279 // can replace HeaderSearchOptions correctly.
280 HeaderSearchOptions HSOpts
;
281 HSOpts
.PrebuiltModuleFiles
["M"] = "incorrect_path";
282 HSOpts
.PrebuiltModuleFiles
["N:Part"] = "incorrect_path";
283 NInfo
->adjustHeaderSearchOptions(HSOpts
);
285 EXPECT_TRUE(StringRef(HSOpts
.PrebuiltModuleFiles
["M"]).ends_with(".pcm"));
287 StringRef(HSOpts
.PrebuiltModuleFiles
["N:Part"]).ends_with(".pcm"));
291 TEST_F(PrerequisiteModulesTests
, ReusabilityTest
) {
292 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
294 CDB
.addFile("foo.h", R
"cpp(
298 CDB
.addFile("M.cppm", R
"cpp(
304 CDB
.addFile("N.cppm", R
"cpp(
310 CDB
.addFile("N-part.cppm", R
"cpp(
311 // Different module name with filename intentionally.
312 export module N:Part;
315 ModulesBuilder
Builder(CDB
);
317 auto NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
321 ParseInputs NInput
= getInputs("N.cppm", CDB
);
322 std::unique_ptr
<CompilerInvocation
> Invocation
=
323 buildCompilerInvocation(NInput
, DiagConsumer
);
324 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
326 // Test that we can still reuse the NInfo after we touch a unrelated file.
328 CDB
.addFile("L.cppm", R
"cpp(
334 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
336 CDB
.addFile("bar.h", R
"cpp(
338 inline void bar(int) {}
340 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
343 // Test that we can't reuse the NInfo after we touch a related file.
345 CDB
.addFile("M.cppm", R
"cpp(
351 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
353 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
354 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
356 CDB
.addFile("foo.h", R
"cpp(
358 inline void foo(int) {}
360 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
362 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
363 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
366 CDB
.addFile("N-part.cppm", R
"cpp(
367 export module N:Part;
368 // Intentioned to make it uncompilable.
369 export int NPart = 4LIdjwldijaw
371 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
372 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
374 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
376 CDB
.addFile("N-part.cppm", R
"cpp(
377 export module N:Part;
378 export int NPart = 43;
381 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
382 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
384 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
386 // Test that if we changed the modification time of the file, the module files
387 // info is still reusable if its content doesn't change.
388 CDB
.addFile("N-part.cppm", R
"cpp(
389 export module N:Part;
390 export int NPart = 43;
392 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
394 CDB
.addFile("N.cppm", R
"cpp(
401 // NInfo should be reusable after we change its content.
402 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
405 // An End-to-End test for modules.
406 TEST_F(PrerequisiteModulesTests
, ParsedASTTest
) {
407 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
409 CDB
.addFile("A.cppm", R
"cpp(
411 export void printA();
414 CDB
.addFile("Use.cpp", R
"cpp(
418 ModulesBuilder
Builder(CDB
);
420 ParseInputs Use
= getInputs("Use.cpp", CDB
);
421 Use
.ModulesManager
= &Builder
;
423 std::unique_ptr
<CompilerInvocation
> CI
=
424 buildCompilerInvocation(Use
, DiagConsumer
);
428 buildPreamble(getFullPath("Use.cpp"), *CI
, Use
, /*InMemory=*/true,
429 /*Callback=*/nullptr);
430 EXPECT_TRUE(Preamble
);
431 EXPECT_TRUE(Preamble
->RequiredModules
);
433 auto AST
= ParsedAST::build(getFullPath("Use.cpp"), Use
, std::move(CI
), {},
437 const NamedDecl
&D
= findDecl(*AST
, "printA");
438 EXPECT_TRUE(D
.isFromASTFile());
441 // An end to end test for code complete in modules
442 TEST_F(PrerequisiteModulesTests
, CodeCompleteTest
) {
443 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
445 CDB
.addFile("A.cppm", R
"cpp(
447 export void printA();
450 llvm::StringLiteral UserContents
= R
"cpp(
457 CDB
.addFile("Use.cpp", UserContents
);
458 Annotations
Test(UserContents
);
460 ModulesBuilder
Builder(CDB
);
462 ParseInputs Use
= getInputs("Use.cpp", CDB
);
463 Use
.ModulesManager
= &Builder
;
465 std::unique_ptr
<CompilerInvocation
> CI
=
466 buildCompilerInvocation(Use
, DiagConsumer
);
470 buildPreamble(getFullPath("Use.cpp"), *CI
, Use
, /*InMemory=*/true,
471 /*Callback=*/nullptr);
472 EXPECT_TRUE(Preamble
);
473 EXPECT_TRUE(Preamble
->RequiredModules
);
475 auto Result
= codeComplete(getFullPath("Use.cpp"), Test
.point(),
476 Preamble
.get(), Use
, {});
477 EXPECT_FALSE(Result
.Completions
.empty());
478 EXPECT_EQ(Result
.Completions
[0].Name
, "printA");
481 TEST_F(PrerequisiteModulesTests
, SignatureHelpTest
) {
482 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
484 CDB
.addFile("A.cppm", R
"cpp(
486 export void printA(int a);
489 llvm::StringLiteral UserContents
= R
"cpp(
496 CDB
.addFile("Use.cpp", UserContents
);
497 Annotations
Test(UserContents
);
499 ModulesBuilder
Builder(CDB
);
501 ParseInputs Use
= getInputs("Use.cpp", CDB
);
502 Use
.ModulesManager
= &Builder
;
504 std::unique_ptr
<CompilerInvocation
> CI
=
505 buildCompilerInvocation(Use
, DiagConsumer
);
509 buildPreamble(getFullPath("Use.cpp"), *CI
, Use
, /*InMemory=*/true,
510 /*Callback=*/nullptr);
511 EXPECT_TRUE(Preamble
);
512 EXPECT_TRUE(Preamble
->RequiredModules
);
514 auto Result
= signatureHelp(getFullPath("Use.cpp"), Test
.point(),
515 *Preamble
.get(), Use
, MarkupKind::PlainText
);
516 EXPECT_FALSE(Result
.signatures
.empty());
517 EXPECT_EQ(Result
.signatures
[0].label
, "printA(int a) -> void");
518 EXPECT_EQ(Result
.signatures
[0].parameters
[0].labelString
, "int a");
521 TEST_F(PrerequisiteModulesTests
, ReusablePrerequisiteModulesTest
) {
522 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
524 CDB
.addFile("M.cppm", R
"cpp(
528 CDB
.addFile("A.cppm", R
"cpp(
531 export int A = 43 + M;
533 CDB
.addFile("B.cppm", R
"cpp(
536 export int B = 44 + M;
539 ModulesBuilder
Builder(CDB
);
541 auto AInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS
);
543 auto BInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS
);
545 HeaderSearchOptions
HSOptsA(TestDir
);
546 HeaderSearchOptions
HSOptsB(TestDir
);
547 AInfo
->adjustHeaderSearchOptions(HSOptsA
);
548 BInfo
->adjustHeaderSearchOptions(HSOptsB
);
550 EXPECT_FALSE(HSOptsA
.PrebuiltModuleFiles
.empty());
551 EXPECT_FALSE(HSOptsB
.PrebuiltModuleFiles
.empty());
553 // Check that we're reusing the module files.
554 EXPECT_EQ(HSOptsA
.PrebuiltModuleFiles
, HSOptsB
.PrebuiltModuleFiles
);
556 // Update M.cppm to check if the modules builder can update correctly.
557 CDB
.addFile("M.cppm", R
"cpp(
559 export constexpr int M = 43;
562 ParseInputs AUse
= getInputs("A.cppm", CDB
);
563 AUse
.ModulesManager
= &Builder
;
564 std::unique_ptr
<CompilerInvocation
> AInvocation
=
565 buildCompilerInvocation(AUse
, DiagConsumer
);
566 EXPECT_FALSE(AInfo
->canReuse(*AInvocation
, FS
.view(TestDir
)));
568 ParseInputs BUse
= getInputs("B.cppm", CDB
);
569 AUse
.ModulesManager
= &Builder
;
570 std::unique_ptr
<CompilerInvocation
> BInvocation
=
571 buildCompilerInvocation(BUse
, DiagConsumer
);
572 EXPECT_FALSE(BInfo
->canReuse(*BInvocation
, FS
.view(TestDir
)));
575 Builder
.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS
);
577 Builder
.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS
);
578 EXPECT_TRUE(NewAInfo
);
579 EXPECT_TRUE(NewBInfo
);
580 HeaderSearchOptions
NewHSOptsA(TestDir
);
581 HeaderSearchOptions
NewHSOptsB(TestDir
);
582 NewAInfo
->adjustHeaderSearchOptions(NewHSOptsA
);
583 NewBInfo
->adjustHeaderSearchOptions(NewHSOptsB
);
585 EXPECT_FALSE(NewHSOptsA
.PrebuiltModuleFiles
.empty());
586 EXPECT_FALSE(NewHSOptsB
.PrebuiltModuleFiles
.empty());
588 EXPECT_EQ(NewHSOptsA
.PrebuiltModuleFiles
, NewHSOptsB
.PrebuiltModuleFiles
);
589 // Check that we didn't reuse the old and stale module files.
590 EXPECT_NE(NewHSOptsA
.PrebuiltModuleFiles
, HSOptsA
.PrebuiltModuleFiles
);
594 } // namespace clang::clangd