// Copyright (c) 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/json_project_writer.h"
#include "base/strings/string_util.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"

using JSONWriter = TestWithScheduler;

TEST_F(JSONWriter, ActionWithResponseFile) {
  Err err;
  TestWithScope setup;

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::ACTION);

  target.sources().push_back(SourceFile("//foo/source1.txt"));
  target.config_values().inputs().push_back(SourceFile("//foo/input1.txt"));
  target.action_values().set_script(SourceFile("//foo/script.py"));

  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  // Make sure we get interesting substitutions for both the args and the
  // response file contents.
  target.action_values().args() =
      SubstitutionList::MakeForTest("{{response_file_name}}");
  target.action_values().rsp_file_contents() =
      SubstitutionList::MakeForTest("-j", "3");
  target.action_values().outputs() =
      SubstitutionList::MakeForTest("//out/Debug/output1.out");

  setup.build_settings()->SetPythonPath(
      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
  std::vector<const Target*> targets;
  targets.push_back(&target);
#if defined(OS_WIN)
  base::FilePath root_path =
      base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
#else
  base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("/path/to/src"));
#endif
  setup.build_settings()->SetRootPath(root_path);
  g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL(".gn")));
  g_scheduler->AddGenDependency(
      root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
  g_scheduler->AddGenDependency(
      root_path.Append(FILE_PATH_LITERAL("build/BUILD.gn")));
  std::string out =
      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      R"_({
   "build_settings": {
      "build_dir": "//out/Debug/",
      "default_toolchain": "//toolchain:default",
      "gen_input_files": [ "//.gn", "//BUILD.gn", "//build/BUILD.gn" ],
)_"
#if defined(OS_WIN)
      "      \"root_path\": \"c:/path/to/src\"\n"
#else
      "      \"root_path\": \"/path/to/src\"\n"
#endif
      R"_(   },
   "targets": {
      "//foo:bar()": {
         "args": [ "{{response_file_name}}" ],
         "check_includes_strict": false,
         "deps": [  ],
         "inputs": [ "//foo/input1.txt" ],
         "metadata": {

         },
         "outputs": [ "//out/Debug/output1.out" ],
         "public": "*",
         "response_file_contents": [ "-j", "3" ],
         "script": "//foo/script.py",
         "sources": [ "//foo/source1.txt" ],
         "testonly": false,
         "toolchain": "",
         "type": "action",
         "visibility": [  ]
      }
   },
   "toolchains": {
      "//toolchain:default": {
         "alink": {
            "command": "ar {{output}} {{source}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}.a" ],
            "weak_framework_switch": "-weak_framework "
         },
         "cc": {
            "command": "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "compile_xcassets": {
            "command": "touch {{output}}"
         },
         "copy": {
            "command": "cp {{source}} {{output}}"
         },
         "copy_bundle_data": {
            "command": "cp {{source}} {{output}}"
         },
         "cxx": {
            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} {{module_deps}} -o {{output}}",
            "command_launcher": "launcher",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "cxx_module": {
            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} {{module_deps_no_self}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm" ],
            "weak_framework_switch": "-weak_framework "
         },
         "link": {
            "command": "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "objc": {
            "command": "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "objcxx": {
            "command": "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "rust_bin": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "outputs": [ "{{root_out_dir}}/{{crate_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_cdylib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_dylib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_macro": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_rlib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".rlib",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_staticlib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".a",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "solink": {
            "command": "ld -shared -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "solink_module": {
            "command": "ld -bundle -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "stamp": {
            "command": "touch {{output}}"
         },
         "swift": {
            "command": "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{target_gen_dir}}/{{target_output_name}}.h", "{{target_out_dir}}/{{module_name}}.swiftmodule" ],
            "partial_outputs": [ "{{target_out_dir}}/{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         }
      }
   }
}
)_";
  EXPECT_EQ(expected_json, out);
}

