Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 | |
| 3 import contextlib | |
| 4 import copy | |
| 5 import datetime | |
| 6 import json | |
| 7 import os | |
| 8 import re | |
| 9 import shutil | |
| 10 import subprocess | |
| 11 import sys | |
| 12 import tempfile | |
| 13 import time | |
| 14 import unittest | |
| 15 | |
| 16 RECIPE_ENGINE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| 17 THIRD_PARTY = os.path.dirname(RECIPE_ENGINE) | |
| 18 sys.path.insert(0, THIRD_PARTY) | |
| 19 | |
| 20 from google import protobuf | |
| 21 from recipe_engine import package | |
| 22 from recipe_engine import package_pb2 | |
| 23 | |
| 24 @contextlib.contextmanager | |
| 25 def _in_directory(target_dir): | |
|
iannucci
2015/08/06 23:57:13
why two copies? :D
| |
| 26 old_dir = os.getcwd() | |
| 27 os.chdir(target_dir) | |
| 28 try: | |
| 29 yield | |
| 30 finally: | |
| 31 os.chdir(old_dir) | |
| 32 | |
| 33 | |
| 34 def _updated_deps(inp, updates): | |
| 35 if inp is None: | |
| 36 return updates | |
| 37 | |
| 38 outp = inp.__class__() | |
| 39 outp.CopyFrom(inp) | |
| 40 for dep in outp.deps: | |
| 41 if dep.project_id in updates: | |
| 42 dep.revision = updates[dep.project_id] | |
| 43 return outp | |
| 44 | |
| 45 | |
| 46 def _get_dep(inp, dep_id): | |
| 47 for dep in inp.deps: | |
| 48 if dep.project_id == dep_id: | |
| 49 return dep | |
| 50 else: | |
| 51 raise Exception('Dependency %s not found in %s' % (dep, inp)) | |
| 52 | |
| 53 | |
| 54 def _to_text(buf): | |
| 55 return protobuf.text_format.MessageToString(buf) | |
| 56 | |
| 57 | |
| 58 def _recstrify(thing): | |
|
iannucci
2015/08/06 23:57:13
don't use?
luqui
2015/08/20 22:45:25
Done.
| |
| 59 if isinstance(thing, basestring): | |
| 60 return str(thing) | |
| 61 elif isinstance(thing, dict): | |
| 62 out = {} | |
| 63 for k,v in thing.iteritems(): | |
| 64 out[str(k)] = _recstrify(v) | |
| 65 return out | |
| 66 elif isinstance(thing, list): | |
| 67 return map(_recstrify, thing) | |
| 68 else: | |
| 69 return thing | |
| 70 | |
| 71 | |
| 72 class MultiRepoTest(unittest.TestCase): | |
| 73 def _run_cmd(self, cmd, env=None): | |
| 74 subprocess.call(cmd, env=env) | |
| 75 | |
| 76 def _create_repo(self, name, spec): | |
| 77 repo_dir = os.path.join(self._root_dir, name) | |
| 78 os.mkdir(repo_dir) | |
| 79 with _in_directory(repo_dir): | |
| 80 self._run_cmd(['git', 'init']) | |
| 81 config_file = os.path.join('infra', 'config', 'recipes.cfg') | |
| 82 os.makedirs(os.path.dirname(config_file)) | |
| 83 package.ProtoFile(config_file).write(spec) | |
| 84 self._run_cmd(['git', 'add', config_file]) | |
| 85 self._run_cmd(['git', 'commit', '-m', 'New recipe package']) | |
| 86 rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() | |
| 87 return { | |
| 88 'root': repo_dir, | |
| 89 'revision': rev, | |
| 90 'spec': spec, | |
| 91 } | |
| 92 | |
| 93 def _commit_in_repo(self, repo, message='Empty commit'): | |
| 94 with _in_directory(repo['root']): | |
| 95 env = dict(os.environ) | |
| 96 self._run_cmd(['git', 'commit', '-a', '--allow-empty', '-m', message], | |
| 97 env=env) | |
| 98 rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() | |
| 99 return { | |
| 100 'root': repo['root'], | |
| 101 'revision': rev, | |
| 102 'spec': repo['spec'], | |
| 103 } | |
| 104 | |
| 105 def setUp(self): | |
| 106 self.maxDiff = None | |
| 107 | |
| 108 self._root_dir = tempfile.mkdtemp() | |
| 109 self._recipe_tool = os.path.join( | |
| 110 os.path.dirname(os.path.dirname(os.path.abspath(__file__))), | |
| 111 'recipes.py') | |
| 112 | |
| 113 def tearDown(self): | |
| 114 shutil.rmtree(self._root_dir) | |
| 115 | |
| 116 def _repo_setup(self, repo_deps): | |
| 117 # In order to avoid a topsort, we require that repo names are in | |
| 118 # alphebetical dependency order -- i.e. later names depend on earlier | |
| 119 # ones. | |
| 120 repos = {} | |
| 121 for k in sorted(repo_deps): | |
| 122 repos[k] = self._create_repo(k, package_pb2.Package( | |
| 123 api_version=1, | |
| 124 project_id=k, | |
| 125 recipes_path='', | |
| 126 deps=[ | |
| 127 package_pb2.DepSpec( | |
| 128 project_id=d, | |
| 129 url=repos[d]['root'], | |
| 130 branch='master', | |
| 131 revision=repos[d]['revision'], | |
| 132 ) | |
| 133 for d in repo_deps[k] | |
| 134 ], | |
| 135 )) | |
| 136 return repos | |
| 137 | |
| 138 def _run_roll(self, repo, expect_updates, commit=False): | |
| 139 with _in_directory(repo['root']): | |
| 140 stdout, _ = ( | |
| 141 subprocess.Popen(['python', self._recipe_tool, 'roll'], | |
| 142 stdout=subprocess.PIPE) | |
| 143 .communicate()) | |
| 144 if expect_updates: | |
| 145 self.assertRegexpMatches(stdout, r'Wrote \S*recipes.cfg') | |
| 146 else: | |
| 147 self.assertRegexpMatches(stdout, r'No consistent rolls found') | |
| 148 | |
| 149 if commit: | |
| 150 assert expect_updates, 'Cannot commit when not expecting updates' | |
| 151 git_match = re.search(r'^git commit .*', stdout, re.MULTILINE) | |
| 152 self.assertTrue(git_match) | |
| 153 git_command = git_match.group(0) | |
| 154 subprocess.call(git_command, shell=True) | |
| 155 rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() | |
| 156 return { | |
| 157 'root': repo['root'], | |
| 158 'revision': rev, | |
| 159 'spec': repo['spec'], | |
| 160 } | |
| 161 | |
| 162 | |
| 163 def _get_spec(self, repo): | |
| 164 proto_file = package.ProtoFile( | |
| 165 os.path.join(repo['root'], 'infra', 'config', 'recipes.cfg')) | |
| 166 return proto_file.read() | |
| 167 | |
| 168 def test_empty_roll(self): | |
| 169 repos = self._repo_setup({ | |
| 170 'a': [], | |
| 171 'b': [ 'a' ], | |
| 172 }) | |
| 173 self._run_roll(repos['b'], expect_updates=False) | |
| 174 | |
| 175 def test_simple_roll(self): | |
| 176 repos = self._repo_setup({ | |
| 177 'a': [], | |
| 178 'b': ['a'], | |
| 179 }) | |
| 180 new_a = self._commit_in_repo(repos['a']) | |
| 181 self._run_roll(repos['b'], expect_updates=True) | |
| 182 self.assertEqual( | |
| 183 _to_text(self._get_spec(repos['b'])), | |
| 184 _to_text(_updated_deps(repos['b']['spec'], { | |
| 185 'a': new_a['revision'], | |
| 186 }))) | |
| 187 self._run_roll(repos['b'], expect_updates=False) | |
| 188 | |
| 189 def test_indepdendent_roll(self): | |
| 190 repos = self._repo_setup({ | |
| 191 'b': [], | |
| 192 'c': [], | |
| 193 'd': ['b', 'c'], | |
| 194 }) | |
| 195 new_b = self._commit_in_repo(repos['b']) | |
| 196 new_c = self._commit_in_repo(repos['c']) | |
| 197 self._run_roll(repos['d'], expect_updates=True) | |
|
iannucci
2015/08/06 23:57:13
when we allow multi-roll ops, this should probably
| |
| 198 # There is no guarantee on the order the two updates come in. | |
| 199 # (Usually we sort by date but these commits are within 1 second) | |
| 200 # However after one roll we expect only one of the two updates to | |
| 201 # have come in. | |
| 202 d_spec = self._get_spec(repos['d']) | |
| 203 self.assertTrue( | |
| 204 (_get_dep(d_spec, 'b').revision == new_b['revision']) | |
| 205 != (_get_dep(d_spec, 'c').revision == new_c['revision'])) | |
| 206 self._run_roll(repos['d'], expect_updates=True) | |
| 207 self.assertEqual( | |
| 208 _to_text(self._get_spec(repos['d'])), | |
| 209 _to_text(_updated_deps(repos['d']['spec'], { | |
| 210 'b': new_b['revision'], | |
| 211 'c': new_c['revision'], | |
| 212 }))) | |
| 213 self._run_roll(repos['d'], expect_updates=False) | |
| 214 | |
| 215 def test_dependent_roll(self): | |
|
iannucci
2015/08/06 23:57:13
add example test for adding/deleting a dependency
| |
| 216 repos = self._repo_setup({ | |
| 217 'a': [], | |
| 218 'b': ['a'], | |
| 219 'c': ['a'], | |
| 220 'd': ['b', 'c'], | |
| 221 }) | |
| 222 new_a = self._commit_in_repo(repos['a']) | |
| 223 new_b = self._run_roll(repos['b'], expect_updates=True, commit=True) | |
| 224 new_c = self._run_roll(repos['c'], expect_updates=True, commit=True) | |
| 225 | |
| 226 # We only expect one roll here because to roll b without c would | |
| 227 # result in an inconsistent revision of a, so we should skip it. | |
| 228 self._run_roll(repos['d'], expect_updates=True) | |
| 229 d_spec = self._get_spec(repos['d']) | |
| 230 self.assertEqual( | |
| 231 _to_text(self._get_spec(repos['d'])), | |
| 232 _to_text(_updated_deps(repos['d']['spec'], { | |
| 233 'b': new_b['revision'], | |
| 234 'c': new_c['revision'], | |
| 235 }))) | |
| 236 self._run_roll(repos['d'], expect_updates=False) | |
| 237 | |
| 238 if __name__ == '__main__': | |
| 239 unittest.main() | |
| OLD | NEW |