// Copyright 2020 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/rust_project_writer.h"
#include "base/files/file_path.h"
#include "base/strings/string_util.h"
#include "gn/filesystem_utils.h"
#include "gn/substitution_list.h"
#include "gn/target.h"
#include "gn/test_with_scheduler.h"
#include "gn/test_with_scope.h"
#include "util/build_config.h"
#include "util/test/test.h"

static void ExpectEqOrShowDiff(const char* expected,
                               const std::string& actual) {
  if (expected != actual) {
    printf("\nExpected: >>>\n%s<<<\n", expected);
    printf("  Actual: >>>\n%s<<<\n", actual.c_str());
  }
  EXPECT_EQ(expected, actual);
}

using RustProjectJSONWriter = TestWithScheduler;

TEST_F(RustProjectJSONWriter, OneRustTarget) {
  Err err;
  TestWithScope setup;
  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile lib("//foo/lib.rs");
  target.sources().push_back(lib);
  target.sources().push_back(
      SourceFile("//out/Debug/gen/foo/bar/generated.rs"));
  target.sources().push_back(
      SourceFile("//out/Debug/gen/foo/bar/generated2.rs"));
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(lib);
  target.rust_values().crate_name() = "foo";
  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"path/foo/lib.rs\",\n"
      "      \"label\": \"//foo:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"path/foo/\",\n"
      "               \"path/out/Debug/gen/foo/bar/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"compiler_args\": [\"--cfg=feature=\\\"foo_enabled\\\"\"],\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\",\n"
      "        \"feature=\\\"foo_enabled\\\"\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, RustTargetDep) {
  Err err;
  TestWithScope setup;

  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
  dep.set_output_type(Target::RUST_LIBRARY);
  dep.visibility().SetPublic();
  SourceFile tlib("//tortoise/lib.rs");
  dep.sources().push_back(tlib);
  dep.source_types_used().Set(SourceFile::SOURCE_RS);
  dep.rust_values().set_crate_root(tlib);
  dep.rust_values().crate_name() = "tortoise";
  dep.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep.OnResolved(&err));

  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile harelib("//hare/lib.rs");
  target.sources().push_back(harelib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(harelib);
  target.rust_values().crate_name() = "hare";
  target.public_deps().push_back(LabelTargetPair(&dep));
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"tortoise/lib.rs\",\n"
      "      \"label\": \"//tortoise:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"tortoise/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    },\n"
      "    {\n"
      "      \"crate_id\": 1,\n"
      "      \"root_module\": \"hare/lib.rs\",\n"
      "      \"label\": \"//hare:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"hare/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "        {\n"
      "          \"crate\": 0,\n"
      "          \"name\": \"tortoise\"\n"
      "        }\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, RustTargetDepTwo) {
  Err err;
  TestWithScope setup;

  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
  dep.set_output_type(Target::RUST_LIBRARY);
  dep.visibility().SetPublic();
  SourceFile tlib("//tortoise/lib.rs");
  dep.sources().push_back(tlib);
  dep.source_types_used().Set(SourceFile::SOURCE_RS);
  dep.rust_values().set_crate_root(tlib);
  dep.rust_values().crate_name() = "tortoise";
  dep.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep.OnResolved(&err));

  Target dep2(setup.settings(), Label(SourceDir("//achilles/"), "bar"));
  dep2.set_output_type(Target::RUST_LIBRARY);
  dep2.visibility().SetPublic();
  SourceFile alib("//achilles/lib.rs");
  dep2.sources().push_back(alib);
  dep2.source_types_used().Set(SourceFile::SOURCE_RS);
  dep2.rust_values().set_crate_root(alib);
  dep2.rust_values().crate_name() = "achilles";
  dep2.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep2.OnResolved(&err));

  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile harelib("//hare/lib.rs");
  target.sources().push_back(harelib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(harelib);
  target.rust_values().crate_name() = "hare";
  target.public_deps().push_back(LabelTargetPair(&dep));
  target.public_deps().push_back(LabelTargetPair(&dep2));
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"tortoise/lib.rs\",\n"
      "      \"label\": \"//tortoise:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"tortoise/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    },\n"
      "    {\n"
      "      \"crate_id\": 1,\n"
      "      \"root_module\": \"achilles/lib.rs\",\n"
      "      \"label\": \"//achilles:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"achilles/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    },\n"
      "    {\n"
      "      \"crate_id\": 2,\n"
      "      \"root_module\": \"hare/lib.rs\",\n"
      "      \"label\": \"//hare:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"hare/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "        {\n"
      "          \"crate\": 0,\n"
      "          \"name\": \"tortoise\"\n"
      "        },\n"
      "        {\n"
      "          \"crate\": 1,\n"
      "          \"name\": \"achilles\"\n"
      "        }\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";
  ExpectEqOrShowDiff(expected_json, out);
}

