Index: scripts/slave/annotated_run.py |
diff --git a/scripts/slave/annotated_run.py b/scripts/slave/annotated_run.py |
index e8840a870206d70d382ebf76466ca3510417934d..ac7be59f327a882b7a5bce335e081bace5575075 100755 |
--- a/scripts/slave/annotated_run.py |
+++ b/scripts/slave/annotated_run.py |
@@ -76,15 +76,12 @@ import collections # Import after polyfill to get OrderedDict on 2.6 |
from common import annotator |
from common import chromium_utils |
from slave import recipe_api |
+from slave import recipe_util |
+from slave import recipe_test_api |
+from slave import recipe_loader |
+ |
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) |
-BUILD_ROOT = os.path.dirname(os.path.dirname(SCRIPT_PATH)) |
-ROOT_PATH = os.path.abspath(os.path.join( |
- SCRIPT_PATH, os.pardir, os.pardir, os.pardir)) |
-MODULE_DIRS = [os.path.join(x, 'recipe_modules') for x in [ |
- SCRIPT_PATH, |
- os.path.join(ROOT_PATH, 'build_internal', 'scripts', 'slave') |
-]] |
class StepPresentation(object): |
@@ -230,60 +227,63 @@ def ensure_sequence_of_steps(step_or_steps): |
assert False, 'Item is not a sequence or a step: %s' % (step_or_steps,) |
-def render_step(step, test_data): |
+def render_step(step, step_test): |
"""Renders a step so that it can be fed to annotator.py. |
Args: |
- test_data: The test data json dictionary for this step, if any. |
+ step_test: The test data json dictionary for this step, if any. |
Passed through unaltered to each placeholder. |
Returns any placeholder instances that were found while rendering the step. |
""" |
- placeholders = collections.defaultdict(list) |
+ placeholders = collections.defaultdict(lambda: collections.defaultdict(list)) |
new_cmd = [] |
for item in step['cmd']: |
- if isinstance(item, recipe_api.Placeholder): |
- # __module__ is in the form of ...recipe_modules.<api_name>.* |
- api_name = item.__module__.split('.')[-2] |
- tdata = None if test_data is None else test_data.get(api_name, {}) |
+ if isinstance(item, recipe_util.Placeholder): |
+ module_name, placeholder_name = item.name_pieces |
+ tdata = step_test.pop_placeholder(item.name_pieces) |
new_cmd.extend(item.render(tdata)) |
- placeholders[api_name].append(item) |
+ placeholders[module_name][placeholder_name].append((item, tdata)) |
else: |
new_cmd.append(item) |
step['cmd'] = new_cmd |
return placeholders |
-def call_placeholders(step_result, placeholders, test_data): |
+def get_placeholder_results(step_result, placeholders): |
class BlankObject(object): |
pass |
- additions = {} |
- for api, items in placeholders.iteritems(): |
- additions[api] = BlankObject() |
- test_datum = None if test_data is None else test_data.get(api, {}) |
- for placeholder in items: |
- placeholder.step_finished(step_result.presentation, additions[api], |
- test_datum) |
- for api, obj in additions.iteritems(): |
- setattr(step_result, api, obj) |
- |
- |
-def step_callback(step, step_history, placeholders, test_data_item): |
+ for module_name, pholders in placeholders.iteritems(): |
+ assert not hasattr(step_result, module_name) |
+ o = BlankObject() |
+ setattr(step_result, module_name, o) |
+ |
+ for placeholder_name, items in pholders.iteritems(): |
+ lst = [ph.result(step_result.presentation, td) for ph, td in items] |
+ setattr(o, placeholder_name+"_all", lst) |
+ setattr(o, placeholder_name, lst[0]) |
agable
2013/09/21 02:05:31
Why? Why not just put the whole list in placeholde
iannucci
2013/09/21 03:12:34
Because people /mostly always/ want the first entr
|
+ |
+ |
+def step_callback(step, step_history, placeholders, step_test): |
+ assert step['name'] not in step_history, ( |
+ 'Step "%s" is already in step_history!' % step['name']) |
+ step_result = StepData(step, None) |
+ step_history[step['name']] = step_result |
+ |
followup_fn = step.pop('followup_fn', None) |
def _inner(annotator_step, retcode): |
- step_result = StepData(step, retcode) |
+ step_result._retcode = retcode # pylint: disable=W0212 |
if retcode > 0: |
step_result.presentation.status = 'FAILURE' |
- step_history[step['name']] = step_result |
annotator_step.annotation_stream.step_cursor(step['name']) |
- if step_result.retcode != 0 and test_data_item is None: |
+ if step_result.retcode != 0 and step_test.enabled: |
# To avoid cluttering the expectations, don't emit this in testmode. |
annotator_step.emit('step returned non-zero exit code: %d' % |
step_result.retcode) |
- call_placeholders(step_result, placeholders, test_data_item) |
+ get_placeholder_results(step_result, placeholders) |
if followup_fn: |
followup_fn(step_result) |
@@ -324,13 +324,12 @@ def main(argv=None): |
def run_steps(stream, build_properties, factory_properties, |
- api=recipe_api.CreateRecipeApi, test_data=None): |
+ api=recipe_loader.CreateRecipeApi, |
+ test=recipe_test_api.DisabledTestData()): |
"""Returns a tuple of (status_code, steps_ran). |
Only one of these values will be set at a time. This is mainly to support the |
testing interface used by unittests/recipes_test.py. |
- |
- test_data should be a dictionary of step_name -> (retcode, json_data) |
""" |
stream.honor_zero_return_code() |
MakeStepsRetval = collections.namedtuple('MakeStepsRetval', |
@@ -346,75 +345,49 @@ def run_steps(stream, build_properties, factory_properties, |
with stream.step('setup_build') as s: |
assert 'recipe' in factory_properties |
recipe = factory_properties['recipe'] |
- |
- # If the recipe is specified as "module:recipe", then it is an recipe |
- # contained in a recipe_module as an example. Look for it in the modules |
- # imported by load_recipe_modules instead of the normal search paths. |
- if ':' in recipe: |
- module_name, recipe = recipe.split(':') |
- assert recipe.endswith('example') |
- RECIPE_MODULES = recipe_api.load_recipe_modules(MODULE_DIRS) |
- try: |
- recipe_module = getattr(getattr(RECIPE_MODULES, module_name), recipe) |
- except AttributeError: |
- s.step_text('recipe not found') |
- s.step_failure() |
- return MakeStepsRetval(2, None) |
- |
- else: |
- recipe_dirs = (os.path.abspath(p) for p in ( |
- os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', |
- 'scripts', 'slave-internal', 'recipes'), |
- os.path.join(SCRIPT_PATH, '..', '..', '..', 'build_internal', |
- 'scripts', 'slave', 'recipes'), |
- os.path.join(SCRIPT_PATH, 'recipes'), |
- )) |
- |
- for recipe_path in (os.path.join(p, recipe) for p in recipe_dirs): |
- recipe_module = chromium_utils.IsolatedImportFromPath(recipe_path) |
- if recipe_module: |
- break |
- else: |
- s.step_text('recipe not found') |
- s.step_failure() |
- return MakeStepsRetval(2, None) |
- |
properties = factory_properties.copy() |
properties.update(build_properties) |
- stream.emit('Running recipe with %s' % (properties,)) |
- steps = recipe_module.GenSteps(api(recipe_module.DEPS, |
- mod_dirs=MODULE_DIRS, |
- properties=properties, |
- step_history=step_history)) |
- assert inspect.isgenerator(steps) |
+ try: |
+ recipe_module = recipe_loader.LoadRecipe(recipe) |
+ stream.emit('Running recipe with %s' % (properties,)) |
+ steps = recipe_module.GenSteps(api(recipe_module.DEPS, |
+ properties=properties, |
+ step_history=step_history)) |
+ assert inspect.isgenerator(steps) |
+ except recipe_loader.NoSuchRecipe as e: |
+ s.step_text('recipe not found: %s' % e) |
+ s.step_failure() |
+ return MakeStepsRetval(2, None) |
+ |
# Execute annotator.py with steps if specified. |
# annotator.py handles the seeding, execution, and annotation of each step. |
failed = False |
- test_mode = test_data is not None |
- |
for step in ensure_sequence_of_steps(steps): |
if failed and not step.get('always_run', False): |
step_result = StepData(step, None) |
step_history[step['name']] = step_result |
continue |
- test_data_item = test_data.pop(step['name'], {}) if test_mode else None |
- placeholders = render_step(step, test_data_item) |
+ if test.enabled: |
+ step_test = step.pop('default_step_data', recipe_api.StepTestData()) |
+ step_test += test.step_data.pop(step['name'], recipe_api.StepTestData()) |
+ else: |
+ step_test = recipe_api.DisabledTestData() |
+ step.pop('default_step_data', None) |
- assert step['name'] not in step_history, ( |
- 'Step "%s" is already in step_history!' % step['name']) |
+ placeholders = render_step(step, step_test) |
- callback = step_callback(step, step_history, placeholders, test_data_item) |
+ callback = step_callback(step, step_history, placeholders, step_test) |
- if not test_mode: |
+ if not test.enabled: |
step_result = annotator.run_step( |
stream, followup_fn=callback, **step) |
else: |
with stream.step(step['name']) as s: |
s.stream = cStringIO.StringIO() |
- step_result = callback(s, test_data_item.pop('$R', 0)) |
+ step_result = callback(s, step_test.retcode) |
lines = filter(None, s.stream.getvalue().splitlines()) |
if lines: |
# Note that '~' sorts after 'z' so that this will be last on each |
@@ -425,8 +398,8 @@ def run_steps(stream, build_properties, factory_properties, |
# TODO(iannucci): Pull this failure calculation into callback. |
failed = annotator.update_build_failure(failed, step_result.retcode, **step) |
- assert not test_mode or test_data == {}, ( |
- "Unconsumed test data! %s" % (test_data,)) |
+ assert not test.enabled or not test.step_data, ( |
+ "Unconsumed test data! %s" % (test.step_data,)) |
return MakeStepsRetval(0 if not failed else 1, step_history) |