You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			1109 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			1109 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
#!/usr/bin/env python3
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
# Copyright (c) 2011 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.
 | 
						|
"""Generate fake repositories for testing."""
 | 
						|
 | 
						|
import atexit
 | 
						|
import datetime
 | 
						|
import errno
 | 
						|
import io
 | 
						|
import logging
 | 
						|
import os
 | 
						|
import pprint
 | 
						|
import random
 | 
						|
import re
 | 
						|
import socket
 | 
						|
import sys
 | 
						|
import tempfile
 | 
						|
import textwrap
 | 
						|
import time
 | 
						|
 | 
						|
# trial_dir must be first for non-system libraries.
 | 
						|
from testing_support import trial_dir
 | 
						|
import gclient_utils
 | 
						|
import scm
 | 
						|
import subprocess2
 | 
						|
 | 
						|
DEFAULT_BRANCH = 'main'
 | 
						|
 | 
						|
 | 
						|
def write(path, content):
 | 
						|
    f = open(path, 'wb')
 | 
						|
    f.write(content.encode())
 | 
						|
    f.close()
 | 
						|
 | 
						|
 | 
						|
join = os.path.join
 | 
						|
 | 
						|
 | 
						|
def read_tree(tree_root):
 | 
						|
    """Returns a dict of all the files in a tree. Defaults to self.root_dir."""
 | 
						|
    tree = {}
 | 
						|
    for root, dirs, files in os.walk(tree_root):
 | 
						|
        for d in filter(lambda x: x.startswith('.'), dirs):
 | 
						|
            dirs.remove(d)
 | 
						|
        for f in [join(root, f) for f in files if not f.startswith('.')]:
 | 
						|
            filepath = f[len(tree_root) + 1:].replace(os.sep, '/')
 | 
						|
            assert len(filepath) > 0, f
 | 
						|
            with io.open(join(root, f), encoding='utf-8') as f:
 | 
						|
                tree[filepath] = f.read()
 | 
						|
    return tree
 | 
						|
 | 
						|
 | 
						|
def dict_diff(dict1, dict2):
 | 
						|
    diff = {}
 | 
						|
    for k, v in dict1.items():
 | 
						|
        if k not in dict2:
 | 
						|
            diff[k] = v
 | 
						|
        elif v != dict2[k]:
 | 
						|
            diff[k] = (v, dict2[k])
 | 
						|
    for k, v in dict2.items():
 | 
						|
        if k not in dict1:
 | 
						|
            diff[k] = v
 | 
						|
    return diff
 | 
						|
 | 
						|
 | 
						|
def commit_git(repo):
 | 
						|
    """Commits the changes and returns the new hash."""
 | 
						|
    subprocess2.check_call(['git', 'add', '-A', '-f'], cwd=repo)
 | 
						|
    subprocess2.check_call(['git', 'commit', '-q', '--message', 'foo'],
 | 
						|
                           cwd=repo)
 | 
						|
    rev = subprocess2.check_output(['git', 'show-ref', '--head', 'HEAD'],
 | 
						|
                                   cwd=repo).split(b' ', 1)[0]
 | 
						|
    rev = rev.decode('utf-8')
 | 
						|
    logging.debug('At revision %s' % rev)
 | 
						|
    return rev
 | 
						|
 | 
						|
 | 
						|