// Test that when outputting dependencies, only Rust deps are returned,
// and that any groups are inspected to see if they include Rust deps.
TEST_F(RustProjectJSONWriter, RustTargetGetDepRustOnly) {
  Err err;
  TestWithScope setup;

  Target dep(setup.settings(), Label(SourceDir("//tortoise/"), "bar"));
  dep.set_output_type(Target::RUST_LIBRARY);
  dep.visibility().SetPublic();
  SourceFile tlib("//tortoise/lib.rs");
  dep.sources().push_back(tlib);
  dep.source_types_used().Set(SourceFile::SOURCE_RS);
  dep.rust_values().set_crate_root(tlib);
  dep.rust_values().crate_name() = "tortoise";
  dep.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep.OnResolved(&err));

  Target dep2(setup.settings(), Label(SourceDir("//achilles/"), "bar"));
  dep2.set_output_type(Target::STATIC_LIBRARY);
  dep2.visibility().SetPublic();
  SourceFile alib("//achilles/lib.o");
  dep2.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep2.OnResolved(&err));

  Target dep3(setup.settings(), Label(SourceDir("//achilles/"), "group"));
  dep3.set_output_type(Target::GROUP);
  dep3.visibility().SetPublic();
  dep3.public_deps().push_back(LabelTargetPair(&dep));
  dep3.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep3.OnResolved(&err));

  Target dep4(setup.settings(), Label(SourceDir("//tortoise/"), "macro"));
  dep4.set_output_type(Target::RUST_PROC_MACRO);
  dep4.visibility().SetPublic();
  SourceFile tmlib("//tortoise/macro/lib.rs");
  dep4.sources().push_back(tmlib);
  dep4.source_types_used().Set(SourceFile::SOURCE_RS);
  dep4.rust_values().set_crate_root(tmlib);
  dep4.rust_values().crate_name() = "tortoise_macro";
  dep4.SetToolchain(setup.toolchain());
  ASSERT_TRUE(dep4.OnResolved(&err));

  Target target(setup.settings(), Label(SourceDir("//hare/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile harelib("//hare/lib.rs");
  target.sources().push_back(harelib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(harelib);
  target.rust_values().crate_name() = "hare";
  target.public_deps().push_back(LabelTargetPair(&dep));
  target.public_deps().push_back(LabelTargetPair(&dep3));
  target.public_deps().push_back(LabelTargetPair(&dep4));
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"tortoise/lib.rs\",\n"
      "      \"label\": \"//tortoise:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"tortoise/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    },\n"
      "    {\n"
      "      \"crate_id\": 1,\n"
      "      \"root_module\": \"tortoise/macro/lib.rs\",\n"
      "      \"label\": \"//tortoise:macro\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"tortoise/macro/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"is_proc_macro\": true,\n"
      "      \"proc_macro_dylib_path\": "
      "\"out/Debug/obj/tortoise/libmacro.so\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    },\n"
      "    {\n"
      "      \"crate_id\": 2,\n"
      "      \"root_module\": \"hare/lib.rs\",\n"
      "      \"label\": \"//hare:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"hare/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"deps\": [\n"
      "        {\n"
      "          \"crate\": 0,\n"
      "          \"name\": \"tortoise\"\n"
      "        },\n"
      "        {\n"
      "          \"crate\": 1,\n"
      "          \"name\": \"tortoise_macro\"\n"
      "        }\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, OneRustTargetWithRustcTargetSet) {
  Err err;
  TestWithScope setup;
  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile lib("//foo/lib.rs");
  target.sources().push_back(lib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(lib);
  target.rust_values().crate_name() = "foo";
  target.config_values().rustflags().push_back("--target=x86-64_unknown");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"path/foo/lib.rs\",\n"
      "      \"label\": \"//foo:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"path/foo/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"target\": \"x86-64_unknown\",\n"
      "      \"compiler_args\": [\"--target=x86-64_unknown\"],\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, OneRustTargetWithRustcTargetSetAlternate) {
  Err err;
  TestWithScope setup;
  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile lib("//foo/lib.rs");
  target.sources().push_back(lib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(lib);
  target.rust_values().crate_name() = "foo";
  target.config_values().rustflags().push_back("--target");
  target.config_values().rustflags().push_back("x86-64_unknown");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"path/foo/lib.rs\",\n"
      "      \"label\": \"//foo:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"path/foo/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"target\": \"x86-64_unknown\",\n"
      "      \"compiler_args\": [\"--target\", \"x86-64_unknown\"],\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, OneRustTargetWithEditionSet) {
  Err err;
  TestWithScope setup;
  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile lib("//foo/lib.rs");
  target.sources().push_back(lib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(lib);
  target.rust_values().crate_name() = "foo";
  target.config_values().rustflags().push_back("--edition=2018");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"path/foo/lib.rs\",\n"
      "      \"label\": \"//foo:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"path/foo/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"compiler_args\": [\"--edition=2018\"],\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2018\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, OneRustTargetWithEditionSetAlternate) {
  Err err;
  TestWithScope setup;
  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::RUST_LIBRARY);
  target.visibility().SetPublic();
  SourceFile lib("//foo/lib.rs");
  target.sources().push_back(lib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(lib);
  target.rust_values().crate_name() = "foo";
  target.config_values().rustflags().push_back("--edition");
  target.config_values().rustflags().push_back("2018");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"path/foo/lib.rs\",\n"
      "      \"label\": \"//foo:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"path/foo/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"compiler_args\": [\"--edition\", \"2018\"],\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2018\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\"\n"
      "      ]\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}

TEST_F(RustProjectJSONWriter, OneRustProcMacroTarget) {
  Err err;
  TestWithScope setup;
  setup.build_settings()->SetRootPath(UTF8ToFilePath("path"));

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::RUST_PROC_MACRO);
  target.visibility().SetPublic();
  SourceFile lib("//foo/lib.rs");
  target.sources().push_back(lib);
  target.source_types_used().Set(SourceFile::SOURCE_RS);
  target.rust_values().set_crate_root(lib);
  target.config_values().rustenv().push_back("TEST_ENV_VAR=baz");
  target.config_values().rustenv().push_back("TEST_ENV_VAR2=baz2");
  target.rust_values().crate_name() = "foo";
  target.config_values().rustflags().push_back("--cfg=feature=\"foo_enabled\"");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::ostringstream stream;
  std::vector<const Target*> targets;
  targets.push_back(&target);
  RustProjectWriter::RenderJSON(setup.build_settings(), targets, stream);
  std::string out = stream.str();
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      "{\n"
      "  \"crates\": [\n"
      "    {\n"
      "      \"crate_id\": 0,\n"
      "      \"root_module\": \"path/foo/lib.rs\",\n"
      "      \"label\": \"//foo:bar\",\n"
      "      \"source\": {\n"
      "          \"include_dirs\": [\n"
      "               \"path/foo/\"\n"
      "          ],\n"
      "          \"exclude_dirs\": []\n"
      "      },\n"
      "      \"compiler_args\": [\"--cfg=feature=\\\"foo_enabled\\\"\"],\n"
      "      \"deps\": [\n"
      "      ],\n"
      "      \"edition\": \"2015\",\n"
      "      \"is_proc_macro\": true,\n"
      "      \"proc_macro_dylib_path\": \"path/out/Debug/obj/foo/libbar.so\",\n"
      "      \"cfg\": [\n"
      "        \"test\",\n"
      "        \"debug_assertions\",\n"
      "        \"feature=\\\"foo_enabled\\\"\"\n"
      "      ],\n"
      "      \"env\": {\n"
      "        \"TEST_ENV_VAR\": \"baz\",\n"
      "        \"TEST_ENV_VAR2\": \"baz2\"\n"
      "      }\n"
      "    }\n"
      "  ]\n"
      "}\n";

  ExpectEqOrShowDiff(expected_json, out);
}
