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 |