| #!/usr/bin/env python |
| # 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 logging |
| import os |
| import pprint |
| import re |
| import socket |
| import sys |
| import tempfile |
| import time |
| |
| # trial_dir must be first for non-system libraries. |
| from testing_support import trial_dir |
| import gclient_utils |
| import scm |
| import subprocess2 |
| |
| |
| def write(path, content): |
| f = open(path, 'wb') |
| f.write(content) |
| 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] = open(join(root, f), 'rU').read() |
| return tree |
| |
| |
| def dict_diff(dict1, dict2): |
| diff = {} |
| for k, v in dict1.iteritems(): |
| if k not in dict2: |
| diff[k] = v |
| elif v != dict2[k]: |
| diff[k] = (v, dict2[k]) |
| for k, v in dict2.iteritems(): |
| 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(' ', 1)[0] |
| logging.debug('At revision %s' % rev) |
| return rev |
| |
| |
| def test_port(host, port): |
| s = socket.socket() |
| try: |
| return s.connect_ex((host, port)) == 0 |
| finally: |
| s.close() |
| |
| |
| def find_free_port(host, base_port): |
| """Finds a listening port free to listen to.""" |
| while base_port < (2<<16): |
| if not test_port(host, base_port): |
| return base_port |
| base_port += 1 |
| assert False, 'Having issues finding an available port' |
| |
| |
| def wait_for_port_to_bind(host, port, process): |
| sock = socket.socket() |
| |
| if sys.platform == 'darwin': |
| # On Mac SnowLeopard, if we attempt to connect to the socket |
| # immediately, it fails with EINVAL and never gets a chance to |
| # connect (putting us into a hard spin and then failing). |
| # Linux doesn't need this. |
| time.sleep(0.2) |
| |
| try: |
| start = datetime.datetime.utcnow() |
| maxdelay = datetime.timedelta(seconds=30) |
| while (datetime.datetime.utcnow() - start) < maxdelay: |
| try: |
| sock.connect((host, port)) |
| logging.debug('%d is now bound' % port) |
| return |
| except (socket.error, EnvironmentError): |
| pass |
| logging.debug('%d is still not bound' % port) |
| finally: |
| sock.close() |
| # The process failed to bind. Kill it and dump its ouput. |
| process.kill() |
| logging.error('%s' % process.communicate()[0]) |
| 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 = [ |
| ('[email protected]', 'foo'), |
| ('[email protected]', '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 = 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: |
| pid = int(self.git_pid_file.read()) |
| self.git_pid_file.close() |
| 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 |
| self.git_pid_file = 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.iteritems(): |
| 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 == None |
| 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] |
| self.git_port = find_free_port(self.host, 20000) |
| self.git_base = 'git://%s:%d/git/' % (self.host, self.git_port) |
| # Start the daemon. |
| self.git_pid_file = tempfile.NamedTemporaryFile() |
| 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) |
| self.check_port_is_free(self.git_port) |
| 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 _commit_git(self, repo, tree): |
| repo_root = join(self.git_root, repo) |
| self._genTree(repo_root, tree) |
| commit_hash = commit_git(repo_root) |
| if self.git_hashes[repo][-1]: |
| new_tree = self.git_hashes[repo][-1][1].copy() |
| new_tree.update(tree) |
| else: |
| new_tree = tree.copy() |
| self.git_hashes[repo].append((commit_hash, new_tree)) |
| |
| 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) |
| |
| def check_port_is_free(self, port): |
| sock = socket.socket() |
| try: |
| sock.connect((self.host, port)) |
| # It worked, throw. |
| assert False, '%d shouldn\'t be bound' % port |
| except (socket.error, EnvironmentError): |
| pass |
| finally: |
| sock.close() |
| |
| def populateGit(self): |
| raise NotImplementedError() |
| |
| |
| class FakeRepos(FakeReposBase): |
| """Implements populateGit().""" |
| NB_GIT_REPOS = 13 |
| |
| 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': """ |
| deps = { |
| 'foo/bar': '/repo_3', |
| } |
| """, |
| }) |
| |
| 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', |
| } |
| # 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', |
| }, |
| 'src/repo4': { |
| 'url': '/repo_4', |
| 'condition': 'False', |
| }, |
| 'src/repo8': '/repo_8', |
| } |
| 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', |
| ]""" % { |
| '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': """ |
| 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': """ |
| 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 <[email protected]> 1253744361 -0700 |
| committer Bob <[email protected]> 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', |
| }) |
| |
| |
| 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.""" |
| 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.assertEquals(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.assertEquals(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.debug('Actual %s\n%s' % (tree_root, pprint.pformat(actual))) |
| logging.debug('Expected\n%s' % pprint.pformat(tree)) |
| logging.debug('Diff\n%s' % pprint.pformat(diff)) |
| self.assertEquals(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.iteritems(): |
| 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 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)) |