TEST_F(JSONWriter, RustTarget) {
  Err err;
  TestWithScope setup;

  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.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::vector<const Target*> targets;
  targets.push_back(&target);
  std::string out =
      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      R"_({
   "build_settings": {
      "build_dir": "//out/Debug/",
      "default_toolchain": "//toolchain:default",
      "gen_input_files": [  ],
      "root_path": ""
   },
   "targets": {
      "//foo:bar()": {
         "allow_circular_includes_from": [  ],
         "check_includes": true,
         "check_includes_strict": false,
         "crate_name": "foo",
         "crate_root": "//foo/lib.rs",
         "deps": [  ],
         "externs": {

         },
         "metadata": {

         },
         "outputs": [ "//out/Debug/obj/foo/libbar.rlib" ],
         "public": "*",
         "sources": [ "//foo/lib.rs" ],
         "testonly": false,
         "toolchain": "",
         "type": "rust_library",
         "visibility": [ "*" ]
      }
   },
   "toolchains": {
      "//toolchain:default": {
         "alink": {
            "command": "ar {{output}} {{source}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}.a" ],
            "weak_framework_switch": "-weak_framework "
         },
         "cc": {
            "command": "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "compile_xcassets": {
            "command": "touch {{output}}"
         },
         "copy": {
            "command": "cp {{source}} {{output}}"
         },
         "copy_bundle_data": {
            "command": "cp {{source}} {{output}}"
         },
         "cxx": {
            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} {{module_deps}} -o {{output}}",
            "command_launcher": "launcher",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "cxx_module": {
            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} {{module_deps_no_self}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm" ],
            "weak_framework_switch": "-weak_framework "
         },
         "link": {
            "command": "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "objc": {
            "command": "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "objcxx": {
            "command": "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "rust_bin": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "outputs": [ "{{root_out_dir}}/{{crate_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_cdylib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_dylib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_macro": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_rlib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".rlib",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_staticlib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".a",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "solink": {
            "command": "ld -shared -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "solink_module": {
            "command": "ld -bundle -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "stamp": {
            "command": "touch {{output}}"
         },
         "swift": {
            "command": "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{target_gen_dir}}/{{target_output_name}}.h", "{{target_out_dir}}/{{module_name}}.swiftmodule" ],
            "partial_outputs": [ "{{target_out_dir}}/{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         }
      }
   }
}
)_";
  EXPECT_EQ(expected_json, out);
}

