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

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

Powered by Google App Engine
This is Rietveld 408576698