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

Side by Side Diff: scripts/slave/annotated_run.py

Issue 23889036: Refactor the way that TestApi works so that it is actually useful. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: rebase Created 7 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 | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Entry point for fully-annotated builds. 6 """Entry point for fully-annotated builds.
7 7
8 This script is part of the effort to move all builds to annotator-based 8 This script is part of the effort to move all builds to annotator-based
9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() 9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory()
10 found in scripts/master/factory/annotator_factory.py executes a single 10 found in scripts/master/factory/annotator_factory.py executes a single
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
69 import sys 69 import sys
70 70
71 import cStringIO 71 import cStringIO
72 72
73 import common.python26_polyfill # pylint: disable=W0611 73 import common.python26_polyfill # pylint: disable=W0611
74 import collections # Import after polyfill to get OrderedDict on 2.6 74 import collections # Import after polyfill to get OrderedDict on 2.6
75 75
76 from common import annotator 76 from common import annotator
77 from common import chromium_utils 77 from common import chromium_utils
78 from slave import recipe_api 78 from slave import recipe_api
79 from slave import recipe_util
80 from slave import recipe_test_api
81 from slave import recipe_loader
82
79 83
80 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) 84 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
81 BUILD_ROOT = os.path.dirname(os.path.dirname(SCRIPT_PATH))
82 ROOT_PATH = os.path.abspath(os.path.join(
83 SCRIPT_PATH, os.pardir, os.pardir, os.pardir))
84 MODULE_DIRS = [os.path.join(x, 'recipe_modules') for x in [
85 SCRIPT_PATH,
86 os.path.join(ROOT_PATH, 'build_internal', 'scripts', 'slave')
87 ]]
88 85
89 86
90 class StepPresentation(object): 87 class StepPresentation(object):
91 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION')) 88 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION'))
92 89
93 def __init__(self): 90 def __init__(self):
94 self._finalized = False 91 self._finalized = False
95 92
96 self._logs = collections.OrderedDict() 93 self._logs = collections.OrderedDict()
97 self._links = collections.OrderedDict() 94 self._links = collections.OrderedDict()
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 for s in fixup_seed_steps(list(flattened(step_or_steps))): 220 for s in fixup_seed_steps(list(flattened(step_or_steps))):
224 yield s 221 yield s
225 elif inspect.isgenerator(step_or_steps): 222 elif inspect.isgenerator(step_or_steps):
226 for i in step_or_steps: 223 for i in step_or_steps:
227 for s in ensure_sequence_of_steps(i): 224 for s in ensure_sequence_of_steps(i):
228 yield s 225 yield s
229 else: 226 else:
230 assert False, 'Item is not a sequence or a step: %s' % (step_or_steps,) 227 assert False, 'Item is not a sequence or a step: %s' % (step_or_steps,)
231 228
232 229
233 def render_step(step, test_data): 230 def render_step(step, step_test):
234 """Renders a step so that it can be fed to annotator.py. 231 """Renders a step so that it can be fed to annotator.py.
235 232
236 Args: 233 Args:
237 test_data: The test data json dictionary for this step, if any. 234 step_test: The test data json dictionary for this step, if any.
238 Passed through unaltered to each placeholder. 235 Passed through unaltered to each placeholder.
239 236
240 Returns any placeholder instances that were found while rendering the step. 237 Returns any placeholder instances that were found while rendering the step.
241 """ 238 """
242 placeholders = collections.defaultdict(list) 239 placeholders = collections.defaultdict(lambda: collections.defaultdict(list))
243 new_cmd = [] 240 new_cmd = []
244 for item in step['cmd']: 241 for item in step['cmd']:
245 if isinstance(item, recipe_api.Placeholder): 242 if isinstance(item, recipe_util.Placeholder):
246 # __module__ is in the form of ...recipe_modules.<api_name>.* 243 module_name, placeholder_name = item.name_pieces
247 api_name = item.__module__.split('.')[-2] 244 tdata = step_test.pop_placeholder(item.name_pieces)
248 tdata = None if test_data is None else test_data.get(api_name, {})
249 new_cmd.extend(item.render(tdata)) 245 new_cmd.extend(item.render(tdata))
250 placeholders[api_name].append(item) 246 placeholders[module_name][placeholder_name].append((item, tdata))
251 else: 247 else:
252 new_cmd.append(item) 248 new_cmd.append(item)
253 step['cmd'] = new_cmd 249 step['cmd'] = new_cmd
254 return placeholders 250 return placeholders
255 251
256 252
257 def call_placeholders(step_result, placeholders, test_data): 253 def get_placeholder_results(step_result, placeholders):
258 class BlankObject(object): 254 class BlankObject(object):
259 pass 255 pass
260 additions = {} 256 for module_name, pholders in placeholders.iteritems():
261 for api, items in placeholders.iteritems(): 257 assert not hasattr(step_result, module_name)
262 additions[api] = BlankObject() 258 o = BlankObject()
263 test_datum = None if test_data is None else test_data.get(api, {}) 259 setattr(step_result, module_name, o)
264 for placeholder in items: 260
265 placeholder.step_finished(step_result.presentation, additions[api], 261 for placeholder_name, items in pholders.iteritems():
266 test_datum) 262 lst = [ph.result(step_result.presentation, td) for ph, td in items]
267 for api, obj in additions.iteritems(): 263 setattr(o, placeholder_name+"_all", lst)
268 setattr(step_result, api, obj) 264 setattr(o, placeholder_name, lst[0])
agable 2013/09/21 02:05:31 Why? Why not just put the whole list in placeholde
iannucci 2013/09/21 03:12:34 Because people /mostly always/ want the first entr
269 265
270 266
271 def step_callback(step, step_history, placeholders, test_data_item): 267 def step_callback(step, step_history, placeholders, step_test):
268 assert step['name'] not in step_history, (
269 'Step "%s" is already in step_history!' % step['name'])
270 step_result = StepData(step, None)
271 step_history[step['name']] = step_result
272
272 followup_fn = step.pop('followup_fn', None) 273 followup_fn = step.pop('followup_fn', None)
273 274
274 def _inner(annotator_step, retcode): 275 def _inner(annotator_step, retcode):
275 step_result = StepData(step, retcode) 276 step_result._retcode = retcode # pylint: disable=W0212
276 if retcode > 0: 277 if retcode > 0:
277 step_result.presentation.status = 'FAILURE' 278 step_result.presentation.status = 'FAILURE'
278 279
279 step_history[step['name']] = step_result
280 annotator_step.annotation_stream.step_cursor(step['name']) 280 annotator_step.annotation_stream.step_cursor(step['name'])
281 if step_result.retcode != 0 and test_data_item is None: 281 if step_result.retcode != 0 and step_test.enabled:
282 # To avoid cluttering the expectations, don't emit this in testmode. 282 # To avoid cluttering the expectations, don't emit this in testmode.
283 annotator_step.emit('step returned non-zero exit code: %d' % 283 annotator_step.emit('step returned non-zero exit code: %d' %
284 step_result.retcode) 284 step_result.retcode)
285 285
286 call_placeholders(step_result, placeholders, test_data_item) 286 get_placeholder_results(step_result, placeholders)
287 287
288 if followup_fn: 288 if followup_fn:
289 followup_fn(step_result) 289 followup_fn(step_result)
290 290
291 step_result.presentation.finalize(annotator_step) 291 step_result.presentation.finalize(annotator_step)
292 return step_result 292 return step_result
293 if followup_fn: 293 if followup_fn:
294 _inner.__name__ = followup_fn.__name__ 294 _inner.__name__ = followup_fn.__name__
295 295
296 return _inner 296 return _inner
(...skipping 20 matching lines...) Expand all
317 def main(argv=None): 317 def main(argv=None):
318 opts, _ = get_args(argv) 318 opts, _ = get_args(argv)
319 319
320 stream = annotator.StructuredAnnotationStream(seed_steps=['setup_build']) 320 stream = annotator.StructuredAnnotationStream(seed_steps=['setup_build'])
321 321
322 ret = run_steps(stream, opts.build_properties, opts.factory_properties) 322 ret = run_steps(stream, opts.build_properties, opts.factory_properties)
323 return ret.status_code 323 return ret.status_code
324 324
325 325
326 def run_steps(stream, build_properties, factory_properties, 326 def run_steps(stream, build_properties, factory_properties,
327 api=recipe_api.CreateRecipeApi, test_data=None): 327 api=recipe_loader.CreateRecipeApi,
328 test=recipe_test_api.DisabledTestData()):
328 """Returns a tuple of (status_code, steps_ran). 329 """Returns a tuple of (status_code, steps_ran).
329 330
330 Only one of these values will be set at a time. This is mainly to support the 331 Only one of these values will be set at a time. This is mainly to support the
331 testing interface used by unittests/recipes_test.py. 332 testing interface used by unittests/recipes_test.py.
332
333 test_data should be a dictionary of step_name -> (retcode, json_data)
334 """ 333 """
335 stream.honor_zero_return_code() 334 stream.honor_zero_return_code()
336 MakeStepsRetval = collections.namedtuple('MakeStepsRetval', 335 MakeStepsRetval = collections.namedtuple('MakeStepsRetval',
337 'status_code steps_ran') 336 'status_code steps_ran')
338 337
339 # TODO(iannucci): Stop this when blamelist becomes sane data. 338 # TODO(iannucci): Stop this when blamelist becomes sane data.
340 if ('blamelist_real' in build_properties and 339 if ('blamelist_real' in build_properties and
341 'blamelist' in build_properties): 340 'blamelist' in build_properties):
342 build_properties['blamelist'] = build_properties['blamelist_real'] 341 build_properties['blamelist'] = build_properties['blamelist_real']
343 del build_properties['blamelist_real'] 342 del build_properties['blamelist_real']
344 343
345 step_history = collections.OrderedDict() 344 step_history = collections.OrderedDict()
346 with stream.step('setup_build') as s: 345 with stream.step('setup_build') as s:
347 assert 'recipe' in factory_properties 346 assert 'recipe' in factory_properties
348 recipe = factory_properties['recipe'] 347 recipe = factory_properties['recipe']
349
350 # If the recipe is specified as "module:recipe", then it is an recipe
351 # contained in a recipe_module as an example. Look for it in the modules
352 # imported by load_recipe_modules instead of the normal search paths.
353 if ':' in recipe:
354 module_name, recipe = recipe.split(':')
355 assert recipe.endswith('example')
356 RECIPE_MODULES = recipe_api.load_recipe_modules(MODULE_DIRS)
357 try:
358 recipe_module = getattr(getattr(RECIPE_MODULES, module_name), recipe)
359 except AttributeError:
360 s.step_text('recipe not found')
361 s.step_failure()
362 return MakeStepsRetval(2, None)
363
364 else:
365 recipe_dirs = (os.path.abspath(p) for p in (
366 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal',
367 'scripts', 'slave-internal', 'recipes'),
368 os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal',
369 'scripts', 'slave', 'recipes'),
370 os.path.join(SCRIPT_PATH, 'recipes'),
371 ))
372
373 for recipe_path in (os.path.join(p, recipe) for p in recipe_dirs):
374 recipe_module = chromium_utils.IsolatedImportFromPath(recipe_path)
375 if recipe_module:
376 break
377 else:
378 s.step_text('recipe not found')
379 s.step_failure()
380 return MakeStepsRetval(2, None)
381
382 properties = factory_properties.copy() 348 properties = factory_properties.copy()
383 properties.update(build_properties) 349 properties.update(build_properties)
384 stream.emit('Running recipe with %s' % (properties,)) 350 try:
385 steps = recipe_module.GenSteps(api(recipe_module.DEPS, 351 recipe_module = recipe_loader.LoadRecipe(recipe)
386 mod_dirs=MODULE_DIRS, 352 stream.emit('Running recipe with %s' % (properties,))
387 properties=properties, 353 steps = recipe_module.GenSteps(api(recipe_module.DEPS,
388 step_history=step_history)) 354 properties=properties,
389 assert inspect.isgenerator(steps) 355 step_history=step_history))
356 assert inspect.isgenerator(steps)
357 except recipe_loader.NoSuchRecipe as e:
358 s.step_text('recipe not found: %s' % e)
359 s.step_failure()
360 return MakeStepsRetval(2, None)
361
390 362
391 # Execute annotator.py with steps if specified. 363 # Execute annotator.py with steps if specified.
392 # annotator.py handles the seeding, execution, and annotation of each step. 364 # annotator.py handles the seeding, execution, and annotation of each step.
393 failed = False 365 failed = False
394 366
395 test_mode = test_data is not None
396
397 for step in ensure_sequence_of_steps(steps): 367 for step in ensure_sequence_of_steps(steps):
398 if failed and not step.get('always_run', False): 368 if failed and not step.get('always_run', False):
399 step_result = StepData(step, None) 369 step_result = StepData(step, None)
400 step_history[step['name']] = step_result 370 step_history[step['name']] = step_result
401 continue 371 continue
402 372
403 test_data_item = test_data.pop(step['name'], {}) if test_mode else None 373 if test.enabled:
404 placeholders = render_step(step, test_data_item) 374 step_test = step.pop('default_step_data', recipe_api.StepTestData())
375 step_test += test.step_data.pop(step['name'], recipe_api.StepTestData())
376 else:
377 step_test = recipe_api.DisabledTestData()
378 step.pop('default_step_data', None)
405 379
406 assert step['name'] not in step_history, ( 380 placeholders = render_step(step, step_test)
407 'Step "%s" is already in step_history!' % step['name'])
408 381
409 callback = step_callback(step, step_history, placeholders, test_data_item) 382 callback = step_callback(step, step_history, placeholders, step_test)
410 383
411 if not test_mode: 384 if not test.enabled:
412 step_result = annotator.run_step( 385 step_result = annotator.run_step(
413 stream, followup_fn=callback, **step) 386 stream, followup_fn=callback, **step)
414 else: 387 else:
415 with stream.step(step['name']) as s: 388 with stream.step(step['name']) as s:
416 s.stream = cStringIO.StringIO() 389 s.stream = cStringIO.StringIO()
417 step_result = callback(s, test_data_item.pop('$R', 0)) 390 step_result = callback(s, step_test.retcode)
418 lines = filter(None, s.stream.getvalue().splitlines()) 391 lines = filter(None, s.stream.getvalue().splitlines())
419 if lines: 392 if lines:
420 # Note that '~' sorts after 'z' so that this will be last on each 393 # Note that '~' sorts after 'z' so that this will be last on each
421 # step. Also use _step to get access to the mutable step dictionary. 394 # step. Also use _step to get access to the mutable step dictionary.
422 # pylint: disable=W0212 395 # pylint: disable=W0212
423 step_result._step['~followup_annotations'] = lines 396 step_result._step['~followup_annotations'] = lines
424 397
425 # TODO(iannucci): Pull this failure calculation into callback. 398 # TODO(iannucci): Pull this failure calculation into callback.
426 failed = annotator.update_build_failure(failed, step_result.retcode, **step) 399 failed = annotator.update_build_failure(failed, step_result.retcode, **step)
427 400
428 assert not test_mode or test_data == {}, ( 401 assert not test.enabled or not test.step_data, (
429 "Unconsumed test data! %s" % (test_data,)) 402 "Unconsumed test data! %s" % (test.step_data,))
430 403
431 return MakeStepsRetval(0 if not failed else 1, step_history) 404 return MakeStepsRetval(0 if not failed else 1, step_history)
432 405
433 406
434 def UpdateScripts(): 407 def UpdateScripts():
435 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'): 408 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'):
436 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS') 409 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS')
437 return False 410 return False
438 stream = annotator.StructuredAnnotationStream(seed_steps=['update_scripts']) 411 stream = annotator.StructuredAnnotationStream(seed_steps=['update_scripts'])
439 with stream.step('update_scripts') as s: 412 with stream.step('update_scripts') as s:
(...skipping 11 matching lines...) Expand all
451 424
452 def shell_main(argv): 425 def shell_main(argv):
453 if UpdateScripts(): 426 if UpdateScripts():
454 return subprocess.call([sys.executable] + argv) 427 return subprocess.call([sys.executable] + argv)
455 else: 428 else:
456 return main(argv) 429 return main(argv)
457 430
458 431
459 if __name__ == '__main__': 432 if __name__ == '__main__':
460 sys.exit(shell_main(sys.argv)) 433 sys.exit(shell_main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | scripts/slave/recipe_api.py » ('j') | scripts/slave/recipe_modules/path/test_api.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698