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 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
79 pretend to run the steps with mock output (SimulationStepRunner). | 79 pretend to run the steps with mock output (SimulationStepRunner). |
80 """ | 80 """ |
81 @property | 81 @property |
82 def stream_engine(self): | 82 def stream_engine(self): |
83 """Return the stream engine that this StepRunner uses, if meaningful. | 83 """Return the stream engine that this StepRunner uses, if meaningful. |
84 | 84 |
85 Users of this method must be prepared to handle None. | 85 Users of this method must be prepared to handle None. |
86 """ | 86 """ |
87 return None | 87 return None |
88 | 88 |
89 def open_step(self, step_dict): | 89 def open_step(self, step_config): |
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 Args: |
93 name: name of the step, will appear in buildbots waterfall | 93 step_config (StepConfig): The step data. |
94 cmd: command to run, list of one or more strings | |
95 cwd: absolute path to working directory for the command | |
96 env: dict with overrides for environment variables | |
97 allow_subannotations: if True, lets the step emit its own annotations | |
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 | |
100 appear in annotator's stdout (and |allow_subannotations| is | |
101 ignored). | |
102 stderr: Path to a file to put step stderr into. If used, stderr won't | |
103 appear in annotator's stderr. | |
104 stdin: Path to a file to read step stdin from. | |
105 step_nest_level: the step's nesting level. | |
106 | 94 |
107 Returns an OpenStep object. | 95 Returns: an OpenStep object. |
108 """ | 96 """ |
109 raise NotImplementedError() | 97 raise NotImplementedError() |
110 | 98 |
111 def run_recipe(self, universe, recipe, properties): | 99 def run_recipe(self, universe, recipe, properties): |
112 """Run the recipe named |recipe|. | 100 """Run the recipe named |recipe|. |
113 | 101 |
114 Args: | 102 Args: |
115 universe: The RecipeUniverse where the recipe lives. | 103 universe: The RecipeUniverse where the recipe lives. |
116 recipe: The recipe name (e.g. 'infra/luci_py') | 104 recipe: The recipe name (e.g. 'infra/luci_py') |
117 properties: a dictionary of properties to pass to the recipe | 105 properties: a dictionary of properties to pass to the recipe |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
156 """Responsible for actually running steps as subprocesses, filtering their | 144 """Responsible for actually running steps as subprocesses, filtering their |
157 output into a stream.""" | 145 output into a stream.""" |
158 | 146 |
159 def __init__(self, stream_engine): | 147 def __init__(self, stream_engine): |
160 self._stream_engine = stream_engine | 148 self._stream_engine = stream_engine |
161 | 149 |
162 @property | 150 @property |
163 def stream_engine(self): | 151 def stream_engine(self): |
164 return self._stream_engine | 152 return self._stream_engine |
165 | 153 |
166 def open_step(self, step_dict): | 154 def open_step(self, step_config): |
167 allow_subannotations = step_dict.get('allow_subannotations', False) | 155 step_stream = self._stream_engine.new_step_stream(step_config) |
168 nest_level = step_dict.pop('step_nest_level', 0) | 156 if not step_config.cmd: |
169 | |
170 step_stream = self._stream_engine.new_step_stream( | |
171 step_dict['name'], | |
172 allow_subannotations=allow_subannotations, | |
173 nest_level=nest_level) | |
174 if not step_dict.get('cmd'): | |
175 class EmptyOpenStep(OpenStep): | 157 class EmptyOpenStep(OpenStep): |
176 def run(inner): | 158 def run(inner): |
177 if 'trigger_specs' in step_dict: | 159 if step_config.trigger_specs: |
178 self._trigger_builds(step_stream, step_dict['trigger_specs']) | 160 self._trigger_builds(step_stream, step_config.trigger_specs) |
179 return types.StepData(step_dict, 0) | 161 return types.StepData(step_config, 0) |
180 | 162 |
181 def finalize(inner): | 163 def finalize(inner): |
182 step_stream.close() | 164 step_stream.close() |
183 | 165 |
184 @property | 166 @property |
185 def stream(inner): | 167 def stream(inner): |
186 return step_stream | 168 return step_stream |
187 | 169 |
188 return EmptyOpenStep() | 170 return EmptyOpenStep() |
189 | 171 |
190 step_dict, placeholders = render_step( | 172 rendered_step = render_step( |
191 step_dict, recipe_test_api.DisabledTestData()) | 173 step_config, recipe_test_api.DisabledTestData() |
192 cmd = map(str, step_dict['cmd']) | 174 ) |
193 step_env = _merge_envs(os.environ, step_dict.get('env', {})) | 175 step_config = None # Make sure we use rendered step config. |
194 self._print_step(step_stream, step_dict, step_env) | 176 |
177 rendered_step = rendered_step._replace( | |
178 config=rendered_step.config._replace( | |
179 cmd=map(str, rendered_step.config.cmd), | |
180 ), | |
181 ) | |
182 | |
183 step_env = _merge_envs(os.environ, (rendered_step.config.env or {})) | |
184 self._print_step(step_stream, rendered_step, step_env) | |
195 | 185 |
196 class ReturnOpenStep(OpenStep): | 186 class ReturnOpenStep(OpenStep): |
197 def run(inner): | 187 def run(inner): |
188 step_config = rendered_step.config | |
198 try: | 189 try: |
199 # Open file handles for IO redirection based on file names in | 190 # Open file handles for IO redirection based on file names in |
200 # step_dict. | 191 # step_config. |
201 handles = { | 192 handles = { |
202 'stdout': step_stream, | 193 'stdout': step_stream, |
203 'stderr': step_stream, | 194 'stderr': step_stream, |
204 'stdin': None, | 195 'stdin': None, |
205 } | 196 } |
206 for key in handles: | 197 for key in handles: |
207 fileName = step_dict.get(key) | 198 fileName = getattr(step_config, key) |
208 if fileName: | 199 if fileName: |
209 handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb') | 200 handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb') |
210 # The subprocess will inherit and close these handles. | 201 # The subprocess will inherit and close these handles. |
211 retcode = self._run_cmd( | 202 retcode = self._run_cmd( |
212 cmd=cmd, timeout=step_dict.get('timeout'), handles=handles, | 203 cmd=step_config.cmd, timeout=step_config.timeout, handles=handles, |
213 env=step_env, cwd=step_dict.get('cwd')) | 204 env=step_env, cwd=step_config.cwd) |
214 except subprocess42.TimeoutExpired as e: | 205 except subprocess42.TimeoutExpired as e: |
215 # TODO(martiniss): mark as exception? | 206 # TODO(martiniss): mark as exception? |
216 raise recipe_api.StepTimeout(step_dict['name'], e.timeout) | 207 raise recipe_api.StepTimeout(step_config.name, e.timeout) |
217 except OSError: | 208 except OSError: |
218 with step_stream.new_log_stream('exception') as l: | 209 with step_stream.new_log_stream('exception') as l: |
219 trace = traceback.format_exc().splitlines() | 210 trace = traceback.format_exc().splitlines() |
220 for line in trace: | 211 for line in trace: |
221 l.write_line(line) | 212 l.write_line(line) |
222 step_stream.set_step_status('EXCEPTION') | 213 step_stream.set_step_status('EXCEPTION') |
223 raise | 214 raise |
224 finally: | 215 finally: |
225 # NOTE(luqui) See the accompanying note in stream.py. | 216 # NOTE(luqui) See the accompanying note in stream.py. |
226 step_stream.reset_subannotation_state() | 217 step_stream.reset_subannotation_state() |
227 | 218 |
228 if 'trigger_specs' in step_dict: | 219 if step_config.trigger_specs: |
229 self._trigger_builds(step_stream, step_dict['trigger_specs']) | 220 self._trigger_builds(step_stream, step_config.trigger_specs) |
230 | 221 |
231 return construct_step_result(step_dict, retcode, placeholders) | 222 return construct_step_result(rendered_step, retcode) |
232 | 223 |
233 def finalize(inner): | 224 def finalize(inner): |
234 step_stream.close() | 225 step_stream.close() |
235 | 226 |
236 @property | 227 @property |
237 def stream(inner): | 228 def stream(inner): |
238 return step_stream | 229 return step_stream |
239 | 230 |
240 return ReturnOpenStep() | 231 return ReturnOpenStep() |
241 | 232 |
(...skipping 30 matching lines...) Expand all Loading... | |
272 while hasattr(value, 'func'): | 263 while hasattr(value, 'func'): |
273 value = value.func | 264 value = value.func |
274 return getattr(value, '__name__', 'UNKNOWN_CALLABLE')+'(...)' | 265 return getattr(value, '__name__', 'UNKNOWN_CALLABLE')+'(...)' |
275 | 266 |
276 def _print_step(self, step_stream, step, env): | 267 def _print_step(self, step_stream, step, env): |
277 """Prints the step command and relevant metadata. | 268 """Prints the step command and relevant metadata. |
278 | 269 |
279 Intended to be similar to the information that Buildbot prints at the | 270 Intended to be similar to the information that Buildbot prints at the |
280 beginning of each non-annotator step. | 271 beginning of each non-annotator step. |
281 """ | 272 """ |
282 step_stream.write_line(' '.join(map(_shell_quote, step['cmd']))) | 273 step_stream.write_line(' '.join(map(_shell_quote, step.config.cmd))) |
283 step_stream.write_line('in dir %s:' % (step.get('cwd') or os.getcwd())) | 274 step_stream.write_line('in dir %s:' % (step.config.cwd or os.getcwd())) |
284 for key, value in sorted(step.items()): | 275 for key, value in sorted(step.config._asdict().items()): |
285 if value is not None: | 276 if value is not None: |
286 step_stream.write_line( | 277 step_stream.write_line( |
287 ' %s: %s' % (key, self._render_step_value(value))) | 278 ' %s: %s' % (key, self._render_step_value(value))) |
288 step_stream.write_line('full environment:') | 279 step_stream.write_line('full environment:') |
289 for key, value in sorted(env.items()): | 280 for key, value in sorted(env.items()): |
290 step_stream.write_line(' %s: %s' % (key, value)) | 281 step_stream.write_line(' %s: %s' % (key, value)) |
291 step_stream.write_line('') | 282 step_stream.write_line('') |
292 | 283 |
293 def _run_cmd(self, cmd, timeout, handles, env, cwd): | 284 def _run_cmd(self, cmd, timeout, handles, env, cwd): |
294 """Runs cmd (subprocess-style). | 285 """Runs cmd (subprocess-style). |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
394 return change | 385 return change |
395 | 386 |
396 | 387 |
397 class SimulationStepRunner(StepRunner): | 388 class SimulationStepRunner(StepRunner): |
398 """Pretends to run steps, instead recording what would have been run. | 389 """Pretends to run steps, instead recording what would have been run. |
399 | 390 |
400 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 |
401 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 |
402 values. | 393 values. |
403 """ | 394 """ |
395 | |
396 # List of attributes in a recipe_api.StepConfig to omit when rendering | |
397 # step history. | |
398 _STEP_CONFIG_RENDER_BLACKLIST = set(( | |
iannucci
2016/09/12 19:41:07
frozenset
| |
399 'nest_level', | |
400 'ok_ret', | |
401 'infra_step', | |
402 'step_test_data', | |
403 )) | |
404 | |
404 def __init__(self, stream_engine, test_data, annotator): | 405 def __init__(self, stream_engine, test_data, annotator): |
405 self._test_data = test_data | 406 self._test_data = test_data |
406 self._stream_engine = stream_engine | 407 self._stream_engine = stream_engine |
407 self._annotator = annotator | 408 self._annotator = annotator |
408 self._step_history = collections.OrderedDict() | 409 self._step_history = collections.OrderedDict() |
409 | 410 |
410 @property | 411 @property |
411 def stream_engine(self): | 412 def stream_engine(self): |
412 return self._stream_engine | 413 return self._stream_engine |
413 | 414 |
414 def open_step(self, step_dict): | 415 def open_step(self, step_config): |
415 # We modify step_dict. In particular, we add ~followup_annotations during | 416 test_data_fn = step_config.step_test_data or recipe_test_api.StepTestData |
416 # finalize, and depend on that side-effect being carried into what we | 417 step_test = self._test_data.pop_step_test_data(step_config.name, |
417 # added to self._step_history, earlier. So copy it here so at least we | 418 test_data_fn) |
418 # keep the modifications local. | 419 rendered_step = render_step(step_config, step_test) |
419 step_dict = dict(step_dict) | 420 step_config = None # Make sure we use rendered step config. |
420 nest_level = step_dict.pop('step_nest_level', 0) | |
421 | |
422 test_data_fn = step_dict.pop('step_test_data', recipe_test_api.StepTestData) | |
423 step_test = self._test_data.pop_step_test_data( | |
424 step_dict['name'], test_data_fn) | |
425 step_dict, placeholders = render_step(step_dict, step_test) | |
426 | 421 |
427 # Layer the simulation step on top of the given stream engine. | 422 # Layer the simulation step on top of the given stream engine. |
428 step_stream = self._stream_engine.new_step_stream( | 423 step_stream = self._stream_engine.new_step_stream(rendered_step.config) |
429 step_dict['name'], | |
430 nest_level=nest_level) | |
431 | 424 |
432 class ReturnOpenStep(OpenStep): | 425 class ReturnOpenStep(OpenStep): |
433 def run(inner): | 426 def run(inner): |
434 timeout = step_dict.get('timeout') | 427 timeout = rendered_step.config.timeout |
435 if (timeout and step_test.times_out_after and | 428 if (timeout and step_test.times_out_after and |
436 step_test.times_out_after > timeout): | 429 step_test.times_out_after > timeout): |
437 raise recipe_api.StepTimeout(step_dict['name'], timeout) | 430 raise recipe_api.StepTimeout(rendered_step.config.name, timeout) |
438 | 431 |
439 self._step_history[step_dict['name']] = step_dict | 432 # Install a placeholder for order. |
440 return construct_step_result(step_dict, step_test.retcode, placeholders) | 433 self._step_history[rendered_step.config.name] = None |
434 return construct_step_result(rendered_step, step_test.retcode) | |
441 | 435 |
442 def finalize(inner): | 436 def finalize(inner): |
437 rs = rendered_step | |
438 | |
443 # 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 |
444 # step. also use _step to get access to the mutable step | 440 # step. also use _step to get access to the mutable step |
445 # dictionary. | 441 # dictionary. |
446 buf = self._annotator.step_buffer(step_dict['name']) | 442 buf = self._annotator.step_buffer(rendered_step.config.name) |
iannucci
2016/09/12 19:41:07
why still use rendered_step here instead of rs?
| |
447 lines = filter(None, buf.getvalue()).splitlines() | 443 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_config to. |
452 step_dict['~followup_annotations'] = lines | 448 rs = rs._replace(followup_annotations=lines) |
453 step_stream.close() | 449 step_stream.close() |
450 self._step_history[rendered_step.config.name] = rs | |
iannucci
2016/09/12 19:41:07
here too?
| |
454 | 451 |
455 @property | 452 @property |
456 def stream(inner): | 453 def stream(inner): |
457 return step_stream | 454 return step_stream |
458 | 455 |
459 return ReturnOpenStep() | 456 return ReturnOpenStep() |
460 | 457 |
461 def run_recipe(self, universe, recipe, properties): | 458 def run_recipe(self, universe, recipe, properties): |
462 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), )) |
463 | 460 |
464 @contextlib.contextmanager | 461 @contextlib.contextmanager |
465 def run_context(self): | 462 def run_context(self): |
466 try: | 463 try: |
467 yield | 464 yield |
468 except Exception as ex: | 465 except Exception as ex: |
469 with self._test_data.should_raise_exception(ex) as should_raise: | 466 with self._test_data.should_raise_exception(ex) as should_raise: |
470 if should_raise: | 467 if should_raise: |
471 raise | 468 raise |
472 | 469 |
473 assert self._test_data.consumed, ( | 470 assert self._test_data.consumed, ( |
474 "Unconsumed test data for steps: %s, (exception %s)" % ( | 471 "Unconsumed test data for steps: %s, (exception %s)" % ( |
475 self._test_data.step_data.keys(), | 472 self._test_data.step_data.keys(), |
476 self._test_data.expected_exception)) | 473 self._test_data.expected_exception)) |
477 | 474 |
475 def _rendered_step_to_dict(self, rs): | |
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) | |
478 if rs.followup_annotations: | |
479 d['~followup_annotations'] = rs.followup_annotations | |
480 return d | |
481 | |
478 @property | 482 @property |
479 def steps_ran(self): | 483 def steps_ran(self): |
480 return self._step_history.values() | 484 return [self._rendered_step_to_dict(rs) |
485 for rs in self._step_history.itervalues()] | |
481 | 486 |
482 | 487 |
488 # Placeholders associated with a rendered step. | |
489 Placeholders = collections.namedtuple('Placeholders', | |
490 ('inputs_cmd', 'outputs_cmd', 'stdout', 'stderr', 'stdin')) | |
491 | |
483 # Result of 'render_step'. | 492 # Result of 'render_step'. |
484 Placeholders = collections.namedtuple( | 493 # |
485 'Placeholders', ['inputs_cmd', 'outputs_cmd', 'stdout', 'stderr', 'stdin']) | 494 # Fields: |
495 # config (recipe_api.StepConfig): The step configuration. | |
496 # placeholders (Placeholders): Placeholders for this rendered step. | |
497 # followup_annotations (list): A list of followup annotation, populated during | |
498 # simulation test. | |
499 RenderedStep = collections.namedtuple('RenderedStep', | |
500 ('config', 'placeholders', 'followup_annotations')) | |
486 | 501 |
487 | 502 |
488 # Singleton object to indicate a value is not set. | 503 # Singleton object to indicate a value is not set. |
489 UNSET_VALUE = object() | 504 UNSET_VALUE = object() |
490 | 505 |
491 | 506 |
492 def render_step(step, step_test): | 507 def render_step(step_config, step_test): |
493 """Renders a step so that it can be fed to annotator.py. | 508 """Renders a step so that it can be fed to annotator.py. |
494 | 509 |
495 Args: | 510 Args: |
496 step: The step to render. | 511 step_config (StepConfig): The step config to render. |
497 step_test: The test data json dictionary for this step, if any. | 512 step_test: The test data json dictionary for this step, if any. |
498 Passed through unaltered to each placeholder. | 513 Passed through unaltered to each placeholder. |
499 | 514 |
500 Returns the rendered step and a Placeholders object representing any | 515 Returns (RenderedStep): the rendered step, including a Placeholders object |
501 placeholder instances that were found while rendering. | 516 representing any placeholder instances that were found while rendering. |
502 """ | 517 """ |
503 rendered_step = dict(step) | |
504 | |
505 # Process 'cmd', rendering placeholders there. | 518 # Process 'cmd', rendering placeholders there. |
506 input_phs = collections.defaultdict(lambda: collections.defaultdict(list)) | 519 input_phs = collections.defaultdict(lambda: collections.defaultdict(list)) |
507 output_phs = collections.defaultdict( | 520 output_phs = collections.defaultdict( |
508 lambda: collections.defaultdict(collections.OrderedDict)) | 521 lambda: collections.defaultdict(collections.OrderedDict)) |
509 new_cmd = [] | 522 new_cmd = [] |
510 for item in step.get('cmd', []): | 523 for item in (step_config.cmd or ()): |
511 if isinstance(item, util.Placeholder): | 524 if isinstance(item, util.Placeholder): |
512 module_name, placeholder_name = item.namespaces | 525 module_name, placeholder_name = item.namespaces |
513 tdata = step_test.pop_placeholder( | 526 tdata = step_test.pop_placeholder( |
514 module_name, placeholder_name, item.name) | 527 module_name, placeholder_name, item.name) |
515 new_cmd.extend(item.render(tdata)) | 528 new_cmd.extend(item.render(tdata)) |
516 if isinstance(item, util.InputPlaceholder): | 529 if isinstance(item, util.InputPlaceholder): |
517 input_phs[module_name][placeholder_name].append((item, tdata)) | 530 input_phs[module_name][placeholder_name].append((item, tdata)) |
518 else: | 531 else: |
519 assert isinstance(item, util.OutputPlaceholder), ( | 532 assert isinstance(item, util.OutputPlaceholder), ( |
520 'Not an OutputPlaceholder: %r' % item) | 533 'Not an OutputPlaceholder: %r' % item) |
521 # This assert ensures that: | 534 # This assert ensures that: |
522 # no two placeholders have the same name | 535 # no two placeholders have the same name |
523 # at most one placeholder has the default name | 536 # at most one placeholder has the default name |
524 assert item.name not in output_phs[module_name][placeholder_name], ( | 537 assert item.name not in output_phs[module_name][placeholder_name], ( |
525 'Step "%s" has multiple output placeholders of %s.%s. Please ' | 538 'Step "%s" has multiple output placeholders of %s.%s. Please ' |
526 'specify explicit and different names for them.' % ( | 539 'specify explicit and different names for them.' % ( |
527 step['name'], module_name, placeholder_name)) | 540 step_config.name, module_name, placeholder_name)) |
528 output_phs[module_name][placeholder_name][item.name] = (item, tdata) | 541 output_phs[module_name][placeholder_name][item.name] = (item, tdata) |
529 else: | 542 else: |
530 new_cmd.append(item) | 543 new_cmd.append(item) |
531 rendered_step['cmd'] = new_cmd | 544 step_config = step_config._replace(cmd=new_cmd) |
532 | 545 |
533 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. | 546 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. |
534 stdio_placeholders = {} | 547 stdio_placeholders = {} |
535 for key in ('stdout', 'stderr', 'stdin'): | 548 for key in ('stdout', 'stderr', 'stdin'): |
536 placeholder = step.get(key) | 549 placeholder = getattr(step_config, key) |
537 tdata = None | 550 tdata = None |
538 if placeholder: | 551 if placeholder: |
539 if key == 'stdin': | 552 if key == 'stdin': |
540 assert isinstance(placeholder, util.InputPlaceholder), ( | 553 assert isinstance(placeholder, util.InputPlaceholder), ( |
541 '%s(%r) should be an InputPlaceholder.' % (key, placeholder)) | 554 '%s(%r) should be an InputPlaceholder.' % (key, placeholder)) |
542 else: | 555 else: |
543 assert isinstance(placeholder, util.OutputPlaceholder), ( | 556 assert isinstance(placeholder, util.OutputPlaceholder), ( |
544 '%s(%r) should be an OutputPlaceholder.' % (key, placeholder)) | 557 '%s(%r) should be an OutputPlaceholder.' % (key, placeholder)) |
545 tdata = getattr(step_test, key) | 558 tdata = getattr(step_test, key) |
546 placeholder.render(tdata) | 559 placeholder.render(tdata) |
547 assert placeholder.backing_file is not None | 560 assert placeholder.backing_file is not None |
548 rendered_step[key] = placeholder.backing_file | 561 step_config = step_config._replace(**{key:placeholder.backing_file}) |
549 stdio_placeholders[key] = (placeholder, tdata) | 562 stdio_placeholders[key] = (placeholder, tdata) |
550 | 563 |
551 return rendered_step, Placeholders( | 564 return RenderedStep( |
552 inputs_cmd=input_phs, outputs_cmd=output_phs, **stdio_placeholders) | 565 config=step_config, |
566 placeholders=Placeholders( | |
567 inputs_cmd=input_phs, | |
568 outputs_cmd=output_phs, | |
569 **stdio_placeholders), | |
570 followup_annotations=None, | |
571 ) | |
553 | 572 |
554 | 573 |
555 def construct_step_result(step, retcode, placeholders): | 574 def construct_step_result(rendered_step, retcode): |
556 """Constructs a StepData step result from step return data. | 575 """Constructs a StepData step result from step return data. |
557 | 576 |
558 The main purpose of this function is to add output placeholder results into | 577 The main purpose of this function is to add output placeholder results into |
559 the step result where output placeholders appeared in the input step. | 578 the step result where output placeholders appeared in the input step. |
560 Also give input placeholders the chance to do the clean-up if needed. | 579 Also give input placeholders the chance to do the clean-up if needed. |
561 """ | 580 """ |
562 | 581 step_result = types.StepData(rendered_step.config, retcode) |
563 step_result = types.StepData(step, retcode) | |
564 | 582 |
565 class BlankObject(object): | 583 class BlankObject(object): |
566 pass | 584 pass |
567 | 585 |
568 # Input placeholders inside step |cmd|. | 586 # Input placeholders inside step |cmd|. |
587 placeholders = rendered_step.placeholders | |
569 for _, pholders in placeholders.inputs_cmd.iteritems(): | 588 for _, pholders in placeholders.inputs_cmd.iteritems(): |
570 for _, items in pholders.iteritems(): | 589 for _, items in pholders.iteritems(): |
571 for ph, td in items: | 590 for ph, td in items: |
572 ph.cleanup(td.enabled) | 591 ph.cleanup(td.enabled) |
573 | 592 |
574 # Output placeholders inside step |cmd|. | 593 # Output placeholders inside step |cmd|. |
575 for module_name, pholders in placeholders.outputs_cmd.iteritems(): | 594 for module_name, pholders in placeholders.outputs_cmd.iteritems(): |
576 assert not hasattr(step_result, module_name) | 595 assert not hasattr(step_result, module_name) |
577 o = BlankObject() | 596 o = BlankObject() |
578 setattr(step_result, module_name, o) | 597 setattr(step_result, module_name, o) |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
661 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 |
662 of the child process. | 681 of the child process. |
663 """ | 682 """ |
664 saved_path = os.environ['PATH'] | 683 saved_path = os.environ['PATH'] |
665 try: | 684 try: |
666 if path is not None: | 685 if path is not None: |
667 os.environ['PATH'] = path | 686 os.environ['PATH'] = path |
668 yield | 687 yield |
669 finally: | 688 finally: |
670 os.environ['PATH'] = saved_path | 689 os.environ['PATH'] = saved_path |
OLD | NEW |