text: //====-- unittests/Frontend/PCHPreambleTest.cpp - FrontendAction tests ---====//
  //
  // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  // See https://llvm.org/LICENSE.txt for license information.
  // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  //
  //===----------------------------------------------------------------------===//
  
  #include "clang/Frontend/ASTUnit.h"
  #include "clang/Frontend/CompilerInvocation.h"
  #include "clang/Frontend/CompilerInstance.h"
  #include "clang/Frontend/FrontendActions.h"
  #include "clang/Frontend/FrontendOptions.h"
  #include "clang/Lex/PreprocessorOptions.h"
  #include "clang/Basic/Diagnostic.h"
  #include "clang/Basic/FileManager.h"
  #include "llvm/Support/FileSystem.h"
  #include "llvm/Support/MemoryBuffer.h"
  #include "llvm/Support/Path.h"
  #include "gtest/gtest.h"
  
  using namespace llvm;
  using namespace clang;
  
  namespace {
  
  class ReadCountingInMemoryFileSystem : public vfs::InMemoryFileSystem
  {
    std::map<std::string, unsigned> ReadCounts;
  
  public:
    ErrorOr<std::unique_ptr<vfs::File>> openFileForRead(const Twine &Path) override
    {
      SmallVector<char, 128> PathVec;
      Path.toVector(PathVec);
      llvm::sys::path::remove_dots(PathVec, true);
      ++ReadCounts[std::string(PathVec.begin(), PathVec.end())];
      return InMemoryFileSystem::openFileForRead(Path);
    }
  
    unsigned GetReadCount(const Twine &Path) const
    {
      auto it = ReadCounts.find(Path.str());
      return it == ReadCounts.end() ? 0 : it->second;
    }
  };
  
  class PCHPreambleTest : public ::testing::Test {
    IntrusiveRefCntPtr<ReadCountingInMemoryFileSystem> VFS;
    StringMap<std::string> RemappedFiles;
    std::shared_ptr<PCHContainerOperations> PCHContainerOpts;
    FileSystemOptions FSOpts;
  
  public:
    void SetUp() override { ResetVFS(); }
    void TearDown() override {}
  
    void ResetVFS() {
      VFS = new ReadCountingInMemoryFileSystem();
      // We need the working directory to be set to something absolute,
      // otherwise it ends up being inadvertently set to the current
      // working directory in the real file system due to a series of
      // unfortunate conditions interacting badly.
      // What's more, this path *must* be absolute on all (real)
      // filesystems, so just '/' won't work (e.g. on Win32).
      VFS->setCurrentWorkingDirectory("//./");
    }
  
    void AddFile(const std::string &Filename, const std::string &Contents) {
      ::time_t now;
      ::time(&now);
      VFS->addFile(Filename, now, MemoryBuffer::getMemBufferCopy(Contents, Filename));
    }
  
    void RemapFile(const std::string &Filename, const std::string &Contents) {
      RemappedFiles[Filename] = Contents;
    }
  
    std::unique_ptr<ASTUnit> ParseAST(const std::string &EntryFile) {
      PCHContainerOpts = std::make_shared<PCHContainerOperations>();
      std::shared_ptr<CompilerInvocation> CI(new CompilerInvocation);
      CI->getFrontendOpts().Inputs.push_back(
        FrontendInputFile(EntryFile, FrontendOptions::getInputKindForExtension(
          llvm::sys::path::extension(EntryFile).substr(1))));
  
      CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";
  
      CI->getPreprocessorOpts().RemappedFileBuffers = GetRemappedFiles();
  
      PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
      PPOpts.RemappedFilesKeepOriginalName = true;
  
      IntrusiveRefCntPtr<DiagnosticsEngine>
        Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions, new DiagnosticConsumer));
  
      FileManager *FileMgr = new FileManager(FSOpts, VFS);
  
      std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
          CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None,
          /*PrecompilePreambleAfterNParses=*/1);
      return AST;
    }
  
    bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
      bool reparseFailed = AST->Reparse(PCHContainerOpts, GetRemappedFiles(), VFS);
      return !reparseFailed;
    }
  
    unsigned GetFileReadCount(const std::string &Filename) const {
      return VFS->GetReadCount(Filename);
    }
  
  private:
    std::vector<std::pair<std::string, llvm::MemoryBuffer *>>
    GetRemappedFiles() const {
      std::vector<std::pair<std::string, llvm::MemoryBuffer *>> Remapped;
      for (const auto &RemappedFile : RemappedFiles) {
        std::unique_ptr<MemoryBuffer> buf = MemoryBuffer::getMemBufferCopy(
          RemappedFile.second, RemappedFile.first());
        Remapped.emplace_back(std::string(RemappedFile.first()), buf.release());
      }
      return Remapped;
    }
  };
  
  TEST_F(PCHPreambleTest, ReparseReusesPreambleWithUnsavedFileNotExistingOnDisk) {
    std::string Header1 = "//./header1.h";
    std::string MainName = "//./main.cpp";
    AddFile(MainName, R"cpp(
  #include "//./header1.h"
  int main() { return ZERO; }
  )cpp");
    RemapFile(Header1, "#define ZERO 0\n");
  
    // Parse with header file provided as unsaved file, which does not exist on
    // disk.
    std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
    ASSERT_TRUE(AST.get());
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
  
    // Reparse and check that the preamble was reused.
    ASSERT_TRUE(ReparseAST(AST));
    ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
  }
  
  TEST_F(PCHPreambleTest, ReparseReusesPreambleAfterUnsavedFileWasCreatedOnDisk) {
    std::string Header1 = "//./header1.h";
    std::string MainName = "//./main.cpp";
    AddFile(MainName, R"cpp(
  #include "//./header1.h"
  int main() { return ZERO; }
  )cpp");
    RemapFile(Header1, "#define ZERO 0\n");
  
    // Parse with header file provided as unsaved file, which does not exist on
    // disk.
    std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
    ASSERT_TRUE(AST.get());
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
  
    // Create the unsaved file also on disk and check that preamble was reused.
    AddFile(Header1, "#define ZERO 0\n");
    ASSERT_TRUE(ReparseAST(AST));
    ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
  }
  
  TEST_F(PCHPreambleTest,
         ReparseReusesPreambleAfterUnsavedFileWasRemovedFromDisk) {
    std::string Header1 = "//./foo/header1.h";
    std::string MainName = "//./main.cpp";
    std::string MainFileContent = R"cpp(
  #include "//./foo/header1.h"
  int main() { return ZERO; }
  )cpp";
    AddFile(MainName, MainFileContent);
    AddFile(Header1, "#define ZERO 0\n");
    RemapFile(Header1, "#define ZERO 0\n");
  
    // Parse with header file provided as unsaved file, which exists on disk.
    std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
    ASSERT_TRUE(AST.get());
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
    ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
  
    // Remove the unsaved file from disk and check that the preamble was reused.
    ResetVFS();
    AddFile(MainName, MainFileContent);
    ASSERT_TRUE(ReparseAST(AST));
    ASSERT_EQ(AST->getPreambleCounterForTests(), 1U);
  }
  
  TEST_F(PCHPreambleTest, ReparseWithOverriddenFileDoesNotInvalidatePreamble) {
    std::string Header1 = "//./header1.h";
    std::string Header2 = "//./header2.h";
    std::string MainName = "//./main.cpp";
    AddFile(Header1, "");
    AddFile(Header2, "#pragma once");
    AddFile(MainName,
      "#include \"//./foo/../header1.h\"\n"
      "#include \"//./foo/../header2.h\"\n"
      "int main() { return ZERO; }");
    RemapFile(Header1, "static const int ZERO = 0;\n");
  
    std::unique_ptr<ASTUnit> AST(ParseAST(MainName));
    ASSERT_TRUE(AST.get());
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
  
    unsigned initialCounts[] = {
      GetFileReadCount(MainName),
      GetFileReadCount(Header1),
      GetFileReadCount(Header2)
    };
  
    ASSERT_TRUE(ReparseAST(AST));
  
    ASSERT_NE(initialCounts[0], GetFileReadCount(MainName));
    ASSERT_EQ(initialCounts[1], GetFileReadCount(Header1));
    ASSERT_EQ(initialCounts[2], GetFileReadCount(Header2));
  }
  
  TEST_F(PCHPreambleTest, ParseWithBom) {
    std::string Header = "//./header.h";
    std::string Main = "//./main.cpp";
    AddFile(Header, "int random() { return 4; }");
    AddFile(Main,
      "\xef\xbb\xbf"
      "#include \"//./header.h\"\n"
      "int main() { return random() -2; }");
  
    std::unique_ptr<ASTUnit> AST(ParseAST(Main));
    ASSERT_TRUE(AST.get());
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
  
    unsigned HeaderReadCount = GetFileReadCount(Header);
  
    ASSERT_TRUE(ReparseAST(AST));
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
    
    // Check preamble PCH was really reused
    ASSERT_EQ(HeaderReadCount, GetFileReadCount(Header));
  
    // Remove BOM
    RemapFile(Main,
      "#include \"//./header.h\"\n"
      "int main() { return random() -2; }");
  
    ASSERT_TRUE(ReparseAST(AST));
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
  
    ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
    HeaderReadCount = GetFileReadCount(Header);
  
    // Add BOM back
    RemapFile(Main,
      "\xef\xbb\xbf"
      "#include \"//./header.h\"\n"
      "int main() { return random() -2; }");
  
    ASSERT_TRUE(ReparseAST(AST));
    ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());
  
    ASSERT_LE(HeaderReadCount, GetFileReadCount(Header));
  }
  
  } // anonymous namespace
  