123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999 |
- #!/usr/bin/env python
- # -*- 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."""
- from __future__ import print_function
- import atexit
- import datetime
- import errno
- 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
- # Attempt |MAX_TRY| times to find a free port. Each time select one port at
- # random from the range [|PORT_START|, |PORT_END|].
- MAX_TRY = 10
- PORT_START = 20000
- PORT_END = 65535
- 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), f
- tree[filepath] = gclient_utils.FileRead(join(root, f))
- 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
- def port_is_free(host, port):
- s = socket.socket()
- try:
- return s.connect_ex((host, port)) != 0
- finally:
- s.close()
- def find_free_port(host):
- """Finds a listening port free to listen to."""
- for _ in range(MAX_TRY):
- base_port = random.randint(PORT_START, PORT_END)
- if port_is_free(host, base_port):
- return base_port
- assert False, 'Having issues finding an available port'
- def wait_for_port_to_bind(host, port, process):
- try:
- start = datetime.datetime.utcnow()
- maxdelay = datetime.timedelta(seconds=30)
- while (datetime.datetime.utcnow() - start) < maxdelay:
- sock = socket.socket()
- try:
- sock.connect((host, port))
- logging.debug('%d is now bound' % port)
- return
- except (socket.error, EnvironmentError):
- # Sleep a little bit to avoid spinning too much.
- time.sleep(0.2)
- logging.debug('%d is still not bound' % port)
- finally:
- sock.close()
- # The process failed to bind. Kill it and dump its ouput.
- process.kill()
- stdout, stderr = process.communicate()
- logging.debug('%s' % stdout)
- logging.error('%s' % stderr)
- assert False, '%d is still not bound' % port
- def wait_for_port_to_free(host, port):
- start = datetime.datetime.utcnow()
- maxdelay = datetime.timedelta(seconds=30)
- while (datetime.datetime.utcnow() - start) < maxdelay:
- try:
- sock = socket.socket()
- sock.connect((host, port))
- logging.debug('%d was bound, waiting to free' % port)
- except (socket.error, EnvironmentError):
- logging.debug('%d now free' % port)
- return
- finally:
- sock.close()
- assert False, '%d is still bound' % port
- 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.gitdaemon = None
- self.git_pid_file_name = None
- self.git_root = None
- self.git_dirty = False
- self.git_port = None
- self.git_base = None
- @property
- def root_dir(self):
- return self.trial.root_dir
- def set_up(self):
- """All late initialization comes here."""
- self.cleanup_dirt()
- if not self.root_dir:
- try:
- # self.root_dir is not set before this call.
- self.trial.set_up()
- self.git_root = join(self.root_dir, 'git')
- finally:
- # Registers cleanup.
- atexit.register(self.tear_down)
- def cleanup_dirt(self):
- """For each dirty repository, destroy it."""
- if self.git_dirty:
- if not self.tear_down_git():
- logging.error('Using both leaking checkout and git dirty checkout')
- 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.gitdaemon:
- logging.debug('Killing git-daemon pid %s' % self.gitdaemon.pid)
- self.gitdaemon.kill()
- self.gitdaemon = None
- if self.git_pid_file_name:
- pid = int(open(self.git_pid_file_name).read())
- logging.debug('Killing git daemon pid %s' % pid)
- try:
- subprocess2.kill_pid(pid)
- except OSError as e:
- if e.errno != errno.ESRCH: # no such process
- raise
- os.remove(self.git_pid_file_name)
- self.git_pid_file_name = None
- wait_for_port_to_free(self.host, self.git_port)
- self.git_port = None
- self.git_base = None
- if not self.trial.SHOULD_LEAK:
- logging.debug('Removing %s' % self.git_root)
- gclient_utils.rmtree(self.git_root)
- else:
- return False
- 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.gitdaemon:
- return True
- assert self.git_pid_file_name == None, self.git_pid_file_name
- 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)]:
- subprocess2.check_call(['git', 'init', '-q', join(self.git_root, repo)])
- self.git_hashes[repo] = [(None, None)]
- git_pid_file = tempfile.NamedTemporaryFile(delete=False)
- self.git_pid_file_name = git_pid_file.name
- git_pid_file.close()
- self.git_port = find_free_port(self.host)
- self.git_base = 'git://%s:%d/git/' % (self.host, self.git_port)
- cmd = ['git', 'daemon',
- '--export-all',
- '--reuseaddr',
- '--base-path=' + self.root_dir,
- '--pid-file=' + self.git_pid_file_name,
- '--port=%d' % self.git_port]
- if self.host == '127.0.0.1':
- cmd.append('--listen=' + self.host)
- # Verify that the port is free.
- if not port_is_free(self.host, self.git_port):
- return False
- # Start the daemon.
- self.gitdaemon = subprocess2.Popen(
- cmd,
- cwd=self.root_dir,
- stdout=subprocess2.PIPE,
- stderr=subprocess2.PIPE)
- wait_for_port_to_bind(self.host, self.git_port, self.gitdaemon)
- self.populateGit()
- self.git_dirty = False
- 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_root, 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_root, 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_root, 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 = 16
- 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)srepo_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',
- })
- 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)srepo_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': ['python', '-c',
- 'open(\\'src/git_hooked1\\', \\'w\\').write(\\'git_hooked1\\')'],
- },
- {
- # Should not be run.
- 'pattern': 'nonexistent',
- 'action': ['python', '-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)srepo_1@%(hash1)s',
- 'src/repo2': '%(git_base)srepo_2@%(hash2)s',
- }
- # Hooks to run after a project is processed but before its dependencies are
- # processed.
- pre_deps_hooks = [
- {
- 'action': ['python', '-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)srepo_1@%(hash1)s',
- 'src/repo2': '%(git_base)srepo_2@%(hash2)s',
- }
- # Hooks to run after a project is processed but before its dependencies are
- # processed.
- pre_deps_hooks = [
- {
- 'action': ['python', '-c',
- 'print("pre-deps hook"); open(\\'src/git_pre_deps_hooked\\', \\'w\\').write(\\'git_pre_deps_hooked\\')'],
- },
- {
- 'action': ['python', '-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)s',
- '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)s',
- ]
- 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': ['python', '-c',
- 'open(\\'src/git_hooked1\\', \\'w\\').write(\\'{hook1_contents}\\')'],
- },
- {
- # Should not be run.
- 'pattern': 'nonexistent',
- 'action': ['python', '-c',
- 'open(\\'src/git_hooked2\\', \\'w\\').write(\\'git_hooked2\\')'],
- },
- ]
- hooks_os = {
- 'mac': [
- {
- 'pattern': '.',
- 'action': ['python', '-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': ['python', '-c',
- 'open(\\'src/should_run\\', \\'w\\').write(\\'should_run\\')'],
- 'condition': 'true_var or True',
- },
- {
- 'action': ['python', '-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/repo2/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': ['python', '-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_hooks
- self._commit_git('repo_15', {
- 'DEPS': textwrap.dedent("""\
- hooks = [{
- "name": "absolute_cwd",
- "pattern": ".",
- "action": ["python", "-c", "pass"]
- }]"""),
- 'origin': 'git/repo_15@2\n'
- })
- # A repo with a hook to be recursed in, with use_relative_hooks
- self._commit_git('repo_16', {
- 'DEPS': textwrap.dedent("""\
- use_relative_paths=True
- use_relative_hooks=True
- hooks = [{
- "name": "relative_cwd",
- "pattern": ".",
- "action": ["python", "relative.py"]
- }]"""),
- 'relative.py': 'pass',
- 'origin': 'git/repo_16@2\n'
- })
- 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)srepo_3',
- 'src/third_party/skia/include': '%(git_base)srepo_4',
- 'src/third_party/skia/src': '%(git_base)srepo_5',
- }"""
- DEPS_post = """deps = {
- 'src/third_party/skia': '%(git_base)srepo_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 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 begining
- 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)
- diff = dict_diff(tree, actual)
- if diff:
- logging.error('Actual %s\n%s' % (tree_root, pprint.pformat(actual)))
- logging.error('Expected\n%s' % pprint.pformat(tree))
- logging.error('Diff\n%s' % pprint.pformat(diff))
- self.assertEqual(diff, {})
- 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():
- result[join(new_root, k)] = 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))
|