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

Side by Side Diff: recipe_engine/step_runner.py

Issue 2332833003: Add better documentation, trigger namedtuple. (Closed)
Patch Set: More cleanups. Created 4 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
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 import StringIO 5 import StringIO
6 import collections 6 import collections
7 import contextlib 7 import contextlib
8 import datetime 8 import datetime
9 import json 9 import json
10 import os 10 import os
(...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after
347 for line in buf.get_buffered(): 347 for line in buf.get_buffered():
348 outstreams[pipe].write_line(line) 348 outstreams[pipe].write_line(line)
349 else: 349 else:
350 proc.wait(timeout) 350 proc.wait(timeout)
351 351
352 return proc.returncode 352 return proc.returncode
353 353
354 def _trigger_builds(self, step, trigger_specs): 354 def _trigger_builds(self, step, trigger_specs):
355 assert trigger_specs is not None 355 assert trigger_specs is not None
356 for trig in trigger_specs: 356 for trig in trigger_specs:
357 builder_name = trig.get('builder_name') 357 builder_name = trig.builder_name
358 if not builder_name: 358 if not builder_name:
359 raise ValueError('Trigger spec: builder_name is not set') 359 raise ValueError('Trigger spec: builder_name is not set')
360 360
361 changes = trig.get('buildbot_changes', []) 361 changes = trig.buildbot_changes or []
362 assert isinstance(changes, list), 'buildbot_changes must be a list' 362 assert isinstance(changes, list), 'buildbot_changes must be a list'
363 changes = map(self._normalize_change, changes) 363 changes = map(self._normalize_change, changes)
364 364
365 step.trigger(json.dumps({ 365 step.trigger(json.dumps({
366 'builderNames': [builder_name], 366 'builderNames': [builder_name],
367 'bucket': trig.get('bucket'), 367 'bucket': trig.bucket,
368 'changes': changes, 368 'changes': changes,
369 # if True and triggering fails asynchronously, fail entire build. 369 # if True and triggering fails asynchronously, fail entire build.
370 'critical': trig.get('critical', True), 370 'critical': trig.critical,
371 'properties': trig.get('properties'), 371 'properties': trig.properties,
372 'tags': trig.get('tags'), 372 'tags': trig.tags,
373 }, sort_keys=True)) 373 }, sort_keys=True))
374 374
375 def _normalize_change(self, change): 375 def _normalize_change(self, change):
376 assert isinstance(change, dict), 'Change is not a dict' 376 assert isinstance(change, dict), 'Change is not a dict'
377 change = change.copy() 377 change = change.copy()
378 378
379 # Convert when_timestamp to UNIX timestamp. 379 # Convert when_timestamp to UNIX timestamp.
380 when = change.get('when_timestamp') 380 when = change.get('when_timestamp')
381 if isinstance(when, datetime.datetime): 381 if isinstance(when, datetime.datetime):
382 when = calendar.timegm(when.utctimetuple()) 382 when = calendar.timegm(when.utctimetuple())
383 change['when_timestamp'] = when 383 change['when_timestamp'] = when
384 384
385 return change 385 return change
386 386
387 387
388 class SimulationStepRunner(StepRunner): 388 class SimulationStepRunner(StepRunner):
389 """Pretends to run steps, instead recording what would have been run. 389 """Pretends to run steps, instead recording what would have been run.
390 390
391 This is the main workhorse of recipes.py simulation_test. Returns the log of 391 This is the main workhorse of recipes.py simulation_test. Returns the log of
392 steps that would have been run in steps_ran. Uses test_data to mock return 392 steps that would have been run in steps_ran. Uses test_data to mock return
393 values. 393 values.
394 """ 394 """
395 395
396 # List of attributes in a recipe_api.StepConfig to omit when rendering
397 # step history.
398 _STEP_CONFIG_RENDER_BLACKLIST = set((
399 'nest_level',
400 'ok_ret',
401 'infra_step',
402 'step_test_data',
403 ))
404
405 def __init__(self, stream_engine, test_data, annotator): 396 def __init__(self, stream_engine, test_data, annotator):
406 self._test_data = test_data 397 self._test_data = test_data
407 self._stream_engine = stream_engine 398 self._stream_engine = stream_engine
408 self._annotator = annotator 399 self._annotator = annotator
409 self._step_history = collections.OrderedDict() 400 self._step_history = collections.OrderedDict()
410 401
411 @property 402 @property
412 def stream_engine(self): 403 def stream_engine(self):
413 return self._stream_engine 404 return self._stream_engine
414 405
(...skipping 17 matching lines...) Expand all
432 # Install a placeholder for order. 423 # Install a placeholder for order.
433 self._step_history[rendered_step.config.name] = None 424 self._step_history[rendered_step.config.name] = None
434 return construct_step_result(rendered_step, step_test.retcode) 425 return construct_step_result(rendered_step, step_test.retcode)
435 426
436 def finalize(inner): 427 def finalize(inner):
437 rs = rendered_step 428 rs = rendered_step
438 429
439 # note that '~' sorts after 'z' so that this will be last on each 430 # note that '~' sorts after 'z' so that this will be last on each
440 # step. also use _step to get access to the mutable step 431 # step. also use _step to get access to the mutable step
441 # dictionary. 432 # dictionary.
442 buf = self._annotator.step_buffer(rendered_step.config.name) 433 buf = self._annotator.step_buffer(rs.config.name)
443 lines = filter(None, buf.getvalue()).splitlines() 434 lines = filter(None, buf.getvalue()).splitlines()
444 lines = [stream.encode_str(x) for x in lines] 435 lines = [stream.encode_str(x) for x in lines]
445 if lines: 436 if lines:
446 # This magically floats into step_history, which we have already 437 # This magically floats into step_history, which we have already
447 # added step_config to. 438 # added step_config to.
448 rs = rs._replace(followup_annotations=lines) 439 rs = rs._replace(followup_annotations=lines)
449 step_stream.close() 440 step_stream.close()
450 self._step_history[rendered_step.config.name] = rs 441 self._step_history[rs.config.name] = rs
451 442
452 @property 443 @property
453 def stream(inner): 444 def stream(inner):
454 return step_stream 445 return step_stream
455 446
456 return ReturnOpenStep() 447 return ReturnOpenStep()
457 448
458 def run_recipe(self, universe, recipe, properties): 449 def run_recipe(self, universe, recipe, properties):
459 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties), )) 450 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties), ))
460 451
461 @contextlib.contextmanager 452 @contextlib.contextmanager
462 def run_context(self): 453 def run_context(self):
463 try: 454 try:
464 yield 455 yield
465 except Exception as ex: 456 except Exception as ex:
466 with self._test_data.should_raise_exception(ex) as should_raise: 457 with self._test_data.should_raise_exception(ex) as should_raise:
467 if should_raise: 458 if should_raise:
468 raise 459 raise
469 460
470 assert self._test_data.consumed, ( 461 assert self._test_data.consumed, (
471 "Unconsumed test data for steps: %s, (exception %s)" % ( 462 "Unconsumed test data for steps: %s, (exception %s)" % (
472 self._test_data.step_data.keys(), 463 self._test_data.step_data.keys(),
473 self._test_data.expected_exception)) 464 self._test_data.expected_exception))
474 465
475 def _rendered_step_to_dict(self, rs): 466 def _rendered_step_to_dict(self, rs):
476 d = dict((k, v) for k, v in rs.config._asdict().iteritems() 467 d = rs.config.render_to_dict()
477 if v and k not in self._STEP_CONFIG_RENDER_BLACKLIST)
478 if rs.followup_annotations: 468 if rs.followup_annotations:
479 d['~followup_annotations'] = rs.followup_annotations 469 d['~followup_annotations'] = rs.followup_annotations
480 return d 470 return d
481 471
482 @property 472 @property
483 def steps_ran(self): 473 def steps_ran(self):
484 return [self._rendered_step_to_dict(rs) 474 return [self._rendered_step_to_dict(rs)
485 for rs in self._step_history.itervalues()] 475 for rs in self._step_history.itervalues()]
486 476
487 477
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after
680 supplied command, and only uses the |env| kwarg for modifying the environment 670 supplied command, and only uses the |env| kwarg for modifying the environment
681 of the child process. 671 of the child process.
682 """ 672 """
683 saved_path = os.environ['PATH'] 673 saved_path = os.environ['PATH']
684 try: 674 try:
685 if path is not None: 675 if path is not None:
686 os.environ['PATH'] = path 676 os.environ['PATH'] = path
687 yield 677 yield
688 finally: 678 finally:
689 os.environ['PATH'] = saved_path 679 os.environ['PATH'] = saved_path
OLDNEW
« recipe_engine/recipe_api.py ('K') | « recipe_engine/run.py ('k') | recipe_engine/stream.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698