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

Unified Diff: scripts/slave/recipe_api.py

Issue 23889036: Refactor the way that TestApi works so that it is actually useful. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Move gclient test_api to got_revisions cl Created 7 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
« no previous file with comments | « scripts/slave/annotated_run.py ('k') | scripts/slave/recipe_modules/android/api.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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):
« no previous file with comments | « scripts/slave/annotated_run.py ('k') | scripts/slave/recipe_modules/android/api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698