Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(68)

Unified Diff: recipe_engine/step_runner.py

Issue 2253943003: Formally define step config, pass to stream. (Closed) Base URL: https://github.com/luci/recipes-py@nest-single-event
Patch Set: Rebase Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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:

Powered by Google App Engine
This is Rietveld 408576698