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.builder_name | 357 builder_name = trig.get('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.buildbot_changes or [] | 361 changes = trig.get('buildbot_changes', []) |
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.bucket, | 367 'bucket': trig.get('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.critical, | 370 'critical': trig.get('critical', True), |
371 'properties': trig.properties, | 371 'properties': trig.get('properties'), |
372 'tags': trig.tags, | 372 'tags': trig.get('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 |
396 def __init__(self, stream_engine, test_data, annotator): | 405 def __init__(self, stream_engine, test_data, annotator): |
397 self._test_data = test_data | 406 self._test_data = test_data |
398 self._stream_engine = stream_engine | 407 self._stream_engine = stream_engine |
399 self._annotator = annotator | 408 self._annotator = annotator |
400 self._step_history = collections.OrderedDict() | 409 self._step_history = collections.OrderedDict() |
401 | 410 |
402 @property | 411 @property |
403 def stream_engine(self): | 412 def stream_engine(self): |
404 return self._stream_engine | 413 return self._stream_engine |
405 | 414 |
(...skipping 17 matching lines...) Expand all Loading... |
423 # Install a placeholder for order. | 432 # Install a placeholder for order. |
424 self._step_history[rendered_step.config.name] = None | 433 self._step_history[rendered_step.config.name] = None |
425 return construct_step_result(rendered_step, step_test.retcode) | 434 return construct_step_result(rendered_step, step_test.retcode) |
426 | 435 |
427 def finalize(inner): | 436 def finalize(inner): |
428 rs = rendered_step | 437 rs = rendered_step |
429 | 438 |
430 # note that '~' sorts after 'z' so that this will be last on each | 439 # note that '~' sorts after 'z' so that this will be last on each |
431 # step. also use _step to get access to the mutable step | 440 # step. also use _step to get access to the mutable step |
432 # dictionary. | 441 # dictionary. |
433 buf = self._annotator.step_buffer(rs.config.name) | 442 buf = self._annotator.step_buffer(rendered_step.config.name) |
434 lines = filter(None, buf.getvalue()).splitlines() | 443 lines = filter(None, buf.getvalue()).splitlines() |
435 lines = [stream.encode_str(x) for x in lines] | 444 lines = [stream.encode_str(x) for x in lines] |
436 if lines: | 445 if lines: |
437 # This magically floats into step_history, which we have already | 446 # This magically floats into step_history, which we have already |
438 # added step_config to. | 447 # added step_config to. |
439 rs = rs._replace(followup_annotations=lines) | 448 rs = rs._replace(followup_annotations=lines) |
440 step_stream.close() | 449 step_stream.close() |
441 self._step_history[rs.config.name] = rs | 450 self._step_history[rendered_step.config.name] = rs |
442 | 451 |
443 @property | 452 @property |
444 def stream(inner): | 453 def stream(inner): |
445 return step_stream | 454 return step_stream |
446 | 455 |
447 return ReturnOpenStep() | 456 return ReturnOpenStep() |
448 | 457 |
449 def run_recipe(self, universe, recipe, properties): | 458 def run_recipe(self, universe, recipe, properties): |
450 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties),
)) | 459 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties),
)) |
451 | 460 |
452 @contextlib.contextmanager | 461 @contextlib.contextmanager |
453 def run_context(self): | 462 def run_context(self): |
454 try: | 463 try: |
455 yield | 464 yield |
456 except Exception as ex: | 465 except Exception as ex: |
457 with self._test_data.should_raise_exception(ex) as should_raise: | 466 with self._test_data.should_raise_exception(ex) as should_raise: |
458 if should_raise: | 467 if should_raise: |
459 raise | 468 raise |
460 | 469 |
461 assert self._test_data.consumed, ( | 470 assert self._test_data.consumed, ( |
462 "Unconsumed test data for steps: %s, (exception %s)" % ( | 471 "Unconsumed test data for steps: %s, (exception %s)" % ( |
463 self._test_data.step_data.keys(), | 472 self._test_data.step_data.keys(), |
464 self._test_data.expected_exception)) | 473 self._test_data.expected_exception)) |
465 | 474 |
466 def _rendered_step_to_dict(self, rs): | 475 def _rendered_step_to_dict(self, rs): |
467 d = rs.config.render_to_dict() | 476 d = dict((k, v) for k, v in rs.config._asdict().iteritems() |
| 477 if v and k not in self._STEP_CONFIG_RENDER_BLACKLIST) |
468 if rs.followup_annotations: | 478 if rs.followup_annotations: |
469 d['~followup_annotations'] = rs.followup_annotations | 479 d['~followup_annotations'] = rs.followup_annotations |
470 return d | 480 return d |
471 | 481 |
472 @property | 482 @property |
473 def steps_ran(self): | 483 def steps_ran(self): |
474 return [self._rendered_step_to_dict(rs) | 484 return [self._rendered_step_to_dict(rs) |
475 for rs in self._step_history.itervalues()] | 485 for rs in self._step_history.itervalues()] |
476 | 486 |
477 | 487 |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
670 supplied command, and only uses the |env| kwarg for modifying the environment | 680 supplied command, and only uses the |env| kwarg for modifying the environment |
671 of the child process. | 681 of the child process. |
672 """ | 682 """ |
673 saved_path = os.environ['PATH'] | 683 saved_path = os.environ['PATH'] |
674 try: | 684 try: |
675 if path is not None: | 685 if path is not None: |
676 os.environ['PATH'] = path | 686 os.environ['PATH'] = path |
677 yield | 687 yield |
678 finally: | 688 finally: |
679 os.environ['PATH'] = saved_path | 689 os.environ['PATH'] = saved_path |
OLD | NEW |