OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 |
OLD | NEW |