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 "ModulesBuilder.h"
15 #include "ScanningProjectModules.h"
16 #include "Annotations.h"
17 #include "CodeComplete.h"
20 #include "support/ThreadsafeFS.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/raw_ostream.h"
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
26 namespace clang::clangd
{
29 class MockDirectoryCompilationDatabase
: public MockCompilationDatabase
{
31 MockDirectoryCompilationDatabase(StringRef TestDir
, const ThreadsafeFS
&TFS
)
32 : MockCompilationDatabase(TestDir
),
33 MockedCDBPtr(std::make_shared
<MockClangCompilationDatabase
>(*this)),
35 this->ExtraClangFlags
.push_back("-std=c++20");
36 this->ExtraClangFlags
.push_back("-c");
39 void addFile(llvm::StringRef Path
, llvm::StringRef Contents
);
41 std::unique_ptr
<ProjectModules
> getProjectModules(PathRef
) const override
{
42 return scanningProjectModules(MockedCDBPtr
, TFS
);
46 class MockClangCompilationDatabase
: public tooling::CompilationDatabase
{
48 MockClangCompilationDatabase(MockDirectoryCompilationDatabase
&MCDB
)
51 std::vector
<tooling::CompileCommand
>
52 getCompileCommands(StringRef FilePath
) const override
{
53 std::optional
<tooling::CompileCommand
> Cmd
=
54 MCDB
.getCompileCommand(FilePath
);
59 std::vector
<std::string
> getAllFiles() const override
{ return Files
; }
61 void AddFile(StringRef File
) { Files
.push_back(File
.str()); }
64 MockDirectoryCompilationDatabase
&MCDB
;
65 std::vector
<std::string
> Files
;
68 std::shared_ptr
<MockClangCompilationDatabase
> MockedCDBPtr
;
69 const ThreadsafeFS
&TFS
;
72 // Add files to the working testing directory and the compilation database.
73 void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path
,
74 llvm::StringRef Contents
) {
75 ASSERT_FALSE(llvm::sys::path::is_absolute(Path
));
77 SmallString
<256> AbsPath(Directory
);
78 llvm::sys::path::append(AbsPath
, Path
);
81 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath
)));
84 llvm::raw_fd_ostream
OS(AbsPath
, EC
);
88 MockedCDBPtr
->AddFile(Path
);
91 class PrerequisiteModulesTests
: public ::testing::Test
{
93 void SetUp() override
{
94 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir
));
97 void TearDown() override
{
98 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir
));
102 // Get the absolute path for file specified by Path under testing working
104 std::string
getFullPath(llvm::StringRef Path
) {
105 SmallString
<128> Result(TestDir
);
106 llvm::sys::path::append(Result
, Path
);
107 EXPECT_TRUE(llvm::sys::fs::exists(Result
.str()));
108 return Result
.str().str();
111 ParseInputs
getInputs(llvm::StringRef FileName
,
112 const GlobalCompilationDatabase
&CDB
) {
113 std::string FullPathName
= getFullPath(FileName
);
116 std::optional
<tooling::CompileCommand
> Cmd
=
117 CDB
.getCompileCommand(FullPathName
);
119 Inputs
.CompileCommand
= std::move(*Cmd
);
122 if (auto Contents
= FS
.view(TestDir
)->getBufferForFile(FullPathName
))
123 Inputs
.Contents
= Contents
->get()->getBuffer().str();
128 SmallString
<256> TestDir
;
129 // FIXME: It will be better to use the MockFS if the scanning process and
130 // build module process doesn't depend on reading real IO.
133 DiagnosticConsumer DiagConsumer
;
136 TEST_F(PrerequisiteModulesTests
, NonModularTest
) {
137 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
139 CDB
.addFile("foo.h", R
"cpp(
143 CDB
.addFile("NonModular.cpp", R
"cpp(
150 ModulesBuilder
Builder(CDB
);
152 // NonModular.cpp is not related to modules. So nothing should be built.
153 auto NonModularInfo
=
154 Builder
.buildPrerequisiteModulesFor(getFullPath("NonModular.cpp"), FS
);
155 EXPECT_TRUE(NonModularInfo
);
157 HeaderSearchOptions HSOpts
;
158 NonModularInfo
->adjustHeaderSearchOptions(HSOpts
);
159 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.empty());
162 buildCompilerInvocation(getInputs("NonModular.cpp", CDB
), DiagConsumer
);
163 EXPECT_TRUE(NonModularInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
166 TEST_F(PrerequisiteModulesTests
, ModuleWithoutDepTest
) {
167 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
169 CDB
.addFile("foo.h", R
"cpp(
173 CDB
.addFile("M.cppm", R
"cpp(
179 ModulesBuilder
Builder(CDB
);
181 auto MInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS
);
184 // Nothing should be built since M doesn't dependent on anything.
185 HeaderSearchOptions HSOpts
;
186 MInfo
->adjustHeaderSearchOptions(HSOpts
);
187 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.empty());
190 buildCompilerInvocation(getInputs("M.cppm", CDB
), DiagConsumer
);
191 EXPECT_TRUE(MInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
194 TEST_F(PrerequisiteModulesTests
, ModuleWithDepTest
) {
195 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
197 CDB
.addFile("foo.h", R
"cpp(
201 CDB
.addFile("M.cppm", R
"cpp(
207 CDB
.addFile("N.cppm", R
"cpp(
213 CDB
.addFile("N-part.cppm", R
"cpp(
214 // Different module name with filename intentionally.
215 export module N:Part;
218 ModulesBuilder
Builder(CDB
);
220 auto NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
223 ParseInputs NInput
= getInputs("N.cppm", CDB
);
224 std::unique_ptr
<CompilerInvocation
> Invocation
=
225 buildCompilerInvocation(NInput
, DiagConsumer
);
226 // Test that `PrerequisiteModules::canReuse` works basically.
227 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
231 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
232 // can appending HeaderSearchOptions correctly.
233 HeaderSearchOptions HSOpts
;
234 NInfo
->adjustHeaderSearchOptions(HSOpts
);
236 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.count("M"));
237 EXPECT_TRUE(HSOpts
.PrebuiltModuleFiles
.count("N:Part"));
242 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
243 // can replace HeaderSearchOptions correctly.
244 HeaderSearchOptions HSOpts
;
245 HSOpts
.PrebuiltModuleFiles
["M"] = "incorrect_path";
246 HSOpts
.PrebuiltModuleFiles
["N:Part"] = "incorrect_path";
247 NInfo
->adjustHeaderSearchOptions(HSOpts
);
249 EXPECT_TRUE(StringRef(HSOpts
.PrebuiltModuleFiles
["M"]).ends_with(".pcm"));
251 StringRef(HSOpts
.PrebuiltModuleFiles
["N:Part"]).ends_with(".pcm"));
255 TEST_F(PrerequisiteModulesTests
, ReusabilityTest
) {
256 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
258 CDB
.addFile("foo.h", R
"cpp(
262 CDB
.addFile("M.cppm", R
"cpp(
268 CDB
.addFile("N.cppm", R
"cpp(
274 CDB
.addFile("N-part.cppm", R
"cpp(
275 // Different module name with filename intentionally.
276 export module N:Part;
279 ModulesBuilder
Builder(CDB
);
281 auto NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
285 ParseInputs NInput
= getInputs("N.cppm", CDB
);
286 std::unique_ptr
<CompilerInvocation
> Invocation
=
287 buildCompilerInvocation(NInput
, DiagConsumer
);
288 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
290 // Test that we can still reuse the NInfo after we touch a unrelated file.
292 CDB
.addFile("L.cppm", R
"cpp(
298 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
300 CDB
.addFile("bar.h", R
"cpp(
302 inline void bar(int) {}
304 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
307 // Test that we can't reuse the NInfo after we touch a related file.
309 CDB
.addFile("M.cppm", R
"cpp(
315 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
317 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
318 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
320 CDB
.addFile("foo.h", R
"cpp(
322 inline void foo(int) {}
324 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
326 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
327 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
330 CDB
.addFile("N-part.cppm", R
"cpp(
331 export module N:Part;
332 // Intentioned to make it uncompilable.
333 export int NPart = 4LIdjwldijaw
335 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
336 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
338 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
340 CDB
.addFile("N-part.cppm", R
"cpp(
341 export module N:Part;
342 export int NPart = 43;
345 EXPECT_FALSE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
346 NInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS
);
348 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
350 // Test that if we changed the modification time of the file, the module files
351 // info is still reusable if its content doesn't change.
352 CDB
.addFile("N-part.cppm", R
"cpp(
353 export module N:Part;
354 export int NPart = 43;
356 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
358 CDB
.addFile("N.cppm", R
"cpp(
365 // NInfo should be reusable after we change its content.
366 EXPECT_TRUE(NInfo
->canReuse(*Invocation
, FS
.view(TestDir
)));
369 // An End-to-End test for modules.
370 TEST_F(PrerequisiteModulesTests
, ParsedASTTest
) {
371 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
373 CDB
.addFile("A.cppm", R
"cpp(
375 export void printA();
378 CDB
.addFile("Use.cpp", R
"cpp(
382 ModulesBuilder
Builder(CDB
);
384 ParseInputs Use
= getInputs("Use.cpp", CDB
);
385 Use
.ModulesManager
= &Builder
;
387 std::unique_ptr
<CompilerInvocation
> CI
=
388 buildCompilerInvocation(Use
, DiagConsumer
);
392 buildPreamble(getFullPath("Use.cpp"), *CI
, Use
, /*InMemory=*/true,
393 /*Callback=*/nullptr);
394 EXPECT_TRUE(Preamble
);
395 EXPECT_TRUE(Preamble
->RequiredModules
);
397 auto AST
= ParsedAST::build(getFullPath("Use.cpp"), Use
, std::move(CI
), {},
401 const NamedDecl
&D
= findDecl(*AST
, "printA");
402 EXPECT_TRUE(D
.isFromASTFile());
405 // An end to end test for code complete in modules
406 TEST_F(PrerequisiteModulesTests
, CodeCompleteTest
) {
407 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
409 CDB
.addFile("A.cppm", R
"cpp(
411 export void printA();
414 llvm::StringLiteral UserContents
= R
"cpp(
421 CDB
.addFile("Use.cpp", UserContents
);
422 Annotations
Test(UserContents
);
424 ModulesBuilder
Builder(CDB
);
426 ParseInputs Use
= getInputs("Use.cpp", CDB
);
427 Use
.ModulesManager
= &Builder
;
429 std::unique_ptr
<CompilerInvocation
> CI
=
430 buildCompilerInvocation(Use
, DiagConsumer
);
434 buildPreamble(getFullPath("Use.cpp"), *CI
, Use
, /*InMemory=*/true,
435 /*Callback=*/nullptr);
436 EXPECT_TRUE(Preamble
);
437 EXPECT_TRUE(Preamble
->RequiredModules
);
439 auto Result
= codeComplete(getFullPath("Use.cpp"), Test
.point(),
440 Preamble
.get(), Use
, {});
441 EXPECT_FALSE(Result
.Completions
.empty());
442 EXPECT_EQ(Result
.Completions
[0].Name
, "printA");
445 TEST_F(PrerequisiteModulesTests
, SignatureHelpTest
) {
446 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
448 CDB
.addFile("A.cppm", R
"cpp(
450 export void printA(int a);
453 llvm::StringLiteral UserContents
= R
"cpp(
460 CDB
.addFile("Use.cpp", UserContents
);
461 Annotations
Test(UserContents
);
463 ModulesBuilder
Builder(CDB
);
465 ParseInputs Use
= getInputs("Use.cpp", CDB
);
466 Use
.ModulesManager
= &Builder
;
468 std::unique_ptr
<CompilerInvocation
> CI
=
469 buildCompilerInvocation(Use
, DiagConsumer
);
473 buildPreamble(getFullPath("Use.cpp"), *CI
, Use
, /*InMemory=*/true,
474 /*Callback=*/nullptr);
475 EXPECT_TRUE(Preamble
);
476 EXPECT_TRUE(Preamble
->RequiredModules
);
478 auto Result
= signatureHelp(getFullPath("Use.cpp"), Test
.point(),
479 *Preamble
.get(), Use
, MarkupKind::PlainText
);
480 EXPECT_FALSE(Result
.signatures
.empty());
481 EXPECT_EQ(Result
.signatures
[0].label
, "printA(int a) -> void");
482 EXPECT_EQ(Result
.signatures
[0].parameters
[0].labelString
, "int a");
485 TEST_F(PrerequisiteModulesTests
, ReusablePrerequisiteModulesTest
) {
486 MockDirectoryCompilationDatabase
CDB(TestDir
, FS
);
488 CDB
.addFile("M.cppm", R
"cpp(
492 CDB
.addFile("A.cppm", R
"cpp(
495 export int A = 43 + M;
497 CDB
.addFile("B.cppm", R
"cpp(
500 export int B = 44 + M;
503 ModulesBuilder
Builder(CDB
);
505 auto AInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS
);
507 auto BInfo
= Builder
.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS
);
509 HeaderSearchOptions
HSOptsA(TestDir
);
510 HeaderSearchOptions
HSOptsB(TestDir
);
511 AInfo
->adjustHeaderSearchOptions(HSOptsA
);
512 BInfo
->adjustHeaderSearchOptions(HSOptsB
);
514 EXPECT_FALSE(HSOptsA
.PrebuiltModuleFiles
.empty());
515 EXPECT_FALSE(HSOptsB
.PrebuiltModuleFiles
.empty());
517 // Check that we're reusing the module files.
518 EXPECT_EQ(HSOptsA
.PrebuiltModuleFiles
, HSOptsB
.PrebuiltModuleFiles
);
520 // Update M.cppm to check if the modules builder can update correctly.
521 CDB
.addFile("M.cppm", R
"cpp(
523 export constexpr int M = 43;
526 ParseInputs AUse
= getInputs("A.cppm", CDB
);
527 AUse
.ModulesManager
= &Builder
;
528 std::unique_ptr
<CompilerInvocation
> AInvocation
=
529 buildCompilerInvocation(AUse
, DiagConsumer
);
530 EXPECT_FALSE(AInfo
->canReuse(*AInvocation
, FS
.view(TestDir
)));
532 ParseInputs BUse
= getInputs("B.cppm", CDB
);
533 AUse
.ModulesManager
= &Builder
;
534 std::unique_ptr
<CompilerInvocation
> BInvocation
=
535 buildCompilerInvocation(BUse
, DiagConsumer
);
536 EXPECT_FALSE(BInfo
->canReuse(*BInvocation
, FS
.view(TestDir
)));
539 Builder
.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS
);
541 Builder
.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS
);
542 EXPECT_TRUE(NewAInfo
);
543 EXPECT_TRUE(NewBInfo
);
544 HeaderSearchOptions
NewHSOptsA(TestDir
);
545 HeaderSearchOptions
NewHSOptsB(TestDir
);
546 NewAInfo
->adjustHeaderSearchOptions(NewHSOptsA
);
547 NewBInfo
->adjustHeaderSearchOptions(NewHSOptsB
);
549 EXPECT_FALSE(NewHSOptsA
.PrebuiltModuleFiles
.empty());
550 EXPECT_FALSE(NewHSOptsB
.PrebuiltModuleFiles
.empty());
552 EXPECT_EQ(NewHSOptsA
.PrebuiltModuleFiles
, NewHSOptsB
.PrebuiltModuleFiles
);
553 // Check that we didn't reuse the old and stale module files.
554 EXPECT_NE(NewHSOptsA
.PrebuiltModuleFiles
, HSOptsA
.PrebuiltModuleFiles
);
558 } // namespace clang::clangd