1//===-- RecordTest.cpp ----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang-include-cleaner/Record.h"
10#include "clang-include-cleaner/Types.h"
11#include "clang/AST/Decl.h"
12#include "clang/Basic/Diagnostic.h"
13#include "clang/Basic/LLVM.h"
14#include "clang/Basic/SourceLocation.h"
15#include "clang/Frontend/CompilerInvocation.h"
16#include "clang/Frontend/FrontendAction.h"
17#include "clang/Frontend/FrontendActions.h"
18#include "clang/Frontend/FrontendOptions.h"
19#include "clang/Serialization/PCHContainerOperations.h"
20#include "clang/Testing/TestAST.h"
21#include "clang/Tooling/Inclusions/StandardLibrary.h"
22#include "llvm/ADT/ArrayRef.h"
23#include "llvm/ADT/IntrusiveRefCntPtr.h"
24#include "llvm/ADT/StringRef.h"
25#include "llvm/Support/Error.h"
26#include "llvm/Support/MemoryBuffer.h"
27#include "llvm/Support/VirtualFileSystem.h"
28#include "llvm/Support/raw_ostream.h"
29#include "llvm/Testing/Annotations/Annotations.h"
30#include "gmock/gmock.h"
31#include "gtest/gtest.h"
32#include <cassert>
33#include <memory>
34#include <optional>
35#include <utility>
36
37namespace clang::include_cleaner {
38namespace {
39using testing::ElementsAreArray;
40using testing::IsEmpty;
41
42// Matches a Decl* if it is a NamedDecl with the given name.
43MATCHER_P(named, N, "") {
44 if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(arg)) {
45 if (N == ND->getNameAsString())
46 return true;
47 }
48 std::string S;
49 llvm::raw_string_ostream OS(S);
50 arg->dump(OS);
51 *result_listener << S;
52 return false;
53}
54
55MATCHER_P(FileNamed, N, "") {
56 if (arg.getFileEntry().tryGetRealPathName() == N)
57 return true;
58 *result_listener << arg.getFileEntry().tryGetRealPathName().str();
59 return false;
60}
61
62class RecordASTTest : public ::testing::Test {
63protected:
64 TestInputs Inputs;
65 RecordedAST Recorded;
66
67 RecordASTTest() {
68 struct RecordAction : public ASTFrontendAction {
69 RecordedAST &Out;
70 RecordAction(RecordedAST &Out) : Out(Out) {}
71 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
72 StringRef) override {
73 return Out.record();
74 }
75 };
76 Inputs.MakeAction = [this] {
77 return std::make_unique<RecordAction>(args&: Recorded);
78 };
79 }
80
81 TestAST build() { return TestAST(Inputs); }
82};
83
84// Top-level decl from the main file is a root, nested ones aren't.
85TEST_F(RecordASTTest, Namespace) {
86 Inputs.Code =
87 R"cpp(
88 namespace ns {
89 int x;
90 namespace {
91 int y;
92 }
93 }
94 )cpp";
95 auto AST = build();
96 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("ns")));
97}
98
99// Decl in included file is not a root.
100TEST_F(RecordASTTest, Inclusion) {
101 Inputs.ExtraFiles["header.h"] = "void headerFunc();";
102 Inputs.Code = R"cpp(
103 #include "header.h"
104 void mainFunc();
105 )cpp";
106 auto AST = build();
107 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("mainFunc")));
108}
109
110// Decl from macro expanded into the main file is a root.
111TEST_F(RecordASTTest, Macros) {
112 Inputs.ExtraFiles["header.h"] = "#define X void x();";
113 Inputs.Code = R"cpp(
114 #include "header.h"
115 X
116 )cpp";
117 auto AST = build();
118 EXPECT_THAT(Recorded.Roots, testing::ElementsAre(named("x")));
119}
120
121// Decl from template instantiation is filtered out from roots.
122TEST_F(RecordASTTest, ImplicitTemplates) {
123 Inputs.ExtraFiles["dispatch.h"] = R"cpp(
124 struct A {
125 static constexpr int value = 1;
126 };
127 template <class Getter>
128 int dispatch() {
129 return Getter::template get<A>();
130 }
131 )cpp";
132 Inputs.Code = R"cpp(
133 #include "dispatch.h"
134 struct MyGetter {
135 template <class T> static int get() { return T::value; }
136 };
137 int v = dispatch<MyGetter>();
138 )cpp";
139 auto AST = build();
140 EXPECT_THAT(Recorded.Roots,
141 testing::ElementsAre(named("MyGetter"), named("v")));
142}
143
144class RecordPPTest : public ::testing::Test {
145protected:
146 TestInputs Inputs;
147 RecordedPP Recorded;
148
149 RecordPPTest() {
150 struct RecordAction : public PreprocessOnlyAction {
151 RecordedPP &Out;
152 RecordAction(RecordedPP &Out) : Out(Out) {}
153
154 void ExecuteAction() override {
155 auto &PP = getCompilerInstance().getPreprocessor();
156 PP.addPPCallbacks(C: Out.record(PP));
157 PreprocessOnlyAction::ExecuteAction();
158 }
159 };
160 Inputs.MakeAction = [this] {
161 return std::make_unique<RecordAction>(args&: Recorded);
162 };
163 }
164
165 TestAST build() { return TestAST(Inputs); }
166};
167
168// Matches an Include with a particular spelling.
169MATCHER_P(spelled, S, "") { return arg.Spelled == S; }
170
171TEST_F(RecordPPTest, CapturesIncludes) {
172 llvm::Annotations MainFile(R"cpp(
173 $H^#include "./header.h"
174 $M^#include <missing.h>
175 )cpp");
176 Inputs.Code = MainFile.code();
177 Inputs.ExtraFiles["header.h"] = "";
178 Inputs.ErrorOK = true; // missing header
179 auto AST = build();
180
181 ASSERT_THAT(
182 Recorded.Includes.all(),
183 testing::ElementsAre(spelled("./header.h"), spelled("missing.h")));
184
185 auto &H = Recorded.Includes.all().front();
186 EXPECT_EQ(H.Line, 2u);
187 EXPECT_EQ(H.HashLocation,
188 AST.sourceManager().getComposedLoc(
189 AST.sourceManager().getMainFileID(), MainFile.point("H")));
190 EXPECT_EQ(H.Resolved, *AST.fileManager().getOptionalFileRef("header.h"));
191 EXPECT_FALSE(H.Angled);
192
193 auto &M = Recorded.Includes.all().back();
194 EXPECT_EQ(M.Line, 3u);
195 EXPECT_EQ(M.HashLocation,
196 AST.sourceManager().getComposedLoc(
197 AST.sourceManager().getMainFileID(), MainFile.point("M")));
198 EXPECT_EQ(M.Resolved, std::nullopt);
199 EXPECT_TRUE(M.Angled);
200}
201
202TEST_F(RecordPPTest, CapturesMacroRefs) {
203 llvm::Annotations Header(R"cpp(
204 #define $def^X 1
205
206 // Refs, but not in main file.
207 #define Y X
208 int one = X;
209 )cpp");
210 llvm::Annotations MainFile(R"cpp(
211 #define EARLY X // not a ref, no definition
212 #include "header.h"
213 #define LATE ^X
214 #define LATE2 ^X // a ref even if not expanded
215
216 int uno = ^X;
217 int jeden = $exp^LATE; // a ref in LATE's expansion
218
219 #define IDENT(X) X // not a ref, shadowed
220 int eins = IDENT(^X);
221
222 #undef ^X
223 // Not refs, rather a new macro with the same name.
224 #define X 2
225 int two = X;
226 )cpp");
227 Inputs.Code = MainFile.code();
228 Inputs.ExtraFiles["header.h"] = Header.code();
229 auto AST = build();
230 const auto &SM = AST.sourceManager();
231
232 SourceLocation Def = SM.getComposedLoc(
233 FID: SM.translateFile(SourceFile: AST.fileManager().getFile(Filename: "header.h").get()),
234 Offset: Header.point(Name: "def"));
235 ASSERT_THAT(Recorded.MacroReferences, Not(IsEmpty()));
236 Symbol OrigX = Recorded.MacroReferences.front().Target;
237 EXPECT_EQ("X", OrigX.macro().Name->getName());
238 EXPECT_EQ(Def, OrigX.macro().Definition);
239
240 std::vector<unsigned> RefOffsets;
241 std::vector<unsigned> ExpOffsets; // Expansion locs of refs in macro locs.
242 for (const auto &Ref : Recorded.MacroReferences) {
243 if (Ref.Target == OrigX) {
244 auto [FID, Off] = SM.getDecomposedLoc(Loc: Ref.RefLocation);
245 if (FID == SM.getMainFileID()) {
246 RefOffsets.push_back(x: Off);
247 } else if (Ref.RefLocation.isMacroID() &&
248 SM.isWrittenInMainFile(Loc: SM.getExpansionLoc(Loc: Ref.RefLocation))) {
249 ExpOffsets.push_back(
250 x: SM.getDecomposedExpansionLoc(Loc: Ref.RefLocation).second);
251 } else {
252 ADD_FAILURE() << Ref.RefLocation.printToString(SM);
253 }
254 }
255 }
256 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points()));
257 EXPECT_THAT(ExpOffsets, ElementsAreArray(MainFile.points("exp")));
258}
259
260TEST_F(RecordPPTest, CapturesConditionalMacroRefs) {
261 llvm::Annotations MainFile(R"cpp(
262 #define X 1
263
264 #ifdef ^X
265 #endif
266
267 #if defined(^X)
268 #endif
269
270 #ifndef ^X
271 #endif
272
273 #ifdef Y
274 #elifdef ^X
275 #endif
276
277 #ifndef ^X
278 #elifndef ^X
279 #endif
280 )cpp");
281
282 Inputs.Code = MainFile.code();
283 Inputs.ExtraArgs.push_back(x: "-std=c++2b");
284 auto AST = build();
285
286 std::vector<unsigned> RefOffsets;
287 SourceManager &SM = AST.sourceManager();
288 for (const auto &Ref : Recorded.MacroReferences) {
289 auto [FID, Off] = SM.getDecomposedLoc(Loc: Ref.RefLocation);
290 ASSERT_EQ(FID, SM.getMainFileID());
291 EXPECT_EQ(Ref.RT, RefType::Ambiguous);
292 EXPECT_EQ("X", Ref.Target.macro().Name->getName());
293 RefOffsets.push_back(x: Off);
294 }
295 EXPECT_THAT(RefOffsets, ElementsAreArray(MainFile.points()));
296}
297
298class PragmaIncludeTest : public ::testing::Test {
299protected:
300 // We don't build an AST, we just run a preprocessor action!
301 TestInputs Inputs;
302 PragmaIncludes PI;
303
304 PragmaIncludeTest() {
305 Inputs.MakeAction = [this] {
306 struct Hook : public PreprocessOnlyAction {
307 public:
308 Hook(PragmaIncludes *Out) : Out(Out) {}
309 bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
310 Out->record(CI);
311 return true;
312 }
313 PragmaIncludes *Out;
314 };
315 return std::make_unique<Hook>(args: &PI);
316 };
317 }
318
319 TestAST build() { return TestAST(Inputs); }
320
321 void createEmptyFiles(llvm::ArrayRef<StringRef> FileNames) {
322 for (llvm::StringRef File : FileNames)
323 Inputs.ExtraFiles[File] = "#pragma once";
324 }
325};
326
327TEST_F(PragmaIncludeTest, IWYUKeep) {
328 Inputs.Code = R"cpp(
329 #include "keep1.h" // IWYU pragma: keep
330 #include "keep2.h" /* IWYU pragma: keep */
331
332 #include "export1.h" // IWYU pragma: export
333 // IWYU pragma: begin_exports
334 #include "export2.h"
335 #include "export3.h"
336 // IWYU pragma: end_exports
337
338 #include "normal.h"
339
340 // IWYU pragma: begin_keep
341 #include "keep3.h"
342 // IWYU pragma: end_keep
343
344 // IWYU pragma: begin_keep
345 #include "keep4.h"
346 // IWYU pragma: begin_keep
347 #include "keep5.h"
348 // IWYU pragma: end_keep
349 #include "keep6.h"
350 // IWYU pragma: end_keep
351 #include <vector>
352 #include <map> // IWYU pragma: keep
353 #include <set> // IWYU pragma: export
354 )cpp";
355 createEmptyFiles(FileNames: {"keep1.h", "keep2.h", "keep3.h", "keep4.h", "keep5.h",
356 "keep6.h", "export1.h", "export2.h", "export3.h",
357 "normal.h", "std/vector", "std/map", "std/set"});
358
359 Inputs.ExtraArgs.push_back(x: "-isystemstd");
360 TestAST Processed = build();
361 auto &FM = Processed.fileManager();
362
363 EXPECT_FALSE(PI.shouldKeep(FM.getFile("normal.h").get()));
364 EXPECT_FALSE(PI.shouldKeep(FM.getFile("std/vector").get()));
365
366 // Keep
367 EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep1.h").get()));
368 EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep2.h").get()));
369 EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep3.h").get()));
370 EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep4.h").get()));
371 EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep5.h").get()));
372 EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep6.h").get()));
373 EXPECT_TRUE(PI.shouldKeep(FM.getFile("std/map").get()));
374
375 // Exports
376 EXPECT_TRUE(PI.shouldKeep(FM.getFile("export1.h").get()));
377 EXPECT_TRUE(PI.shouldKeep(FM.getFile("export2.h").get()));
378 EXPECT_TRUE(PI.shouldKeep(FM.getFile("export3.h").get()));
379 EXPECT_TRUE(PI.shouldKeep(FM.getFile("std/set").get()));
380}
381
382TEST_F(PragmaIncludeTest, IWYUPrivate) {
383 Inputs.Code = R"cpp(
384 #include "public.h"
385 )cpp";
386 Inputs.ExtraFiles["public.h"] = R"cpp(
387 #include "private.h"
388 #include "private2.h"
389 )cpp";
390 Inputs.ExtraFiles["private.h"] = R"cpp(
391 // IWYU pragma: private, include "public2.h"
392 )cpp";
393 Inputs.ExtraFiles["private2.h"] = R"cpp(
394 // IWYU pragma: private
395 )cpp";
396 TestAST Processed = build();
397 auto PrivateFE = Processed.fileManager().getFile(Filename: "private.h");
398 assert(PrivateFE);
399 EXPECT_TRUE(PI.isPrivate(PrivateFE.get()));
400 EXPECT_EQ(PI.getPublic(PrivateFE.get()), "\"public2.h\"");
401
402 auto PublicFE = Processed.fileManager().getFile(Filename: "public.h");
403 assert(PublicFE);
404 EXPECT_EQ(PI.getPublic(PublicFE.get()), ""); // no mapping.
405 EXPECT_FALSE(PI.isPrivate(PublicFE.get()));
406
407 auto Private2FE = Processed.fileManager().getFile(Filename: "private2.h");
408 assert(Private2FE);
409 EXPECT_TRUE(PI.isPrivate(Private2FE.get()));
410}
411
412TEST_F(PragmaIncludeTest, IWYUExport) {
413 Inputs.Code = R"cpp(// Line 1
414 #include "export1.h"
415 #include "export2.h"
416 )cpp";
417 Inputs.ExtraFiles["export1.h"] = R"cpp(
418 #include "private.h" // IWYU pragma: export
419 )cpp";
420 Inputs.ExtraFiles["export2.h"] = R"cpp(
421 #include "export3.h"
422 )cpp";
423 Inputs.ExtraFiles["export3.h"] = R"cpp(
424 #include "private.h" // IWYU pragma: export
425 )cpp";
426 Inputs.ExtraFiles["private.h"] = "";
427 TestAST Processed = build();
428 const auto &SM = Processed.sourceManager();
429 auto &FM = Processed.fileManager();
430
431 EXPECT_THAT(PI.getExporters(FM.getFile("private.h").get(), FM),
432 testing::UnorderedElementsAre(FileNamed("export1.h"),
433 FileNamed("export3.h")));
434
435 EXPECT_TRUE(PI.getExporters(FM.getFile("export1.h").get(), FM).empty());
436 EXPECT_TRUE(PI.getExporters(FM.getFile("export2.h").get(), FM).empty());
437 EXPECT_TRUE(PI.getExporters(FM.getFile("export3.h").get(), FM).empty());
438 EXPECT_TRUE(
439 PI.getExporters(SM.getFileEntryForID(SM.getMainFileID()), FM).empty());
440}
441
442TEST_F(PragmaIncludeTest, IWYUExportForStandardHeaders) {
443 Inputs.Code = R"cpp(
444 #include "export.h"
445 )cpp";
446 Inputs.ExtraFiles["export.h"] = R"cpp(
447 #include <string> // IWYU pragma: export
448 )cpp";
449 Inputs.ExtraFiles["string"] = "";
450 Inputs.ExtraArgs = {"-isystem."};
451 TestAST Processed = build();
452 auto &FM = Processed.fileManager();
453 EXPECT_THAT(PI.getExporters(*tooling::stdlib::Header::named("<string>"), FM),
454 testing::UnorderedElementsAre(FileNamed("export.h")));
455 EXPECT_THAT(PI.getExporters(llvm::cantFail(FM.getFileRef("string")), FM),
456 testing::UnorderedElementsAre(FileNamed("export.h")));
457}
458
459TEST_F(PragmaIncludeTest, IWYUExportBlock) {
460 Inputs.Code = R"cpp(// Line 1
461 #include "normal.h"
462 )cpp";
463 Inputs.ExtraFiles["normal.h"] = R"cpp(
464 #include "foo.h"
465
466 // IWYU pragma: begin_exports
467 #include "export1.h"
468 #include "private1.h"
469 // IWYU pragma: end_exports
470 )cpp";
471 Inputs.ExtraFiles["export1.h"] = R"cpp(
472 // IWYU pragma: begin_exports
473 #include "private1.h"
474 #include "private2.h"
475 // IWYU pragma: end_exports
476
477 #include "bar.h"
478 #include "private3.h" // IWYU pragma: export
479 )cpp";
480 createEmptyFiles(
481 FileNames: {"private1.h", "private2.h", "private3.h", "foo.h", "bar.h"});
482 TestAST Processed = build();
483 auto &FM = Processed.fileManager();
484
485 EXPECT_THAT(PI.getExporters(FM.getFile("private1.h").get(), FM),
486 testing::UnorderedElementsAre(FileNamed("export1.h"),
487 FileNamed("normal.h")));
488 EXPECT_THAT(PI.getExporters(FM.getFile("private2.h").get(), FM),
489 testing::UnorderedElementsAre(FileNamed("export1.h")));
490 EXPECT_THAT(PI.getExporters(FM.getFile("private3.h").get(), FM),
491 testing::UnorderedElementsAre(FileNamed("export1.h")));
492
493 EXPECT_TRUE(PI.getExporters(FM.getFile("foo.h").get(), FM).empty());
494 EXPECT_TRUE(PI.getExporters(FM.getFile("bar.h").get(), FM).empty());
495}
496
497TEST_F(PragmaIncludeTest, SelfContained) {
498 Inputs.Code = R"cpp(
499 #include "guarded.h"
500
501 #include "unguarded.h"
502 )cpp";
503 Inputs.ExtraFiles["guarded.h"] = R"cpp(
504 #pragma once
505 )cpp";
506 Inputs.ExtraFiles["unguarded.h"] = "";
507 TestAST Processed = build();
508 auto &FM = Processed.fileManager();
509 EXPECT_TRUE(PI.isSelfContained(FM.getFile("guarded.h").get()));
510 EXPECT_FALSE(PI.isSelfContained(FM.getFile("unguarded.h").get()));
511}
512
513TEST_F(PragmaIncludeTest, AlwaysKeep) {
514 Inputs.Code = R"cpp(
515 #include "always_keep.h"
516 #include "usual.h"
517 )cpp";
518 Inputs.ExtraFiles["always_keep.h"] = R"cpp(
519 #pragma once
520 // IWYU pragma: always_keep
521 )cpp";
522 Inputs.ExtraFiles["usual.h"] = "#pragma once";
523 TestAST Processed = build();
524 auto &FM = Processed.fileManager();
525 EXPECT_TRUE(PI.shouldKeep(FM.getFile("always_keep.h").get()));
526 EXPECT_FALSE(PI.shouldKeep(FM.getFile("usual.h").get()));
527}
528
529TEST_F(PragmaIncludeTest, ExportInUnnamedBuffer) {
530 llvm::StringLiteral Filename = "test.cpp";
531 auto Code = R"cpp(#include "exporter.h")cpp";
532 Inputs.ExtraFiles["exporter.h"] = R"cpp(
533 #pragma once
534 #include "foo.h" // IWYU pragma: export
535 )cpp";
536 Inputs.ExtraFiles["foo.h"] = "";
537
538 auto Clang = std::make_unique<CompilerInstance>(
539 args: std::make_shared<PCHContainerOperations>());
540 Clang->createDiagnostics();
541
542 Clang->setInvocation(std::make_unique<CompilerInvocation>());
543 ASSERT_TRUE(CompilerInvocation::CreateFromArgs(
544 Clang->getInvocation(), {Filename.data()}, Clang->getDiagnostics(),
545 "clang"));
546
547 // Create unnamed memory buffers for all the files.
548 auto VFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
549 VFS->addFile(Path: Filename, /*ModificationTime=*/0,
550 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: Code, /*BufferName=*/""));
551 for (const auto &Extra : Inputs.ExtraFiles)
552 VFS->addFile(Path: Extra.getKey(), /*ModificationTime=*/0,
553 Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: Extra.getValue(),
554 /*BufferName=*/""));
555 auto *FM = Clang->createFileManager(VFS);
556 ASSERT_TRUE(Clang->ExecuteAction(*Inputs.MakeAction()));
557 EXPECT_THAT(
558 PI.getExporters(llvm::cantFail(FM->getFileRef("foo.h")), *FM),
559 testing::ElementsAre(llvm::cantFail(FM->getFileRef("exporter.h"))));
560}
561
562TEST_F(PragmaIncludeTest, OutlivesFMAndSM) {
563 Inputs.Code = R"cpp(
564 #include "public.h"
565 )cpp";
566 Inputs.ExtraFiles["public.h"] = R"cpp(
567 #include "private.h"
568 #include "private2.h" // IWYU pragma: export
569 )cpp";
570 Inputs.ExtraFiles["private.h"] = R"cpp(
571 // IWYU pragma: private, include "public.h"
572 )cpp";
573 Inputs.ExtraFiles["private2.h"] = R"cpp(
574 // IWYU pragma: private
575 )cpp";
576 build(); // Fills up PI, file/source manager used is destroyed afterwards.
577 Inputs.MakeAction = nullptr; // Don't populate PI anymore.
578
579 // Now this build gives us a new File&Source Manager.
580 TestAST Processed = build();
581 auto &FM = Processed.fileManager();
582 auto PrivateFE = FM.getFile(Filename: "private.h");
583 assert(PrivateFE);
584 EXPECT_EQ(PI.getPublic(PrivateFE.get()), "\"public.h\"");
585
586 auto Private2FE = FM.getFile(Filename: "private2.h");
587 assert(Private2FE);
588 EXPECT_THAT(PI.getExporters(Private2FE.get(), FM),
589 testing::ElementsAre(llvm::cantFail(FM.getFileRef("public.h"))));
590}
591
592TEST_F(PragmaIncludeTest, CanRecordManyTimes) {
593 Inputs.Code = R"cpp(
594 #include "public.h"
595 )cpp";
596 Inputs.ExtraFiles["public.h"] = R"cpp(
597 #include "private.h"
598 )cpp";
599 Inputs.ExtraFiles["private.h"] = R"cpp(
600 // IWYU pragma: private, include "public.h"
601 )cpp";
602
603 TestAST Processed = build();
604 auto &FM = Processed.fileManager();
605 auto PrivateFE = FM.getFile(Filename: "private.h");
606 llvm::StringRef Public = PI.getPublic(File: PrivateFE.get());
607 EXPECT_EQ(Public, "\"public.h\"");
608
609 // This build populates same PI during build, but this time we don't have
610 // any IWYU pragmas. Make sure strings from previous recordings are still
611 // alive.
612 Inputs.Code = "";
613 build();
614 EXPECT_EQ(Public, "\"public.h\"");
615}
616} // namespace
617} // namespace clang::include_cleaner
618

source code of clang-tools-extra/include-cleaner/unittests/RecordTest.cpp