[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clangd / unittests / PrerequisiteModulesTest.cpp
blob1bb8e19cce23e07ba5483f04d8fcbc0736e87e48
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 //===----------------------------------------------------------------------===//
10 /// FIXME: Skip testing on windows temporarily due to the different escaping
11 /// code mode.
12 #ifndef _WIN32
14 #include "ModulesBuilder.h"
15 #include "ScanningProjectModules.h"
16 #include "Annotations.h"
17 #include "CodeComplete.h"
18 #include "Compiler.h"
19 #include "TestTU.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 {
27 namespace {
29 class MockDirectoryCompilationDatabase : public MockCompilationDatabase {
30 public:
31 MockDirectoryCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
32 : MockCompilationDatabase(TestDir),
33 MockedCDBPtr(std::make_shared<MockClangCompilationDatabase>(*this)),
34 TFS(TFS) {
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);
45 private:
46 class MockClangCompilationDatabase : public tooling::CompilationDatabase {
47 public:
48 MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB)
49 : MCDB(MCDB) {}
51 std::vector<tooling::CompileCommand>
52 getCompileCommands(StringRef FilePath) const override {
53 std::optional<tooling::CompileCommand> Cmd =
54 MCDB.getCompileCommand(FilePath);
55 EXPECT_TRUE(Cmd);
56 return {*Cmd};
59 std::vector<std::string> getAllFiles() const override { return Files; }
61 void AddFile(StringRef File) { Files.push_back(File.str()); }
63 private:
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);
80 ASSERT_FALSE(
81 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
83 std::error_code EC;
84 llvm::raw_fd_ostream OS(AbsPath, EC);
85 ASSERT_FALSE(EC);
86 OS << Contents;
88 MockedCDBPtr->AddFile(Path);
91 class PrerequisiteModulesTests : public ::testing::Test {
92 protected:
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));
101 public:
102 // Get the absolute path for file specified by Path under testing working
103 // directory.
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);
115 ParseInputs Inputs;
116 std::optional<tooling::CompileCommand> Cmd =
117 CDB.getCompileCommand(FullPathName);
118 EXPECT_TRUE(Cmd);
119 Inputs.CompileCommand = std::move(*Cmd);
120 Inputs.TFS = &FS;
122 if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName))
123 Inputs.Contents = Contents->get()->getBuffer().str();
125 return Inputs;
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.
131 RealThreadsafeFS FS;
133 DiagnosticConsumer DiagConsumer;
136 TEST_F(PrerequisiteModulesTests, NonModularTest) {
137 MockDirectoryCompilationDatabase CDB(TestDir, FS);
139 CDB.addFile("foo.h", R"cpp(
140 inline void foo() {}
141 )cpp");
143 CDB.addFile("NonModular.cpp", R"cpp(
144 #include "foo.h"
145 void use() {
146 foo();
148 )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());
161 auto Invocation =
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(
170 inline void foo() {}
171 )cpp");
173 CDB.addFile("M.cppm", R"cpp(
174 module;
175 #include "foo.h"
176 export module M;
177 )cpp");
179 ModulesBuilder Builder(CDB);
181 auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS);
182 EXPECT_TRUE(MInfo);
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());
189 auto Invocation =
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(
198 inline void foo() {}
199 )cpp");
201 CDB.addFile("M.cppm", R"cpp(
202 module;
203 #include "foo.h"
204 export module M;
205 )cpp");
207 CDB.addFile("N.cppm", R"cpp(
208 export module N;
209 import :Part;
210 import M;
211 )cpp");
213 CDB.addFile("N-part.cppm", R"cpp(
214 // Different module name with filename intentionally.
215 export module N:Part;
216 )cpp");
218 ModulesBuilder Builder(CDB);
220 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
221 EXPECT_TRUE(NInfo);
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)));
230 // Check that
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"));
241 // Check that
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"));
250 EXPECT_TRUE(
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(
259 inline void foo() {}
260 )cpp");
262 CDB.addFile("M.cppm", R"cpp(
263 module;
264 #include "foo.h"
265 export module M;
266 )cpp");
268 CDB.addFile("N.cppm", R"cpp(
269 export module N;
270 import :Part;
271 import M;
272 )cpp");
274 CDB.addFile("N-part.cppm", R"cpp(
275 // Different module name with filename intentionally.
276 export module N:Part;
277 )cpp");
279 ModulesBuilder Builder(CDB);
281 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
282 EXPECT_TRUE(NInfo);
283 EXPECT_TRUE(NInfo);
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(
293 module;
294 #include "foo.h"
295 export module L;
296 export int ll = 43;
297 )cpp");
298 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
300 CDB.addFile("bar.h", R"cpp(
301 inline void bar() {}
302 inline void bar(int) {}
303 )cpp");
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(
310 module;
311 #include "foo.h"
312 export module M;
313 export int mm = 44;
314 )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(
321 inline void foo() {}
322 inline void foo(int) {}
323 )cpp");
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
334 )cpp");
335 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
336 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
337 EXPECT_TRUE(NInfo);
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;
343 )cpp");
344 EXPECT_TRUE(NInfo);
345 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
346 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
347 EXPECT_TRUE(NInfo);
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;
355 )cpp");
356 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
358 CDB.addFile("N.cppm", R"cpp(
359 export module N;
360 import :Part;
361 import M;
363 export int nn = 43;
364 )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(
374 export module A;
375 export void printA();
376 )cpp");
378 CDB.addFile("Use.cpp", R"cpp(
379 import A;
380 )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);
389 EXPECT_TRUE(CI);
391 auto Preamble =
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), {},
398 Preamble);
399 EXPECT_TRUE(AST);
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(
410 export module A;
411 export void printA();
412 )cpp");
414 llvm::StringLiteral UserContents = R"cpp(
415 import A;
416 void func() {
417 print^
419 )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);
431 EXPECT_TRUE(CI);
433 auto Preamble =
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(
449 export module A;
450 export void printA(int a);
451 )cpp");
453 llvm::StringLiteral UserContents = R"cpp(
454 import A;
455 void func() {
456 printA(^);
458 )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);
470 EXPECT_TRUE(CI);
472 auto Preamble =
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(
489 export module M;
490 export int M = 43;
491 )cpp");
492 CDB.addFile("A.cppm", R"cpp(
493 export module A;
494 import M;
495 export int A = 43 + M;
496 )cpp");
497 CDB.addFile("B.cppm", R"cpp(
498 export module B;
499 import M;
500 export int B = 44 + M;
501 )cpp");
503 ModulesBuilder Builder(CDB);
505 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
506 EXPECT_TRUE(AInfo);
507 auto BInfo = Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
508 EXPECT_TRUE(BInfo);
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(
522 export module M;
523 export constexpr int M = 43;
524 )cpp");
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)));
538 auto NewAInfo =
539 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
540 auto NewBInfo =
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);
557 } // namespace
558 } // namespace clang::clangd
560 #endif