class FakeReposBase(object):
 | 
						|
    """Generate git repositories to test gclient functionality.
 | 
						|
 | 
						|
  Many DEPS functionalities need to be tested: Var, deps_os, hooks,
 | 
						|
  use_relative_paths.
 | 
						|
 | 
						|
  And types of dependencies: Relative urls, Full urls, git.
 | 
						|
 | 
						|
  populateGit() needs to be implemented by the subclass.
 | 
						|
  """
 | 
						|
    # Hostname
 | 
						|
    NB_GIT_REPOS = 1
 | 
						|
    USERS = [
 | 
						|
        ('user1@example.com', 'foo Fuß'),
 | 
						|
        ('user2@example.com', 'bar'),
 | 
						|
    ]
 | 
						|
 | 
						|
    def __init__(self, host=None):
 | 
						|
        self.trial = trial_dir.TrialDir('repos')
 | 
						|
        self.host = host or '127.0.0.1'
 | 
						|
        # Format is { repo: [ None, (hash, tree), (hash, tree), ... ], ... }
 | 
						|
        # so reference looks like self.git_hashes[repo][rev][0] for hash and
 | 
						|
        # self.git_hashes[repo][rev][1] for it's tree snapshot.
 | 
						|
        # It is 1-based too.
 | 
						|
        self.git_hashes = {}
 | 
						|
        self.git_pid_file_name = None
 | 
						|
        self.git_base = None
 | 
						|
        self.initialized = False
 | 
						|
 | 
						|
    @property
 | 
						|
    def root_dir(self):
 | 
						|
        return self.trial.root_dir
 | 
						|
 | 
						|
    def set_up(self):
 | 
						|
        """All late initialization comes here."""
 | 
						|
        if not self.root_dir:
 | 
						|
            try:
 | 
						|
                # self.root_dir is not set before this call.
 | 
						|
                self.trial.set_up()
 | 
						|
                self.git_base = join(self.root_dir, 'git') + os.sep
 | 
						|
            finally:
 | 
						|
                # Registers cleanup.
 | 
						|
                atexit.register(self.tear_down)
 | 
						|
 | 
						|
    def tear_down(self):
 | 
						|
        """Kills the servers and delete the directories."""
 | 
						|
        self.tear_down_git()
 | 
						|
        # This deletes the directories.
 | 
						|
        self.trial.tear_down()
 | 
						|
        self.trial = None
 | 
						|
 | 
						|
    def tear_down_git(self):
 | 
						|
        if self.trial.SHOULD_LEAK:
 | 
						|
            return False
 | 
						|
        logging.debug('Removing %s' % self.git_base)
 | 
						|
        gclient_utils.rmtree(self.git_base)
 | 
						|
        return True
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _genTree(root, tree_dict):
 | 
						|
        """For a dictionary of file contents, generate a filesystem."""
 | 
						|
        if not os.path.isdir(root):
 | 
						|
            os.makedirs(root)
 | 
						|
        for (k, v) in tree_dict.items():
 | 
						|
            k_os = k.replace('/', os.sep)
 | 
						|
            k_arr = k_os.split(os.sep)
 | 
						|
            if len(k_arr) > 1:
 | 
						|
                p = os.sep.join([root] + k_arr[:-1])
 | 
						|
                if not os.path.isdir(p):
 | 
						|
                    os.makedirs(p)
 | 
						|
            if v is None:
 | 
						|
                os.remove(join(root, k))
 | 
						|
            else:
 | 
						|
                write(join(root, k), v)
 | 
						|
 | 
						|
    def set_up_git(self):
 | 
						|
        """Creates git repositories and start the servers."""
 | 
						|
        self.set_up()
 | 
						|
        if self.initialized:
 | 
						|
            return True
 | 
						|
        try:
 | 
						|
            subprocess2.check_output(['git', '--version'])
 | 
						|
        except (OSError, subprocess2.CalledProcessError):
 | 
						|
            return False
 | 
						|
        for repo in ['repo_%d' % r for r in range(1, self.NB_GIT_REPOS + 1)]:
 | 
						|
            # TODO(crbug.com/114712) use git.init -b and remove 'checkout' once
 | 
						|
            # git is upgraded to 2.28 on all builders.
 | 
						|
            subprocess2.check_call(
 | 
						|
                ['git', 'init', '-q',
 | 
						|
                 join(self.git_base, repo)])
 | 
						|
            subprocess2.check_call(
 | 
						|
                ['git', 'checkout', '-q', '-b', DEFAULT_BRANCH],
 | 
						|
                cwd=join(self.git_base, repo))
 | 
						|
            self.git_hashes[repo] = [(None, None)]
 | 
						|
        self.populateGit()
 | 
						|
        self.initialized = True
 | 
						|
        return True
 | 
						|
 | 
						|
    def _git_rev_parse(self, path):
 | 
						|
        return subprocess2.check_output(['git', 'rev-parse', 'HEAD'],
 | 
						|
                                        cwd=path).strip()
 | 
						|
 | 
						|
    def _commit_git(self, repo, tree, base=None):
 | 
						|
        repo_root = join(self.git_base, repo)
 | 
						|
        if base:
 | 
						|
            base_commit = self.git_hashes[repo][base][0]
 | 
						|
            subprocess2.check_call(['git', 'checkout', base_commit],
 | 
						|
                                   cwd=repo_root)
 | 
						|
        self._genTree(repo_root, tree)
 | 
						|
        commit_hash = commit_git(repo_root)
 | 
						|
        base = base or -1
 | 
						|
        if self.git_hashes[repo][base][1]:
 | 
						|
            new_tree = self.git_hashes[repo][base][1].copy()
 | 
						|
            new_tree.update(tree)
 | 
						|
        else:
 | 
						|
            new_tree = tree.copy()
 | 
						|
        self.git_hashes[repo].append((commit_hash, new_tree))
 | 
						|
 | 
						|
    def _create_ref(self, repo, ref, revision):
 | 
						|
        repo_root = join(self.git_base, repo)
 | 
						|
        subprocess2.check_call(
 | 
						|
            ['git', 'update-ref', ref, self.git_hashes[repo][revision][0]],
 | 
						|
            cwd=repo_root)
 | 
						|
 | 
						|
    def _fast_import_git(self, repo, data):
 | 
						|
        repo_root = join(self.git_base, repo)
 | 
						|
        logging.debug('%s: fast-import %s', repo, data)
 | 
						|
        subprocess2.check_call(['git', 'fast-import', '--quiet'],
 | 
						|
                               cwd=repo_root,
 | 
						|
                               stdin=data.encode())
 | 
						|
 | 
						|
    def populateGit(self):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
 | 
						|
