| 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 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 90 """Constructs an OpenStep object which can be used to actually run a step. | 90 """Constructs an OpenStep object which can be used to actually run a step. |
| 91 | 91 |
| 92 step_dict parameters: | 92 step_dict parameters: |
| 93 name: name of the step, will appear in buildbots waterfall | 93 name: name of the step, will appear in buildbots waterfall |
| 94 cmd: command to run, list of one or more strings | 94 cmd: command to run, list of one or more strings |
| 95 cwd: absolute path to working directory for the command | 95 cwd: absolute path to working directory for the command |
| 96 env: dict with overrides for environment variables | 96 env: dict with overrides for environment variables |
| 97 allow_subannotations: if True, lets the step emit its own annotations | 97 allow_subannotations: if True, lets the step emit its own annotations |
| 98 trigger_specs: a list of trigger specifications, see also _trigger_builds. | 98 trigger_specs: a list of trigger specifications, see also _trigger_builds. |
| 99 stdout: Path to a file to put step stdout into. If used, stdout won't | 99 stdout: Path to a file to put step stdout into. If used, stdout won't |
| 100 appear in annotator's stdout (and |allow_subannotations| is | 100 appear in annotator's stdout (and |allow_subannotations| is |
| 101 ignored). | 101 ignored). |
| 102 stderr: Path to a file to put step stderr into. If used, stderr won't | 102 stderr: Path to a file to put step stderr into. If used, stderr won't |
| 103 appear in annotator's stderr. | 103 appear in annotator's stderr. |
| 104 stdin: Path to a file to read step stdin from. | 104 stdin: Path to a file to read step stdin from. |
| 105 step_nest_level: the step's nesting level. |
| 105 | 106 |
| 106 Returns an OpenStep object. | 107 Returns an OpenStep object. |
| 107 """ | 108 """ |
| 108 raise NotImplementedError() | 109 raise NotImplementedError() |
| 109 | 110 |
| 110 def run_recipe(self, universe, recipe, properties): | 111 def run_recipe(self, universe, recipe, properties): |
| 111 """Run the recipe named |recipe|. | 112 """Run the recipe named |recipe|. |
| 112 | 113 |
| 113 Args: | 114 Args: |
| 114 universe: The RecipeUniverse where the recipe lives. | 115 universe: The RecipeUniverse where the recipe lives. |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 157 | 158 |
| 158 def __init__(self, stream_engine): | 159 def __init__(self, stream_engine): |
| 159 self._stream_engine = stream_engine | 160 self._stream_engine = stream_engine |
| 160 | 161 |
| 161 @property | 162 @property |
| 162 def stream_engine(self): | 163 def stream_engine(self): |
| 163 return self._stream_engine | 164 return self._stream_engine |
| 164 | 165 |
| 165 def open_step(self, step_dict): | 166 def open_step(self, step_dict): |
| 166 allow_subannotations = step_dict.get('allow_subannotations', False) | 167 allow_subannotations = step_dict.get('allow_subannotations', False) |
| 168 nest_level = step_dict.pop('step_nest_level', 0) |
| 169 |
| 167 step_stream = self._stream_engine.new_step_stream( | 170 step_stream = self._stream_engine.new_step_stream( |
| 168 step_dict['name'], | 171 step_dict['name'], |
| 169 allow_subannotations=allow_subannotations) | 172 allow_subannotations=allow_subannotations, |
| 173 nest_level=nest_level) |
| 170 if not step_dict.get('cmd'): | 174 if not step_dict.get('cmd'): |
| 171 class EmptyOpenStep(OpenStep): | 175 class EmptyOpenStep(OpenStep): |
| 172 def run(inner): | 176 def run(inner): |
| 173 if 'trigger_specs' in step_dict: | 177 if 'trigger_specs' in step_dict: |
| 174 self._trigger_builds(step_stream, step_dict['trigger_specs']) | 178 self._trigger_builds(step_stream, step_dict['trigger_specs']) |
| 175 return types.StepData(step_dict, 0) | 179 return types.StepData(step_dict, 0) |
| 176 | 180 |
| 177 def finalize(inner): | 181 def finalize(inner): |
| 178 step_stream.close() | 182 step_stream.close() |
| 179 | 183 |
| 180 @property | 184 @property |
| 181 def stream(inner): | 185 def stream(inner): |
| 182 return step_stream | 186 return step_stream |
| 183 | 187 |
| 184 return EmptyOpenStep() | 188 return EmptyOpenStep() |
| 185 | 189 |
| 186 step_dict, placeholders = render_step( | 190 step_dict, placeholders = render_step( |
| 187 step_dict, recipe_test_api.DisabledTestData()) | 191 step_dict, recipe_test_api.DisabledTestData()) |
| 188 cmd = map(str, step_dict['cmd']) | 192 cmd = map(str, step_dict['cmd']) |
| 189 step_env = _merge_envs(os.environ, step_dict.get('env', {})) | 193 step_env = _merge_envs(os.environ, step_dict.get('env', {})) |
| 190 if 'nest_level' in step_dict: | |
| 191 step_stream.step_nest_level(step_dict['nest_level']) | |
| 192 self._print_step(step_stream, step_dict, step_env) | 194 self._print_step(step_stream, step_dict, step_env) |
| 193 | 195 |
| 194 class ReturnOpenStep(OpenStep): | 196 class ReturnOpenStep(OpenStep): |
| 195 def run(inner): | 197 def run(inner): |
| 196 try: | 198 try: |
| 197 # Open file handles for IO redirection based on file names in | 199 # Open file handles for IO redirection based on file names in |
| 198 # step_dict. | 200 # step_dict. |
| 199 handles = { | 201 handles = { |
| 200 'stdout': step_stream, | 202 'stdout': step_stream, |
| 201 'stderr': step_stream, | 203 'stderr': step_stream, |
| (...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 392 return change | 394 return change |
| 393 | 395 |
| 394 | 396 |
| 395 class SimulationStepRunner(StepRunner): | 397 class SimulationStepRunner(StepRunner): |
| 396 """Pretends to run steps, instead recording what would have been run. | 398 """Pretends to run steps, instead recording what would have been run. |
| 397 | 399 |
| 398 This is the main workhorse of recipes.py simulation_test. Returns the log of | 400 This is the main workhorse of recipes.py simulation_test. Returns the log of |
| 399 steps that would have been run in steps_ran. Uses test_data to mock return | 401 steps that would have been run in steps_ran. Uses test_data to mock return |
| 400 values. | 402 values. |
| 401 """ | 403 """ |
| 402 def __init__(self, stream_engine, test_data): | 404 def __init__(self, stream_engine, test_data, annotator): |
| 403 self._test_data = test_data | 405 self._test_data = test_data |
| 404 self._stream_engine = stream_engine | 406 self._stream_engine = stream_engine |
| 407 self._annotator = annotator |
| 405 self._step_history = collections.OrderedDict() | 408 self._step_history = collections.OrderedDict() |
| 406 | 409 |
| 407 @property | 410 @property |
| 408 def stream_engine(self): | 411 def stream_engine(self): |
| 409 return self._stream_engine | 412 return self._stream_engine |
| 410 | 413 |
| 411 def open_step(self, step_dict): | 414 def open_step(self, step_dict): |
| 412 # We modify step_dict. In particular, we add ~followup_annotations during | 415 # We modify step_dict. In particular, we add ~followup_annotations during |
| 413 # finalize, and depend on that side-effect being carried into what we | 416 # finalize, and depend on that side-effect being carried into what we |
| 414 # added to self._step_history, earlier. So copy it here so at least we | 417 # added to self._step_history, earlier. So copy it here so at least we |
| 415 # keep the modifications local. | 418 # keep the modifications local. |
| 416 step_dict = dict(step_dict) | 419 step_dict = dict(step_dict) |
| 420 nest_level = step_dict.pop('step_nest_level', 0) |
| 417 | 421 |
| 418 test_data_fn = step_dict.pop('step_test_data', recipe_test_api.StepTestData) | 422 test_data_fn = step_dict.pop('step_test_data', recipe_test_api.StepTestData) |
| 419 step_test = self._test_data.pop_step_test_data( | 423 step_test = self._test_data.pop_step_test_data( |
| 420 step_dict['name'], test_data_fn) | 424 step_dict['name'], test_data_fn) |
| 421 step_dict, placeholders = render_step(step_dict, step_test) | 425 step_dict, placeholders = render_step(step_dict, step_test) |
| 422 outstream = StringIO.StringIO() | |
| 423 | 426 |
| 424 # Layer the simulation step on top of the given stream engine. | 427 # Layer the simulation step on top of the given stream engine. |
| 425 step_stream = stream.ProductStreamEngine.StepStream( | 428 step_stream = self._stream_engine.new_step_stream( |
| 426 self._stream_engine.new_step_stream(step_dict['name']), | 429 step_dict['name'], |
| 427 stream.BareAnnotationStepStream(outstream)) | 430 nest_level=nest_level) |
| 428 | 431 |
| 429 class ReturnOpenStep(OpenStep): | 432 class ReturnOpenStep(OpenStep): |
| 430 def run(inner): | 433 def run(inner): |
| 431 timeout = step_dict.get('timeout') | 434 timeout = step_dict.get('timeout') |
| 432 if (timeout and step_test.times_out_after and | 435 if (timeout and step_test.times_out_after and |
| 433 step_test.times_out_after > timeout): | 436 step_test.times_out_after > timeout): |
| 434 raise recipe_api.StepTimeout(step_dict['name'], timeout) | 437 raise recipe_api.StepTimeout(step_dict['name'], timeout) |
| 435 | 438 |
| 436 self._step_history[step_dict['name']] = step_dict | 439 self._step_history[step_dict['name']] = step_dict |
| 437 return construct_step_result(step_dict, step_test.retcode, placeholders) | 440 return construct_step_result(step_dict, step_test.retcode, placeholders) |
| 438 | 441 |
| 439 def finalize(inner): | 442 def finalize(inner): |
| 440 # note that '~' sorts after 'z' so that this will be last on each | 443 # note that '~' sorts after 'z' so that this will be last on each |
| 441 # step. also use _step to get access to the mutable step | 444 # step. also use _step to get access to the mutable step |
| 442 # dictionary. | 445 # dictionary. |
| 443 lines = filter(None, outstream.getvalue()).splitlines() | 446 buf = self._annotator.step_buffer(step_dict['name']) |
| 447 lines = filter(None, buf.getvalue()).splitlines() |
| 444 lines = [stream.encode_str(x) for x in lines] | 448 lines = [stream.encode_str(x) for x in lines] |
| 445 if lines: | 449 if lines: |
| 446 # This magically floats into step_history, which we have already | 450 # This magically floats into step_history, which we have already |
| 447 # added step_dict to. | 451 # added step_dict to. |
| 448 step_dict['~followup_annotations'] = lines | 452 step_dict['~followup_annotations'] = lines |
| 453 step_stream.close() |
| 449 | 454 |
| 450 @property | 455 @property |
| 451 def stream(inner): | 456 def stream(inner): |
| 452 return step_stream | 457 return step_stream |
| 453 | 458 |
| 454 return ReturnOpenStep() | 459 return ReturnOpenStep() |
| 455 | 460 |
| 456 def run_recipe(self, universe, recipe, properties): | 461 def run_recipe(self, universe, recipe, properties): |
| 457 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties),
)) | 462 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties),
)) |
| 458 | 463 |
| (...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 656 supplied command, and only uses the |env| kwarg for modifying the environment | 661 supplied command, and only uses the |env| kwarg for modifying the environment |
| 657 of the child process. | 662 of the child process. |
| 658 """ | 663 """ |
| 659 saved_path = os.environ['PATH'] | 664 saved_path = os.environ['PATH'] |
| 660 try: | 665 try: |
| 661 if path is not None: | 666 if path is not None: |
| 662 os.environ['PATH'] = path | 667 os.environ['PATH'] = path |
| 663 yield | 668 yield |
| 664 finally: | 669 finally: |
| 665 os.environ['PATH'] = saved_path | 670 os.environ['PATH'] = saved_path |
| OLD | NEW |