clang-tools 20.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 "ModulesBuilder.h"
16#include "Annotations.h"
17#include "CodeComplete.h"
18#include "Compiler.h"
19#include "TestTU.h"
21#include "llvm/Support/FileSystem.h"
22#include "llvm/Support/raw_ostream.h"
23#include "gmock/gmock.h"
24#include "gtest/gtest.h"
25
26namespace clang::clangd {
27namespace {
28
29class MockDirectoryCompilationDatabase : public MockCompilationDatabase {
30public:
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");
37 }
38
39 void addFile(llvm::StringRef Path, llvm::StringRef Contents);
40
41 std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
42 return scanningProjectModules(MockedCDBPtr, TFS);
43 }
44
45private:
46 class MockClangCompilationDatabase : public tooling::CompilationDatabase {
47 public:
48 MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB)
49 : MCDB(MCDB) {}
50
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};
57 }
58
59 std::vector<std::string> getAllFiles() const override { return Files; }
60
61 void AddFile(StringRef File) { Files.push_back(File.str()); }
62
63 private:
64 MockDirectoryCompilationDatabase &MCDB;
65 std::vector<std::string> Files;
66 };
67
68 std::shared_ptr<MockClangCompilationDatabase> MockedCDBPtr;
69 const ThreadsafeFS &TFS;
70};
71
72// Add files to the working testing directory and the compilation database.
73void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path,
74 llvm::StringRef Contents) {
75 ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
76
77 SmallString<256> AbsPath(Directory);
78 llvm::sys::path::append(AbsPath, Path);
79
80 ASSERT_FALSE(
81 llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
82
83 std::error_code EC;
84 llvm::raw_fd_ostream OS(AbsPath, EC);
85 ASSERT_FALSE(EC);
86 OS << Contents;
87
88 MockedCDBPtr->AddFile(Path);
89}
90
91class PrerequisiteModulesTests : public ::testing::Test {
92protected:
93 void SetUp() override {
94 ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir));
95 }
96
97 void TearDown() override {
98 ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir));
99 }
100
101public:
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();
109 }
110
111 ParseInputs getInputs(llvm::StringRef FileName,
112 const GlobalCompilationDatabase &CDB) {
113 std::string FullPathName = getFullPath(FileName);
114
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;
121
122 if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName))
123 Inputs.Contents = Contents->get()->getBuffer().str();
124
125 return Inputs;
126 }
127
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;
132
134};
135
136TEST_F(PrerequisiteModulesTests, NonModularTest) {
137 MockDirectoryCompilationDatabase CDB(TestDir, FS);
138
139 CDB.addFile("foo.h", R"cpp(
140inline void foo() {}
141 )cpp");
142
143 CDB.addFile("NonModular.cpp", R"cpp(
144#include "foo.h"
145void use() {
146 foo();
147}
148 )cpp");
149
150 ModulesBuilder Builder(CDB);
151
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);
156
157 HeaderSearchOptions HSOpts;
158 NonModularInfo->adjustHeaderSearchOptions(HSOpts);
159 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
160
161 auto Invocation =
162 buildCompilerInvocation(getInputs("NonModular.cpp", CDB), DiagConsumer);
163 EXPECT_TRUE(NonModularInfo->canReuse(*Invocation, FS.view(TestDir)));
164}
165
166TEST_F(PrerequisiteModulesTests, ModuleWithoutDepTest) {
167 MockDirectoryCompilationDatabase CDB(TestDir, FS);
168
169 CDB.addFile("foo.h", R"cpp(
170inline void foo() {}
171 )cpp");
172
173 CDB.addFile("M.cppm", R"cpp(
174module;
175#include "foo.h"
176export module M;
177 )cpp");
178
179 ModulesBuilder Builder(CDB);
180
181 auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS);
182 EXPECT_TRUE(MInfo);
183
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());
188
189 auto Invocation =
190 buildCompilerInvocation(getInputs("M.cppm", CDB), DiagConsumer);
191 EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir)));
192}
193
194TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) {
195 MockDirectoryCompilationDatabase CDB(TestDir, FS);
196
197 CDB.addFile("foo.h", R"cpp(
198inline void foo() {}
199 )cpp");
200
201 CDB.addFile("M.cppm", R"cpp(
202module;
203#include "foo.h"
204export module M;
205 )cpp");
206
207 CDB.addFile("N.cppm", R"cpp(
208export module N;
209import :Part;
210import M;
211 )cpp");
212
213 CDB.addFile("N-part.cppm", R"cpp(
214// Different module name with filename intentionally.
215export module N:Part;
216 )cpp");
217
218 ModulesBuilder Builder(CDB);
219
220 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
221 EXPECT_TRUE(NInfo);
222
223 ParseInputs NInput = getInputs("N.cppm", CDB);
224 std::unique_ptr<CompilerInvocation> Invocation =
226 // Test that `PrerequisiteModules::canReuse` works basically.
227 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
228
229 {
230 // Check that
231 // `PrerequisiteModules::adjustHeaderSearchOptions(HeaderSearchOptions&)`
232 // can appending HeaderSearchOptions correctly.
233 HeaderSearchOptions HSOpts;
234 NInfo->adjustHeaderSearchOptions(HSOpts);
235
236 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
237 EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N:Part"));
238 }
239
240 {
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);
248
249 EXPECT_TRUE(StringRef(HSOpts.PrebuiltModuleFiles["M"]).ends_with(".pcm"));
250 EXPECT_TRUE(
251 StringRef(HSOpts.PrebuiltModuleFiles["N:Part"]).ends_with(".pcm"));
252 }
253}
254
255TEST_F(PrerequisiteModulesTests, ReusabilityTest) {
256 MockDirectoryCompilationDatabase CDB(TestDir, FS);
257
258 CDB.addFile("foo.h", R"cpp(
259inline void foo() {}
260 )cpp");
261
262 CDB.addFile("M.cppm", R"cpp(
263module;
264#include "foo.h"
265export module M;
266 )cpp");
267
268 CDB.addFile("N.cppm", R"cpp(
269export module N;
270import :Part;
271import M;
272 )cpp");
273
274 CDB.addFile("N-part.cppm", R"cpp(
275// Different module name with filename intentionally.
276export module N:Part;
277 )cpp");
278
279 ModulesBuilder Builder(CDB);
280
281 auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
282 EXPECT_TRUE(NInfo);
283 EXPECT_TRUE(NInfo);
284
285 ParseInputs NInput = getInputs("N.cppm", CDB);
286 std::unique_ptr<CompilerInvocation> Invocation =
288 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
289
290 // Test that we can still reuse the NInfo after we touch a unrelated file.
291 {
292 CDB.addFile("L.cppm", R"cpp(
293module;
294#include "foo.h"
295export module L;
296export int ll = 43;
297 )cpp");
298 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
299
300 CDB.addFile("bar.h", R"cpp(
301inline void bar() {}
302inline void bar(int) {}
303 )cpp");
304 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
305 }
306
307 // Test that we can't reuse the NInfo after we touch a related file.
308 {
309 CDB.addFile("M.cppm", R"cpp(
310module;
311#include "foo.h"
312export module M;
313export int mm = 44;
314 )cpp");
315 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
316
317 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
318 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
319
320 CDB.addFile("foo.h", R"cpp(
321inline void foo() {}
322inline void foo(int) {}
323 )cpp");
324 EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
325
326 NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
327 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
328 }
329
330 CDB.addFile("N-part.cppm", R"cpp(
331export module N:Part;
332// Intentioned to make it uncompilable.
333export 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)));
339
340 CDB.addFile("N-part.cppm", R"cpp(
341export module N:Part;
342export 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)));
349
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(
353export module N:Part;
354export int NPart = 43;
355 )cpp");
356 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
357
358 CDB.addFile("N.cppm", R"cpp(
359export module N;
360import :Part;
361import M;
362
363export int nn = 43;
364 )cpp");
365 // NInfo should be reusable after we change its content.
366 EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
367}
368
369// An End-to-End test for modules.
370TEST_F(PrerequisiteModulesTests, ParsedASTTest) {
371 MockDirectoryCompilationDatabase CDB(TestDir, FS);
372
373 CDB.addFile("A.cppm", R"cpp(
374export module A;
375export void printA();
376 )cpp");
377
378 CDB.addFile("Use.cpp", R"cpp(
379import A;
380)cpp");
381
382 ModulesBuilder Builder(CDB);
383
384 ParseInputs Use = getInputs("Use.cpp", CDB);
385 Use.ModulesManager = &Builder;
386
387 std::unique_ptr<CompilerInvocation> CI =
389 EXPECT_TRUE(CI);
390
391 auto Preamble =
392 buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
393 /*Callback=*/nullptr);
394 EXPECT_TRUE(Preamble);
395 EXPECT_TRUE(Preamble->RequiredModules);
396
397 auto AST = ParsedAST::build(getFullPath("Use.cpp"), Use, std::move(CI), {},
398 Preamble);
399 EXPECT_TRUE(AST);
400
401 const NamedDecl &D = findDecl(*AST, "printA");
402 EXPECT_TRUE(D.isFromASTFile());
403}
404
405} // namespace
406} // namespace clang::clangd
407
408#endif
CodeCompletionBuilder Builder
StringRef FileName
DiagnosticConsumer DiagConsumer
SmallString< 256 > TestDir
llvm::StringRef Directory
std::unique_ptr< CompilerInvocation > CI
llvm::raw_string_ostream OS
Definition: TraceTests.cpp:160
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.
Definition: ParsedAST.cpp:403
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(std::nullopt_t CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
Definition: ThreadsafeFS.h:32
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:219
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::string Path
A typedef to represent a file path.
Definition: Path.h:26
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:95
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:591
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:29