class FakeRepos(FakeReposBase):
 | 
						|
    """Implements populateGit()."""
 | 
						|
    NB_GIT_REPOS = 20
 | 
						|
 | 
						|
    def populateGit(self):
 | 
						|
        # Testing:
 | 
						|
        # - dependency disappear
 | 
						|
        # - dependency renamed
 | 
						|
        # - versioned and unversioned reference
 | 
						|
        # - relative and full reference
 | 
						|
        # - deps_os
 | 
						|
        # - var
 | 
						|
        # - hooks
 | 
						|
        # TODO(maruel):
 | 
						|
        # - use_relative_paths
 | 
						|
        self._commit_git('repo_3', {
 | 
						|
            'origin': 'git/repo_3@1\n',
 | 
						|
        })
 | 
						|
 | 
						|
        self._commit_git('repo_3', {
 | 
						|
            'origin': 'git/repo_3@2\n',
 | 
						|
        })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1',
 | 
						|
            {
 | 
						|
                'DEPS': """
 | 
						|
vars = {
 | 
						|
  'DummyVariable': 'repo',
 | 
						|
  'false_var': False,
 | 
						|
  'false_str_var': 'False',
 | 
						|
  'true_var': True,
 | 
						|
  'true_str_var': 'True',
 | 
						|
  'str_var': 'abc',
 | 
						|
  'cond_var': 'false_str_var and true_var',
 | 
						|
}
 | 
						|
# Nest the args file in a sub-repo, to make sure we don't try to
 | 
						|
# write it before we've cloned everything.
 | 
						|
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',
 | 
						|
]
 | 
						|
deps = {
 | 
						|
  'src/repo2': {
 | 
						|
    'url': %(git_base)r + 'repo_2',
 | 
						|
    'condition': 'True',
 | 
						|
  },
 | 
						|
  'src/repo2/repo3': '/' + Var('DummyVariable') + '_3@%(hash3)s',
 | 
						|
  # Test that deps where condition evaluates to False are skipped.
 | 
						|
  'src/repo5': {
 | 
						|
    'url': '/repo_5',
 | 
						|
    'condition': 'False',
 | 
						|
  },
 | 
						|
}
 | 
						|
deps_os = {
 | 
						|
  'mac': {
 | 
						|
    'src/repo4': '/repo_4',
 | 
						|
  },
 | 
						|
}""" % {
 | 
						|
                    'git_base': self.git_base,
 | 
						|
                    # See self.__init__() for the format. Grab's the hash of the
 | 
						|
                    # first commit in repo_2. Only keep the first 7 character
 | 
						|
                    # because of: TODO(maruel): http://crosbug.com/3591 We need
 | 
						|
                    # to strip the hash..  duh.
 | 
						|
                    'hash3': self.git_hashes['repo_3'][1][0][:7]
 | 
						|
                },
 | 
						|
                'origin': 'git/repo_1@1\n',
 | 
						|
                'foo bar': 'some file with a space',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_2', {
 | 
						|
                'origin':
 | 
						|
                'git/repo_2@1\n',
 | 
						|
                'DEPS':
 | 
						|
                """
 | 
						|
vars = {
 | 
						|
  'repo2_false_var': 'False',
 | 
						|
}
 | 
						|
 | 
						|
deps = {
 | 
						|
  'foo/bar': {
 | 
						|
    'url': '/repo_3',
 | 
						|
    'condition': 'repo2_false_var',
 | 
						|
  }
 | 
						|
}
 | 
						|
""",
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git('repo_2', {
 | 
						|
            'origin': 'git/repo_2@2\n',
 | 
						|
        })
 | 
						|
 | 
						|
        self._commit_git('repo_4', {
 | 
						|
            'origin': 'git/repo_4@1\n',
 | 
						|
        })
 | 
						|
 | 
						|
        self._commit_git('repo_4', {
 | 
						|
            'origin': 'git/repo_4@2\n',
 | 
						|
        })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1',
 | 
						|
            {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo2': %(git_base)r + 'repo_2@%(hash)s',
 | 
						|
  'src/repo2/repo_renamed': '/repo_3',
 | 
						|
  'src/should_not_process': {
 | 
						|
    'url': '/repo_4',
 | 
						|
    'condition': 'False',
 | 
						|
  }
 | 
						|
}
 | 
						|
# I think this is wrong to have the hooks run from the base of the gclient
 | 
						|
# checkout. It's maybe a bit too late to change that behavior.
 | 
						|
hooks = [
 | 
						|
  {
 | 
						|
    'pattern': '.',
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'open(\\'src/git_hooked1\\', \\'w\\').write(\\'git_hooked1\\')'],
 | 
						|
  },
 | 
						|
  {
 | 
						|
    # Should not be run.
 | 
						|
    'pattern': 'nonexistent',
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'open(\\'src/git_hooked2\\', \\'w\\').write(\\'git_hooked2\\')'],
 | 
						|
  },
 | 
						|
]
 | 
						|
""" % {
 | 
						|
                    'git_base': self.git_base,
 | 
						|
                    # See self.__init__() for the format. Grab's the hash of the
 | 
						|
                    # first commit in repo_2. Only keep the first 7 character
 | 
						|
                    # because of: TODO(maruel): http://crosbug.com/3591 We need
 | 
						|
                    # to strip the hash.. duh.
 | 
						|
                    'hash': self.git_hashes['repo_2'][1][0][:7]
 | 
						|
                },
 | 
						|
                'origin': 'git/repo_1@2\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git('repo_5', {'origin': 'git/repo_5@1\n'})
 | 
						|
        self._commit_git(
 | 
						|
            'repo_5', {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo1': %(git_base)r + 'repo_1@%(hash1)s',
 | 
						|
  'src/repo2': %(git_base)r + 'repo_2@%(hash2)s',
 | 
						|
}
 | 
						|
 | 
						|
# Hooks to run after a project is processed but before its dependencies are
 | 
						|
# processed.
 | 
						|
pre_deps_hooks = [
 | 
						|
  {
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'print("pre-deps hook"); open(\\'src/git_pre_deps_hooked\\', \\'w\\').write(\\'git_pre_deps_hooked\\')'],
 | 
						|
  }
 | 
						|
]
 | 
						|
""" % {
 | 
						|
                    'git_base': self.git_base,
 | 
						|
                    'hash1': self.git_hashes['repo_1'][2][0][:7],
 | 
						|
                    'hash2': self.git_hashes['repo_2'][1][0][:7],
 | 
						|
                },
 | 
						|
                'origin': 'git/repo_5@2\n',
 | 
						|
            })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_5', {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo1': %(git_base)r + 'repo_1@%(hash1)s',
 | 
						|
  'src/repo2': %(git_base)r + 'repo_2@%(hash2)s',
 | 
						|
}
 | 
						|
 | 
						|
# Hooks to run after a project is processed but before its dependencies are
 | 
						|
# processed.
 | 
						|
