// Copyright 2019 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/config.h"
#include "gn/scheduler.h"
#include "gn/scope.h"
#include "gn/test_with_scheduler.h"
#include "gn/test_with_scope.h"
#include "util/test/test.h"

using RustFunctionsTarget = TestWithScheduler;

// Checks that the appropriate crate type is used.
TEST_F(RustFunctionsTarget, CrateName) {
  TestWithScope setup;

  // The target generator needs a place to put the targets or it will fail.
  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput exe_input(
      "executable(\"foo\") {\n"
      "  crate_name = \"foo_crate\"\n"
      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(exe_input.has_error());
  Err err;
  exe_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_name(),
            "foo_crate");

  TestParseInput lib_input(
      "executable(\"foo\") {\n"
      "  sources = [ \"lib.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(lib_input.has_error());
  err = Err();
  lib_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_name(),
            "foo")
      << item_collector.back()->AsTarget()->rust_values().crate_name();
}

// Checks that the appropriate crate root is found.
TEST_F(RustFunctionsTarget, CrateRootFind) {
  TestWithScope setup;

  // The target generator needs a place to put the targets or it will fail.
  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput normal_input(
      "executable(\"foo\") {\n"
      "  crate_root = \"foo.rs\""
      "  sources = [ \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(normal_input.has_error());
  Err err;
  normal_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(
      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
      "/foo.rs");

  TestParseInput normal_shlib_input(
      "shared_library(\"foo\") {\n"
      "  crate_root = \"foo.rs\""
      "  crate_type = \"dylib\"\n"
      "  sources = [ \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(normal_shlib_input.has_error());
  err = Err();
  normal_shlib_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(
      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
      "/foo.rs");

  TestParseInput exe_input(
      "executable(\"foo\") {\n"
      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(exe_input.has_error());
  err = Err();
  exe_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(
      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
      "/main.rs");

  TestParseInput lib_input(
      "rust_library(\"libfoo\") {\n"
      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(lib_input.has_error());
  err = Err();
  lib_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(
      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
      "/lib.rs");

  TestParseInput singlesource_input(
      "executable(\"bar\") {\n"
      "  sources = [ \"bar.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(singlesource_input.has_error());
  err = Err();
  singlesource_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(
      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
      "/bar.rs");

  TestParseInput error_input(
      "rust_library(\"foo\") {\n"
      "  sources = [ \"foo.rs\", \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(error_input.has_error());
  err = Err();
  error_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_TRUE(err.has_error());
  EXPECT_EQ("Missing \"crate_root\" and missing \"lib.rs\" in sources.",
            err.message());

  TestParseInput nosources_input(
      "executable(\"bar\") {\n"
      "  crate_root = \"bar.rs\"\n"
      "}\n");
  ASSERT_FALSE(nosources_input.has_error());
  err = Err();
  nosources_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(
      item_collector.back()->AsTarget()->rust_values().crate_root().value(),
      "/bar.rs");
}

// Checks that the appropriate crate type is used.
TEST_F(RustFunctionsTarget, CrateTypeSelection) {
  TestWithScope setup;

  // The target generator needs a place to put the targets or it will fail.
  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput lib_input(
      "shared_library(\"libfoo\") {\n"
      "  crate_type = \"dylib\"\n"
      "  sources = [ \"lib.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(lib_input.has_error());
  Err err;
  lib_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_type(),
            RustValues::CRATE_DYLIB);

  TestParseInput exe_non_default_input(
      "executable(\"foo\") {\n"
      "  crate_type = \"rlib\"\n"
      "  sources = [ \"main.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(exe_non_default_input.has_error());
  err = Err();
  exe_non_default_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
  ASSERT_EQ(item_collector.back()->AsTarget()->rust_values().crate_type(),
            RustValues::CRATE_RLIB);

  TestParseInput lib_error_input(
      "shared_library(\"foo\") {\n"
      "  crate_type = \"bad\"\n"
      "  sources = [ \"lib.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(lib_error_input.has_error());
  err = Err();
  lib_error_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_TRUE(err.has_error());
  EXPECT_EQ("Inadmissible crate type \"bad\".", err.message()) << err.message();

  TestParseInput lib_missing_error_input(
      "shared_library(\"foo\") {\n"
      "  sources = [ \"lib.rs\" ]\n"
      "}\n");
  ASSERT_FALSE(lib_missing_error_input.has_error());
  err = Err();
  lib_missing_error_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_TRUE(err.has_error());
  EXPECT_EQ("Must set \"crate_type\" on a Rust \"shared_library\".",
            err.message());
}

// Checks that the appropriate config values are propagated.
TEST_F(RustFunctionsTarget, ConfigValues) {
  TestWithScope setup;

  // The target generator needs a place to put the targets or it will fail.
  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput exe_input(
      "config(\"foo\") {\n"
      "  rustflags = [ \"-Cdebuginfo=2\" ]\n"
      "  rustenv = [ \"RUST_BACKTRACE=1\" ]"
      "}\n");
  ASSERT_FALSE(exe_input.has_error());
  Err err;
  exe_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();

  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustflags().size(),
            1U);
  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustflags()[0],
            "-Cdebuginfo=2");
  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustenv().size(),
            1U);
  EXPECT_EQ(item_collector.back()->AsConfig()->own_values().rustenv()[0],
            "RUST_BACKTRACE=1");
}

// Checks that set_defaults works properly.
TEST_F(RustFunctionsTarget, SetDefaults) {
  TestWithScope setup;

  // The target generator needs a place to put the targets or it will fail.
  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput exe_input(
      "config(\"foo\") {\n"
      "  rustflags = [ \"-Cdebuginfo=2\" ]\n"
      "  rustenv = [ \"RUST_BACKTRACE=1\" ]"
      "}\n"
      "set_defaults(\"rust_library\") {\n"
      "  configs = [ \":foo\" ]\n"
      "}\n");
  ASSERT_FALSE(exe_input.has_error());
  Err err;
  exe_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message() << err.message();

  EXPECT_EQ(setup.scope()
                ->GetTargetDefaults("rust_library")
                ->GetValue("configs")
                ->type(),
            Value::LIST);
  EXPECT_EQ(setup.scope()
                ->GetTargetDefaults("rust_library")
                ->GetValue("configs")
                ->list_value()
                .size(),
            1U);
  EXPECT_EQ(setup.scope()
                ->GetTargetDefaults("rust_library")
                ->GetValue("configs")
                ->list_value()[0]
                .type(),
            Value::STRING);
  EXPECT_EQ(setup.scope()
                ->GetTargetDefaults("rust_library")
                ->GetValue("configs")
                ->list_value()[0]
                .string_value(),
            ":foo");
}

// Checks aliased_deps parsing.
TEST_F(RustFunctionsTarget, AliasedDeps) {
  TestWithScope setup;

  // The target generator needs a place to put the targets or it will fail.
  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput exe_input(
      "executable(\"foo\") {\n"
      "  sources = [ \"main.rs\" ]\n"
      "  deps = [ \"//bar\", \"//baz\" ]\n"
      "  aliased_deps = {\n"
      "    bar_renamed = \"//bar\"\n"
      "    baz_renamed = \"//baz:baz\"\n"
      "  }\n"
      "}\n");
  ASSERT_FALSE(exe_input.has_error());
  Err err;
  exe_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();

  EXPECT_EQ(
      item_collector.back()->AsTarget()->rust_values().aliased_deps().size(),
      2U);
}

TEST_F(RustFunctionsTarget, PublicConfigs) {
  TestWithScope setup;

  Scope::ItemVector item_collector;
  setup.scope()->set_item_collector(&item_collector);
  setup.scope()->set_source_dir(SourceDir("/"));

  TestParseInput exe_input(
      "config(\"bar\") {\n"
      "  defines = [ \"DOOM_MELON\" ]"
      "}\n"
      "executable(\"foo\") {\n"
      "  crate_name = \"foo_crate\"\n"
      "  sources = [ \"foo.rs\", \"lib.rs\", \"main.rs\" ]\n"
      "  public_configs = [ \":bar\" ]"
      "}\n");
  ASSERT_FALSE(exe_input.has_error());
  Err err;
  exe_input.parsed()->Execute(setup.scope(), &err);
  ASSERT_FALSE(err.has_error()) << err.message();
}