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" 18 #include "ScanningProjectModules.h" 19 #include "TestTU.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" 26 27 namespace clang::clangd { 28 namespace { 29 30 class MockDirectoryCompilationDatabase : public MockCompilationDatabase { 31 public: 32 MockDirectoryCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS) 33 : MockCompilationDatabase(TestDir), 34 MockedCDBPtr(std::make_shared<MockClangCompilationDatabase>(*this)), 35 TFS(TFS) { 36 this->ExtraClangFlags.push_back("-std=c++20"); 37 this->ExtraClangFlags.push_back("-c"); 38 } 39 40 void addFile(llvm::StringRef Path, llvm::StringRef Contents); 41 42 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override { 43 return scanningProjectModules(MockedCDBPtr, TFS); 44 } 45 46 private: 47 class MockClangCompilationDatabase : public tooling::CompilationDatabase { 48 public: 49 MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB) 50 : MCDB(MCDB) {} 51 52 std::vector<tooling::CompileCommand> 53 getCompileCommands(StringRef FilePath) const override { 54 std::optional<tooling::CompileCommand> Cmd = 55 MCDB.getCompileCommand(FilePath); 56 EXPECT_TRUE(Cmd); 57 return {*Cmd}; 58 } 59 60 std::vector<std::string> getAllFiles() const override { return Files; } 61 62 void AddFile(StringRef File) { Files.push_back(File.str()); } 63 64 private: 65 MockDirectoryCompilationDatabase &MCDB; 66 std::vector<std::string> Files; 67 }; 68 69 std::shared_ptr<MockClangCompilationDatabase> MockedCDBPtr; 70 const ThreadsafeFS &TFS; 71 }; 72 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)); 77 78 SmallString<256> AbsPath(Directory); 79 llvm::sys::path::append(AbsPath, Path); 80 81 ASSERT_FALSE( 82 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath))); 83 84 std::error_code EC; 85 llvm::raw_fd_ostream OS(AbsPath, EC); 86 ASSERT_FALSE(EC); 87 OS << Contents; 88 89 MockedCDBPtr->AddFile(Path); 90 } 91 92 class PrerequisiteModulesTests : public ::testing::Test { 93 protected: 94 void SetUp() override { 95 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir)); 96 } 97 98 void TearDown() override { 99 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir)); 100 } 101 102 public: 103 // Get the absolute path for file specified by Path under testing working 104 // directory. 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(); 110 } 111 112 ParseInputs getInputs(llvm::StringRef FileName, 113 const GlobalCompilationDatabase &CDB) { 114 std::string FullPathName = getFullPath(FileName); 115 116 ParseInputs Inputs; 117 std::optional<tooling::CompileCommand> Cmd = 118 CDB.getCompileCommand(FullPathName); 119 EXPECT_TRUE(Cmd); 120 Inputs.CompileCommand = std::move(*Cmd); 121 Inputs.TFS = &FS; 122 123 if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName)) 124 Inputs.Contents = Contents->get()->getBuffer().str(); 125 126 return Inputs; 127 } 128 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. 132 RealThreadsafeFS FS; 133 134 DiagnosticConsumer DiagConsumer; 135 }; 136 137 TEST_F(PrerequisiteModulesTests, NonModularTest) { 138 MockDirectoryCompilationDatabase CDB(TestDir, FS); 139 140 CDB.addFile("foo.h", R"cpp( 141 inline void foo() {} 142 )cpp"); 143 144 CDB.addFile("NonModular.cpp", R"cpp( 145 #include "foo.h" 146 void use() { 147 foo(); 148 } 149 )cpp"); 150 151 ModulesBuilder Builder(CDB); 152 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); 157 158 HeaderSearchOptions HSOpts; 159 NonModularInfo->adjustHeaderSearchOptions(HSOpts); 160 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty()); 161 162 auto Invocation = 163 buildCompilerInvocation(getInputs("NonModular.cpp", CDB), DiagConsumer); 164 EXPECT_TRUE(NonModularInfo->canReuse(*Invocation, FS.view(TestDir))); 165 } 166 167 TEST_F(PrerequisiteModulesTests, ModuleWithoutDepTest) { 168 MockDirectoryCompilationDatabase CDB(TestDir, FS); 169 170 CDB.addFile("foo.h", R"cpp( 171 inline void foo() {} 172 )cpp"); 173 174 CDB.addFile("M.cppm", R"cpp( 175 module; 176 #include "foo.h" 177 export module M; 178 )cpp"); 179 180 ModulesBuilder Builder(CDB); 181 182 auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS); 183 EXPECT_TRUE(MInfo); 184 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()); 189 190 auto Invocation = 191 buildCompilerInvocation(getInputs("M.cppm", CDB), DiagConsumer); 192 EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir))); 193 } 194 195 TEST_F(PrerequisiteModulesTests, ModuleWithArgumentPatch) { 196 MockDirectoryCompilationDatabase CDB(TestDir, FS); 197 198 CDB.ExtraClangFlags.push_back("-invalid-unknown-flag"); 199 200 CDB.addFile("Dep.cppm", R"cpp( 201 export module Dep; 202 )cpp"); 203 204 CDB.addFile("M.cppm", R"cpp( 205 export module M; 206 import Dep; 207 )cpp"); 208 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")); 212 EXPECT_TRUE( 213 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty()); 214 215 // Set the mangler to filter out the invalid flag 216 ProjectModules->setCommandMangler( 217 [](tooling::CompileCommand &Command, PathRef) { 218 auto const It = 219 std::find(Command.CommandLine.begin(), Command.CommandLine.end(), 220 "-invalid-unknown-flag"); 221 Command.CommandLine.erase(It); 222 }); 223 224 // And now it returns a non-empty list of required modules since the 225 // compilation succeeded 226 EXPECT_FALSE( 227 ProjectModules->getRequiredModules(getFullPath("M.cppm")).empty()); 228 } 229 230 TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) { 231 MockDirectoryCompilationDatabase CDB(TestDir, FS); 232 233 CDB.addFile("foo.h", R"cpp( 234 inline void foo() {} 235 )cpp"); 236 237 CDB.addFile("M.cppm", R"cpp( 238 module; 239 #include "foo.h" 240 export module M; 241 )cpp"); 242 243 CDB.addFile("N.cppm", R"cpp( 244 export module N; 245 import :Part; 246 import M; 247 )cpp"); 248 249 CDB.addFile("N-part.cppm", R"cpp( 250 // Different module name with filename intentionally. 251 export module N:Part; 252 )cpp"); 253 254 ModulesBuilder Builder(CDB); 255 256 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS); 257 EXPECT_TRUE(NInfo); 258 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))); 264 265 { 266 // Check that 267 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)` 268 // can appending HeaderSearchOptions correctly. 269 HeaderSearchOptions HSOpts; 270 NInfo->adjustHeaderSearchOptions(HSOpts); 271 272 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M")); 273 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N:Part")); 274 } 275 276 { 277 // Check that 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); 284 285 EXPECT_TRUE(StringRef(HSOpts.PrebuiltModuleFiles["M"]).ends_with(".pcm")); 286 EXPECT_TRUE( 287 StringRef(HSOpts.PrebuiltModuleFiles["N:Part"]).ends_with(".pcm")); 288 } 289 } 290 291 TEST_F(PrerequisiteModulesTests, ReusabilityTest) { 292 MockDirectoryCompilationDatabase CDB(TestDir, FS); 293 294 CDB.addFile("foo.h", R"cpp( 295 inline void foo() {} 296 )cpp"); 297 298 CDB.addFile("M.cppm", R"cpp( 299 module; 300 #include "foo.h" 301 export module M; 302 )cpp"); 303 304 CDB.addFile("N.cppm", R"cpp( 305 export module N; 306 import :Part; 307 import M; 308 )cpp"); 309 310 CDB.addFile("N-part.cppm", R"cpp( 311 // Different module name with filename intentionally. 312 export module N:Part; 313 )cpp"); 314 315 ModulesBuilder Builder(CDB); 316 317 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS); 318 EXPECT_TRUE(NInfo); 319 EXPECT_TRUE(NInfo); 320 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))); 325 326 // Test that we can still reuse the NInfo after we touch a unrelated file. 327 { 328 CDB.addFile("L.cppm", R"cpp( 329 module; 330 #include "foo.h" 331 export module L; 332 export int ll = 43; 333 )cpp"); 334 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 335 336 CDB.addFile("bar.h", R"cpp( 337 inline void bar() {} 338 inline void bar(int) {} 339 )cpp"); 340 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 341 } 342 343 // Test that we can't reuse the NInfo after we touch a related file. 344 { 345 CDB.addFile("M.cppm", R"cpp( 346 module; 347 #include "foo.h" 348 export module M; 349 export int mm = 44; 350 )cpp"); 351 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 352 353 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS); 354 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 355 356 CDB.addFile("foo.h", R"cpp( 357 inline void foo() {} 358 inline void foo(int) {} 359 )cpp"); 360 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 361 362 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS); 363 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 364 } 365 366 CDB.addFile("N-part.cppm", R"cpp( 367 export module N:Part; 368 // Intentioned to make it uncompilable. 369 export int NPart = 4LIdjwldijaw 370 )cpp"); 371 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 372 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS); 373 EXPECT_TRUE(NInfo); 374 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 375 376 CDB.addFile("N-part.cppm", R"cpp( 377 export module N:Part; 378 export int NPart = 43; 379 )cpp"); 380 EXPECT_TRUE(NInfo); 381 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 382 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS); 383 EXPECT_TRUE(NInfo); 384 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 385 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; 391 )cpp"); 392 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 393 394 CDB.addFile("N.cppm", R"cpp( 395 export module N; 396 import :Part; 397 import M; 398 399 export int nn = 43; 400 )cpp"); 401 // NInfo should be reusable after we change its content. 402 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir))); 403 } 404 405 // An End-to-End test for modules. 406 TEST_F(PrerequisiteModulesTests, ParsedASTTest) { 407 MockDirectoryCompilationDatabase CDB(TestDir, FS); 408 409 CDB.addFile("A.cppm", R"cpp( 410 export module A; 411 export void printA(); 412 )cpp"); 413 414 CDB.addFile("Use.cpp", R"cpp( 415 import A; 416 )cpp"); 417 418 ModulesBuilder Builder(CDB); 419 420 ParseInputs Use = getInputs("Use.cpp", CDB); 421 Use.ModulesManager = &Builder; 422 423 std::unique_ptr<CompilerInvocation> CI = 424 buildCompilerInvocation(Use, DiagConsumer); 425 EXPECT_TRUE(CI); 426 427 auto Preamble = 428 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true, 429 /*Callback=*/nullptr); 430 EXPECT_TRUE(Preamble); 431 EXPECT_TRUE(Preamble->RequiredModules); 432 433 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Use, std::move(CI), {}, 434 Preamble); 435 EXPECT_TRUE(AST); 436 437 const NamedDecl &D = findDecl(*AST, "printA"); 438 EXPECT_TRUE(D.isFromASTFile()); 439 } 440 441 // An end to end test for code complete in modules 442 TEST_F(PrerequisiteModulesTests, CodeCompleteTest) { 443 MockDirectoryCompilationDatabase CDB(TestDir, FS); 444 445 CDB.addFile("A.cppm", R"cpp( 446 export module A; 447 export void printA(); 448 )cpp"); 449 450 llvm::StringLiteral UserContents = R"cpp( 451 import A; 452 void func() { 453 print^ 454 } 455 )cpp"; 456 457 CDB.addFile("Use.cpp", UserContents); 458 Annotations Test(UserContents); 459 460 ModulesBuilder Builder(CDB); 461 462 ParseInputs Use = getInputs("Use.cpp", CDB); 463 Use.ModulesManager = &Builder; 464 465 std::unique_ptr<CompilerInvocation> CI = 466 buildCompilerInvocation(Use, DiagConsumer); 467 EXPECT_TRUE(CI); 468 469 auto Preamble = 470 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true, 471 /*Callback=*/nullptr); 472 EXPECT_TRUE(Preamble); 473 EXPECT_TRUE(Preamble->RequiredModules); 474 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"); 479 } 480 481 TEST_F(PrerequisiteModulesTests, SignatureHelpTest) { 482 MockDirectoryCompilationDatabase CDB(TestDir, FS); 483 484 CDB.addFile("A.cppm", R"cpp( 485 export module A; 486 export void printA(int a); 487 )cpp"); 488 489 llvm::StringLiteral UserContents = R"cpp( 490 import A; 491 void func() { 492 printA(^); 493 } 494 )cpp"; 495 496 CDB.addFile("Use.cpp", UserContents); 497 Annotations Test(UserContents); 498 499 ModulesBuilder Builder(CDB); 500 501 ParseInputs Use = getInputs("Use.cpp", CDB); 502 Use.ModulesManager = &Builder; 503 504 std::unique_ptr<CompilerInvocation> CI = 505 buildCompilerInvocation(Use, DiagConsumer); 506 EXPECT_TRUE(CI); 507 508 auto Preamble = 509 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true, 510 /*Callback=*/nullptr); 511 EXPECT_TRUE(Preamble); 512 EXPECT_TRUE(Preamble->RequiredModules); 513 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"); 519 } 520 521 TEST_F(PrerequisiteModulesTests, ReusablePrerequisiteModulesTest) { 522 MockDirectoryCompilationDatabase CDB(TestDir, FS); 523 524 CDB.addFile("M.cppm", R"cpp( 525 export module M; 526 export int M = 43; 527 )cpp"); 528 CDB.addFile("A.cppm", R"cpp( 529 export module A; 530 import M; 531 export int A = 43 + M; 532 )cpp"); 533 CDB.addFile("B.cppm", R"cpp( 534 export module B; 535 import M; 536 export int B = 44 + M; 537 )cpp"); 538 539 ModulesBuilder Builder(CDB); 540 541 auto AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS); 542 EXPECT_TRUE(AInfo); 543 auto BInfo = Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS); 544 EXPECT_TRUE(BInfo); 545 HeaderSearchOptions HSOptsA(TestDir); 546 HeaderSearchOptions HSOptsB(TestDir); 547 AInfo->adjustHeaderSearchOptions(HSOptsA); 548 BInfo->adjustHeaderSearchOptions(HSOptsB); 549 550 EXPECT_FALSE(HSOptsA.PrebuiltModuleFiles.empty()); 551 EXPECT_FALSE(HSOptsB.PrebuiltModuleFiles.empty()); 552 553 // Check that we're reusing the module files. 554 EXPECT_EQ(HSOptsA.PrebuiltModuleFiles, HSOptsB.PrebuiltModuleFiles); 555 556 // Update M.cppm to check if the modules builder can update correctly. 557 CDB.addFile("M.cppm", R"cpp( 558 export module M; 559 export constexpr int M = 43; 560 )cpp"); 561 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))); 567 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))); 573 574 auto NewAInfo = 575 Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS); 576 auto NewBInfo = 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); 584 585 EXPECT_FALSE(NewHSOptsA.PrebuiltModuleFiles.empty()); 586 EXPECT_FALSE(NewHSOptsB.PrebuiltModuleFiles.empty()); 587 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); 591 } 592 593 } // namespace 594 } // namespace clang::clangd 595 596 #endif 597