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

Side by Side Diff: scripts/slave/recipe_loader.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: License headers 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « scripts/slave/recipe_configs_util.py ('k') | scripts/slave/recipe_modules/android/api.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4 import copy
5 import imp
6 import inspect
7 import os
8 import sys
9
10 from common import chromium_utils
11
12 from .recipe_util import RECIPE_DIRS, MODULE_DIRS
13 from .recipe_api import RecipeApi
14 from .recipe_test_api import RecipeTestApi, DisabledTestData, ModuleTestData
15
16
17 def load_recipe_modules(mod_dirs):
18 def patchup_module(name, submod):
19 submod.NAME = name
20 submod.CONFIG_CTX = getattr(submod, 'CONFIG_CTX', None)
21 submod.DEPS = frozenset(getattr(submod, 'DEPS', ()))
22
23 if hasattr(submod, 'config'):
24 for v in submod.config.__dict__.itervalues():
25 if hasattr(v, 'I_AM_A_CONFIG_CTX'):
26 assert not submod.CONFIG_CTX, (
27 'More than one configuration context: %s' % (submod.config))
28 submod.CONFIG_CTX = v
29 assert submod.CONFIG_CTX, 'Config file, but no config context?'
30
31 submod.API = getattr(submod, 'API', None)
32 for v in submod.api.__dict__.itervalues():
33 if inspect.isclass(v) and issubclass(v, RecipeApi):
34 assert not submod.API, (
35 'More than one Api subclass: %s' % submod.api)
36 submod.API = v
37 assert submod.API, 'Submodule has no api? %s' % (submod)
38
39 submod.TEST_API = getattr(submod, 'TEST_API', None)
40 if hasattr(submod, 'test_api'):
41 for v in submod.test_api.__dict__.itervalues():
42 if inspect.isclass(v) and issubclass(v, RecipeTestApi):
43 assert not submod.TEST_API, (
44 'More than one TestApi subclass: %s' % submod.api)
45 submod.TEST_API = v
46 assert submod.API, (
47 'Submodule has test_api.py but no TestApi subclass? %s'
48 % (submod)
49 )
50
51 RM = 'RECIPE_MODULES'
52 def find_and_load(fullname, modname, path):
53 if fullname not in sys.modules or fullname == RM:
54 try:
55 fil, pathname, descr = imp.find_module(modname,
56 [os.path.dirname(path)])
57 imp.load_module(fullname, fil, pathname, descr)
58 finally:
59 if fil:
60 fil.close()
61 return sys.modules[fullname]
62
63 def recursive_import(path, prefix=None, skip_fn=lambda name: False):
64 modname = os.path.splitext(os.path.basename(path))[0]
65 if prefix:
66 fullname = '%s.%s' % (prefix, modname)
67 else:
68 fullname = RM
69 m = find_and_load(fullname, modname, path)
70 if not os.path.isdir(path):
71 return m
72
73 for subitem in os.listdir(path):
74 subpath = os.path.join(path, subitem)
75 subname = os.path.splitext(subitem)[0]
76 if skip_fn(subname):
77 continue
78 if os.path.isdir(subpath):
79 if not os.path.exists(os.path.join(subpath, '__init__.py')):
80 continue
81 elif not subpath.endswith('.py') or subitem.startswith('__init__.py'):
82 continue
83
84 submod = recursive_import(subpath, fullname, skip_fn=skip_fn)
85
86 if not hasattr(m, subname):
87 setattr(m, subname, submod)
88 else:
89 prev = getattr(m, subname)
90 assert submod is prev, (
91 'Conflicting modules: %s and %s' % (prev, m))
92
93 return m
94
95 imp.acquire_lock()
96 try:
97 if RM not in sys.modules:
98 sys.modules[RM] = imp.new_module(RM)
99 # First import all the APIs and configs
100 for root in mod_dirs:
101 if os.path.isdir(root):
102 recursive_import(root, skip_fn=lambda name: name.endswith('_config'))
103
104 # Then fixup all the modules
105 for name, submod in sys.modules[RM].__dict__.iteritems():
106 if name[0] == '_':
107 continue
108 patchup_module(name, submod)
109
110 # Then import all the config extenders.
111 for root in mod_dirs:
112 if os.path.isdir(root):
113 recursive_import(root)
114 return sys.modules[RM]
115 finally:
116 imp.release_lock()
117
118
119 def CreateApi(mod_dirs, names, test_data=DisabledTestData(), required=None,
120 optional=None, kwargs=None):
121 """
122 Given a list of module names, return an instance of RecipeApi which contains
123 those modules as direct members.
124
125 So, if you pass ['foobar'], you'll get an instance back which contains a
126 'foobar' attribute which itself is a RecipeApi instance from the 'foobar'
127 module.
128
129 Args:
130 names (list): A list of module names to include in the returned RecipeApi.
131 mod_dirs (list): A list of paths to directories which contain modules.
132 test_data (TestData): ...
133 kwargs: Data passed to each module api. Usually this will contain:
134 properties (dict): the properties dictionary (used by the properties
135 module)
136 step_history (OrderedDict): the step history object (used by the
137 step_history module!)
138 """
139 kwargs = kwargs or {}
140 recipe_modules = load_recipe_modules(mod_dirs)
141
142 inst_maps = {}
143 if required:
144 inst_maps[required[0]] = { None: required[1]() }
145 if optional:
146 inst_maps[optional[0]] = { None: optional[1]() }
147
148 dep_map = {None: set(names)}
149 def create_maps(name):
150 if name not in dep_map:
151 module = getattr(recipe_modules, name)
152
153 dep_map[name] = set(module.DEPS)
154 map(create_maps, dep_map[name])
155
156 mod_test = DisabledTestData()
157 if test_data.enabled:
158 mod_test = test_data.mod_data.get(name, ModuleTestData())
159
160 if required:
161 api = getattr(module, required[0])
162 inst_maps[required[0]][name] = api(module=module,
163 test_data=mod_test, **kwargs)
164 if optional:
165 api = getattr(module, optional[0], None) or optional[1]
166 inst_maps[optional[0]][name] = api(module=module,
167 test_data=mod_test)
168
169 map(create_maps, names)
170
171 if required:
172 MapDependencies(dep_map, inst_maps[required[0]])
173 if optional:
174 MapDependencies(dep_map, inst_maps[optional[0]])
175 if required:
176 for name, module in inst_maps[required[0]].iteritems():
177 module.test_api = inst_maps[optional[0]][name]
178
179 return inst_maps[(required or optional)[0]][None]
180
181
182 def MapDependencies(dep_map, inst_map):
183 # NOTE: this is 'inefficient', but correct and compact.
184 dep_map = copy.deepcopy(dep_map)
185 while dep_map:
186 did_something = False
187 to_pop = []
188 for api_name, deps in dep_map.iteritems():
189 to_remove = []
190 for dep in [d for d in deps if d not in dep_map]:
191 # Grab the injection site
192 obj = inst_map[api_name].m
193 assert not hasattr(obj, dep)
194 setattr(obj, dep, inst_map[dep])
195 to_remove.append(dep)
196 did_something = True
197 map(deps.remove, to_remove)
198 if not deps:
199 to_pop.append(api_name)
200 did_something = True
201 map(dep_map.pop, to_pop)
202 assert did_something, 'Did nothing on this loop. %s' % dep_map
203
204
205 def CreateTestApi(names):
206 return CreateApi(MODULE_DIRS(), names, optional=('TEST_API', RecipeTestApi))
207
208
209 def CreateRecipeApi(names, test_data=DisabledTestData(), **kwargs):
210 return CreateApi(MODULE_DIRS(), names, test_data=test_data, kwargs=kwargs,
211 required=('API', RecipeApi),
212 optional=('TEST_API', RecipeTestApi))
213
214
215 class NoSuchRecipe(Exception):
216 pass
217
218
219 def LoadRecipe(recipe):
220 # If the recipe is specified as "module:recipe", then it is an recipe
221 # contained in a recipe_module as an example. Look for it in the modules
222 # imported by load_recipe_modules instead of the normal search paths.
223 if ':' in recipe:
224 module_name, example = recipe.split(':')
225 assert example.endswith('example')
226 RECIPE_MODULES = load_recipe_modules(MODULE_DIRS())
227 try:
228 return getattr(getattr(RECIPE_MODULES, module_name), example)
229 except AttributeError:
230 pass
231 else:
232 for recipe_path in (os.path.join(p, recipe) for p in RECIPE_DIRS()):
233 recipe_module = chromium_utils.IsolatedImportFromPath(recipe_path)
234 if recipe_module:
235 return recipe_module
236 raise NoSuchRecipe(recipe)
237
238
239 def find_recipes(path, predicate):
240 for root, _dirs, files in os.walk(path):
241 for recipe in (f for f in files if predicate(f)):
242 recipe_path = os.path.join(root, recipe)
243 yield recipe_path
244
245
246 def loop_over_recipes():
247 for path in RECIPE_DIRS():
248 for recipe in find_recipes(
249 path, lambda f: f.endswith('.py') and f[0] != '_'):
250 yield recipe, recipe[len(path)+1:-len('.py')]
251 for path in MODULE_DIRS():
252 for recipe in find_recipes(
253 path, lambda f: f.endswith('example.py')):
254 module_name = os.path.dirname(recipe)[len(path)+1:]
255 yield recipe, '%s:example' % module_name
OLDNEW
« no previous file with comments | « scripts/slave/recipe_configs_util.py ('k') | scripts/slave/recipe_modules/android/api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698