Index: recipe_engine/step_runner.py |
diff --git a/recipe_engine/step_runner.py b/recipe_engine/step_runner.py |
index 7947ff3705c74c131ed1e8471b73e7648ffcebaa..1b0448c97fcdbab3ddcaf13e34db2e5380684fc1 100644 |
--- a/recipe_engine/step_runner.py |
+++ b/recipe_engine/step_runner.py |
@@ -86,25 +86,13 @@ class StepRunner(object): |
""" |
return None |
- def open_step(self, step_dict): |
+ def open_step(self, step_config): |
"""Constructs an OpenStep object which can be used to actually run a step. |
- step_dict parameters: |
- name: name of the step, will appear in buildbots waterfall |
- cmd: command to run, list of one or more strings |
- cwd: absolute path to working directory for the command |
- env: dict with overrides for environment variables |
- allow_subannotations: if True, lets the step emit its own annotations |
- trigger_specs: a list of trigger specifications, see also _trigger_builds. |
- stdout: Path to a file to put step stdout into. If used, stdout won't |
- appear in annotator's stdout (and |allow_subannotations| is |
- ignored). |
- stderr: Path to a file to put step stderr into. If used, stderr won't |
- appear in annotator's stderr. |
- stdin: Path to a file to read step stdin from. |
- step_nest_level: the step's nesting level. |
- |
- Returns an OpenStep object. |
+ Args: |
+ step_config (StepConfig): The step data. |
+ |
+ Returns: an OpenStep object. |
""" |
raise NotImplementedError() |
@@ -163,20 +151,14 @@ class SubprocessStepRunner(StepRunner): |
def stream_engine(self): |
return self._stream_engine |
- def open_step(self, step_dict): |
- allow_subannotations = step_dict.get('allow_subannotations', False) |
- nest_level = step_dict.pop('step_nest_level', 0) |
- |
- step_stream = self._stream_engine.new_step_stream( |
- step_dict['name'], |
- allow_subannotations=allow_subannotations, |
- nest_level=nest_level) |
- if not step_dict.get('cmd'): |
+ def open_step(self, step_config): |
+ step_stream = self._stream_engine.new_step_stream(step_config) |
+ if not step_config.cmd: |
class EmptyOpenStep(OpenStep): |
def run(inner): |
- if 'trigger_specs' in step_dict: |
- self._trigger_builds(step_stream, step_dict['trigger_specs']) |
- return types.StepData(step_dict, 0) |
+ if step_config.trigger_specs: |
+ self._trigger_builds(step_stream, step_config.trigger_specs) |
+ return types.StepData(step_config, 0) |
def finalize(inner): |
step_stream.close() |
@@ -187,33 +169,42 @@ class SubprocessStepRunner(StepRunner): |
return EmptyOpenStep() |
- step_dict, placeholders = render_step( |
- step_dict, recipe_test_api.DisabledTestData()) |
- cmd = map(str, step_dict['cmd']) |
- step_env = _merge_envs(os.environ, step_dict.get('env', {})) |
- self._print_step(step_stream, step_dict, step_env) |
+ rendered_step = render_step( |
+ step_config, recipe_test_api.DisabledTestData() |
+ ) |
+ step_config = None # Make sure we use rendered step config. |
+ |
+ rendered_step = rendered_step._replace( |
+ config=rendered_step.config._replace( |
+ cmd=map(str, rendered_step.config.cmd), |
+ ), |
+ ) |
+ |
+ step_env = _merge_envs(os.environ, (rendered_step.config.env or {})) |
+ self._print_step(step_stream, rendered_step, step_env) |
class ReturnOpenStep(OpenStep): |
def run(inner): |
+ step_config = rendered_step.config |
try: |
# Open file handles for IO redirection based on file names in |
- # step_dict. |
+ # step_config. |
handles = { |
'stdout': step_stream, |
'stderr': step_stream, |
'stdin': None, |
} |
for key in handles: |
- fileName = step_dict.get(key) |
+ fileName = getattr(step_config, key) |
if fileName: |
handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb') |
# The subprocess will inherit and close these handles. |
retcode = self._run_cmd( |
- cmd=cmd, timeout=step_dict.get('timeout'), handles=handles, |
- env=step_env, cwd=step_dict.get('cwd')) |
+ cmd=step_config.cmd, timeout=step_config.timeout, handles=handles, |
+ env=step_env, cwd=step_config.cwd) |
except subprocess42.TimeoutExpired as e: |
# TODO(martiniss): mark as exception? |
- raise recipe_api.StepTimeout(step_dict['name'], e.timeout) |
+ raise recipe_api.StepTimeout(step_config.name, e.timeout) |
except OSError: |
with step_stream.new_log_stream('exception') as l: |
trace = traceback.format_exc().splitlines() |
@@ -225,10 +216,10 @@ class SubprocessStepRunner(StepRunner): |
# NOTE(luqui) See the accompanying note in stream.py. |
step_stream.reset_subannotation_state() |
- if 'trigger_specs' in step_dict: |
- self._trigger_builds(step_stream, step_dict['trigger_specs']) |
+ if step_config.trigger_specs: |
+ self._trigger_builds(step_stream, step_config.trigger_specs) |
- return construct_step_result(step_dict, retcode, placeholders) |
+ return construct_step_result(rendered_step, retcode) |
def finalize(inner): |
step_stream.close() |
@@ -279,9 +270,9 @@ class SubprocessStepRunner(StepRunner): |
Intended to be similar to the information that Buildbot prints at the |
beginning of each non-annotator step. |
""" |
- step_stream.write_line(' '.join(map(_shell_quote, step['cmd']))) |
- step_stream.write_line('in dir %s:' % (step.get('cwd') or os.getcwd())) |
- for key, value in sorted(step.items()): |
+ step_stream.write_line(' '.join(map(_shell_quote, step.config.cmd))) |
+ step_stream.write_line('in dir %s:' % (step.config.cwd or os.getcwd())) |
+ for key, value in sorted(step.config._asdict().items()): |
if value is not None: |
step_stream.write_line( |
' %s: %s' % (key, self._render_step_value(value))) |
@@ -401,6 +392,16 @@ class SimulationStepRunner(StepRunner): |
steps that would have been run in steps_ran. Uses test_data to mock return |
values. |
""" |
+ |
+ # List of attributes in a recipe_api.StepConfig to omit when rendering |
+ # step history. |
+ _STEP_CONFIG_RENDER_BLACKLIST = set(( |
iannucci
2016/09/12 19:41:07
frozenset
|
+ 'nest_level', |
+ 'ok_ret', |
+ 'infra_step', |
+ 'step_test_data', |
+ )) |
+ |
def __init__(self, stream_engine, test_data, annotator): |
self._test_data = test_data |
self._stream_engine = stream_engine |
@@ -411,46 +412,42 @@ class SimulationStepRunner(StepRunner): |
def stream_engine(self): |
return self._stream_engine |
- def open_step(self, step_dict): |
- # We modify step_dict. In particular, we add ~followup_annotations during |
- # finalize, and depend on that side-effect being carried into what we |
- # added to self._step_history, earlier. So copy it here so at least we |
- # keep the modifications local. |
- step_dict = dict(step_dict) |
- nest_level = step_dict.pop('step_nest_level', 0) |
- |
- test_data_fn = step_dict.pop('step_test_data', recipe_test_api.StepTestData) |
- step_test = self._test_data.pop_step_test_data( |
- step_dict['name'], test_data_fn) |
- step_dict, placeholders = render_step(step_dict, step_test) |
+ def open_step(self, step_config): |
+ test_data_fn = step_config.step_test_data or recipe_test_api.StepTestData |
+ step_test = self._test_data.pop_step_test_data(step_config.name, |
+ test_data_fn) |
+ rendered_step = render_step(step_config, step_test) |
+ step_config = None # Make sure we use rendered step config. |
# Layer the simulation step on top of the given stream engine. |
- step_stream = self._stream_engine.new_step_stream( |
- step_dict['name'], |
- nest_level=nest_level) |
+ step_stream = self._stream_engine.new_step_stream(rendered_step.config) |
class ReturnOpenStep(OpenStep): |
def run(inner): |
- timeout = step_dict.get('timeout') |
+ timeout = rendered_step.config.timeout |
if (timeout and step_test.times_out_after and |
step_test.times_out_after > timeout): |
- raise recipe_api.StepTimeout(step_dict['name'], timeout) |
+ raise recipe_api.StepTimeout(rendered_step.config.name, timeout) |
- self._step_history[step_dict['name']] = step_dict |
- return construct_step_result(step_dict, step_test.retcode, placeholders) |
+ # Install a placeholder for order. |
+ self._step_history[rendered_step.config.name] = None |
+ return construct_step_result(rendered_step, step_test.retcode) |
def finalize(inner): |
+ rs = rendered_step |
+ |
# note that '~' sorts after 'z' so that this will be last on each |
# step. also use _step to get access to the mutable step |
# dictionary. |
- buf = self._annotator.step_buffer(step_dict['name']) |
+ 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?
|
lines = filter(None, buf.getvalue()).splitlines() |
lines = [stream.encode_str(x) for x in lines] |
if lines: |
# This magically floats into step_history, which we have already |
- # added step_dict to. |
- step_dict['~followup_annotations'] = lines |
+ # added step_config to. |
+ rs = rs._replace(followup_annotations=lines) |
step_stream.close() |
+ self._step_history[rendered_step.config.name] = rs |
iannucci
2016/09/12 19:41:07
here too?
|
@property |
def stream(inner): |
@@ -475,39 +472,55 @@ class SimulationStepRunner(StepRunner): |
self._test_data.step_data.keys(), |
self._test_data.expected_exception)) |
+ def _rendered_step_to_dict(self, rs): |
+ d = dict((k, v) for k, v in rs.config._asdict().iteritems() |
+ if v and k not in self._STEP_CONFIG_RENDER_BLACKLIST) |
+ if rs.followup_annotations: |
+ d['~followup_annotations'] = rs.followup_annotations |
+ return d |
+ |
@property |
def steps_ran(self): |
- return self._step_history.values() |
+ return [self._rendered_step_to_dict(rs) |
+ for rs in self._step_history.itervalues()] |
+# Placeholders associated with a rendered step. |
+Placeholders = collections.namedtuple('Placeholders', |
+ ('inputs_cmd', 'outputs_cmd', 'stdout', 'stderr', 'stdin')) |
+ |
# Result of 'render_step'. |
-Placeholders = collections.namedtuple( |
- 'Placeholders', ['inputs_cmd', 'outputs_cmd', 'stdout', 'stderr', 'stdin']) |
+# |
+# Fields: |
+# config (recipe_api.StepConfig): The step configuration. |
+# placeholders (Placeholders): Placeholders for this rendered step. |
+# followup_annotations (list): A list of followup annotation, populated during |
+# simulation test. |
+RenderedStep = collections.namedtuple('RenderedStep', |
+ ('config', 'placeholders', 'followup_annotations')) |
# Singleton object to indicate a value is not set. |
UNSET_VALUE = object() |
-def render_step(step, step_test): |
+def render_step(step_config, step_test): |
"""Renders a step so that it can be fed to annotator.py. |
Args: |
- step: The step to render. |
+ step_config (StepConfig): The step config to render. |
step_test: The test data json dictionary for this step, if any. |
Passed through unaltered to each placeholder. |
- Returns the rendered step and a Placeholders object representing any |
- placeholder instances that were found while rendering. |
+ Returns (RenderedStep): the rendered step, including a Placeholders object |
+ representing any placeholder instances that were found while rendering. |
""" |
- rendered_step = dict(step) |
- |
# Process 'cmd', rendering placeholders there. |
input_phs = collections.defaultdict(lambda: collections.defaultdict(list)) |
output_phs = collections.defaultdict( |
lambda: collections.defaultdict(collections.OrderedDict)) |
new_cmd = [] |
- for item in step.get('cmd', []): |
+ for item in (step_config.cmd or ()): |
if isinstance(item, util.Placeholder): |
module_name, placeholder_name = item.namespaces |
tdata = step_test.pop_placeholder( |
@@ -524,16 +537,16 @@ def render_step(step, step_test): |
assert item.name not in output_phs[module_name][placeholder_name], ( |
'Step "%s" has multiple output placeholders of %s.%s. Please ' |
'specify explicit and different names for them.' % ( |
- step['name'], module_name, placeholder_name)) |
+ step_config.name, module_name, placeholder_name)) |
output_phs[module_name][placeholder_name][item.name] = (item, tdata) |
else: |
new_cmd.append(item) |
- rendered_step['cmd'] = new_cmd |
+ step_config = step_config._replace(cmd=new_cmd) |
# Process 'stdout', 'stderr' and 'stdin' placeholders, if given. |
stdio_placeholders = {} |
for key in ('stdout', 'stderr', 'stdin'): |
- placeholder = step.get(key) |
+ placeholder = getattr(step_config, key) |
tdata = None |
if placeholder: |
if key == 'stdin': |
@@ -545,27 +558,33 @@ def render_step(step, step_test): |
tdata = getattr(step_test, key) |
placeholder.render(tdata) |
assert placeholder.backing_file is not None |
- rendered_step[key] = placeholder.backing_file |
+ step_config = step_config._replace(**{key:placeholder.backing_file}) |
stdio_placeholders[key] = (placeholder, tdata) |
- return rendered_step, Placeholders( |
- inputs_cmd=input_phs, outputs_cmd=output_phs, **stdio_placeholders) |
+ return RenderedStep( |
+ config=step_config, |
+ placeholders=Placeholders( |
+ inputs_cmd=input_phs, |
+ outputs_cmd=output_phs, |
+ **stdio_placeholders), |
+ followup_annotations=None, |
+ ) |
-def construct_step_result(step, retcode, placeholders): |
+def construct_step_result(rendered_step, retcode): |
"""Constructs a StepData step result from step return data. |
The main purpose of this function is to add output placeholder results into |
the step result where output placeholders appeared in the input step. |
Also give input placeholders the chance to do the clean-up if needed. |
""" |
- |
- step_result = types.StepData(step, retcode) |
+ step_result = types.StepData(rendered_step.config, retcode) |
class BlankObject(object): |
pass |
# Input placeholders inside step |cmd|. |
+ placeholders = rendered_step.placeholders |
for _, pholders in placeholders.inputs_cmd.iteritems(): |
for _, items in pholders.iteritems(): |
for ph, td in items: |