// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "testing/file_test/test_file.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "common/error_test_helpers.h"
#include "testing/base/file_helpers.h"
#include "testing/file_test/file_test_base.h"

// The current BUILD structure of this library means that in order to unit-test
// test_file.h, we have to pretend to be a file_test. That means there has to
// be a definition of CarbonFileTestManifest (normally generated by a file_test
// Bazel rule), and there has to be a registered FileTestFactory, so we define
// them both as dummies.
// TODO: restructure BUILD rules to avoid the need for this hack.

// Dummy empty manifest.
// NOLINTNEXTLINE(readability-identifier-naming): manifest.cpp dictates spelling
const char* CarbonFileTestManifest[] = {nullptr};

namespace Carbon::Testing {

// Dummy unusable FileTestBase.
class DummyFileTest : public FileTestBase {
 public:
  DummyFileTest(llvm::StringRef /*exe_path*/, llvm::StringRef test_name)
      : FileTestBase(test_name) {}

  auto Run(const llvm::SmallVector<llvm::StringRef>& /*test_args*/,
           llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& /*fs*/,
           FILE* /*input_stream*/, llvm::raw_pwrite_stream& /*output_stream*/,
           llvm::raw_pwrite_stream& /*error_stream*/) const
      -> ErrorOr<RunResult> override {
    CARBON_FATAL("Called method of dummy object");
  }

  auto GetDefaultArgs() const -> llvm::SmallVector<std::string> override {
    CARBON_FATAL("Called method of dummy object");
  }
};
CARBON_FILE_TEST_FACTORY(DummyFileTest)

namespace {

using ::testing::_;
using ::testing::StrEq;

// Returns the non-check lines of test_file as a string.
auto NonCheckFileContents(const TestFile& test_file) -> std::string {
  std::string buffer;
  llvm::raw_string_ostream out(buffer);
  for (const auto& line : test_file.non_check_lines) {
    line.Print(out);
    out << "\n";
  }
  return buffer;
}

// Returns a string consisting of 7 repetitions of `c`, for use as a simulated
// merge conflict marker. We use this in test inputs instead of writing the
// conflict markers directly in the string literals so that tools don't treat
// them as genuine merge conflicts in this file.
auto Marker(char c) -> std::string { return std::string(7, c); }

TEST(AutoupdateTest, SnapshotMergeConflict) {
  std::string initial_contents = R"(
// AUTOUPDATE
Some text
)" + Marker('<') + R"(
// CHECK:STDOUT: side 1
)" + Marker('=') + R"(
// CHECK:STDOUT: side 2
)" + Marker('>') + R"(
More text
)";

  constexpr std::string_view ExpectedContents = R"(
// AUTOUPDATE
Some text
More text
)";
  auto file_path = *WriteTestFile("tmp", initial_contents);
  auto test_file =
      ProcessTestFile(file_path.c_str(), /*running_autoupdate=*/true);
  ASSERT_THAT(test_file, IsSuccess(_)) << test_file.error();
  EXPECT_THAT(NonCheckFileContents(*test_file), StrEq(ExpectedContents));
}

TEST(AutoupdateTest, SnapshotTheeWayMergeConflict) {
  std::string initial_contents = R"(
// AUTOUPDATE
Some text
)" + Marker('<') + R"(
// CHECK:STDOUT: side 1
)" + Marker('|') + R"(
// CHECK:STDOUT: base
)" + Marker('=') + R"(
// CHECK:STDOUT: side 2
)" + Marker('>') + R"(
More text
)";

  constexpr std::string_view ExpectedContents = R"(
// AUTOUPDATE
Some text
More text
)";
  auto file_path = *WriteTestFile("tmp", initial_contents);
  auto test_file =
      ProcessTestFile(file_path.c_str(), /*running_autoupdate=*/true);
  ASSERT_THAT(test_file, IsSuccess(_)) << test_file.error();
  EXPECT_THAT(NonCheckFileContents(*test_file), StrEq(ExpectedContents));
}

TEST(AutoupdateTest, DiffMergeConflict) {
  std::string initial_contents = R"(
// AUTOUPDATE
Some text
)" + Marker('<') + R"(
)" + Marker('%') + R"(
)" + Marker('\\') + R"(
 // CHECK:STDOUT: unchanged
-// CHECK:STDOUT: base
+// CHECK:STDOUT: side 1
)" + Marker('+') + R"(
// CHECK:STDOUT: side 2
)" + Marker('>') + R"(
More text
)";

  constexpr std::string_view ExpectedContents = R"(
// AUTOUPDATE
Some text
More text
)";
  auto file_path = *WriteTestFile("tmp", initial_contents);
  auto test_file =
      ProcessTestFile(file_path.c_str(), /*running_autoupdate=*/true);
  ASSERT_THAT(test_file, IsSuccess(_)) << test_file.error();
  EXPECT_THAT(NonCheckFileContents(*test_file), StrEq(ExpectedContents));
}

TEST(AutoupdateTest, NonCheckInDiffRegion) {
  std::string initial_contents = R"(
// AUTOUPDATE
Some text
)" + Marker('<') + R"(
)" + Marker('%') + R"(
)" + Marker('\\') + R"(
 // unchanged
-// CHECK:STDOUT: base
+// CHECK:STDOUT: side 1
)" + Marker('+') + R"(
// CHECK:STDOUT: side 2
)" + Marker('>') + R"(
More text
)";

  auto file_path = *WriteTestFile("tmp", initial_contents);
  auto test_file =
      ProcessTestFile(file_path.c_str(), /*running_autoupdate=*/true);
  ASSERT_THAT(test_file, IsError(_));
}

}  // namespace
}  // namespace Carbon::Testing