pre_deps_hooks = [
 | 
						|
  {
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'print("pre-deps hook"); open(\\'src/git_pre_deps_hooked\\', \\'w\\').write(\\'git_pre_deps_hooked\\')'],
 | 
						|
  },
 | 
						|
  {
 | 
						|
    'action': ['python3', '-c', 'import sys; sys.exit(1)'],
 | 
						|
  }
 | 
						|
]
 | 
						|
""" % {
 | 
						|
                    'git_base': self.git_base,
 | 
						|
                    'hash1': self.git_hashes['repo_1'][2][0][:7],
 | 
						|
                    'hash2': self.git_hashes['repo_2'][1][0][:7],
 | 
						|
                },
 | 
						|
                'origin': 'git/repo_5@3\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_6', {
 | 
						|
                'DEPS': """
 | 
						|
vars = {
 | 
						|
  'DummyVariable': 'repo',
 | 
						|
  'git_base': %(git_base)r,
 | 
						|
  'hook1_contents': 'git_hooked1',
 | 
						|
  'repo5_var': '/repo_5',
 | 
						|
 | 
						|
  'false_var': False,
 | 
						|
  'false_str_var': 'False',
 | 
						|
  'true_var': True,
 | 
						|
  'true_str_var': 'True',
 | 
						|
  'str_var': 'abc',
 | 
						|
  'cond_var': 'false_str_var and true_var',
 | 
						|
}
 | 
						|
 | 
						|
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 = [
 | 
						|
  %(git_base)r,
 | 
						|
]
 | 
						|
deps = {
 | 
						|
  'src/repo2': {
 | 
						|
    'url': Var('git_base') + 'repo_2@%(hash)s',
 | 
						|
    'condition': 'true_str_var',
 | 
						|
  },
 | 
						|
  'src/repo4': {
 | 
						|
    'url': '/repo_4',
 | 
						|
    'condition': 'False',
 | 
						|
  },
 | 
						|
  # Entries can have a None repository, which has the effect of either:
 | 
						|
  # - disabling a dep checkout (e.g. in a .gclient solution to prevent checking
 | 
						|
  # out optional large repos, or in deps_os where some repos aren't used on some
 | 
						|
  # platforms)
 | 
						|
  # - allowing a completely local directory to be processed by gclient (handy
 | 
						|
  # for dealing with "custom" DEPS, like buildspecs).
 | 
						|
  '/repoLocal': {
 | 
						|
    'url': None,
 | 
						|
  },
 | 
						|
  'src/repo8': '/repo_8',
 | 
						|
  'src/repo15': '/repo_15',
 | 
						|
  'src/repo16': '/repo_16',
 | 
						|
}
 | 
						|
deps_os ={
 | 
						|
  'mac': {
 | 
						|
    # This entry should not appear in flattened DEPS' |deps|.
 | 
						|
    'src/mac_repo': '{repo5_var}',
 | 
						|
  },
 | 
						|
  'unix': {
 | 
						|
    # This entry should not appear in flattened DEPS' |deps|.
 | 
						|
    'src/unix_repo': '{repo5_var}',
 | 
						|
  },
 | 
						|
  'win': {
 | 
						|
    # This entry should not appear in flattened DEPS' |deps|.
 | 
						|
    'src/win_repo': '{repo5_var}',
 | 
						|
  },
 | 
						|
}
 | 
						|
hooks = [
 | 
						|
  {
 | 
						|
    'pattern': '.',
 | 
						|
    'condition': 'True',
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'open(\\'src/git_hooked1\\', \\'w\\').write(\\'{hook1_contents}\\')'],
 | 
						|
  },
 | 
						|
  {
 | 
						|
    # Should not be run.
 | 
						|
    'pattern': 'nonexistent',
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'open(\\'src/git_hooked2\\', \\'w\\').write(\\'git_hooked2\\')'],
 | 
						|
  },
 | 
						|
]
 | 
						|
hooks_os = {
 | 
						|
  'mac': [
 | 
						|
    {
 | 
						|
      'pattern': '.',
 | 
						|
      'action': ['python3', '-c',
 | 
						|
                 'open(\\'src/git_hooked_mac\\', \\'w\\').write('
 | 
						|
                     '\\'git_hooked_mac\\')'],
 | 
						|
    },
 | 
						|
  ],
 | 
						|
}
 | 
						|
recursedeps = [
 | 
						|
  'src/repo2',
 | 
						|
  'src/repo8',
 | 
						|
  'src/repo15',
 | 
						|
  'src/repo16',
 | 
						|
]""" % {
 | 
						|
                    'git_base': self.git_base,
 | 
						|
                    'hash': self.git_hashes['repo_2'][1][0][:7]
 | 
						|
                },
 | 
						|
                'origin': 'git/repo_6@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_7', {
 | 
						|
                'DEPS': """
 | 
						|
vars = {
 | 
						|
  'true_var': 'True',
 | 
						|
  'false_var': 'true_var and False',
 | 
						|
}
 | 
						|
hooks = [
 | 
						|
  {
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'open(\\'src/should_run\\', \\'w\\').write(\\'should_run\\')'],
 | 
						|
    'condition': 'true_var or True',
 | 
						|
  },
 | 
						|
  {
 | 
						|
    'action': ['python3', '-c',
 | 
						|
               'open(\\'src/should_not_run\\', \\'w\\').write(\\'should_not_run\\')'],
 | 
						|
    'condition': 'false_var',
 | 
						|
  },
 | 
						|
]""",
 | 
						|
                'origin': 'git/repo_7@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_8', {
 | 
						|
                'DEPS': """
 | 
						|