TEST_F(JSONWriter, ForEachWithResponseFile) {
  Err err;
  TestWithScope setup;

  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
  target.set_output_type(Target::ACTION_FOREACH);

  target.sources().push_back(SourceFile("//foo/input1.txt"));
  target.action_values().set_script(SourceFile("//foo/script.py"));

  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  // Make sure we get interesting substitutions for both the args and the
  // response file contents.
  target.action_values().args() = SubstitutionList::MakeForTest(
      "{{source}}", "{{source_file_part}}", "{{response_file_name}}");
  target.action_values().rsp_file_contents() =
      SubstitutionList::MakeForTest("-j", "{{source_name_part}}");
  target.action_values().outputs() =
      SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");

  setup.build_settings()->SetPythonPath(
      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
  std::vector<const Target*> targets;
  targets.push_back(&target);
#if defined(OS_WIN)
  base::FilePath root_path =
      base::FilePath(FILE_PATH_LITERAL("c:/path/to/src"));
#else
  base::FilePath root_path = base::FilePath(FILE_PATH_LITERAL("/path/to/src"));
#endif
  setup.build_settings()->SetRootPath(root_path);
  g_scheduler->AddGenDependency(root_path.Append(FILE_PATH_LITERAL(".gn")));
  g_scheduler->AddGenDependency(
      root_path.Append(FILE_PATH_LITERAL("BUILD.gn")));
  std::string out =
      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      R"_({
   "build_settings": {
      "build_dir": "//out/Debug/",
      "default_toolchain": "//toolchain:default",
      "gen_input_files": [ "//.gn", "//BUILD.gn" ],
)_"
#if defined(OS_WIN)
      "      \"root_path\": \"c:/path/to/src\"\n"
#else
      "      \"root_path\": \"/path/to/src\"\n"
#endif
      R"_(   },
   "targets": {
      "//foo:bar()": {
         "args": [ "{{source}}", "{{source_file_part}}", "{{response_file_name}}" ],
         "check_includes_strict": false,
         "deps": [  ],
         "metadata": {

         },
         "output_patterns": [ "//out/Debug/{{source_name_part}}.out" ],
         "outputs": [ "//out/Debug/input1.out" ],
         "public": "*",
         "response_file_contents": [ "-j", "{{source_name_part}}" ],
         "script": "//foo/script.py",
         "source_outputs": {
            "//foo/input1.txt": [ "input1.out" ]
         },
         "sources": [ "//foo/input1.txt" ],
         "testonly": false,
         "toolchain": "",
         "type": "action_foreach",
         "visibility": [  ]
      }
   },
   "toolchains": {
      "//toolchain:default": {
         "alink": {
            "command": "ar {{output}} {{source}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}.a" ],
            "weak_framework_switch": "-weak_framework "
         },
         "cc": {
            "command": "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "compile_xcassets": {
            "command": "touch {{output}}"
         },
         "copy": {
            "command": "cp {{source}} {{output}}"
         },
         "copy_bundle_data": {
            "command": "cp {{source}} {{output}}"
         },
         "cxx": {
            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} {{module_deps}} -o {{output}}",
            "command_launcher": "launcher",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "cxx_module": {
            "command": "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} {{module_deps_no_self}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.pcm" ],
            "weak_framework_switch": "-weak_framework "
         },
         "link": {
            "command": "ld -o {{target_output_name}} {{source}} {{ldflags}} {{libs}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "objc": {
            "command": "objcc {{source}} {{cflags}} {{cflags_objc}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "objcxx": {
            "command": "objcxx {{source}} {{cflags}} {{cflags_objcc}} {{defines}} {{include_dirs}} -o {{output}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         },
         "rust_bin": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "outputs": [ "{{root_out_dir}}/{{crate_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_cdylib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_dylib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_macro": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_rlib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".rlib",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "rust_staticlib": {
            "command": "{{rustenv}} rustc --crate-name {{crate_name}} {{source}} --crate-type {{crate_type}} {{rustflags}} -o {{output}} {{rustdeps}} {{externs}}",
            "default_output_extension": ".a",
            "framework_dir_switch": "-Lframework=",
            "framework_switch": "-lframework=",
            "lib_dir_switch": "-Lnative=",
            "lib_switch": "-l",
            "linker_arg": "-Clink-arg=",
            "output_prefix": "lib",
            "outputs": [ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-lframework="
         },
         "solink": {
            "command": "ld -shared -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "solink_module": {
            "command": "ld -bundle -o {{target_output_name}}.so {{inputs}} {{ldflags}} {{libs}}",
            "default_output_extension": ".so",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "output_prefix": "lib",
            "outputs": [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ],
            "weak_framework_switch": "-weak_framework "
         },
         "stamp": {
            "command": "touch {{output}}"
         },
         "swift": {
            "command": "swiftc --module-name {{module_name}} {{module_dirs}} {{inputs}}",
            "framework_dir_switch": "-F",
            "framework_switch": "-framework ",
            "lib_dir_switch": "-L",
            "lib_switch": "-l",
            "outputs": [ "{{target_gen_dir}}/{{target_output_name}}.h", "{{target_out_dir}}/{{module_name}}.swiftmodule" ],
            "partial_outputs": [ "{{target_out_dir}}/{{source_name_part}}.o" ],
            "weak_framework_switch": "-weak_framework "
         }
      }
   }
}
)_";
  EXPECT_EQ(expected_json, out);
}

