| 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 |