deps_os ={
 | 
						|
  'mac': {
 | 
						|
    'src/recursed_os_repo': '/repo_5',
 | 
						|
  },
 | 
						|
  'unix': {
 | 
						|
    'src/recursed_os_repo': '/repo_5',
 | 
						|
  },
 | 
						|
}""",
 | 
						|
                'origin': 'git/repo_8@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_9', {
 | 
						|
                'DEPS': """
 | 
						|
vars = {
 | 
						|
  'str_var': 'xyz',
 | 
						|
}
 | 
						|
gclient_gn_args_file = 'src/repo8/gclient.args'
 | 
						|
gclient_gn_args = [
 | 
						|
  'str_var',
 | 
						|
]
 | 
						|
deps = {
 | 
						|
  'src/repo8': '/repo_8',
 | 
						|
 | 
						|
  # This entry should appear in flattened file,
 | 
						|
  # but not recursed into, since it's not
 | 
						|
  # in recursedeps.
 | 
						|
  'src/repo7': '/repo_7',
 | 
						|
}
 | 
						|
deps_os = {
 | 
						|
  'android': {
 | 
						|
    # This entry should only appear in flattened |deps_os|,
 | 
						|
    # not |deps|, even when used with |recursedeps|.
 | 
						|
    'src/repo4': '/repo_4',
 | 
						|
  }
 | 
						|
}
 | 
						|
recursedeps = [
 | 
						|
  'src/repo4',
 | 
						|
  'src/repo8',
 | 
						|
]""",
 | 
						|
                'origin': 'git/repo_9@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_10', {
 | 
						|
                'DEPS': """
 | 
						|
gclient_gn_args_from = 'src/repo9'
 | 
						|
deps = {
 | 
						|
  'src/repo9': '/repo_9',
 | 
						|
 | 
						|
  # This entry should appear in flattened file,
 | 
						|
  # but not recursed into, since it's not
 | 
						|
  # in recursedeps.
 | 
						|
  'src/repo6': '/repo_6',
 | 
						|
}
 | 
						|
deps_os = {
 | 
						|
  'mac': {
 | 
						|
    'src/repo11': '/repo_11',
 | 
						|
  },
 | 
						|
  'ios': {
 | 
						|
    'src/repo11': '/repo_11',
 | 
						|
  }
 | 
						|
}
 | 
						|