TEST_F(JSONWriter, FilterTargetsWithDataDeps) {
  Err err;
  TestWithScope setup;

  // Create targets :a, :b, :c
  Target target_a(setup.settings(), Label(SourceDir("//foo/"), "a"));
  target_a.set_output_type(Target::STATIC_LIBRARY);
  target_a.visibility().SetPublic();

  Target target_b(setup.settings(), Label(SourceDir("//foo/"), "b"));
  target_b.set_output_type(Target::STATIC_LIBRARY);
  target_b.visibility().SetPublic();

  Target target_c(setup.settings(), Label(SourceDir("//foo/"), "c"));
  target_c.set_output_type(Target::STATIC_LIBRARY);
  target_c.visibility().SetPublic();

  // :a depends on :b, and data_depends on :c
  target_a.private_deps().push_back(LabelTargetPair(&target_b));
  target_a.data_deps().push_back(LabelTargetPair(&target_c));

  target_a.SetToolchain(setup.toolchain());
  target_b.SetToolchain(setup.toolchain());
  target_c.SetToolchain(setup.toolchain());

  ASSERT_TRUE(target_a.OnResolved(&err));
  ASSERT_TRUE(target_b.OnResolved(&err));
  ASSERT_TRUE(target_c.OnResolved(&err));

  std::vector<const Target*> all_targets = {&target_a, &target_b, &target_c};
  std::vector<const Target*> filtered;

  // Only DEPS_LINKED (should include :a and :b, but not :c)
  ASSERT_TRUE(JSONProjectWriter::FilterTargets(
      setup.build_settings(), all_targets, &filtered, "//foo:a",
      /*filter_with_data_deps=*/false, &err));
  std::set<Label> labels_linked;
  for (const Target* t : filtered)
    labels_linked.insert(t->label());
  EXPECT_GT(labels_linked.count(Label(SourceDir("//foo/"), "a")), 0u);
  EXPECT_GT(labels_linked.count(Label(SourceDir("//foo/"), "b")), 0u);
  EXPECT_EQ(labels_linked.count(Label(SourceDir("//foo/"), "c")), 0u);

  // DEPS_ALL (should include :a, :b, :c)
  ASSERT_TRUE(JSONProjectWriter::FilterTargets(
      setup.build_settings(), all_targets, &filtered, "//foo:a",
      /*filter_with_data_deps=*/true, &err));
  std::set<Label> labels_all;
  for (const Target* t : filtered)
    labels_all.insert(t->label());
  EXPECT_GT(labels_all.count(Label(SourceDir("//foo/"), "a")), 0u);
  EXPECT_GT(labels_all.count(Label(SourceDir("//foo/"), "b")), 0u);
  EXPECT_GT(labels_all.count(Label(SourceDir("//foo/"), "c")), 0u);
}

TEST_F(JSONWriter, GroupWithData) {
  Err err;
  TestWithScope setup;

  Target target(setup.settings(), Label(SourceDir("//foo/"), "docs"));
  target.set_output_type(Target::GROUP);
  target.data().push_back("README.md");
  target.data().push_back("docs/help.txt");
  target.SetToolchain(setup.toolchain());
  ASSERT_TRUE(target.OnResolved(&err));

  std::vector<const Target*> targets = {&target};
  std::string out =
      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
#endif
  const char expected_json[] =
      R"_({
   "build_settings": {
      "build_dir": "//out/Debug/",
      "default_toolchain": "//toolchain:default",
      "gen_input_files": [  ],
      "root_path": ""
   },
   "targets": {
      "//foo:docs()": {
         "check_includes_strict": false,
         "data": [ "README.md", "docs/help.txt" ],
         "deps": [  ],
         "metadata": {

         },
         "public": "*",
         "testonly": false,
         "toolchain": "",
         "type": "group",
         "visibility": [  ]
      }
   },
   "toolchains": {
)_";

  EXPECT_TRUE(out.starts_with(expected_json)) << out;
}
