// Copyright 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gn/ninja_outputs_writer.h"

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "gn/builder_record.h"
#include "gn/filesystem_utils.h"
#include "gn/ninja_target_writer.h"
#include "gn/resolved_target_data.h"
#include "gn/setup.h"
#include "gn/switches.h"
#include "gn/test_with_scheduler.h"
#include "util/test/test.h"

using NinjaOutputsWriterTest = TestWithScheduler;
using NinjaOutputsMap = NinjaOutputsWriter::MapType;

static void WriteFile(const base::FilePath& file, const std::string& data) {
  CHECK_EQ(static_cast<int>(data.size()),  // Way smaller than INT_MAX.
           base::WriteFile(file, data.data(), data.size()));
}

// Collects Ninja outputs for each target. Used by multiple background threads.
struct TargetWriteInfo {
  std::mutex lock;
  NinjaOutputsMap ninja_outputs_map;
};

// Called on worker thread to write the ninja file.
void BackgroundDoWrite(TargetWriteInfo* write_info, const Target* target) {
  std::vector<OutputFile> target_ninja_outputs;
  ResolvedTargetData resolved;
  std::string rule = NinjaTargetWriter::RunAndWriteFile(target, &resolved,
                                                        &target_ninja_outputs);

  std::lock_guard<std::mutex> lock(write_info->lock);
  write_info->ninja_outputs_map.emplace(target,
                                        std::move(target_ninja_outputs));
}

static void ItemResolvedAndGeneratedCallback(TargetWriteInfo* write_info,
                                             const BuilderRecord* record) {
  const Item* item = record->item();
  const Target* target = item->AsTarget();
  if (target) {
    g_scheduler->ScheduleWork(
        [write_info, target]() { BackgroundDoWrite(write_info, target); });
  }
}

TEST_F(NinjaOutputsWriterTest, OutputsFile) {
  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);

  const char kDotfileContents[] = R"(
buildconfig = "//BUILDCONFIG.gn"
)";

  const char kBuildConfigContents[] = R"(
set_default_toolchain("//toolchain:default")
)";

  const char kToolchainBuildContents[] = R"##(
toolchain("default") {
  tool("stamp") {
    command = "stamp"
  }
}

toolchain("secondary") {
  tool("stamp") {
    command = "stamp2"
  }
}
)##";

  const char kBuildGnContents[] = R"##(
group("foo") {
  deps = [ ":bar", ":zoo(//toolchain:secondary)" ]
}

action("bar") {
  script = "//:run_bar_script.py"
  outputs = [ "$root_build_dir/bar.output" ]
  args = []
}

group("zoo") {
}
)##";

  // Create a temp directory containing the build.
  base::ScopedTempDir in_temp_dir;
  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
  base::FilePath in_path = in_temp_dir.GetPath();

  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILD.gn")), kBuildGnContents);
  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")),
            kBuildConfigContents);
  WriteFile(in_path.Append(FILE_PATH_LITERAL(".gn")), kDotfileContents);

  EXPECT_TRUE(
      base::CreateDirectory(in_path.Append(FILE_PATH_LITERAL("toolchain"))));

  WriteFile(in_path.Append(FILE_PATH_LITERAL("toolchain/BUILD.gn")),
            kToolchainBuildContents);

  cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path));

  base::FilePath outputs_json_path(FILE_PATH_LITERAL("ninja_outputs.json"));
  cmdline.AppendSwitch("--ninja-outputs-file",
                       FilePathToUTF8(outputs_json_path));

  // Create another temp dir for writing the generated files to.
  base::ScopedTempDir build_temp_dir;
  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());

  // Run setup
  Setup setup;
  EXPECT_TRUE(
      setup.DoSetup(FilePathToUTF8(build_temp_dir.GetPath()), true, cmdline));

  TargetWriteInfo write_info;

  setup.builder().set_resolved_and_generated_callback(
      [&write_info](const BuilderRecord* record) {
        ItemResolvedAndGeneratedCallback(&write_info, record);
      });

  // Do the actual load.
  ASSERT_TRUE(setup.Run());

  StringOutputBuffer out =
      NinjaOutputsWriter::GenerateJSON(write_info.ninja_outputs_map);

  // Verify that the generated file is here.
  std::string generated = out.str();
  std::string expected = R"##({
  "//:bar": [
    "bar.output",
    "phony/bar"
  ],
  "//:foo": [
    "phony/foo"
  ],
  "//:zoo": [
  ],
  "//:zoo(//toolchain:secondary)": [
  ]
})##";

  EXPECT_EQ(generated, expected);
}