recursedeps = [
 | 
						|
  'src/repo9',
 | 
						|
  'src/repo11',
 | 
						|
]""",
 | 
						|
                'origin': 'git/repo_10@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_11', {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo12': '/repo_12',
 | 
						|
}""",
 | 
						|
                'origin': 'git/repo_11@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git('repo_12', {
 | 
						|
            'origin': 'git/repo_12@1\n',
 | 
						|
        })
 | 
						|
 | 
						|
        self._fast_import_git(
 | 
						|
            'repo_12', """blob
 | 
						|
mark :1
 | 
						|
data 6
 | 
						|
Hello
 | 
						|
 | 
						|
blob
 | 
						|
mark :2
 | 
						|
data 4
 | 
						|
Bye
 | 
						|
 | 
						|
reset refs/changes/1212
 | 
						|
commit refs/changes/1212
 | 
						|
mark :3
 | 
						|
author Bob <bob@example.com> 1253744361 -0700
 | 
						|
committer Bob <bob@example.com> 1253744361 -0700
 | 
						|
data 8
 | 
						|
A and B
 | 
						|
M 100644 :1 a
 | 
						|
M 100644 :2 b
 | 
						|
""")
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_13', {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo12': '/repo_12',
 | 
						|
}""",
 | 
						|
                'origin': 'git/repo_13@1\n',
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_13', {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo12': '/repo_12@refs/changes/1212',
 | 
						|
}""",
 | 
						|
                'origin': 'git/repo_13@2\n',
 | 
						|
            })
 | 
						|
 | 
						|
        # src/repo12 is now a CIPD dependency.
 | 
						|
        self._commit_git(
 | 
						|
            'repo_13', {
 | 
						|
                'DEPS': """
 | 
						|
deps = {
 | 
						|
  'src/repo12': {
 | 
						|
    'packages': [
 | 
						|
      {
 | 
						|
        'package': 'foo',
 | 
						|
        'version': '1.3',
 | 
						|
      },
 | 
						|
    ],
 | 
						|
    'dep_type': 'cipd',
 | 
						|
  },
 | 
						|
}
 | 
						|
hooks = [{
 | 
						|
  # make sure src/repo12 exists and is a CIPD dir.
 | 
						|
  'action': ['python3', '-c', 'with open("src/repo12/_cipd"): pass'],
 | 
						|
}]
 | 
						|
""",
 | 
						|
                'origin': 'git/repo_13@3\n'
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_14', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent("""\
 | 
						|
        vars = {}
 | 
						|
        deps = {
 | 
						|
          'src/cipd_dep': {
 | 
						|
            'packages': [
 | 
						|
              {
 | 
						|
                'package': 'package0',
 | 
						|
                'version': '0.1',
 | 
						|
              },
 | 
						|
            ],
 | 
						|
            'dep_type': 'cipd',
 | 
						|
          },
 | 
						|
          'src/another_cipd_dep': {
 | 
						|
            'packages': [
 | 
						|
              {
 | 
						|
                'package': 'package1',
 | 
						|
                'version': '1.1-cr0',
 | 
						|
              },
 | 
						|
              {
 | 
						|
                'package': 'package2',
 | 
						|
                'version': '1.13',
 | 
						|
              },
 | 
						|
            ],
 | 
						|
            'dep_type': 'cipd',
 | 
						|
          },
 | 
						|
          'src/cipd_dep_with_cipd_variable': {
 | 
						|
            'packages': [
 | 
						|
              {
 | 
						|
                'package': 'package3/${{platform}}',
 | 
						|
                'version': '1.2',
 | 
						|
              },
 | 
						|
            ],
 | 
						|
            'dep_type': 'cipd',
 | 
						|
          },
 | 
						|
        }"""),
 | 
						|
                'origin':
 | 
						|
                'git/repo_14@2\n'
 | 
						|
            })
 | 
						|
 | 
						|
        # A repo with a hook to be recursed in, without use_relative_paths
 | 
						|
        self._commit_git(
 | 
						|
            'repo_15', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent("""\
 | 
						|
        hooks = [{
 | 
						|
          "name": "absolute_cwd",
 | 
						|
          "pattern": ".",
 | 
						|
          "action": ["python3", "-c", "pass"]
 | 
						|
        }]"""),
 | 
						|
                'origin':
 | 
						|
                'git/repo_15@2\n'
 | 
						|
            })
 | 
						|
        # A repo with a hook to be recursed in, with use_relative_paths
 | 
						|
        self._commit_git(
 | 
						|
            'repo_16', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent("""\
 | 
						|
        use_relative_paths=True
 | 
						|
        hooks = [{
 | 
						|
          "name": "relative_cwd",
 | 
						|
          "pattern": ".",
 | 
						|
          "action": ["python3", "relative.py"]
 | 
						|
        }]"""),
 | 
						|
                'relative.py':
 | 
						|
                'pass',
 | 
						|
                'origin':
 | 
						|
                'git/repo_16@2\n'
 | 
						|
            })
 | 
						|
        # A repo with a gclient_gn_args_file and use_relative_paths
 | 
						|
        self._commit_git(
 | 
						|
            'repo_17', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent("""\
 | 
						|
        use_relative_paths=True
 | 
						|
        vars = {
 | 
						|
          'toto': 'tata',
 | 
						|
        }
 | 
						|
        gclient_gn_args_file = 'repo17_gclient.args'
 | 
						|
        gclient_gn_args = [
 | 
						|
          'toto',
 | 
						|
        ]"""),
 | 
						|
                'origin':
 | 
						|
                'git/repo_17@2\n'
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_18', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent("""\
 | 
						|
        deps = {
 | 
						|
          'src/cipd_dep': {
 | 
						|
            'packages': [
 | 
						|
              {
 | 
						|
                'package': 'package0',
 | 
						|
                'version': 'package0-fake-tag:1.0',
 | 
						|
              },
 | 
						|
              {
 | 
						|
                'package': 'package0/${{platform}}',
 | 
						|
                'version': 'package0/${{platform}}-fake-tag:1.0',
 | 
						|
              },
 | 
						|
              {
 | 
						|
                'package': 'package1/another',
 | 
						|
                'version': 'package1/another-fake-tag:1.0',
 | 
						|
              },
 | 
						|
            ],
 | 
						|
            'dep_type': 'cipd',
 | 
						|
          },
 | 
						|
        }"""),
 | 
						|
                'origin':
 | 
						|
                'git/repo_18@2\n'
 | 
						|
            })
 | 
						|
 | 
						|
        # a relative path repo
 | 
						|
        self._commit_git(
 | 
						|
            'repo_19', {
 | 
						|
                'DEPS': """
 | 
						|
 | 
						|
git_dependencies = "SUBMODULES"
 | 
						|
use_relative_paths = True
 | 
						|
vars = {
 | 
						|
  'foo_checkout': True,
 | 
						|
}
 | 
						|
deps = {
 | 
						|
  "some_repo": {
 | 
						|
    "url": '/repo_2@%(hash_2)s',
 | 
						|
    "condition": "not foo_checkout",
 | 
						|
  },
 | 
						|
  "chicken/dickens": {
 | 
						|
    "url": '/repo_3@%(hash_3)s',
 | 
						|
  },
 | 
						|
  "weird/deps": {
 | 
						|
    "url": '/repo_1'
 | 
						|
  },
 | 
						|
  "bar": {
 | 
						|
    "packages": [{
 | 
						|
      "package": "lemur",
 | 
						|
      "version": "version:1234",
 | 
						|
     }],
 | 
						|
     "dep_type": "cipd",
 | 
						|
  },
 | 
						|
}""" % {
 | 
						|
                    'hash_2': self.git_hashes['repo_2'][1][0],
 | 
						|
                    'hash_3': self.git_hashes['repo_3'][1][0],
 | 
						|
                },
 | 
						|
            })
 | 
						|
 | 
						|
        # a non-relative_path repo
 | 
						|
        self._commit_git(
 | 
						|
            'repo_20', {
 | 
						|
                'DEPS': """
 | 
						|
 | 
						|
git_dependencies = "SUBMODULES"
 | 
						|
vars = {
 | 
						|
  'foo_checkout': True,
 | 
						|
}
 | 
						|
deps = {
 | 
						|
  "foo/some_repo": {
 | 
						|
    "url": '/repo_2@%(hash_2)s',
 | 
						|
    "condition": "not foo_checkout",
 | 
						|
  },
 | 
						|
  "foo/chicken/dickens": {
 | 
						|
    "url": '/repo_3@%(hash_3)s',
 | 
						|
  },
 | 
						|
  "foo/weird/deps": {
 | 
						|
    "url": '/repo_1'
 | 
						|
  },
 | 
						|
  "foo/bar": {
 | 
						|
    "packages": [{
 | 
						|
      "package": "lemur",
 | 
						|
      "version": "version:1234",
 | 
						|
     }],
 | 
						|
     "dep_type": "cipd",
 | 
						|
  },
 | 
						|
}""" % {
 | 
						|
                    'hash_2': self.git_hashes['repo_2'][1][0],
 | 
						|
                    'hash_3': self.git_hashes['repo_3'][1][0],
 | 
						|
                },
 | 
						|
            })
 | 
						|
 | 
						|
 | 
						|
class FakeRepoSkiaDEPS(FakeReposBase):
 | 
						|
    """Simulates the Skia DEPS transition in Chrome."""
 | 
						|
 | 
						|
    NB_GIT_REPOS = 5
 | 
						|
 | 
						|
    DEPS_git_pre = """deps = {
 | 
						|
  'src/third_party/skia/gyp': %(git_base)r + 'repo_3',
 | 
						|
  'src/third_party/skia/include': %(git_base)r + 'repo_4',
 | 
						|
  'src/third_party/skia/src': %(git_base)r + 'repo_5',
 | 
						|
}"""
 | 
						|
 | 
						|
    DEPS_post = """deps = {
 | 
						|
  'src/third_party/skia': %(git_base)r + 'repo_1',
 | 
						|
}"""
 | 
						|
 | 
						|
    def populateGit(self):
 | 
						|
        # Skia repo.
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1', {
 | 
						|
                'skia_base_file': 'root-level file.',
 | 
						|
                'gyp/gyp_file': 'file in the gyp directory',
 | 
						|
                'include/include_file': 'file in the include directory',
 | 
						|
                'src/src_file': 'file in the src directory',
 | 
						|
            })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_3',
 | 
						|
            {  # skia/gyp
 | 
						|
                'gyp_file': 'file in the gyp directory',
 | 
						|
            })
 | 
						|
        self._commit_git('repo_4', { # skia/include
 | 
						|
            'include_file': 'file in the include directory',
 | 
						|
        })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_5',
 | 
						|
            {  # skia/src
 | 
						|
                'src_file': 'file in the src directory',
 | 
						|
            })
 | 
						|
 | 
						|
        # Chrome repo.
 | 
						|
        self._commit_git(
 | 
						|
            'repo_2', {
 | 
						|
                'DEPS': self.DEPS_git_pre % {
 | 
						|
                    'git_base': self.git_base
 | 
						|
                },
 | 
						|
                'myfile': 'src/trunk/src@1'
 | 
						|
            })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_2', {
 | 
						|
                'DEPS': self.DEPS_post % {
 | 
						|
                    'git_base': self.git_base
 | 
						|
                },
 | 
						|
                'myfile': 'src/trunk/src@2'
 | 
						|
            })
 | 
						|
 | 
						|
 | 
						|
class FakeRepoBlinkDEPS(FakeReposBase):
 | 
						|
    """Simulates the Blink DEPS transition in Chrome."""
 | 
						|
 | 
						|
    NB_GIT_REPOS = 2
 | 
						|
    DEPS_pre = 'deps = {"src/third_party/WebKit": "%(git_base)srepo_2",}'
 | 
						|
    DEPS_post = 'deps = {}'
 | 
						|
 | 
						|
    def populateGit(self):
 | 
						|
        # Blink repo.
 | 
						|
        self._commit_git(
 | 
						|
            'repo_2', {
 | 
						|
                'OWNERS': 'OWNERS-pre',
 | 
						|
                'Source/exists_always': '_ignored_',
 | 
						|
                'Source/exists_before_but_not_after': '_ignored_',
 | 
						|
            })
 | 
						|
 | 
						|
        # Chrome repo.
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1', {
 | 
						|
                'DEPS': self.DEPS_pre % {
 | 
						|
                    'git_base': self.git_base
 | 
						|
                },
 | 
						|
                'myfile': 'myfile@1',
 | 
						|
                '.gitignore': '/third_party/WebKit',
 | 
						|
            })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1', {
 | 
						|
                'DEPS': self.DEPS_post % {
 | 
						|
                    'git_base': self.git_base
 | 
						|
                },
 | 
						|
                'myfile': 'myfile@2',
 | 
						|
                '.gitignore': '',
 | 
						|
                'third_party/WebKit/OWNERS': 'OWNERS-post',
 | 
						|
                'third_party/WebKit/Source/exists_always': '_ignored_',
 | 
						|
                'third_party/WebKit/Source/exists_after_but_not_before':
 | 
						|
                '_ignored',
 | 
						|
            })
 | 
						|
 | 
						|
    def populateSvn(self):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
 | 
						|
class FakeRepoNoSyncDEPS(FakeReposBase):
 | 
						|
    """Simulates a repo with some DEPS changes."""
 | 
						|
 | 
						|
    NB_GIT_REPOS = 2
 | 
						|
 | 
						|
    def populateGit(self):
 | 
						|
        self._commit_git('repo_2', {'myfile': 'then egg'})
 | 
						|
        self._commit_git('repo_2', {'myfile': 'before egg!'})
 | 
						|
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent(
 | 
						|
                    """\
 | 
						|
          deps = {
 | 
						|
            'src/repo2': {
 | 
						|
              'url': %(git_base)r + 'repo_2@%(repo2hash)s',
 | 
						|
            },
 | 
						|
          }""" % {
 | 
						|
                        'git_base': self.git_base,
 | 
						|
                        'repo2hash': self.git_hashes['repo_2'][1][0][:7]
 | 
						|
                    })
 | 
						|
            })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1', {
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent(
 | 
						|
                    """\
 | 
						|
          deps = {
 | 
						|
            'src/repo2': {
 | 
						|
              'url': %(git_base)r + 'repo_2@%(repo2hash)s',
 | 
						|
            },
 | 
						|
          }""" % {
 | 
						|
                        'git_base': self.git_base,
 | 
						|
                        'repo2hash': self.git_hashes['repo_2'][2][0][:7]
 | 
						|
                    })
 | 
						|
            })
 | 
						|
        self._commit_git(
 | 
						|
            'repo_1', {
 | 
						|
                'foo_file':
 | 
						|
                'chicken content',
 | 
						|
                'DEPS':
 | 
						|
                textwrap.dedent(
 | 
						|
                    """\
 | 
						|
          deps = {
 | 
						|
            'src/repo2': {
 | 
						|
              'url': %(git_base)r + 'repo_2@%(repo2hash)s',
 | 
						|
            },
 | 
						|
          }""" % {
 | 
						|
                        'git_base': self.git_base,
 | 
						|
                        'repo2hash': self.git_hashes['repo_2'][1][0][:7]
 | 
						|
                    })
 | 
						|
            })
 | 
						|
 | 
						|
        self._commit_git('repo_1', {'foo_file': 'chicken content@4'})
 | 
						|
 | 
						|
 | 
						|
class FakeReposTestBase(trial_dir.TestCase):
 | 
						|
    """This is vaguely inspired by twisted."""
 | 
						|
    # Static FakeRepos instances. Lazy loaded.
 | 
						|
    CACHED_FAKE_REPOS = {}
 | 
						|
    # Override if necessary.
 | 
						|
    FAKE_REPOS_CLASS = FakeRepos
 | 
						|
 | 
						|
    def setUp(self):
 | 
						|
        super(FakeReposTestBase, self).setUp()
 | 
						|
        if not self.FAKE_REPOS_CLASS in self.CACHED_FAKE_REPOS:
 | 
						|
            self.CACHED_FAKE_REPOS[
 | 
						|
                self.FAKE_REPOS_CLASS] = self.FAKE_REPOS_CLASS()
 | 
						|
        self.FAKE_REPOS = self.CACHED_FAKE_REPOS[self.FAKE_REPOS_CLASS]
 | 
						|
        # No need to call self.FAKE_REPOS.setUp(), it will be called by the
 | 
						|
        # child class. Do not define tearDown(), since super's version does the
 | 
						|
        # right thing and self.FAKE_REPOS is kept across tests.
 | 
						|
 | 
						|
    @property
 | 
						|
    def git_base(self):
 | 
						|
        """Shortcut."""
 | 
						|
        return self.FAKE_REPOS.git_base
 | 
						|
 | 
						|
    def checkString(self, expected, result, msg=None):
 | 
						|
        """Prints the diffs to ease debugging."""
 | 
						|
        self.assertEqual(expected.splitlines(), result.splitlines(), msg)
 | 
						|
        if expected != result:
 | 
						|
            # Strip the beginning
 | 
						|
            while expected and result and expected[0] == result[0]:
 | 
						|
                expected = expected[1:]
 | 
						|
                result = result[1:]
 | 
						|
            # The exception trace makes it hard to read so dump it too.
 | 
						|
            if '\n' in result:
 | 
						|
                print(result)
 | 
						|
        self.assertEqual(expected, result, msg)
 | 
						|
 | 
						|
    def check(self, expected, results):
 | 
						|
        """Checks stdout, stderr, returncode."""
 | 
						|
        self.checkString(expected[0], results[0])
 | 
						|
        self.checkString(expected[1], results[1])
 | 
						|
        self.assertEqual(expected[2], results[2])
 | 
						|
 | 
						|
    def assertTree(self, tree, tree_root=None):
 | 
						|
        """Diff the checkout tree with a dict."""
 | 
						|
        if not tree_root:
 | 
						|
            tree_root = self.root_dir
 | 
						|
        actual = read_tree(tree_root)
 | 
						|
        self.assertEqual(sorted(tree.keys()), sorted(actual.keys()))
 | 
						|
        self.assertEqual(tree, actual)
 | 
						|
 | 
						|
    def mangle_git_tree(self, *args):
 | 
						|
        """Creates a 'virtual directory snapshot' to compare with the actual
 | 
						|
    result on disk."""
 | 
						|
        result = {}
 | 
						|
        for item, new_root in args:
 | 
						|
            repo, rev = item.split('@', 1)
 | 
						|
            tree = self.gittree(repo, rev)
 | 
						|
            for k, v in tree.items():
 | 
						|
                path = join(new_root, k).replace(os.sep, '/')
 | 
						|
                result[path] = v
 | 
						|
        return result
 | 
						|
 | 
						|
    def githash(self, repo, rev):
 | 
						|
        """Sort-hand: Returns the hash for a git 'revision'."""
 | 
						|
        return self.FAKE_REPOS.git_hashes[repo][int(rev)][0]
 | 
						|
 | 
						|
    def gittree(self, repo, rev):
 | 
						|
        """Sort-hand: returns the directory tree for a git 'revision'."""
 | 
						|
        return self.FAKE_REPOS.git_hashes[repo][int(rev)][1]
 | 
						|
 | 
						|
    def gitrevparse(self, repo):
 | 
						|
        """Returns the actual revision for a given repo."""
 | 
						|
        return self.FAKE_REPOS._git_rev_parse(repo).decode('utf-8')
 | 
						|
 | 
						|
 | 
						|
def main(argv):
 | 
						|
    fake = FakeRepos()
 | 
						|
    print('Using %s' % fake.root_dir)
 | 
						|
    try:
 | 
						|
        fake.set_up_git()
 | 
						|
        print(
 | 
						|
            'Fake setup, press enter to quit or Ctrl-C to keep the checkouts.')
 | 
						|
        sys.stdin.readline()
 | 
						|
    except KeyboardInterrupt:
 | 
						|
        trial_dir.TrialDir.SHOULD_LEAK.leak = True
 | 
						|
    return 0
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    sys.exit(main(sys.argv))
 |