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 |