Chromium Code Reviews| Index: scripts/slave/recipe_api.py |
| diff --git a/scripts/slave/recipe_api.py b/scripts/slave/recipe_api.py |
| index 91fcc12ab205db4d3a126b0381a9f6d5686e99e4..6ba1ce3713e6f1f938982d621f465a866105b6fa 100644 |
| --- a/scripts/slave/recipe_api.py |
| +++ b/scripts/slave/recipe_api.py |
| @@ -2,6 +2,7 @@ |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| +import copy |
| import functools |
| import imp |
| import inspect |
| @@ -48,6 +49,12 @@ class InputDataPlaceholder(Placeholder): |
| class ModuleInjectionSite(object): |
| pass |
| +class RecipeTestApi(object): |
| + def __init__(self, module=None): |
| + """Note: Injected dependencies are NOT available in __init__().""" |
| + # If we're the 'root' api, inject directly into 'self'. |
| + # Otherwise inject into 'self.m' |
| + self.m = self if module is None else ModuleInjectionSite() |
| class RecipeApi(object): |
| """ |
| @@ -69,6 +76,10 @@ class RecipeApi(object): |
| # Otherwise inject into 'self.m' |
| self.m = self if module is None else ModuleInjectionSite() |
| + # If our module has a test api, it gets injected here. |
| + self.test_api = None |
| + |
| + |
| def _sanitize_config_vars(self, config_name, CONFIG_VARS): |
| params = self.get_config_defaults(config_name) |
| params.update(CONFIG_VARS) |
| @@ -121,6 +132,7 @@ def load_recipe_modules(mod_dirs): |
| def patchup_module(submod): |
| submod.CONFIG_CTX = getattr(submod, 'CONFIG_CTX', None) |
| submod.API = getattr(submod, 'API', None) |
| + submod.TEST_API = getattr(submod, 'TEST_API', None) |
| submod.DEPS = frozenset(getattr(submod, 'DEPS', ())) |
| if hasattr(submod, 'config'): |
| @@ -139,6 +151,17 @@ def load_recipe_modules(mod_dirs): |
| assert submod.API, 'Submodule has no api? %s' % (submod) |
| + if hasattr(submod, 'test_api'): |
| + for v in submod.test_api.__dict__.itervalues(): |
| + if inspect.isclass(v) and issubclass(v, RecipeTestApi): |
| + assert not submod.TEST_API, ( |
| + 'More than one TestApi subclass: %s' % submod.api) |
| + submod.TEST_API = v |
| + assert submod.API, ( |
| + 'Submodule has test_api.py but no TestApi subclass? %s' |
| + % (submod) |
| + ) |
| + |
| RM = 'RECIPE_MODULES' |
| def find_and_load(fullname, modname, path): |
| if fullname not in sys.modules or fullname == RM: |
| @@ -207,7 +230,8 @@ def load_recipe_modules(mod_dirs): |
| imp.release_lock() |
| -def CreateRecipeApi(names, mod_dirs, mocks=None, **kwargs): |
| +def CreateApi(mod_dirs, names, mocks=None, required=None, |
|
agable
2013/09/16 20:29:25
If 'required' and 'optional' are not antitheses of
|
| + optional=None, kwargs=None): |
| """ |
| Given a list of module names, return an instance of RecipeApi which contains |
|
agable
2013/09/16 20:29:25
Update docstring to reflect new argument ordering.
|
| those modules as direct members. |
| @@ -221,16 +245,21 @@ def CreateRecipeApi(names, mod_dirs, mocks=None, **kwargs): |
| mod_dirs (list): A list of paths to directories which contain modules. |
| mocks (dict): An optional dict of {<modname>: <mock data>}. Each module |
| expects its own mock data. |
| - **kwargs: Data passed to each module api. Usually this will contain: |
| + kwargs: Data passed to each module api. Usually this will contain: |
| properties (dict): the properties dictionary (used by the properties |
| module) |
| step_history (OrderedDict): the step history object (used by the |
| step_history module!) |
| """ |
| - |
| + kwargs = kwargs or {} |
| recipe_modules = load_recipe_modules(mod_dirs) |
| - inst_map = {None: RecipeApi()} |
| + inst_maps = {} |
| + if required: |
|
agable
2013/09/16 20:29:25
Definitely not a fan of the 'required' and 'option
|
| + inst_maps[required[0]] = { None: required[1]() } |
| + if optional: |
| + inst_maps[optional[0]] = { None: optional[1]() } |
| + |
| dep_map = {None: set(names)} |
| def create_maps(name): |
| if name not in dep_map: |
| @@ -240,11 +269,29 @@ def CreateRecipeApi(names, mod_dirs, mocks=None, **kwargs): |
| map(create_maps, dep_map[name]) |
| mock = None if mocks is None else mocks.get(name, {}) |
| - inst_map[name] = module.API(module=module, mock=mock, **kwargs) |
| + if required: |
| + api = getattr(module, required[0]) |
| + inst_maps[required[0]][name] = api(module=module, mock=mock, **kwargs) |
| + if optional: |
| + api = getattr(module, optional[0], None) or optional[1] |
| + inst_maps[optional[0]][name] = api(module=module) |
|
agable
2013/09/16 20:29:25
As long as you're treating these as "required" and
|
| + |
| map(create_maps, names) |
| + if required: |
| + MapDependencies(dep_map, inst_maps[required[0]]) |
| + if optional: |
| + MapDependencies(dep_map, inst_maps[optional[0]]) |
| + if required: |
| + for name, module in inst_maps[required[0]].iteritems(): |
| + module.test_api = inst_maps[optional[0]][name] |
| + |
| + return inst_maps[(required or optional)[0]][None] |
| + |
| + |
| +def MapDependencies(dep_map, inst_map): |
|
agable
2013/09/16 20:29:25
While we're here, please rename "to_remove" and "t
|
| # NOTE: this is 'inefficient', but correct and compact. |
| - did_something = True |
| + dep_map = copy.deepcopy(dep_map) |
| while dep_map: |
| did_something = False |
| to_pop = [] |
| @@ -264,7 +311,15 @@ def CreateRecipeApi(names, mod_dirs, mocks=None, **kwargs): |
| map(dep_map.pop, to_pop) |
| assert did_something, 'Did nothing on this loop. %s' % dep_map |
| - return inst_map[None] |
| + |
| +def CreateRecipeApi(mod_dirs, names, mocks=None, **kwargs): |
| + return CreateApi(mod_dirs, names, mocks=mocks, kwargs=kwargs, |
| + required=('API', RecipeApi), |
| + optional=('TEST_API', RecipeTestApi)) |
| + |
| + |
| +def CreateTestApi(mod_dirs, names): |
| + return CreateApi(mod_dirs, names, optional=('TEST_API', RecipeTestApi)) |
| def wrap_followup(kwargs, pre=False): |