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

Side by Side Diff: scripts/slave/unittests/annotated_run_test.py

Issue 1501663002: annotated_run.py: Add LogDog bootstrapping. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Updated, actually works. Created 4 years, 11 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 | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 2
3 # Copyright 2015 The Chromium Authors. All rights reserved. 3 # Copyright 2015 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """Tests that the tools/build annotated_run wrapper actually runs.""" 7 """Tests that the tools/build annotated_run wrapper actually runs."""
8 8
9 import collections
10 import contextlib
9 import json 11 import json
12 import logging
10 import os 13 import os
11 import subprocess 14 import subprocess
15 import sys
16 import tempfile
12 import unittest 17 import unittest
13 18
19 import test_env # pylint: disable=W0403,W0611
20
21 import mock
22 from common import chromium_utils
23 from common import env
24 from slave import annotated_run
25 from slave import gce
26
14 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 27 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15 28
29
30 MockOptions = collections.namedtuple('MockOptions',
31 ('dry_run', 'logdog_force', 'logdog_butler_path', 'logdog_annotee_path',
32 'logdog_verbose', 'logdog_service_account_json'))
33
34
16 class AnnotatedRunTest(unittest.TestCase): 35 class AnnotatedRunTest(unittest.TestCase):
17 def test_example(self): 36 def test_example(self):
18 build_properties = { 37 build_properties = {
19 'recipe': 'annotated_run_test', 38 'recipe': 'annotated_run_test',
20 'true_prop': True, 39 'true_prop': True,
21 'num_prop': 123, 40 'num_prop': 123,
22 'string_prop': '321', 41 'string_prop': '321',
23 'dict_prop': {'foo': 'bar'}, 42 'dict_prop': {'foo': 'bar'},
24 } 43 }
25 44
26 script_path = os.path.join(BASE_DIR, 'annotated_run.py') 45 script_path = os.path.join(BASE_DIR, 'annotated_run.py')
27 exit_code = subprocess.call([ 46 exit_code = subprocess.call([
28 'python', script_path, 47 'python', script_path,
29 '--build-properties=%s' % json.dumps(build_properties)]) 48 '--build-properties=%s' % json.dumps(build_properties)])
30 self.assertEqual(exit_code, 0) 49 self.assertEqual(exit_code, 0)
31 50
51 @mock.patch('slave.annotated_run._run_command')
52 @mock.patch('slave.annotated_run.main')
53 @mock.patch('sys.platform', return_value='win')
54 @mock.patch('tempfile.mkstemp', side_effect=Exception('failure'))
55 @mock.patch('os.environ', {})
56 def test_update_scripts_must_run(self, _tempfile_mkstemp, _sys_platform,
57 main, run_command):
58 annotated_run.main.side_effect = Exception('Test error!')
59 annotated_run._run_command.return_value = (0, "")
60 annotated_run.shell_main(['annotated_run.py', 'foo'])
61
62 gclient_path = os.path.join(env.Build, os.pardir, 'depot_tools',
63 'gclient.bat')
64 run_command.assert_has_calls([
65 mock.call([gclient_path, 'sync', '--force', '--verbose', '--jobs=2'],
66 cwd=env.Build),
67 mock.call([sys.executable, 'annotated_run.py', 'foo']),
68 ])
69 main.assert_not_called()
70
71
72 class _AnnotatedRunExecTestBase(unittest.TestCase):
73 def setUp(self):
74 logging.basicConfig(level=logging.ERROR+1)
75
76 self.maxDiff = None
77 self._patchers = []
78 map(self._patch, (
79 mock.patch('slave.annotated_run._run_command'),
80 mock.patch('os.path.exists'),
81 mock.patch('os.getcwd'),
82 ))
83
84 self.rt = annotated_run.Runtime()
85 self.tdir = self.rt.tempdir()
86 self.opts = MockOptions(
87 dry_run=False,
88 logdog_force=False,
89 logdog_annotee_path=None,
90 logdog_butler_path=None,
91 logdog_verbose=False,
92 logdog_service_account_json=None)
93 self.config = annotated_run.Config(
94 run_cmd=['run.py'],
95 logdog_pubsub=None,
96 logdog_platform=None,
97 )
98 self.properties = {
99 'recipe': 'example/recipe',
100 'mastername': 'master.random',
101 'buildername': 'builder',
102 }
103 self.cwd = os.path.join('home', 'user')
104 self.rpy_path = os.path.join(env.Build, 'scripts', 'slave', 'recipes.py')
105 self.recipe_args = [
106 sys.executable, '-u', self.rpy_path, 'run',
107 '--workdir=%s' % (self.cwd,),
108 '--properties-file=%s' % (self._tp('recipe_properties.json'),),
109 'example/recipe']
110
111 # Use public recipes.py path.
112 os.getcwd.return_value = self.cwd
113 os.path.exists.return_value = False
114
115 def tearDown(self):
116 self.rt.close()
117 for p in reversed(self._patchers):
118 p.stop()
119
120 def _tp(self, *p):
121 return os.path.join(*((self.tdir,) + p))
122
123 def _patch(self, patcher):
124 self._patchers.append(patcher)
125 patcher.start()
126 return patcher
127
128 def _assertRecipeProperties(self, value):
129 # Double-translate "value", since JSON converts strings to unicode.
130 value = json.loads(json.dumps(value))
131 with open(self._tp('recipe_properties.json')) as fd:
132 self.assertEqual(json.load(fd), value)
133
134
135 class AnnotatedRunExecTest(_AnnotatedRunExecTestBase):
136
137 def test_exec_successful(self):
138 annotated_run._run_command.return_value = (0, '')
139
140 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config,
141 self.properties)
142 self.assertEqual(rv, 0)
143 self._assertRecipeProperties(self.properties)
144
145 annotated_run._run_command.assert_called_once_with(self.recipe_args,
146 dry_run=False)
147
148
149 class AnnotatedRunLogDogExecTest(_AnnotatedRunExecTestBase):
150
151 def setUp(self):
152 super(AnnotatedRunLogDogExecTest, self).setUp()
153 self._orig_whitelist = annotated_run.LOGDOG_WHITELIST_MASTER_BUILDERS
154 annotated_run.LOGDOG_WHITELIST_MASTER_BUILDERS = {
155 'master.some': [
156 'yesbuilder',
157 ],
158
159 'master.all': [
160 annotated_run.WHITELIST_ALL,
161 ],
162 }
163 self.properties.update({
164 'mastername': 'master.some',
165 'buildername': 'nobuilder',
166 'buildnumber': 1337,
167 })
168 self.config = self.config._replace(
169 logdog_pubsub=annotated_run.PubSubConfig(project='test', topic='logs'),
170 logdog_platform=annotated_run.LogDogPlatform(
171 butler=annotated_run.CipdBinary('cipd/butler', 'head', 'butler'),
172 annotee=annotated_run.CipdBinary('cipd/annotee', 'head', 'annotee'),
173 credential_path=os.path.join('path', 'to', 'creds.json'),
174 streamserver='unix',
175 ),
176 )
177 self.is_gce = False
178
179 def is_gce():
180 return self.is_gce
181 is_gce_patch = mock.patch('slave.gce.Authenticator.is_gce',
182 side_effect=is_gce)
183 is_gce_patch.start()
184 self._patchers.append(is_gce_patch)
185
186 def tearDown(self):
187 annotated_run.LOGDOG_WHITELIST_MASTER_BUILDERS = self._orig_whitelist
188 super(AnnotatedRunLogDogExecTest, self).tearDown()
189
190 def _assertAnnoteeCommand(self, value):
191 # Double-translate "value", since JSON converts strings to unicode.
192 value = json.loads(json.dumps(value))
193 with open(self._tp('logdog_bootstrap', 'annotee_cmd.json')) as fd:
194 self.assertEqual(json.load(fd), value)
195
196 def test_should_run_logdog(self):
197 self.assertFalse(annotated_run._should_run_logdog({
198 'mastername': 'master.undefined', 'buildername': 'any'}))
199 self.assertFalse(annotated_run._should_run_logdog({
200 'mastername': 'master.some', 'buildername': 'nobuilder'}))
201 self.assertTrue(annotated_run._should_run_logdog({
202 'mastername': 'master.some', 'buildername': 'yesbuilder'}))
203 self.assertTrue(annotated_run._should_run_logdog({
204 'mastername': 'master.all', 'buildername': 'anybuilder'}))
205
206 @mock.patch('slave.annotated_run.is_executable', return_value=True)
207 @mock.patch('slave.annotated_run._get_service_account_json')
208 def test_exec_with_whitelist_builder_runs_logdog(self, service_account,
209 _is_executable):
210 self.properties['buildername'] = 'yesbuilder'
211
212 butler_path = self._tp('logdog_bootstrap', 'cipd', 'butler')
213 annotee_path = self._tp('logdog_bootstrap', 'cipd', 'annotee')
214 service_account.return_value = 'creds.json'
215 annotated_run._run_command.return_value = (0, '')
216
217 self._patch(mock.patch('tempfile.mkdtemp', return_value='foo'))
218 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config,
219 self.properties)
220 self.assertEqual(rv, 0)
221
222 streamserver_uri = 'unix:%s' % (os.path.join('foo', 'butler.sock'),)
223 service_account.assert_called_once_with(
224 self.opts, self.config.logdog_platform.credential_path)
225 annotated_run._run_command.assert_called_with(
226 [butler_path,
227 '-prefix', 'bb/master.some/yesbuilder/1337',
228 '-output', 'pubsub,project="test",topic="logs"',
229 '-service-account-json', 'creds.json',
230 'run',
231 '-stdout', 'tee=stdout',
232 '-stderr', 'tee=stderr',
233 '-streamserver-uri', streamserver_uri,
234 '--',
235 annotee_path,
236 '-butler-stream-server', streamserver_uri,
237 '-annotate', 'tee',
238 '-name-base', 'recipes',
239 '-print-summary',
240 '-tee',
241 '-json-args-path', self._tp('logdog_bootstrap',
242 'annotee_cmd.json'),
243 ],
244 dry_run=False)
245 self._assertRecipeProperties(self.properties)
246 self._assertAnnoteeCommand(self.recipe_args)
247
248 @mock.patch('slave.annotated_run._logdog_bootstrap', return_value=0)
249 def test_runs_bootstrap_when_forced(self, lb):
250 opts = self.opts._replace(logdog_force=True)
251 rv = annotated_run._exec_recipe(self.rt, opts, self.tdir, self.config,
252 self.properties)
253 self.assertEqual(rv, 0)
254 lb.assert_called_once()
255 annotated_run._run_command.assert_called_once()
256
257 @mock.patch('slave.annotated_run._logdog_bootstrap', return_value=2)
258 def test_forwards_error_code(self, lb):
259 opts = self.opts._replace(
260 logdog_force=True)
261 rv = annotated_run._exec_recipe(self.rt, opts, self.tdir, self.config,
262 self.properties)
263 self.assertEqual(rv, 2)
264 lb.assert_called_once()
265
266 @mock.patch('slave.annotated_run._logdog_bootstrap',
267 side_effect=Exception('Unhandled situation.'))
268 def test_runs_directly_if_bootstrap_fails(self, lb):
269 annotated_run._run_command.return_value = (123, '')
270
271 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config,
272 self.properties)
273 self.assertEqual(rv, 123)
274
275 lb.assert_called_once()
276 annotated_run._run_command.assert_called_once_with(self.recipe_args,
277 dry_run=False)
278
279 @mock.patch('slave.annotated_run._logdog_install_cipd')
280 @mock.patch('slave.annotated_run._get_service_account_json')
281 def test_runs_directly_if_logdog_error(self, service_account, cipd):
282 self.properties['buildername'] = 'yesbuilder'
283
284 cipd.return_value = ('butler', 'annotee')
285 service_account.return_value = 'creds.json'
286 def error_for_logdog(args, **kw):
287 if len(args) > 0 and args[0] == 'butler':
288 return (250, '')
289 return (4, '')
290 annotated_run._run_command.side_effect = error_for_logdog
291
292 self._patch(mock.patch('tempfile.mkdtemp', return_value='foo'))
293 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config,
294 self.properties)
295 self.assertEqual(rv, 4)
296
297 streamserver_uri = 'unix:%s' % (os.path.join('foo', 'butler.sock'),)
298 service_account.assert_called_once_with(
299 self.opts, self.config.logdog_platform.credential_path)
300 annotated_run._run_command.assert_has_calls([
301 mock.call([
302 'butler',
303 '-prefix', 'bb/master.some/yesbuilder/1337',
304 '-output', 'pubsub,project="test",topic="logs"',
305 '-service-account-json', 'creds.json',
306 'run',
307 '-stdout', 'tee=stdout',
308 '-stderr', 'tee=stderr',
309 '-streamserver-uri', streamserver_uri,
310 '--',
311 'annotee',
312 '-butler-stream-server', streamserver_uri,
313 '-annotate', 'tee',
314 '-name-base', 'recipes',
315 '-print-summary',
316 '-tee',
317 '-json-args-path', self._tp('logdog_bootstrap',
318 'annotee_cmd.json'),
319 ], dry_run=False),
320 mock.call(self.recipe_args, dry_run=False),
321 ])
322
323 @mock.patch('os.path.isfile')
324 def test_can_find_credentials(self, isfile):
325 isfile.return_value = True
326
327 service_account_json = annotated_run._get_service_account_json(
328 self.opts, 'creds.json')
329 self.assertEqual(service_account_json, 'creds.json')
330
331 def test_uses_no_credentials_on_gce(self):
332 self.is_gce = True
333 service_account_json = annotated_run._get_service_account_json(
334 self.opts, ('foo', 'bar'))
335 self.assertIsNone(service_account_json)
336
337 def test_cipd_install(self):
338 annotated_run._run_command.return_value = (0, '')
339
340 pkgs = annotated_run._logdog_install_cipd(self.tdir,
341 annotated_run.CipdBinary('infra/foo', 'v0', 'foo'),
342 annotated_run.CipdBinary('infra/bar', 'v1', 'baz'),
343 )
344 self.assertEqual(pkgs, (self._tp('foo'), self._tp('baz')))
345
346 annotated_run._run_command.assert_called_once_with([
347 sys.executable,
348 os.path.join(env.Build, 'scripts', 'slave', 'cipd.py'),
349 '--dest-directory', self.tdir,
350 '--json-output', os.path.join(self.tdir, 'packages.json'),
351 '-P', 'infra/foo@v0',
352 '-P', 'infra/bar@v1',
353 ])
354
355 def test_cipd_install_failure_raises_bootstrap_error(self):
356 annotated_run._run_command.return_value = (1, '')
357
358 self.assertRaises(annotated_run.LogDogBootstrapError,
359 annotated_run._logdog_install_cipd,
360 self.tdir,
361 annotated_run.CipdBinary('infra/foo', 'v0', 'foo'),
362 annotated_run.CipdBinary('infra/bar', 'v1', 'baz'),
363 )
364
365
32 if __name__ == '__main__': 366 if __name__ == '__main__':
33 unittest.main() 367 unittest.main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698