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

Unified Diff: scripts/slave/annotated_run.py

Issue 1076643002: Reland of 'Make allow_subannotations more robust' (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Fixed SET_BUILD_PROPERTY parse failure Created 5 years, 8 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
« no previous file with comments | « scripts/common/annotator.py ('k') | scripts/slave/recipe_modules/step/api.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: scripts/slave/annotated_run.py
diff --git a/scripts/slave/annotated_run.py b/scripts/slave/annotated_run.py
index 1c0f7bb69d9f84db336fdb70d86d4b07dc35b157..cfaa6d4e9da9574409c8011e3371ee16c2511f62 100755
--- a/scripts/slave/annotated_run.py
+++ b/scripts/slave/annotated_run.py
@@ -205,6 +205,11 @@ class StepData(object):
def retcode(self):
return self._retcode
+ @retcode.setter
+ def retcode(self, val):
+ assert self._retcode is None, 'Can\'t override already-defined retcode'
+ self._retcode = val
+
@property
def presentation(self):
return self._presentation
@@ -447,7 +452,6 @@ class RecipeEngine(object):
Recipe modules that are aware of the engine:
* properties - uses engine.properties.
- * step_history - uses engine.step_history.
* step - uses engine.create_step(...).
This class acts mostly as a documentation of expected public engine interface.
@@ -495,6 +499,13 @@ class RecipeEngine(object):
raise NotImplementedError
+def _merge(*dicts):
+ result = {}
+ for d in dicts:
+ result.update(d)
+ return result
+
+
class SequentialRecipeEngine(RecipeEngine):
"""Always runs step sequentially. Currently the engine used by default."""
def __init__(self, stream, properties, test_data):
@@ -502,10 +513,11 @@ class SequentialRecipeEngine(RecipeEngine):
self._stream = stream
self._properties = properties
self._test_data = test_data
- self._step_history = collections.OrderedDict()
+ self._step_results = collections.OrderedDict()
+ self._step_disambiguation_index = {}
- self._previous_step_annotation = None
- self._previous_step_result = None
+ self._annotation = None
+ self._step_result = None
self._api = None
@property
@@ -515,14 +527,14 @@ class SequentialRecipeEngine(RecipeEngine):
@property
def previous_step_result(self):
"""Allows api.step to get the active result from any context."""
- return self._previous_step_result
+ return self._step_result
def _emit_results(self):
- annotation = self._previous_step_annotation
- step_result = self._previous_step_result
+ annotation = self._annotation
+ step_result = self._step_result
- self._previous_step_annotation = None
- self._previous_step_result = None
+ self._annotation = None
+ self._step_result = None
if not annotation or not step_result:
return
@@ -539,47 +551,124 @@ class SequentialRecipeEngine(RecipeEngine):
step_result._step['~followup_annotations'] = lines
annotation.step_ended()
- def run_step(self, step):
- ok_ret = step.pop('ok_ret')
- infra_step = step.pop('infra_step')
+ def _disambiguate_name(self, step_name):
+ if step_name in self._step_disambiguation_index:
+ self._step_disambiguation_index[step_name] += 1
+ step_name += ' (%s)' % self._step_disambiguation_index[step_name]
+ else:
+ self._step_disambiguation_index[step_name] = 1
+ return step_name
- test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData)
- step_test = self._test_data.pop_step_test_data(step['name'],
- test_data_fn)
- placeholders = render_step(step, step_test)
+ def _disambiguate_step(self, step):
+ """Disambiguates step (destructively) by adding an index afterward. E.g.
- self._step_history[step['name']] = step
- self._emit_results()
+ gclient sync
+ gclient sync (2)
+ ...
+ """
+ step['name'] = self._disambiguate_name(step['name'])
- step_result = None
+ def _subannotator(self):
+ class Subannotator(object):
+ # We use ann as the self argument because we are closing over the
+ # SequentialRecipeEngine self.
+ # pylint: disable=e0213
+ def BUILD_STEP(ann, name):
+ self._open_step({'name': self._disambiguate_name(name)})
- if not self._test_data.enabled:
- self._previous_step_annotation, retcode = annotator.run_step(
- self._stream, **step)
+ def STEP_WARNINGS(ann):
+ self._step_result.presentation.status = 'WARNING'
+ def STEP_FAILURE(ann):
+ self._step_result.presentation.status = 'FAILURE'
+ def STEP_EXCEPTION(ann):
+ self._step_result.presentation.status = 'EXCEPTION'
+
+ def STEP_TEXT(ann, msg):
+ self._step_result.presentation.step_text = msg
+
+ def STEP_LINK(ann, link_label, link_url):
+ self._step_result.presentation.links[link_label] = link_url
+
+ def STEP_LOG_LINE(ann, log_label, log_line):
+ self._step_result.presentation.logs[log_label] += log_line
+ def STEP_LOG_END(ann, log_label):
+ # We do step finalization all at once.
+ pass
+
+ def SET_BUILD_PROPERTY(ann, name, value):
+ self._step_result.presentation.properties[name] = json.loads(value)
+
+ def STEP_SUMMARY_TEXT(ann, msg):
+ self._step_result.presentation.step_summary_text = msg
- step_result = StepData(step, retcode)
- self._previous_step_annotation.annotation_stream.step_cursor(step['name'])
+ return Subannotator()
+
+ def _open_step(self, step):
+ self._emit_results()
+ step_result = StepData(step, None)
+ self._step_results[step['name']] = step_result
+ self._step_result = step_result
+ self._annotation = self._stream.step(step['name'])
+ self._annotation.step_started()
+ if self._test_data.enabled:
+ self._annotation.stream = cStringIO.StringIO()
+
+ def _step_kernel(self, step, step_test, subannotator=None):
+ if not self._test_data.enabled:
+ # Warning: run_step can change the current self._annotation and
+ # self._step_result if it uses a subannotator.
+ retcode = annotator.run_step(
+ self._stream,
+ step_annotation=self._annotation,
+ subannotator=subannotator,
+ **step)
+ self._step_result.retcode = retcode
+ # TODO(luqui): What is the purpose of this line?
+ self._annotation.annotation_stream.step_cursor(
+ self._step_result.step['name'])
else:
- self._previous_step_annotation = annotation = self._stream.step(
- step['name'])
- annotation.step_started()
try:
- annotation.stream = cStringIO.StringIO()
-
- step_result = StepData(step, step_test.retcode)
+ self._step_result.retcode = step_test.retcode
except OSError:
exc_type, exc_value, exc_tb = sys.exc_info()
trace = traceback.format_exception(exc_type, exc_value, exc_tb)
trace_lines = ''.join(trace).split('\n')
- annotation.write_log_lines('exception', filter(None, trace_lines))
- annotation.step_exception()
+ self._annotation.write_log_lines(
+ 'exception', filter(None, trace_lines))
+ self._annotation.step_exception()
+
+ return self._step_result.retcode
+
+ def run_step(self, step):
+ self._disambiguate_step(step)
+ ok_ret = step.pop('ok_ret')
+ infra_step = step.pop('infra_step')
+ allow_subannotations = step.get('allow_subannotations', False)
+
+ test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData)
+ step_test = self._test_data.pop_step_test_data(step['name'], test_data_fn)
+ placeholders = render_step(step, step_test)
+
+ if allow_subannotations:
+ # TODO(luqui) Make this hierarchical.
+ self._open_step(step)
+ start_annotation = self._annotation
+ retcode = self._step_kernel(step, step_test,
+ subannotator=self._subannotator())
+
+ # Open a closing step for presentation modifications.
+ if self._annotation != start_annotation:
+ self._open_step({ 'name': step['name'] + ' (end)' })
+ self._step_result.retcode = retcode
+ else:
+ self._open_step(step)
+ self._step_kernel(step, step_test)
- get_placeholder_results(step_result, placeholders)
- self._previous_step_result = step_result
+ get_placeholder_results(self._step_result, placeholders)
- if step_result.retcode in ok_ret:
- step_result.presentation.status = 'SUCCESS'
- return step_result
+ if self._step_result.retcode in ok_ret:
+ self._step_result.presentation.status = 'SUCCESS'
+ return self._step_result
else:
if not infra_step:
state = 'FAILURE'
@@ -588,13 +677,13 @@ class SequentialRecipeEngine(RecipeEngine):
state = 'EXCEPTION'
exc = recipe_api.InfraFailure
- step_result.presentation.status = state
+ self._step_result.presentation.status = state
if step_test.enabled:
# To avoid cluttering the expectations, don't emit this in testmode.
- self._previous_step_annotation.emit(
- 'step returned non-zero exit code: %d' % step_result.retcode)
+ self._annotation.emit(
+ 'step returned non-zero exit code: %d' % self._step_result.retcode)
- raise exc(step['name'], step_result)
+ raise exc(step['name'], self._step_result)
def run(self, steps_function, api):
@@ -606,18 +695,18 @@ class SequentialRecipeEngine(RecipeEngine):
try:
retcode = steps_function(api)
assert retcode is None, (
- "Non-None return from GenSteps is not supported yet")
+ 'Non-None return from GenSteps is not supported yet')
assert not self._test_data.enabled or not self._test_data.step_data, (
- "Unconsumed test data! %s" % (self._test_data.step_data,))
+ 'Unconsumed test data! %s' % (self._test_data.step_data,))
finally:
self._emit_results()
except recipe_api.StepFailure as f:
retcode = f.retcode or 1
final_result = {
- "name": "$final_result",
- "reason": f.reason,
- "status_code": retcode
+ 'name': '$final_result',
+ 'reason': f.reason,
+ 'status_code': retcode
}
except Exception as ex:
@@ -625,9 +714,9 @@ class SequentialRecipeEngine(RecipeEngine):
retcode = -1
final_result = {
- "name": "$final_result",
- "reason": "Uncaught Exception: %r" % ex,
- "status_code": retcode
+ 'name': '$final_result',
+ 'reason': 'Uncaught Exception: %r' % ex,
+ 'status_code': retcode
}
with self._stream.step('Uncaught Exception') as s:
@@ -638,9 +727,10 @@ class SequentialRecipeEngine(RecipeEngine):
raise
if final_result is not None:
- self._step_history[final_result['name']] = final_result
+ self._step_results[final_result['name']] = (
+ StepData(final_result, final_result['status_code']))
- return RecipeExecutionResult(retcode, self._step_history)
+ return RecipeExecutionResult(retcode, self._step_results)
def create_step(self, step): # pylint: disable=R0201
# This version of engine doesn't do anything, just converts step to dict
« no previous file with comments | « scripts/common/annotator.py ('k') | scripts/slave/recipe_modules/step/api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698