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