#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Smoke tests for gclient.py.

Shell out 'gclient' and run git tests.
"""

import json
import logging
import os
import sys
import unittest

import gclient_smoketest_base

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT_DIR)

import subprocess2  # noqa: E402
from testing_support.fake_repos import join  # noqa: E402


class GClientSmokeGIT(gclient_smoketest_base.GClientSmokeBase):
    def setUp(self):
        super(GClientSmokeGIT, self).setUp()
        self.env["PATH"] = (
            os.path.join(ROOT_DIR, "testing_support")
            + os.pathsep
            + self.env["PATH"]
        )
        self.env["GCLIENT_SUPPRESS_GIT_VERSION_WARNING"] = "1"
        self.env["GCLIENT_SUPPRESS_SUBMODULE_WARNING"] = "1"
        self.enabled = self.FAKE_REPOS.set_up_git()
        if not self.enabled:
            self.skipTest("git fake repos not available")

    def testGitmodules_relative(self):
        self.gclient(
            ["config", self.git_base + "repo_19", "--name", "dir", "--managed"],
            cwd=self.git_base + "repo_19",
        )
        self.gclient(["sync"], cwd=self.git_base + "repo_19")
        self.gclient(
            ["gitmodules"], cwd=self.git_base + os.path.join("repo_19", "dir")
        )

        gitmodules = os.path.join(
            self.git_base, "repo_19", "dir", ".gitmodules"
        )
        with open(gitmodules) as f:
            contents = f.read().splitlines()
            self.assertEqual(
                [
                    '[submodule "some_repo"]',
                    "\tpath = some_repo",
                    "\turl = /repo_2",
                    "\tgclient-condition = not foo_checkout",
                    '[submodule "chicken/dickens"]',
                    "\tpath = chicken/dickens",
                    "\turl = /repo_3",
                    "\tgclient-recursedeps = true",
                ],
                contents,
            )

    def testGitmodules_not_relative(self):
        self.gclient(
            ["config", self.git_base + "repo_20", "--name", "foo", "--managed"],
            cwd=self.git_base + "repo_20",
        )
        self.gclient(["sync"], cwd=self.git_base + "repo_20")
        self.gclient(
            ["gitmodules"], cwd=self.git_base + os.path.join("repo_20", "foo")
        )

        gitmodules = os.path.join(
            self.git_base, "repo_20", "foo", ".gitmodules"
        )
        with open(gitmodules) as f:
            contents = f.read().splitlines()
            self.assertEqual(
                [
                    '[submodule "some_repo"]',
                    "\tpath = some_repo",
                    "\turl = /repo_2",
                    "\tgclient-condition = not foo_checkout",
                    '[submodule "chicken/dickens"]',
                    "\tpath = chicken/dickens",
                    "\turl = /repo_3",
                    "\tgclient-recursedeps = true",
                ],
                contents,
            )

    def testGitmodules_migration_no_git_suffix(self):
        self.gclient(
            ["config", self.git_base + "repo_21", "--name", "foo", "--managed"],
            cwd=self.git_base + "repo_21",
        )
        # We are not running gclient sync since dependencies don't exist
        self.gclient(["gitmodules"], cwd=self.git_base + "repo_21")

        gitmodules = os.path.join(self.git_base, "repo_21", ".gitmodules")
        with open(gitmodules) as f:
            contents = f.read().splitlines()
            self.assertEqual(
                [
                    '[submodule "bar"]',
                    "\tpath = bar",
                    "\turl = https://example.googlesource.com/repo.git",
                ],
                contents,
            )
        # force migration
        os.remove(gitmodules)
        self.gclient(["gitmodules"], cwd=self.git_base + "repo_21")

        gitmodules = os.path.join(self.git_base, "repo_21", ".gitmodules")
        with open(gitmodules) as f:
            contents = f.read().splitlines()
            self.assertEqual(
                [
                    '[submodule "bar"]',
                    "\tpath = bar",
                    "\turl = https://example.googlesource.com/repo",
                ],
                contents,
            )

    def testGitmodules_migration_recursdeps(self):
        self.gclient(
            ["config", self.git_base + "repo_24", "--name", "foo", "--managed"],
            cwd=self.git_base + "repo_24",
        )
        # We are not running gclient sync since dependencies don't exist
        self.gclient(["gitmodules"], cwd=self.git_base + "repo_24")

        gitmodules = os.path.join(self.git_base, "repo_24", ".gitmodules")
        with open(gitmodules) as f:
            contents = f.read().splitlines()
            self.assertEqual(
                [
                    '[submodule "bar"]',
                    "\tpath = bar",
                    "\turl = https://example.googlesource.com/repo",
                ],
                contents,
            )
        # force migration
        os.remove(gitmodules)
        self.gclient(["gitmodules"], cwd=self.git_base + "repo_24")

        gitmodules = os.path.join(self.git_base, "repo_24", ".gitmodules")
        with open(gitmodules) as f:
            contents = f.read().splitlines()
            self.assertEqual(
                [
                    '[submodule "bar"]',
                    "\tpath = bar",
                    "\turl = https://example.googlesource.com/repo",
                    "\tgclient-recursedeps = true",
                ],
                contents,
            )

    def testGitmodules_not_in_gclient(self):
        with self.assertRaisesRegex(AssertionError, "from a gclient workspace"):
            self.gclient(["gitmodules"], cwd=self.root_dir)

    def testSync(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        # Test unversioned checkout.
        self.parseGclient(
            ["sync", "--deps", "mac", "--jobs", "1"],
            ["running", "running", "running"],
        )
        # TODO(maruel): http://crosbug.com/3582 hooks run even if not matching,
        # must add sync parsing to get the list of updated files.
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)

        # Manually remove git_hooked1 before syncing to make sure it's not
        # recreated.
        os.remove(join(self.root_dir, "src", "git_hooked1"))

        # Test incremental versioned sync: sync backward.
        self.parseGclient(
            [
                "sync",
                "--jobs",
                "1",
                "--revision",
                "src@" + self.githash("repo_1", 1),
                "--deps",
                "mac",
                "--delete_unversioned_trees",
            ],
            ["deleting"],
        )
        tree = self.mangle_git_tree(
            ("repo_1@1", "src"),
            ("repo_2@2", "src/repo2"),
            ("repo_3@1", "src/repo2/repo3"),
            ("repo_4@2", "src/repo4"),
        )
        tree["src/git_hooked2"] = "git_hooked2"
        tree["src/repo2/gclient.args"] = "\n".join(
            [
                "# Generated from 'DEPS'",
                "false_var = false",
                "false_str_var = false",
                "true_var = true",
                "true_str_var = true",
                'str_var = "abc"',
                "cond_var = false",
            ]
        )
        self.assertTree(tree)
        # Test incremental sync: delete-unversioned_trees isn't there.
        self.parseGclient(
            ["sync", "--deps", "mac", "--jobs", "1"], ["running", "running"]
        )
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@1", "src/repo2/repo3"),
            ("repo_3@2", "src/repo2/repo_renamed"),
            ("repo_4@2", "src/repo4"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        tree["src/repo2/gclient.args"] = "\n".join(
            [
                "# Generated from 'DEPS'",
                "false_var = false",
                "false_str_var = false",
                "true_var = true",
                "true_str_var = true",
                'str_var = "abc"',
                "cond_var = false",
            ]
        )
        self.assertTree(tree)

    def testSyncJsonOutput(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        output_json = os.path.join(self.root_dir, "output.json")
        self.gclient(["sync", "--deps", "mac", "--output-json", output_json])
        with open(output_json) as f:
            output_json = json.load(f)

        out = {
            "solutions": {
                "src/": {
                    "scm": "git",
                    "url": self.git_base + "repo_1",
                    "revision": self.githash("repo_1", 2),
                    "was_processed": True,
                    "was_synced": True,
                },
                "src/repo2/": {
                    "scm": "git",
                    "url": self.git_base
                    + "repo_2@"
                    + self.githash("repo_2", 1)[:7],
                    "revision": self.githash("repo_2", 1),
                    "was_processed": True,
                    "was_synced": True,
                },
                "src/repo2/repo_renamed/": {
                    "scm": "git",
                    "url": self.git_base + "repo_3",
                    "revision": self.githash("repo_3", 2),
                    "was_processed": True,
                    "was_synced": True,
                },
                "src/should_not_process/": {
                    "scm": None,
                    "url": self.git_base + "repo_4",
                    "revision": None,
                    "was_processed": False,
                    "was_synced": True,
                },
            },
        }
        self.assertEqual(out, output_json)

    def testSyncIgnoredSolutionName(self):
        """TODO(maruel): This will become an error soon."""
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.parseGclient(
            [
                "sync",
                "--deps",
                "mac",
                "--jobs",
                "1",
                "--revision",
                "invalid@" + self.githash("repo_1", 1),
            ],
            ["running", "running", "running"],
            "Please fix your script, having invalid --revision flags "
            "will soon be considered an error.\n",
        )
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)

    def testSyncNoSolutionName(self):
        # When no solution name is provided, gclient uses the first solution
        # listed.
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.parseGclient(
            [
                "sync",
                "--deps",
                "mac",
                "--jobs",
                "1",
                "--revision",
                self.githash("repo_1", 1),
            ],
            ["running"],
        )
        tree = self.mangle_git_tree(
            ("repo_1@1", "src"),
            ("repo_2@2", "src/repo2"),
            ("repo_3@1", "src/repo2/repo3"),
            ("repo_4@2", "src/repo4"),
        )
        tree["src/repo2/gclient.args"] = "\n".join(
            [
                "# Generated from 'DEPS'",
                "false_var = false",
                "false_str_var = false",
                "true_var = true",
                "true_str_var = true",
                'str_var = "abc"',
                "cond_var = false",
            ]
        )
        self.assertTree(tree)

    def testSyncJobs(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        # Test unversioned checkout.
        self.parseGclient(
            ["sync", "--deps", "mac", "--jobs", "8"],
            ["running", "running", "running"],
            untangle=True,
        )
        # TODO(maruel): http://crosbug.com/3582 hooks run even if not matching,
        # must add sync parsing to get the list of updated files.
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)

        # Manually remove git_hooked1 before syncing to make sure it's not
        # recreated.
        os.remove(join(self.root_dir, "src", "git_hooked1"))

        # Test incremental versioned sync: sync backward.
        # Use --jobs 1 otherwise the order is not deterministic.
        self.parseGclient(
            [
                "sync",
                "--revision",
                "src@" + self.githash("repo_1", 1),
                "--deps",
                "mac",
                "--delete_unversioned_trees",
                "--jobs",
                "1",
            ],
            ["deleting"],
            untangle=True,
        )
        tree = self.mangle_git_tree(
            ("repo_1@1", "src"),
            ("repo_2@2", "src/repo2"),
            ("repo_3@1", "src/repo2/repo3"),
            ("repo_4@2", "src/repo4"),
        )
        tree["src/git_hooked2"] = "git_hooked2"
        tree["src/repo2/gclient.args"] = "\n".join(
            [
                "# Generated from 'DEPS'",
                "false_var = false",
                "false_str_var = false",
                "true_var = true",
                "true_str_var = true",
                'str_var = "abc"',
                "cond_var = false",
            ]
        )
        self.assertTree(tree)
        # Test incremental sync: delete-unversioned_trees isn't there.
        self.parseGclient(
            ["sync", "--deps", "mac", "--jobs", "8"],
            ["running", "running"],
            untangle=True,
        )
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@1", "src/repo2/repo3"),
            ("repo_3@2", "src/repo2/repo_renamed"),
            ("repo_4@2", "src/repo4"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        tree["src/repo2/gclient.args"] = "\n".join(
            [
                "# Generated from 'DEPS'",
                "false_var = false",
                "false_str_var = false",
                "true_var = true",
                "true_str_var = true",
                'str_var = "abc"',
                "cond_var = false",
            ]
        )
        self.assertTree(tree)

    def testSyncFetch(self):
        self.gclient(
            ["config", self.git_base + "repo_13", "--name", "src", "--managed"]
        )
        self.gclient(
            ["sync", "-v", "-v", "-v", "--revision", self.githash("repo_13", 2)]
        )

    def testSyncFetchUpdate(self):
        self.gclient(
            ["config", self.git_base + "repo_13", "--name", "src", "--managed"]
        )

        # Sync to an earlier revision first, one that doesn't refer to
        # non-standard refs.
        self.gclient(
            ["sync", "-v", "-v", "-v", "--revision", self.githash("repo_13", 1)]
        )

        # Make sure update that pulls a non-standard ref works.
        self.gclient(
            ["sync", "-v", "-v", "-v", "--revision", self.githash("repo_13", 2)]
        )

    def testSyncDirect(self):
        self.gclient(
            ["config", self.git_base + "repo_12", "--name", "src", "--managed"]
        )
        self.gclient(
            ["sync", "-v", "-v", "-v", "--revision", "refs/changes/1212"]
        )

    def testSyncUnmanaged(self):
        self.gclient(
            [
                "config",
                "--spec",
                'solutions=[{"name":"src", "url": %r, "managed": False}]'
                % (self.git_base + "repo_5"),
            ]
        )
        self.gclient(["sync", "--revision", "src@" + self.githash("repo_5", 2)])
        self.gclient(
            ["sync", "--revision", "src/repo1@%s" % self.githash("repo_1", 1)]
        )
        # src is unmanaged, so gclient shouldn't have updated it. It should've
        # stayed synced at @2
        tree = self.mangle_git_tree(
            ("repo_5@2", "src"),
            ("repo_1@1", "src/repo1"),
            ("repo_2@1", "src/repo2"),
        )
        tree["src/git_pre_deps_hooked"] = "git_pre_deps_hooked"
        self.assertTree(tree)

    def testSyncUrl(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(
            [
                "sync",
                "-v",
                "-v",
                "-v",
                "--revision",
                "src/repo2@%s" % self.githash("repo_2", 1),
                "--revision",
                "%srepo_2@%s" % (self.git_base, self.githash("repo_2", 2)),
            ]
        )
        # repo_2 should've been synced to @2 instead of @1, since URLs override
        # paths.
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@2", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)

    def testSyncPatchRef(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(
            [
                "sync",
                "-v",
                "-v",
                "-v",
                "--revision",
                "src/repo2@%s" % self.githash("repo_2", 1),
                "--patch-ref",
                "%srepo_2@refs/heads/main:%s"
                % (self.git_base, self.githash("repo_2", 2)),
            ]
        )
        # Assert that repo_2 files coincide with revision @2 (the patch ref)
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@2", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)
        # Assert that HEAD revision of repo_2 is @1 (the base we synced to)
        # since we should have done a soft reset.
        self.assertEqual(
            self.githash("repo_2", 1),
            self.gitrevparse(os.path.join(self.root_dir, "src/repo2")),
        )

    def testSyncPatchRefNoHooks(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(
            [
                "sync",
                "-v",
                "-v",
                "-v",
                "--revision",
                "src/repo2@%s" % self.githash("repo_2", 1),
                "--patch-ref",
                "%srepo_2@refs/heads/main:%s"
                % (self.git_base, self.githash("repo_2", 2)),
                "--nohooks",
            ]
        )
        # Assert that repo_2 files coincide with revision @2 (the patch ref)
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@2", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        self.assertTree(tree)
        # Assert that HEAD revision of repo_2 is @1 (the base we synced to)
        # since we should have done a soft reset.
        self.assertEqual(
            self.githash("repo_2", 1),
            self.gitrevparse(os.path.join(self.root_dir, "src/repo2")),
        )

    def testInstallHooks_no_existing_hook(self):
        repo = os.path.join(self.git_base, "repo_18")
        self.gclient(["config", repo, "--name", "src", "--managed"], cwd=repo)
        self.gclient(["sync"], cwd=repo)
        self.gclient(["installhooks"], cwd=repo)

        hook = os.path.join(repo, "src", ".git", "hooks", "pre-commit")
        with open(hook) as f:
            contents = f.read()
            self.assertIn('python3 "$GCLIENT_PRECOMMIT"', contents)

        # Later runs should only inform that the hook was already installed.
        stdout, _, _ = self.gclient(["installhooks"], cwd=repo)
        self.assertIn(
            f"{hook} already contains the gclient pre-commit hook.", stdout
        )

    def testInstallHooks_existing_hook(self):
        repo = os.path.join(self.git_base, "repo_19")
        self.gclient(["config", repo, "--name", "src", "--managed"], cwd=repo)
        self.gclient(["sync"], cwd=repo)

        # Create an existing pre-commit hook.
        hook = os.path.join(repo, "src", ".git", "hooks", "pre-commit")
        hook_contents = ["#!/bin/sh", "echo hook"]
        with open(hook, "w") as f:
            f.write("\n".join(hook_contents))

        stdout, _, _ = self.gclient(["installhooks"], cwd=repo)
        self.assertIn("Please append the following lines to the hook", stdout)

        # The orignal hook is left unchanged.
        with open(hook) as f:
            contents = f.read().splitlines()
            self.assertEqual(hook_contents, contents)

    def testRunHooks(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)

        os.remove(join(self.root_dir, "src", "git_hooked1"))
        os.remove(join(self.root_dir, "src", "git_hooked2"))
        # runhooks runs all hooks even if not matching by design.
        out = self.parseGclient(
            ["runhooks", "--deps", "mac"], ["running", "running"]
        )
        self.assertEqual(1, len(out[0]))
        self.assertEqual(1, len(out[1]))
        tree = self.mangle_git_tree(
            ("repo_1@2", "src"),
            ("repo_2@1", "src/repo2"),
            ("repo_3@2", "src/repo2/repo_renamed"),
        )
        tree["src/git_hooked1"] = "git_hooked1"
        tree["src/git_hooked2"] = "git_hooked2"
        self.assertTree(tree)

    def testRunHooksCondition(self):
        self.gclient(
            ["config", self.git_base + "repo_7", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        tree = self.mangle_git_tree(("repo_7@1", "src"))
        tree["src/should_run"] = "should_run"
        self.assertTree(tree)

    def testPreDepsHooks(self):
        self.gclient(
            ["config", self.git_base + "repo_5", "--name", "src", "--managed"]
        )
        expectation = [
            ("running", self.root_dir),  # git clone
            ("running", self.root_dir),  # pre-deps hook
        ]
        out = self.parseGclient(
            [
                "sync",
                "--deps",
                "mac",
                "--jobs=1",
                "--revision",
                "src@" + self.githash("repo_5", 2),
            ],
            expectation,
        )
        self.assertEqual("Cloning into ", out[0][1][:13])
        # parseGClient may produce hook slowness warning, so we expect either 2
        # or 3 blocks.
        self.assertIn(len(out[1]), [2, 3], out[1])
        self.assertEqual("pre-deps hook", out[1][1])
        tree = self.mangle_git_tree(
            ("repo_5@2", "src"),
            ("repo_1@2", "src/repo1"),
            ("repo_2@1", "src/repo2"),
        )
        tree["src/git_pre_deps_hooked"] = "git_pre_deps_hooked"
        self.assertTree(tree)

        os.remove(join(self.root_dir, "src", "git_pre_deps_hooked"))

        # Pre-DEPS hooks don't run with runhooks.
        self.gclient(["runhooks", "--deps", "mac"])
        tree = self.mangle_git_tree(
            ("repo_5@2", "src"),
            ("repo_1@2", "src/repo1"),
            ("repo_2@1", "src/repo2"),
        )
        self.assertTree(tree)

        # Pre-DEPS hooks run when syncing with --nohooks.
        self.gclient(
            [
                "sync",
                "--deps",
                "mac",
                "--nohooks",
                "--revision",
                "src@" + self.githash("repo_5", 2),
            ]
        )
        tree = self.mangle_git_tree(
            ("repo_5@2", "src"),
            ("repo_1@2", "src/repo1"),
            ("repo_2@1", "src/repo2"),
        )
        tree["src/git_pre_deps_hooked"] = "git_pre_deps_hooked"
        self.assertTree(tree)

        os.remove(join(self.root_dir, "src", "git_pre_deps_hooked"))

        # Pre-DEPS hooks don't run with --noprehooks
        self.gclient(
            [
                "sync",
                "--deps",
                "mac",
                "--noprehooks",
                "--revision",
                "src@" + self.githash("repo_5", 2),
            ]
        )
        tree = self.mangle_git_tree(
            ("repo_5@2", "src"),
            ("repo_1@2", "src/repo1"),
            ("repo_2@1", "src/repo2"),
        )
        self.assertTree(tree)

    def testPreDepsHooksError(self):
        self.gclient(
            ["config", self.git_base + "repo_5", "--name", "src", "--managed"]
        )
        expectated_stdout = [
            ("running", self.root_dir),  # git clone
            ("running", self.root_dir),  # pre-deps hook
            ("running", self.root_dir),  # pre-deps hook (fails)
        ]
        expected_stderr = (
            "Error: Command 'python3 -c import sys; "
            "sys.exit(1)' returned non-zero exit status 1 in %s\n"
            % (self.root_dir)
        )
        stdout, stderr, retcode = self.gclient(
            [
                "sync",
                "--deps",
                "mac",
                "--jobs=1",
                "--revision",
                "src@" + self.githash("repo_5", 3),
            ],
            error_ok=True,
        )
        self.assertEqual(stderr, expected_stderr)
        self.assertEqual(2, retcode)
        self.checkBlock(stdout, expectated_stdout)

    def testRevInfo(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        results = self.gclient(["revinfo", "--deps", "mac"])
        out = (
            "src: %(base)srepo_1\n"
            "src/repo2: %(base)srepo_2@%(hash2)s\n"
            "src/repo2/repo_renamed: %(base)srepo_3\n"
            % {
                "base": self.git_base,
                "hash2": self.githash("repo_2", 1)[:7],
            }
        )
        self.check((out, "", 0), results)

    def testRevInfoActual(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        results = self.gclient(["revinfo", "--deps", "mac", "--actual"])
        out = (
            "src: %(base)srepo_1@%(hash1)s\n"
            "src/repo2: %(base)srepo_2@%(hash2)s\n"
            "src/repo2/repo_renamed: %(base)srepo_3@%(hash3)s\n"
            % {
                "base": self.git_base,
                "hash1": self.githash("repo_1", 2),
                "hash2": self.githash("repo_2", 1),
                "hash3": self.githash("repo_3", 2),
            }
        )
        self.check((out, "", 0), results)

    def testRevInfoFilterPath(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        results = self.gclient(["revinfo", "--deps", "mac", "--filter", "src"])
        out = "src: %(base)srepo_1\n" % {
            "base": self.git_base,
        }
        self.check((out, "", 0), results)

    def testRevInfoFilterURL(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        results = self.gclient(
            ["revinfo", "--deps", "mac", "--filter", "%srepo_2" % self.git_base]
        )
        out = "src/repo2: %(base)srepo_2@%(hash2)s\n" % {
            "base": self.git_base,
            "hash2": self.githash("repo_2", 1)[:7],
        }
        self.check((out, "", 0), results)

    def testRevInfoFilterURLOrPath(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        results = self.gclient(
            [
                "revinfo",
                "--deps",
                "mac",
                "--filter",
                "src",
                "--filter",
                "%srepo_2" % self.git_base,
            ]
        )
        out = "src: %(base)srepo_1\nsrc/repo2: %(base)srepo_2@%(hash2)s\n" % {
            "base": self.git_base,
            "hash2": self.githash("repo_2", 1)[:7],
        }
        self.check((out, "", 0), results)

    def testRevInfoJsonOutput(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--deps", "mac"])
        output_json = os.path.join(self.root_dir, "output.json")
        self.gclient(["revinfo", "--deps", "mac", "--output-json", output_json])
        with open(output_json) as f:
            output_json = json.load(f)

        out = {
            "src": {
                "url": self.git_base + "repo_1",
                "rev": None,
            },
            "src/repo2": {
                "url": self.git_base + "repo_2",
                "rev": self.githash("repo_2", 1)[:7],
            },
            "src/repo2/repo_renamed": {
                "url": self.git_base + "repo_3",
                "rev": None,
            },
        }
        self.assertEqual(out, output_json)

    def testRevInfoJsonOutputSnapshot(self):
        self.gclient(["config", self.git_base + "repo_1", "--name", "src"])
        self.gclient(["sync", "--deps", "mac"])
        output_json = os.path.join(self.root_dir, "output.json")
        self.gclient(
            [
                "revinfo",
                "--deps",
                "mac",
                "--snapshot",
                "--output-json",
                output_json,
            ]
        )
        with open(output_json) as f:
            output_json = json.load(f)

        out = [
            {
                "solution_url": self.git_base + "repo_1",
                "managed": False,
                "name": "src",
                "deps_file": "DEPS",
                "custom_deps": {
                    "src/repo2": "%srepo_2@%s"
                    % (self.git_base, self.githash("repo_2", 1)),
                    "src/repo2/repo_renamed": "%srepo_3@%s"
                    % (self.git_base, self.githash("repo_3", 2)),
                    "src/should_not_process": None,
                },
            }
        ]
        self.assertEqual(out, output_json)

    def testSetDep(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        fake_deps = os.path.join(self.git_base, "repo_1", "DEPS")
        with open(fake_deps, "w") as f:
            f.write(
                "\n".join(
                    [
                        "vars = { ",
                        '  "foo_var": "foo_val",',
                        '  "foo_rev": "foo_rev",',
                        "}",
                        "deps = {",
                        '  "foo": {',
                        '    "url": "url@{foo_rev}",',
                        "  },",
                        '  "bar": "url@bar_rev",',
                        "}",
                    ]
                )
            )

        self.gclient(
            [
                "setdep",
                "-r",
                "foo@new_foo",
                "-r",
                "bar@new_bar",
                "--var",
                "foo_var=new_val",
                "--deps-file",
                fake_deps,
            ],
            cwd=self.git_base + "repo_1",
        )

        with open(fake_deps) as f:
            contents = f.read().splitlines()

        self.assertEqual(
            [
                "vars = { ",
                '  "foo_var": "new_val",',
                '  "foo_rev": "new_foo",',
                "}",
                "deps = {",
                '  "foo": {',
                '    "url": "url@{foo_rev}",',
                "  },",
                '  "bar": "url@new_bar",',
                "}",
            ],
            contents,
        )

    def testSetDep_Submodules(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        with open(os.path.join(self.git_base, ".gclient"), "w") as f:
            f.write("")

        fake_deps = os.path.join(self.git_base, "repo_1", "DEPS")
        gitmodules = os.path.join(self.git_base, "repo_1", ".gitmodules")
        with open(fake_deps, "w") as f:
            f.write(
                "\n".join(
                    [
                        'git_dependencies = "SYNC"',
                        "vars = { ",
                        '  "foo_var": "foo_val",',
                        '  "foo_rev": "foo_rev",',
                        "}",
                        "deps = { ",
                        '  "repo_1/foo": "https://foo" + Var("foo_rev"),',
                        '  "repo_1/bar": "https://bar@barrev",',
                        "}",
                    ]
                )
            )

        with open(gitmodules, "w") as f:
            f.write(
                "\n".join(
                    [
                        '[submodule "foo"]',
                        "  url = https://foo",
                        "  path = foo",
                        '[submodule "bar"]',
                        "  url = https://bar",
                        "  path = bar",
                    ]
                )
            )

        subprocess2.call(
            [
                "git",
                "update-index",
                "--add",
                "--cacheinfo",
                "160000,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,foo",
            ],
            cwd=self.git_base + "repo_1",
        )
        subprocess2.call(
            [
                "git",
                "update-index",
                "--add",
                "--cacheinfo",
                "160000,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,bar",
            ],
            cwd=self.git_base + "repo_1",
        )

        self.gclient(
            [
                "setdep",
                "-r",
                "repo_1/foo@new_foo",
                "--var",
                "foo_var=new_val",
                "-r",
                "repo_1/bar@bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
            ],
            cwd=self.git_base + "repo_1",
        )

        with open(fake_deps) as f:
            contents = f.read().splitlines()

        self.assertEqual(
            [
                'git_dependencies = "SYNC"',
                "vars = { ",
                '  "foo_var": "new_val",',
                '  "foo_rev": "new_foo",',
                "}",
                "deps = { ",
                '  "repo_1/foo": "https://foo" + Var("foo_rev"),',
                '  "repo_1/bar": '
                '"https://bar@bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",',
                "}",
            ],
            contents,
        )

    def testSetDep_Submodules_relative(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        fake_deps = os.path.join(self.git_base, "repo_1", "DEPS")
        gitmodules = os.path.join(self.git_base, "repo_1", ".gitmodules")
        with open(fake_deps, "w") as f:
            f.write(
                "\n".join(
                    [
                        'git_dependencies = "SUBMODULES"',
                        "use_relative_paths = True",
                        "vars = { ",
                        '  "foo_var": "foo_val",',
                        '  "foo_rev": "foo_rev",',
                        "}",
                    ]
                )
            )

        with open(gitmodules, "w") as f:
            f.write(
                "\n".join(
                    ['[submodule "foo"]', "  url = https://foo", "  path = foo"]
                )
            )

        subprocess2.call(
            [
                "git",
                "update-index",
                "--add",
                "--cacheinfo",
                "160000,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,foo",
            ],
            cwd=self.git_base + "repo_1",
        )

        self.gclient(
            [
                "setdep",
                "-r",
                "foo@new_foo",
                "--var",
                "foo_var=new_val",
                "--deps-file",
                fake_deps,
            ],
            cwd=self.git_base + "repo_1",
        )

        with open(fake_deps) as f:
            contents = f.read().splitlines()

        self.assertEqual(
            [
                'git_dependencies = "SUBMODULES"',
                "use_relative_paths = True",
                "vars = { ",
                '  "foo_var": "new_val",',
                '  "foo_rev": "foo_rev",',
                "}",
            ],
            contents,
        )

    def testSetDep_BuiltinVariables(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"],
            cwd=self.git_base,
        )

        fake_deps = os.path.join(self.root_dir, "DEPS")
        with open(fake_deps, "w") as f:
            f.write(
                "\n".join(
                    [
                        "vars = { ",
                        '  "foo_var": "foo_val",',
                        '  "foo_rev": "foo_rev",',
                        "}",
                        "deps = {",
                        '  "foo": {',
                        '    "url": "url@{foo_rev}",',
                        "  },",
                        '  "bar": "url@bar_rev",',
                        "}",
                        "hooks = [{",
                        '  "name": "uses_builtin_var",',
                        '  "pattern": ".",',
                        '  "action": ["python3", "fake.py",',
                        '             "--with-android={checkout_android}"],',
                        "}]",
                    ]
                )
            )

        self.gclient(
            [
                "setdep",
                "-r",
                "foo@new_foo",
                "-r",
                "bar@new_bar",
                "--var",
                "foo_var=new_val",
                "--deps-file",
                fake_deps,
            ],
            cwd=self.git_base + "repo_1",
        )

        with open(fake_deps) as f:
            contents = f.read().splitlines()

        self.assertEqual(
            [
                "vars = { ",
                '  "foo_var": "new_val",',
                '  "foo_rev": "new_foo",',
                "}",
                "deps = {",
                '  "foo": {',
                '    "url": "url@{foo_rev}",',
                "  },",
                '  "bar": "url@new_bar",',
                "}",
                "hooks = [{",
                '  "name": "uses_builtin_var",',
                '  "pattern": ".",',
                '  "action": ["python3", "fake.py",',
                '             "--with-android={checkout_android}"],',
                "}]",
            ],
            contents,
        )

    def testGetDep(self):
        fake_deps = os.path.join(self.root_dir, "DEPS.fake")
        with open(fake_deps, "w") as f:
            f.write(
                "\n".join(
                    [
                        "vars = { ",
                        '  "foo_var": "foo_val",',
                        '  "foo_rev": "foo_rev",',
                        "}",
                        "deps = {",
                        '  "foo": {',
                        '    "url": "url@{foo_rev}",',
                        "  },",
                        '  "bar": "url@bar_rev",',
                        "}",
                    ]
                )
            )

        results = self.gclient(
            [
                "getdep",
                "-r",
                "foo",
                "-r",
                "bar",
                "--var",
                "foo_var",
                "--deps-file",
                fake_deps,
            ]
        )

        self.assertEqual(
            [
                "foo_val",
                "foo_rev",
                "bar_rev",
            ],
            results[0].splitlines(),
        )

    def testGetDep_Submodule(self):
        self.gclient(
            ["config", self.git_base + "repo_20", "--name", "src", "--managed"]
        )
        subprocess2.call(
            [
                "git",
                "update-index",
                "--add",
                "--cacheinfo",
                "160000,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,bar",
            ],
            cwd=self.git_base + "repo_20",
        )

        results = self.gclient(
            [
                "getdep",
                "-r",
                "foo/bar:lemur",
                "-r",
                "bar",
                "--var",
                "foo_checkout",
            ],
            cwd=self.git_base + "repo_20",
        )

        self.assertEqual(
            [
                "True",
                "version:1234",
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            ],
            results[0].splitlines(),
        )

    def testGetDep_BuiltinVariables(self):
        self.gclient(
            ["config", self.git_base + "repo_1", "--name", "src", "--managed"]
        )
        fake_deps = os.path.join(self.root_dir, "DEPS.fake")
        with open(fake_deps, "w") as f:
            f.write(
                "\n".join(
                    [
                        "vars = { ",
                        '  "foo_var": "foo_val",',
                        '  "foo_rev": "foo_rev",',
                        "}",
                        "deps = {",
                        '  "foo": {',
                        '    "url": "url@{foo_rev}",',
                        "  },",
                        '  "bar": "url@bar_rev",',
                        "}",
                        "hooks = [{",
                        '  "name": "uses_builtin_var",',
                        '  "pattern": ".",',
                        '  "action": ["python3", "fake.py",',
                        '             "--with-android={checkout_android}"],',
                        "}]",
                    ]
                )
            )

        results = self.gclient(
            [
                "getdep",
                "-r",
                "foo",
                "-r",
                "bar",
                "--var",
                "foo_var",
                "--deps-file",
                fake_deps,
            ]
        )

        self.assertEqual(
            [
                "foo_val",
                "foo_rev",
                "bar_rev",
            ],
            results[0].splitlines(),
        )

    def testGetConfig(self):
        gclient_file = os.path.join(self.root_dir, ".gclient")
        with open(gclient_file, "w") as f:
            f.write(
                "\n".join(
                    [
                        "solutions = [",
                        "  {",
                        '    "name": "src",',
                        '    "url": "https://chromium.googlesource.com/chromium/src.git",',
                        '    "deps_file": "DEPS",',
                        '    "managed": False,',
                        '    "custom_deps": {',
                        '      "src/third_party/WebKit": None,',
                        "    },",
                        '    "custom_vars": {',
                        '      "checkout_android": True,',
                        "    },",
                        '    "custom_hooks": [',
                        '      {"name": "my_hook", "action": ["python3", "do_something.py"]},',
                        "    ],",
                        "  },",
                        "]",
                        'target_os = ["android", "chromeos"]',
                        "target_os_only = True",
                        'target_cpu = ["arm", "x64"]',
                        "target_cpu_only = False",
                        'cache_dir = "/path/to/cache"',
                        "hooks = [",
                        '  {"name": "global_hook", "action": ["python3", "global.py"]}',
                        "]",
                        'target_os_tuple = ("ios", "mac")',
                    ]
                )
            )

        # Test printing all config.
        results = self.gclient(["getconfig"])
        parsed_config = json.loads(results[0])
        self.assertEqual(parsed_config["solutions"][0]["name"], "src")
        self.assertEqual(
            parsed_config["solutions"][0]["custom_deps"],
            {"src/third_party/WebKit": None},
        )
        self.assertEqual(
            parsed_config["solutions"][0]["custom_vars"],
            {"checkout_android": True},
        )
        self.assertEqual(
            parsed_config["solutions"][0]["custom_hooks"],
            [{"name": "my_hook", "action": ["python3", "do_something.py"]}],
        )
        self.assertEqual(parsed_config["target_os"], ["android", "chromeos"])
        self.assertEqual(parsed_config["target_os_only"], True)
        self.assertEqual(parsed_config["target_cpu"], ["arm", "x64"])
        self.assertEqual(parsed_config["target_cpu_only"], False)
        self.assertEqual(parsed_config["cache_dir"], "/path/to/cache")
        self.assertEqual(
            parsed_config["hooks"],
            [{"name": "global_hook", "action": ["python3", "global.py"]}],
        )
        self.assertEqual(parsed_config["target_os_tuple"], ["ios", "mac"])

        # Test querying individual keys.
        results = self.gclient(
            [
                "getconfig",
                "target_os",
                "target_cpu_only",
                "target_os_tuple",
                "cache_dir",
            ]
        )
        lines = results[0].splitlines()
        self.assertEqual(json.loads(lines[0]), ["android", "chromeos"])
        self.assertEqual(lines[1], "false")
        self.assertEqual(json.loads(lines[2]), ["ios", "mac"])
        self.assertEqual(lines[3], "/path/to/cache")

        # Test pre-validation: querying a mix of valid and invalid keys should not print anything to stdout.
        stdout, stderr, returncode = self.gclient(
            ["getconfig", "target_os", "nonexistent_key"], error_ok=True
        )
        self.assertEqual(2, returncode)
        self.assertEqual("", stdout.strip())
        self.assertIn(
            "Key nonexistent_key not found in .gclient config", stderr
        )

    def testGetConfigEmpty(self):
        gclient_file = os.path.join(self.root_dir, ".gclient")
        with open(gclient_file, "w") as f:
            f.write("")

        # Test printing all config, which should output {} since .gclient is empty but exists.
        stdout, stderr, returncode = self.gclient(["getconfig"])
        self.assertEqual(0, returncode)
        self.assertEqual("{}", stdout.strip())

        # Test querying keys when empty.
        stdout, stderr, returncode = self.gclient(
            ["getconfig", "target_os"], error_ok=True
        )
        self.assertEqual(2, returncode)
        self.assertIn("Key target_os not found in .gclient config", stderr)

        # Test when .gclient is missing.
        os.remove(gclient_file)
        stdout, stderr, returncode = self.gclient(["getconfig"], error_ok=True)
        self.assertEqual(1, returncode)
        self.assertIn("Could not find .gclient configuration file.", stderr)

    # TODO(crbug.com/1024683): Enable for windows.
    @unittest.skipIf(sys.platform == "win32", "not yet fixed on win")
    def testFlatten(self):
        output_deps = os.path.join(self.root_dir, "DEPS.flattened")
        self.assertFalse(os.path.exists(output_deps))

        self.gclient(
            [
                "config",
                self.git_base + "repo_6",
                "--name",
                "src",
                # This should be ignored because 'custom_true_var' isn't
                # defined in the DEPS.
                "--custom-var",
                "custom_true_var=True",
                # This should override 'true_var=True' from the DEPS.
                "--custom-var",
                'true_var="False"',
            ]
        )
        self.gclient(["sync"])
        self.gclient(
            ["flatten", "-v", "-v", "-v", "--output-deps", output_deps]
        )

        # Assert we can sync to the flattened DEPS we just wrote.
        solutions = [
            {
                "url": self.git_base + "repo_6",
                "name": "src",
                "deps_file": output_deps,
            }
        ]
        self.gclient(["sync", "--spec=solutions=%s" % solutions])

        with open(output_deps) as f:
            deps_contents = f.read()

        self.assertEqual(
            [
                'gclient_gn_args_file = "src/repo2/gclient.args"',
                "gclient_gn_args = ['false_var', 'false_str_var', 'true_var', "
                "'true_str_var', 'str_var', 'cond_var']",
                "allowed_hosts = [",
                '  "' + self.git_base + '",',
                "]",
                "",
                "deps = {",
                '  # "src" -> "src/repo2" -> "foo/bar"',
                '  "foo/bar": {',
                '    "url": "' + self.git_base + 'repo_3",',
                "    \"condition\": '(repo2_false_var) and (true_str_var)',",
                "  },",
                "",
                '  # "src"',
                '  "src": {',
                '    "url": "' + self.git_base + 'repo_6",',
                "  },",
                "",
                '  # "src" -> "src/mac_repo"',
                '  "src/mac_repo": {',
                '    "url": "' + self.git_base + 'repo_5",',
                "    \"condition\": 'checkout_mac',",
                "  },",
                "",
                '  # "src" -> "src/repo8" -> "src/recursed_os_repo"',
                '  "src/recursed_os_repo": {',
                '    "url": "' + self.git_base + 'repo_5",',
                "    \"condition\": '(checkout_linux) or (checkout_mac)',",
                "  },",
                "",
                '  # "src" -> "src/repo15"',
                '  "src/repo15": {',
                '    "url": "' + self.git_base + 'repo_15",',
                "  },",
                "",
                '  # "src" -> "src/repo16"',
                '  "src/repo16": {',
                '    "url": "' + self.git_base + 'repo_16",',
                "  },",
                "",
                '  # "src" -> "src/repo2"',
                '  "src/repo2": {',
                '    "url": "'
                + self.git_base
                + 'repo_2@%s",' % (self.githash("repo_2", 1)[:7]),
                "    \"condition\": 'true_str_var',",
                "  },",
                "",
                '  # "src" -> "src/repo4"',
                '  "src/repo4": {',
                '    "url": "' + self.git_base + 'repo_4",',
                "    \"condition\": 'False',",
                "  },",
                "",
                '  # "src" -> "src/repo8"',
                '  "src/repo8": {',
                '    "url": "' + self.git_base + 'repo_8",',
                "  },",
                "",
                '  # "src" -> "src/unix_repo"',
                '  "src/unix_repo": {',
                '    "url": "' + self.git_base + 'repo_5",',
                "    \"condition\": 'checkout_linux',",
                "  },",
                "",
                '  # "src" -> "src/win_repo"',
                '  "src/win_repo": {',
                '    "url": "' + self.git_base + 'repo_5",',
                "    \"condition\": 'checkout_win',",
                "  },",
                "",
                "}",
                "",
                "hooks = [",
                '  # "src"',
                "  {",
                '    "pattern": ".",',
                "    \"condition\": 'True',",
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                "        \"open('src/git_hooked1', 'w')"
                ".write('git_hooked1')\",",
                "    ]",
                "  },",
                "",
                '  # "src"',
                "  {",
                '    "pattern": "nonexistent",',
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                "        \"open('src/git_hooked2', 'w').write('git_hooked2')\",",
                "    ]",
                "  },",
                "",
                '  # "src"',
                "  {",
                '    "pattern": ".",',
                "    \"condition\": 'checkout_mac',",
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                "        \"open('src/git_hooked_mac', 'w').write("
                "'git_hooked_mac')\",",
                "    ]",
                "  },",
                "",
                '  # "src" -> "src/repo15"',
                "  {",
                '    "name": "absolute_cwd",',
                '    "pattern": ".",',
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                '        "pass",',
                "    ]",
                "  },",
                "",
                '  # "src" -> "src/repo16"',
                "  {",
                '    "name": "relative_cwd",',
                '    "pattern": ".",',
                '    "cwd": "src/repo16",',
                '    "action": [',
                '        "python3",',
                '        "relative.py",',
                "    ]",
                "  },",
                "",
                "]",
                "",
                "vars = {",
                '  # "src"',
                "  \"DummyVariable\": 'repo',",
                "",
                '  # "src"',
                "  \"cond_var\": 'false_str_var and true_var',",
                "",
                '  # "src"',
                "  \"false_str_var\": 'False',",
                "",
                '  # "src"',
                '  "false_var": False,',
                "",
                '  # "src"',
                '  "git_base": \'' + self.git_base + "',",
                "",
                '  # "src"',
                "  \"hook1_contents\": 'git_hooked1',",
                "",
                '  # "src" -> "src/repo2"',
                "  \"repo2_false_var\": 'False',",
                "",
                '  # "src"',
                "  \"repo5_var\": '/repo_5',",
                "",
                '  # "src"',
                "  \"str_var\": 'abc',",
                "",
                '  # "src"',
                "  \"true_str_var\": 'True',",
                "",
                '  # "src" [custom_var override]',
                "  \"true_var\": 'False',",
                "",
                "}",
                "",
                "# " + self.git_base + "repo_15, DEPS",
                "# " + self.git_base + "repo_16, DEPS",
                "# "
                + self.git_base
                + "repo_2@%s, DEPS" % (self.githash("repo_2", 1)[:7]),
                "# " + self.git_base + "repo_6, DEPS",
                "# " + self.git_base + "repo_8, DEPS",
            ],
            deps_contents.splitlines(),
        )

    # TODO(crbug.com/1024683): Enable for windows.
    @unittest.skipIf(sys.platform == "win32", "not yet fixed on win")
    def testFlattenPinAllDeps(self):
        output_deps = os.path.join(self.root_dir, "DEPS.flattened")
        self.assertFalse(os.path.exists(output_deps))

        self.gclient(
            ["config", self.git_base + "repo_6", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--process-all-deps"])
        self.gclient(
            [
                "flatten",
                "-v",
                "-v",
                "-v",
                "--output-deps",
                output_deps,
                "--pin-all-deps",
            ]
        )

        with open(output_deps) as f:
            deps_contents = f.read()

        self.assertEqual(
            [
                'gclient_gn_args_file = "src/repo2/gclient.args"',
                "gclient_gn_args = ['false_var', 'false_str_var', 'true_var', "
                "'true_str_var', 'str_var', 'cond_var']",
                "allowed_hosts = [",
                '  "' + self.git_base + '",',
                "]",
                "",
                "deps = {",
                '  # "src" -> "src/repo2" -> "foo/bar"',
                '  "foo/bar": {',
                '    "url": "'
                + self.git_base
                + 'repo_3@%s",' % (self.githash("repo_3", 2)),
                "    \"condition\": '(repo2_false_var) and (true_str_var)',",
                "  },",
                "",
                '  # "src"',
                '  "src": {',
                '    "url": "'
                + self.git_base
                + 'repo_6@%s",' % (self.githash("repo_6", 1)),
                "  },",
                "",
                '  # "src" -> "src/mac_repo"',
                '  "src/mac_repo": {',
                '    "url": "'
                + self.git_base
                + 'repo_5@%s",' % (self.githash("repo_5", 3)),
                "    \"condition\": 'checkout_mac',",
                "  },",
                "",
                '  # "src" -> "src/repo8" -> "src/recursed_os_repo"',
                '  "src/recursed_os_repo": {',
                '    "url": "'
                + self.git_base
                + 'repo_5@%s",' % (self.githash("repo_5", 3)),
                "    \"condition\": '(checkout_linux) or (checkout_mac)',",
                "  },",
                "",
                '  # "src" -> "src/repo15"',
                '  "src/repo15": {',
                '    "url": "'
                + self.git_base
                + 'repo_15@%s",' % (self.githash("repo_15", 1)),
                "  },",
                "",
                '  # "src" -> "src/repo16"',
                '  "src/repo16": {',
                '    "url": "'
                + self.git_base
                + 'repo_16@%s",' % (self.githash("repo_16", 1)),
                "  },",
                "",
                '  # "src" -> "src/repo2"',
                '  "src/repo2": {',
                '    "url": "'
                + self.git_base
                + 'repo_2@%s",' % (self.githash("repo_2", 1)),
                "    \"condition\": 'true_str_var',",
                "  },",
                "",
                '  # "src" -> "src/repo4"',
                '  "src/repo4": {',
                '    "url": "'
                + self.git_base
                + 'repo_4@%s",' % (self.githash("repo_4", 2)),
                "    \"condition\": 'False',",
                "  },",
                "",
                '  # "src" -> "src/repo8"',
                '  "src/repo8": {',
                '    "url": "'
                + self.git_base
                + 'repo_8@%s",' % (self.githash("repo_8", 1)),
                "  },",
                "",
                '  # "src" -> "src/unix_repo"',
                '  "src/unix_repo": {',
                '    "url": "'
                + self.git_base
                + 'repo_5@%s",' % (self.githash("repo_5", 3)),
                "    \"condition\": 'checkout_linux',",
                "  },",
                "",
                '  # "src" -> "src/win_repo"',
                '  "src/win_repo": {',
                '    "url": "'
                + self.git_base
                + 'repo_5@%s",' % (self.githash("repo_5", 3)),
                "    \"condition\": 'checkout_win',",
                "  },",
                "",
                "}",
                "",
                "hooks = [",
                '  # "src"',
                "  {",
                '    "pattern": ".",',
                "    \"condition\": 'True',",
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                "        \"open('src/git_hooked1', 'w')"
                ".write('git_hooked1')\",",
                "    ]",
                "  },",
                "",
                '  # "src"',
                "  {",
                '    "pattern": "nonexistent",',
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                "        \"open('src/git_hooked2', 'w').write('git_hooked2')\",",
                "    ]",
                "  },",
                "",
                '  # "src"',
                "  {",
                '    "pattern": ".",',
                "    \"condition\": 'checkout_mac',",
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                "        \"open('src/git_hooked_mac', 'w').write("
                "'git_hooked_mac')\",",
                "    ]",
                "  },",
                "",
                '  # "src" -> "src/repo15"',
                "  {",
                '    "name": "absolute_cwd",',
                '    "pattern": ".",',
                '    "cwd": ".",',
                '    "action": [',
                '        "python3",',
                '        "-c",',
                '        "pass",',
                "    ]",
                "  },",
                "",
                '  # "src" -> "src/repo16"',
                "  {",
                '    "name": "relative_cwd",',
                '    "pattern": ".",',
                '    "cwd": "src/repo16",',
                '    "action": [',
                '        "python3",',
                '        "relative.py",',
                "    ]",
                "  },",
                "",
                "]",
                "",
                "vars = {",
                '  # "src"',
                "  \"DummyVariable\": 'repo',",
                "",
                '  # "src"',
                "  \"cond_var\": 'false_str_var and true_var',",
                "",
                '  # "src"',
                "  \"false_str_var\": 'False',",
                "",
                '  # "src"',
                '  "false_var": False,',
                "",
                '  # "src"',
                '  "git_base": \'' + self.git_base + "',",
                "",
                '  # "src"',
                "  \"hook1_contents\": 'git_hooked1',",
                "",
                '  # "src" -> "src/repo2"',
                "  \"repo2_false_var\": 'False',",
                "",
                '  # "src"',
                "  \"repo5_var\": '/repo_5',",
                "",
                '  # "src"',
                "  \"str_var\": 'abc',",
                "",
                '  # "src"',
                "  \"true_str_var\": 'True',",
                "",
                '  # "src"',
                '  "true_var": True,',
                "",
                "}",
                "",
                "# "
                + self.git_base
                + "repo_15@%s, DEPS" % (self.githash("repo_15", 1)),
                "# "
                + self.git_base
                + "repo_16@%s, DEPS" % (self.githash("repo_16", 1)),
                "# "
                + self.git_base
                + "repo_2@%s, DEPS" % (self.githash("repo_2", 1)),
                "# "
                + self.git_base
                + "repo_6@%s, DEPS" % (self.githash("repo_6", 1)),
                "# "
                + self.git_base
                + "repo_8@%s, DEPS" % (self.githash("repo_8", 1)),
            ],
            deps_contents.splitlines(),
        )

    # TODO(crbug.com/1024683): Enable for windows.
    @unittest.skipIf(sys.platform == "win32", "not yet fixed on win")
    def testFlattenRecursedeps(self):
        output_deps = os.path.join(self.root_dir, "DEPS.flattened")
        self.assertFalse(os.path.exists(output_deps))

        output_deps_files = os.path.join(self.root_dir, "DEPS.files")
        self.assertFalse(os.path.exists(output_deps_files))

        self.gclient(
            ["config", self.git_base + "repo_10", "--name", "src", "--managed"]
        )
        self.gclient(["sync", "--process-all-deps"])
        self.gclient(
            [
                "flatten",
                "-v",
                "-v",
                "-v",
                "--output-deps",
                output_deps,
                "--output-deps-files",
                output_deps_files,
            ]
        )

        with open(output_deps) as f:
            deps_contents = f.read()

        self.assertEqual(
            [
                'gclient_gn_args_file = "src/repo8/gclient.args"',
                "gclient_gn_args = ['str_var']",
                "deps = {",
                '  # "src"',
                '  "src": {',
                '    "url": "' + self.git_base + 'repo_10",',
                "  },",
                "",
                '  # "src" -> "src/repo9" -> "src/repo8" -> "src/recursed_os_repo"',
                '  "src/recursed_os_repo": {',
                '    "url": "' + self.git_base + 'repo_5",',
                "    \"condition\": '(checkout_linux) or (checkout_mac)',",
                "  },",
                "",
                '  # "src" -> "src/repo11"',
                '  "src/repo11": {',
                '    "url": "' + self.git_base + 'repo_11",',
                "    \"condition\": '(checkout_ios) or (checkout_mac)',",
                "  },",
                "",
                '  # "src" -> "src/repo11" -> "src/repo12"',
                '  "src/repo12": {',
                '    "url": "' + self.git_base + 'repo_12",',
                "    \"condition\": '(checkout_ios) or (checkout_mac)',",
                "  },",
                "",
                '  # "src" -> "src/repo9" -> "src/repo4"',
                '  "src/repo4": {',
                '    "url": "' + self.git_base + 'repo_4",',
                "    \"condition\": 'checkout_android',",
                "  },",
                "",
                '  # "src" -> "src/repo6"',
                '  "src/repo6": {',
                '    "url": "' + self.git_base + 'repo_6",',
                "  },",
                "",
                '  # "src" -> "src/repo9" -> "src/repo7"',
                '  "src/repo7": {',
                '    "url": "' + self.git_base + 'repo_7",',
                "  },",
                "",
                '  # "src" -> "src/repo9" -> "src/repo8"',
                '  "src/repo8": {',
                '    "url": "' + self.git_base + 'repo_8",',
                "  },",
                "",
                '  # "src" -> "src/repo9"',
                '  "src/repo9": {',
                '    "url": "' + self.git_base + 'repo_9",',
                "  },",
                "",
                "}",
                "",
                "vars = {",
                '  # "src" -> "src/repo9"',
                "  \"str_var\": 'xyz',",
                "",
                "}",
                "",
                "# " + self.git_base + "repo_10, DEPS",
                "# " + self.git_base + "repo_11, DEPS",
                "# " + self.git_base + "repo_8, DEPS",
                "# " + self.git_base + "repo_9, DEPS",
            ],
            deps_contents.splitlines(),
        )

        with open(output_deps_files) as f:
            deps_files_contents = json.load(f)

        self.assertEqual(
            [
                {
                    "url": self.git_base + "repo_10",
                    "deps_file": "DEPS",
                    "hierarchy": [["src", self.git_base + "repo_10"]],
                },
                {
                    "url": self.git_base + "repo_11",
                    "deps_file": "DEPS",
                    "hierarchy": [
                        ["src", self.git_base + "repo_10"],
                        ["src/repo11", self.git_base + "repo_11"],
                    ],
                },
                {
                    "url": self.git_base + "repo_8",
                    "deps_file": "DEPS",
                    "hierarchy": [
                        ["src", self.git_base + "repo_10"],
                        ["src/repo9", self.git_base + "repo_9"],
                        ["src/repo8", self.git_base + "repo_8"],
                    ],
                },
                {
                    "url": self.git_base + "repo_9",
                    "deps_file": "DEPS",
                    "hierarchy": [
                        ["src", self.git_base + "repo_10"],
                        ["src/repo9", self.git_base + "repo_9"],
                    ],
                },
            ],
            deps_files_contents,
        )

    # TODO(crbug.com/1024683): Enable for windows.
    @unittest.skipIf(sys.platform == "win32", "not yet fixed on win")
    def testFlattenCipd(self):
        output_deps = os.path.join(self.root_dir, "DEPS.flattened")
        self.assertFalse(os.path.exists(output_deps))

        self.gclient(
            ["config", self.git_base + "repo_14", "--name", "src", "--managed"]
        )
        self.gclient(["sync"])
        self.gclient(
            ["flatten", "-v", "-v", "-v", "--output-deps", output_deps]
        )

        with open(output_deps) as f:
            deps_contents = f.read()

        self.assertEqual(
            [
                "deps = {",
                '  # "src"',
                '  "src": {',
                '    "url": "' + self.git_base + 'repo_14",',
                "  },",
                "",
                '  # "src" -> src/another_cipd_dep',
                '  "src/another_cipd_dep": {',
                '    "packages": [',
                "      {",
                '        "package": "package1",',
                '        "version": "1.1-cr0",',
                "      },",
                "      {",
                '        "package": "package2",',
                '        "version": "1.13",',
                "      },",
                "    ],",
                '    "dep_type": "cipd",',
                "  },",
                "",
                '  # "src" -> src/cipd_dep',
                '  "src/cipd_dep": {',
                '    "packages": [',
                "      {",
                '        "package": "package0",',
                '        "version": "0.1",',
                "      },",
                "    ],",
                '    "dep_type": "cipd",',
                "  },",
                "",
                '  # "src" -> src/cipd_dep_with_cipd_variable',
                '  "src/cipd_dep_with_cipd_variable": {',
                '    "packages": [',
                "      {",
                '        "package": "package3/${{platform}}",',
                '        "version": "1.2",',
                "      },",
                "    ],",
                '    "dep_type": "cipd",',
                "  },",
                "",
                "}",
                "",
                "# " + self.git_base + "repo_14, DEPS",
            ],
            deps_contents.splitlines(),
        )

    def testRelativeGNArgsFile(self):
        self.gclient(
            ["config", self.git_base + "repo_17", "--name", "src", "--managed"]
        )
        self.gclient(
            [
                "sync",
            ]
        )

        tree = self.mangle_git_tree(("repo_17@1", "src"))
        tree["src/repo17_gclient.args"] = "\n".join(
            [
                "# Generated from 'DEPS'",
                'toto = "tata"',
            ]
        )
        self.assertTree(tree)


if __name__ == "__main__":
    if "-v" in sys.argv:
        logging.basicConfig(level=logging.DEBUG)
    unittest.main()
