OLD | NEW |
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._get_service_account_json') |
| 207 def test_exec_with_whitelist_builder_runs_logdog(self, service_account): |
| 208 self.properties['buildername'] = 'yesbuilder' |
| 209 |
| 210 butler_path = self._tp('logdog_bootstrap', 'cipd', 'butler') |
| 211 annotee_path = self._tp('logdog_bootstrap', 'cipd', 'annotee') |
| 212 service_account.return_value = 'creds.json' |
| 213 annotated_run._run_command.return_value = (0, '') |
| 214 |
| 215 self._patch(mock.patch('tempfile.mkdtemp', return_value='foo')) |
| 216 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config, |
| 217 self.properties) |
| 218 self.assertEqual(rv, 0) |
| 219 |
| 220 streamserver_uri = 'unix:%s' % (os.path.join('foo', 'butler.sock'),) |
| 221 service_account.assert_called_once_with( |
| 222 self.opts, self.config.logdog_platform.credential_path) |
| 223 annotated_run._run_command.assert_called_with( |
| 224 [butler_path, |
| 225 '-prefix', 'bb/master.some/yesbuilder/1337', |
| 226 '-output', 'pubsub,project="test",topic="logs"', |
| 227 '-service-account-json', 'creds.json', |
| 228 'run', |
| 229 '-stdout', 'tee=stdout', |
| 230 '-stderr', 'tee=stderr', |
| 231 '-streamserver-uri', streamserver_uri, |
| 232 '--', |
| 233 annotee_path, |
| 234 '-butler-stream-server', streamserver_uri, |
| 235 '-annotate', 'tee', |
| 236 '-name-base', 'recipes', |
| 237 '-print-summary', |
| 238 '-tee', |
| 239 '-json-args-path', self._tp('logdog_bootstrap', |
| 240 'annotee_cmd.json'), |
| 241 ], |
| 242 dry_run=False) |
| 243 self._assertRecipeProperties(self.properties) |
| 244 self._assertAnnoteeCommand(self.recipe_args) |
| 245 |
| 246 @mock.patch('slave.annotated_run._logdog_bootstrap', return_value=0) |
| 247 def test_runs_bootstrap_when_forced(self, lb): |
| 248 opts = self.opts._replace(logdog_force=True) |
| 249 rv = annotated_run._exec_recipe(self.rt, opts, self.tdir, self.config, |
| 250 self.properties) |
| 251 self.assertEqual(rv, 0) |
| 252 lb.assert_called_once() |
| 253 annotated_run._run_command.assert_called_once() |
| 254 |
| 255 @mock.patch('slave.annotated_run._logdog_bootstrap', return_value=2) |
| 256 def test_forwards_error_code(self, lb): |
| 257 opts = self.opts._replace( |
| 258 logdog_force=True) |
| 259 rv = annotated_run._exec_recipe(self.rt, opts, self.tdir, self.config, |
| 260 self.properties) |
| 261 self.assertEqual(rv, 2) |
| 262 lb.assert_called_once() |
| 263 |
| 264 @mock.patch('slave.annotated_run._logdog_bootstrap', |
| 265 side_effect=Exception('Unhandled situation.')) |
| 266 def test_runs_directly_if_bootstrap_fails(self, lb): |
| 267 annotated_run._run_command.return_value = (123, '') |
| 268 |
| 269 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config, |
| 270 self.properties) |
| 271 self.assertEqual(rv, 123) |
| 272 |
| 273 lb.assert_called_once() |
| 274 annotated_run._run_command.assert_called_once_with(self.recipe_args, |
| 275 dry_run=False) |
| 276 |
| 277 @mock.patch('slave.annotated_run._logdog_install_cipd') |
| 278 @mock.patch('slave.annotated_run._get_service_account_json') |
| 279 def test_runs_directly_if_logdog_error(self, service_account, cipd): |
| 280 self.properties['buildername'] = 'yesbuilder' |
| 281 |
| 282 cipd.return_value = ('butler', 'annotee') |
| 283 service_account.return_value = 'creds.json' |
| 284 def error_for_logdog(args, **kw): |
| 285 if len(args) > 0 and args[0] == 'butler': |
| 286 return (250, '') |
| 287 return (4, '') |
| 288 annotated_run._run_command.side_effect = error_for_logdog |
| 289 |
| 290 self._patch(mock.patch('tempfile.mkdtemp', return_value='foo')) |
| 291 rv = annotated_run._exec_recipe(self.rt, self.opts, self.tdir, self.config, |
| 292 self.properties) |
| 293 self.assertEqual(rv, 4) |
| 294 |
| 295 streamserver_uri = 'unix:%s' % (os.path.join('foo', 'butler.sock'),) |
| 296 service_account.assert_called_once_with( |
| 297 self.opts, self.config.logdog_platform.credential_path) |
| 298 annotated_run._run_command.assert_has_calls([ |
| 299 mock.call([ |
| 300 'butler', |
| 301 '-prefix', 'bb/master.some/yesbuilder/1337', |
| 302 '-output', 'pubsub,project="test",topic="logs"', |
| 303 '-service-account-json', 'creds.json', |
| 304 'run', |
| 305 '-stdout', 'tee=stdout', |
| 306 '-stderr', 'tee=stderr', |
| 307 '-streamserver-uri', streamserver_uri, |
| 308 '--', |
| 309 'annotee', |
| 310 '-butler-stream-server', streamserver_uri, |
| 311 '-annotate', 'tee', |
| 312 '-name-base', 'recipes', |
| 313 '-print-summary', |
| 314 '-tee', |
| 315 '-json-args-path', self._tp('logdog_bootstrap', |
| 316 'annotee_cmd.json'), |
| 317 ], dry_run=False), |
| 318 mock.call(self.recipe_args, dry_run=False), |
| 319 ]) |
| 320 |
| 321 @mock.patch('os.path.isfile') |
| 322 def test_can_find_credentials(self, isfile): |
| 323 isfile.return_value = True |
| 324 |
| 325 service_account_json = annotated_run._get_service_account_json( |
| 326 self.opts, 'creds.json') |
| 327 self.assertEqual(service_account_json, 'creds.json') |
| 328 |
| 329 def test_uses_no_credentials_on_gce(self): |
| 330 self.is_gce = True |
| 331 service_account_json = annotated_run._get_service_account_json( |
| 332 self.opts, ('foo', 'bar')) |
| 333 self.assertIsNone(service_account_json) |
| 334 |
| 335 def test_cipd_install(self): |
| 336 annotated_run._run_command.return_value = (0, '') |
| 337 |
| 338 pkgs = annotated_run._logdog_install_cipd(self.tdir, |
| 339 annotated_run.CipdBinary('infra/foo', 'v0', 'foo'), |
| 340 annotated_run.CipdBinary('infra/bar', 'v1', 'baz'), |
| 341 ) |
| 342 self.assertEqual(pkgs, (self._tp('foo'), self._tp('baz'))) |
| 343 |
| 344 annotated_run._run_command.assert_called_once_with([ |
| 345 sys.executable, |
| 346 os.path.join(env.Build, 'scripts', 'slave', 'cipd.py'), |
| 347 '--dest-directory', self.tdir, |
| 348 '--json-output', os.path.join(self.tdir, 'packages.json'), |
| 349 '-P', 'infra/foo@v0', |
| 350 '-P', 'infra/bar@v1', |
| 351 ]) |
| 352 |
| 353 def test_cipd_install_failure_raises_bootstrap_error(self): |
| 354 annotated_run._run_command.return_value = (1, '') |
| 355 |
| 356 self.assertRaises(annotated_run.LogDogBootstrapError, |
| 357 annotated_run._logdog_install_cipd, |
| 358 self.tdir, |
| 359 annotated_run.CipdBinary('infra/foo', 'v0', 'foo'), |
| 360 annotated_run.CipdBinary('infra/bar', 'v1', 'baz'), |
| 361 ) |
| 362 |
| 363 |
32 if __name__ == '__main__': | 364 if __name__ == '__main__': |
33 unittest.main() | 365 unittest.main() |
OLD | NEW |