Chromium Code Reviews| 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 |