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

Side by Side Diff: scripts/slave/recipe_test_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: once more... 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
OLDNEW
(Empty)
1 import collections
2 import copy
3 import functools
4
5 from .recipe_util import ModuleInjectionSite
6
7 def combineify(name, dest, a, b):
8 """
9 Combines dictionary members in two objects into a third one using addition.
10
11 Args:
12 name - the name of the member
13 dest - the destination object
14 a - the first source object
15 b - the second source object
16 """
17 dest_dict = getattr(dest, name)
18 dest_dict.update(getattr(a, name))
19 for k, v in getattr(b, name).iteritems():
20 if k in dest_dict:
21 dest_dict[k] += v
22 else:
23 dest_dict[k] = v
24
25
26 class BaseTestData(object):
27 def __init__(self, enabled=True):
28 super(BaseTestData, self).__init__()
29 self._enabled = enabled
30
31 @property
32 def enabled(self):
33 return self._enabled
34
35
36 class PlaceholderTestData(BaseTestData):
37 def __init__(self, data=None):
38 super(PlaceholderTestData, self).__init__()
39 self.data = data
40
41 def __repr__(self):
42 return "PlaceholderTestData(%s)" % self.data
43
44 def __copy__(self):
45 ret = PlaceholderTestData()
46 ret.data = copy.deepcopy(self.data)
47 return ret
48
49
50 class StepTestData(BaseTestData):
51 """
52 Mutable container for per-step test data.
53
54 This data is consumed while running the recipe (during
55 annotated_run.run_steps).
56 """
57 def __init__(self):
58 super(StepTestData, self).__init__()
59 # { (module, placeholder) -> [data] }
60 self.placeholder_data = collections.defaultdict(list)
61 self._retcode = None
62
63 def __add__(self, other):
64 assert isinstance(other, StepTestData)
65 ret = StepTestData()
66
67 # We don't use combineify to merge placeholder_data here because
68 # simply concatenating placeholder_data's list value is not meaningful to
69 # consumers of this object.
70 # Producers of this object should use the append() method.
71 ret.placeholder_data.update(copy.deepcopy(self.placeholder_data))
72 for k, v in other.placeholder_data.iteritems():
73 assert k not in ret.placeholder_data
74 ret.placeholder_data[k] = copy.deepcopy(v)
75
76 # pylint: disable=W0212
77 ret._retcode = self._retcode
78 if other._retcode is not None:
79 assert ret._retcode is None
80 ret._retcode = other._retcode
81 return ret
82
83 def append(self, other):
84 self._retcode = self._retcode or other._retcode # pylint: disable=W0212
85 combineify('placeholder_data', self, self, other)
86 return self
87
88 def pop_placeholder(self, name_pieces):
89 l = self.placeholder_data[name_pieces]
90 if l:
91 return l.pop(0)
92 else:
93 return PlaceholderTestData()
94
95 @property
96 def retcode(self): # pylint: disable=E0202
97 return self._retcode or 0
98
99 @retcode.setter
100 def retcode(self, value): # pylint: disable=E0202
101 self._retcode = value
102
103 def __repr__(self):
104 return "StepTestData(%s)" % str({
105 'placeholder_data': dict(self.placeholder_data.iteritems()),
106 'retcode': self._retcode,
107 })
108
109
110 class ModuleTestData(BaseTestData, dict):
111 """
112 Mutable container for test data for a specific module.
113
114 This test data is consumed at module load time (i.e. when CreateRecipeApi
115 runs).
116 """
117 def __add__(self, other):
118 assert isinstance(other, ModuleTestData)
119 ret = ModuleTestData()
120 ret.update(self)
121 ret.update(other)
122 return ret
123
124 def __repr__(self):
125 return "ModuleTestData(%s)" % super(ModuleTestData, self).__repr__()
126
127
128 class TestData(BaseTestData):
129 def __init__(self, name=None):
130 super(TestData, self).__init__()
131 self.name = name
132 self.properties = {} # key -> val
133 self.mod_data = collections.defaultdict(ModuleTestData)
134 self.step_data = collections.defaultdict(StepTestData)
135
136 def __add__(self, other):
137 assert isinstance(other, TestData)
138 ret = TestData(self.name or other.name)
139
140 ret.properties.update(self.properties)
141 ret.properties.update(other.properties)
142
143 combineify('mod_data', ret, self, other)
144 combineify('step_data', ret, self, other)
145
146 return ret
147
148 def empty(self):
149 return not self.step_data
150
151 def __repr__(self):
152 return "TestData(%s)" % str({
153 'name': self.name,
154 'properties': self.properties,
155 'mod_data': dict(self.mod_data.iteritems()),
156 'step_data': dict(self.step_data.iteritems()),
157 })
158
159
160 class DisabledTestData(BaseTestData):
161 def __init__(self):
162 super(DisabledTestData, self).__init__(False)
163
164 def __getattr__(self, name):
165 return self
166
167 def pop_placeholder(self, _name_pieces):
168 return self
169
170
171 def static_wraps(func):
172 wrapped_fn = func
173 if isinstance(func, staticmethod):
174 wrapped_fn = func.__func__
175 return functools.wraps(wrapped_fn)
176
177
178 def static_call(obj, func, *args, **kwargs):
179 if isinstance(func, staticmethod):
180 return func.__get__(obj)(*args, **kwargs)
181 else:
182 return func(obj, *args, **kwargs)
183
184
185 def mod_test_data(func):
186 @static_wraps(func)
187 def inner(self, *args, **kwargs):
188 assert isinstance(self, RecipeTestApi)
189 mod_name = self._module.NAME # pylint: disable=W0212
190 ret = TestData(None)
191 data = static_call(self, func, *args, **kwargs)
192 ret.mod_data[mod_name][inner.__name__] = data
193 return ret
194 return inner
195
196
197 def placeholder_step_data(func):
198 @static_wraps(func)
199 def inner(self, *args, **kwargs):
200 assert isinstance(self, RecipeTestApi)
201 mod_name = self._module.NAME # pylint: disable=W0212
202 ret = StepTestData()
203 placeholder_data, retcode = static_call(self, func, *args, **kwargs)
204 ret.placeholder_data[(mod_name, inner.__name__)].append(
205 PlaceholderTestData(placeholder_data))
206 ret.retcode = retcode
207 return ret
208 return inner
209
210
211 class RecipeTestApi(object):
agable 2013/09/23 22:57:58 You're going to have to document this *really well
iannucci 2013/09/24 02:18:51 Well... with a data set of 1, all things are unexp
212 def __init__(self, module=None, test_data=DisabledTestData()):
213 """Note: Injected dependencies are NOT available in __init__()."""
214 # If we're the 'root' api, inject directly into 'self'.
215 # Otherwise inject into 'self.m'
216 self.m = self if module is None else ModuleInjectionSite()
217 self._module = module
218
219 assert isinstance(test_data, (ModuleTestData, DisabledTestData))
220 self._test_data = test_data
221
222 @staticmethod
223 def test(name):
224 return TestData(name)
225
226 @staticmethod
227 def step_data(name, *data):
228 assert all(isinstance(d, StepTestData) for d in data)
229 ret = TestData(None)
230 ret.step_data[name] = reduce(sum, data)
231 return ret
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698