//===- unittests/Serialization/PreambleInNamedModulesTest.cpp -------------===//
//
// 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/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/PrecompiledPreamble.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"

#include "gtest/gtest.h"

using namespace llvm;
using namespace clang;

namespace {

class PreambleInNamedModulesTest : public ::testing::Test {
  void SetUp() override {
    ASSERT_FALSE(sys::fs::createUniqueDirectory("modules-test", TestDir));
  }

  void TearDown() override { sys::fs::remove_directories(TestDir); }

public:
  using PathType = SmallString<256>;

  PathType TestDir;

  void addFile(StringRef Path, StringRef Contents, PathType &AbsPath) {
    ASSERT_FALSE(sys::path::is_absolute(Path));

    AbsPath = TestDir;
    sys::path::append(AbsPath, Path);

    ASSERT_FALSE(
        sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));

    std::error_code EC;
    llvm::raw_fd_ostream OS(AbsPath, EC);
    ASSERT_FALSE(EC);
    OS << Contents;
  }

  void addFile(StringRef Path, StringRef Contents) {
    PathType UnusedAbsPath;
    addFile(Path, Contents, UnusedAbsPath);
  }
};

// Testing that the use of Preamble in named modules can work basically.
// See https://github.com/llvm/llvm-project/issues/80570
TEST_F(PreambleInNamedModulesTest, BasicTest) {
  addFile("foo.h", R"cpp(
enum class E {
    A,
    B,
    C,
    D
};
  )cpp");

  PathType MainFilePath;
  addFile("A.cppm", R"cpp(
module;
#include "foo.h"
export module A;
export using ::E;
  )cpp",
          MainFilePath);

  IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS =
      llvm::vfs::createPhysicalFileSystem();
  IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
      CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions());

  CreateInvocationOptions CIOpts;
  CIOpts.Diags = Diags;
  CIOpts.VFS = VFS;

  const char *Args[] = {"clang++", "-std=c++20", "-working-directory",
                        TestDir.c_str(), MainFilePath.c_str()};
  std::shared_ptr<CompilerInvocation> Invocation =
      createInvocation(Args, CIOpts);
  ASSERT_TRUE(Invocation);

  llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> ContentsBuffer =
      llvm::MemoryBuffer::getFile(MainFilePath, /*IsText=*/true);
  EXPECT_TRUE(ContentsBuffer);
  std::unique_ptr<MemoryBuffer> Buffer = std::move(*ContentsBuffer);

  PreambleBounds Bounds =
      ComputePreambleBounds(Invocation->getLangOpts(), *Buffer, 0);

  PreambleCallbacks Callbacks;
  llvm::ErrorOr<PrecompiledPreamble> BuiltPreamble = PrecompiledPreamble::Build(
      *Invocation, Buffer.get(), Bounds, *Diags, VFS,
      std::make_shared<PCHContainerOperations>(),
      /*StoreInMemory=*/false, /*StoragePath=*/TestDir, Callbacks);

  ASSERT_FALSE(Diags->hasErrorOccurred());

  EXPECT_TRUE(BuiltPreamble);
  EXPECT_TRUE(BuiltPreamble->CanReuse(*Invocation, *Buffer, Bounds, *VFS));
  BuiltPreamble->OverridePreamble(*Invocation, VFS, Buffer.get());

  auto Clang = std::make_unique<CompilerInstance>(
      std::make_shared<PCHContainerOperations>());
  Clang->setInvocation(std::move(Invocation));
  Clang->setDiagnostics(Diags.get());

  if (auto VFSWithRemapping = createVFSFromCompilerInvocation(
          Clang->getInvocation(), Clang->getDiagnostics(), VFS))
    VFS = VFSWithRemapping;

  Clang->createFileManager(VFS);
  EXPECT_TRUE(Clang->createTarget());

  Buffer.release();

  SyntaxOnlyAction Action;
  EXPECT_TRUE(Clang->ExecuteAction(Action));
  EXPECT_FALSE(Clang->getDiagnosticsPtr()->hasErrorOccurred());
}

} // namespace
