Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(34)

Side by Side Diff: recipe_engine/unittests/multi_repo_test.py

Issue 1344583003: Recipe package system. (Closed) Base URL: git@github.com:luci/recipes-py.git@master
Patch Set: Recompiled proto Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « recipe_engine/unittests/doc_test.py ('k') | recipe_engine/unittests/package_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
17 os.path.abspath(__file__))))
18 sys.path.insert(0, os.path.join(ROOT_DIR, 'recipe_engine', 'third_party'))
19 sys.path.insert(0, ROOT_DIR)
20
21 from google import protobuf
22 from recipe_engine import package
23 from recipe_engine import package_pb2
24
25 @contextlib.contextmanager
26 def _in_directory(target_dir):
27 old_dir = os.getcwd()
28 os.chdir(target_dir)
29 try:
30 yield
31 finally:
32 os.chdir(old_dir)
33
34
35 def _updated_deps(inp, updates):
36 if inp is None:
37 return updates
38
39 outp = inp.__class__()
40 outp.CopyFrom(inp)
41 for dep in outp.deps:
42 if dep.project_id in updates:
43 dep.revision = updates[dep.project_id]
44 return outp
45
46
47 def _get_dep(inp, dep_id):
48 for dep in inp.deps:
49 if dep.project_id == dep_id:
50 return dep
51 else:
52 raise Exception('Dependency %s not found in %s' % (dep, inp))
53
54
55 def _to_text(buf):
56 return protobuf.text_format.MessageToString(buf)
57
58
59 def _recstrify(thing):
60 if isinstance(thing, basestring):
61 return str(thing)
62 elif isinstance(thing, dict):
63 out = {}
64 for k,v in thing.iteritems():
65 out[str(k)] = _recstrify(v)
66 return out
67 elif isinstance(thing, list):
68 return map(_recstrify, thing)
69 else:
70 return thing
71
72
73 class RecipeRollError(Exception):
74 def __init__(self, stdout, stderr):
75 self.stdout = stdout
76 self.stderr = stderr
77
78
79 class MultiRepoTest(unittest.TestCase):
80 def _run_cmd(self, cmd, env=None):
81 subprocess.call(cmd, env=env)
82
83 def _create_repo(self, name, spec):
84 repo_dir = os.path.join(self._root_dir, name)
85 os.mkdir(repo_dir)
86 with _in_directory(repo_dir):
87 self._run_cmd(['git', 'init'])
88 config_file = os.path.join('infra', 'config', 'recipes.cfg')
89 os.makedirs(os.path.dirname(config_file))
90 package.ProtoFile(config_file).write(spec)
91 self._run_cmd(['git', 'add', config_file])
92 self._run_cmd(['git', 'commit', '-m', 'New recipe package'])
93 rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
94 return {
95 'root': repo_dir,
96 'revision': rev,
97 'spec': spec,
98 }
99
100 def _commit_in_repo(self, repo, message='Empty commit'):
101 with _in_directory(repo['root']):
102 env = dict(os.environ)
103 self._run_cmd(['git', 'commit', '-a', '--allow-empty', '-m', message],
104 env=env)
105 rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
106 return {
107 'root': repo['root'],
108 'revision': rev,
109 'spec': repo['spec'],
110 }
111
112 def setUp(self):
113 self.maxDiff = None
114
115 self._root_dir = tempfile.mkdtemp()
116 self._recipe_tool = os.path.join(ROOT_DIR, 'recipes.py')
117
118 def tearDown(self):
119 shutil.rmtree(self._root_dir)
120
121 def _repo_setup(self, repo_deps):
122 # In order to avoid a topsort, we require that repo names are in
123 # alphebetical dependency order -- i.e. later names depend on earlier
124 # ones.
125 repos = {}
126 for k in sorted(repo_deps):
127 repos[k] = self._create_repo(k, package_pb2.Package(
128 api_version=1,
129 project_id=k,
130 recipes_path='',
131 deps=[
132 package_pb2.DepSpec(
133 project_id=d,
134 url=repos[d]['root'],
135 branch='master',
136 revision=repos[d]['revision'],
137 )
138 for d in repo_deps[k]
139 ],
140 ))
141 return repos
142
143 def _run_roll(self, repo, expect_updates, commit=False):
144 with _in_directory(repo['root']):
145 popen = subprocess.Popen([
146 'python', self._recipe_tool,
147 '--package', os.path.join(repo['root'], 'infra', 'config', 'recipes.cf g'),
148 'roll'],
149 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
150 stdout, stderr = popen.communicate()
151
152 if popen.returncode != 0:
153 raise RecipeRollError(stdout, stderr)
154
155 if expect_updates:
156 self.assertRegexpMatches(stdout, r'Wrote \S*recipes.cfg')
157 else:
158 self.assertRegexpMatches(stdout, r'No consistent rolls found')
159
160 if commit:
161 assert expect_updates, 'Cannot commit when not expecting updates'
162 git_match = re.search(r'^git commit .*', stdout, re.MULTILINE)
163 self.assertTrue(git_match)
164 git_command = git_match.group(0)
165 subprocess.call(git_command, shell=True)
166 rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
167 return {
168 'root': repo['root'],
169 'revision': rev,
170 'spec': repo['spec'],
171 }
172
173
174 def _get_spec(self, repo):
175 proto_file = package.ProtoFile(
176 os.path.join(repo['root'], 'infra', 'config', 'recipes.cfg'))
177 return proto_file.read()
178
179 def test_empty_roll(self):
180 repos = self._repo_setup({
181 'a': [],
182 'b': [ 'a' ],
183 })
184 self._run_roll(repos['b'], expect_updates=False)
185
186 def test_simple_roll(self):
187 repos = self._repo_setup({
188 'a': [],
189 'b': ['a'],
190 })
191 new_a = self._commit_in_repo(repos['a'])
192 self._run_roll(repos['b'], expect_updates=True)
193 self.assertEqual(
194 _to_text(self._get_spec(repos['b'])),
195 _to_text(_updated_deps(repos['b']['spec'], {
196 'a': new_a['revision'],
197 })))
198 self._run_roll(repos['b'], expect_updates=False)
199
200 def test_indepdendent_roll(self):
201 repos = self._repo_setup({
202 'b': [],
203 'c': [],
204 'd': ['b', 'c'],
205 })
206 new_b = self._commit_in_repo(repos['b'])
207 new_c = self._commit_in_repo(repos['c'])
208 self._run_roll(repos['d'], expect_updates=True)
209 # There is no guarantee on the order the two updates come in.
210 # (Usually we sort by date but these commits are within 1 second)
211 # However after one roll we expect only one of the two updates to
212 # have come in.
213 d_spec = self._get_spec(repos['d'])
214 self.assertTrue(
215 (_get_dep(d_spec, 'b').revision == new_b['revision'])
216 != (_get_dep(d_spec, 'c').revision == new_c['revision']))
217 self._run_roll(repos['d'], expect_updates=True)
218 self.assertEqual(
219 _to_text(self._get_spec(repos['d'])),
220 _to_text(_updated_deps(repos['d']['spec'], {
221 'b': new_b['revision'],
222 'c': new_c['revision'],
223 })))
224 self._run_roll(repos['d'], expect_updates=False)
225
226 def test_dependent_roll(self):
227 repos = self._repo_setup({
228 'a': [],
229 'b': ['a'],
230 'c': ['a'],
231 'd': ['b', 'c'],
232 })
233 new_a = self._commit_in_repo(repos['a'])
234 new_b = self._run_roll(repos['b'], expect_updates=True, commit=True)
235 new_c = self._run_roll(repos['c'], expect_updates=True, commit=True)
236
237 # We only expect one roll here because to roll b without c would
238 # result in an inconsistent revision of a, so we should skip it.
239 self._run_roll(repos['d'], expect_updates=True)
240 d_spec = self._get_spec(repos['d'])
241 self.assertEqual(
242 _to_text(self._get_spec(repos['d'])),
243 _to_text(_updated_deps(repos['d']['spec'], {
244 'b': new_b['revision'],
245 'c': new_c['revision'],
246 })))
247 self._run_roll(repos['d'], expect_updates=False)
248
249 def test_cyclic_dependency(self):
250 repos = self._repo_setup({
251 'a': [],
252 'b': ['a'],
253 })
254 config_file = os.path.join(
255 repos['a']['root'], 'infra', 'config', 'recipes.cfg')
256 package.ProtoFile(config_file).write(
257 package_pb2.Package(
258 api_version=1,
259 project_id='a',
260 recipes_path='',
261 deps=[
262 package_pb2.DepSpec(
263 project_id='b',
264 url=repos['b']['root'],
265 branch='master',
266 revision=repos['b']['revision'],
267 ),
268 ],
269 )
270 )
271 self._commit_in_repo(repos['a'])
272 with self.assertRaises(RecipeRollError) as raises:
273 self._run_roll(repos['b'], expect_updates=True)
274 self.assertRegexpMatches(raises.exception.stderr, 'CyclicDependencyError')
275
276 if __name__ == '__main__':
277 unittest.main()
OLDNEW
« no previous file with comments | « recipe_engine/unittests/doc_test.py ('k') | recipe_engine/unittests/package_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698