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