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

Side by Side Diff: recipe_engine/post_process.py

Issue 2387763003: Add initial postprocess unit test thingy. (Closed)
Patch Set: Implement partial checker_test, rewrite VerifySubset Created 4 years, 2 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
OLDNEW
(Empty)
1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file.
4
5 """This file contains post process filters for use with the
6 RecipeTestApi.post_process method in GenTests.
7 """
8
9 import re
10
11 from collections import defaultdict, OrderedDict, namedtuple
12
13
14 class Filter(object):
15 """Filter is the interface of the object returned by NewFilter."""
16
17 def include(self, step_name, *fields):
18 """Include adds a step to the included steps set.
19
20 Additionally, if any specified fields are provided, they will be the total
21 set of fields in the filtered step. The 'name' field is always included. If
22 fields is omitted, the entire step will be included.
23
24 Args:
25 step_name (str) - The name of the step to include
26 fields (str) - The field(s) to include. Omit to include all fields.
27
28 Returns the new filter.
29 """
30 raise NotImplementedError()
31
32 def include_re(self, step_name_re, at_least=1, at_most=None, *fields):
33 """This includes all steps which match the given regular expression.
34
35 If a step matches both an include() directive as well as include_re(), the
36 include() directive will take precedence.
37
38 Args:
39 step_name_re (str or regex) - the regular expression of step names to
40 match.
41 at_least (int) - the number of steps that this regular expression MUST
42 match.
43 at_most (int) - the maximum number of steps that this regular expression
44 MUST NOT exceed.
45 fields (str) - the field(s) to include in the matched steps. Omit to
46 include all fields.
47
48 Returns the new filter.
49 """
50 raise NotImplementedError()
51
52
53 class _filterImpl(Filter):
martiniss 2016/10/10 18:53:12 why do we have this class hierarchy?
54 _reEntry = namedtuple('_reEntry', 'at_most at_least fields')
55
56 def __init__(self, data, re_data):
57 self.data = data # {step_name: frozenset(fields)}
58 self.re_data = re_data # {regex: _reEntry}
59
60 def __call__(self, check, step_odict):
61 includes = self.data.copy()
62 re_data = self.re_data.copy()
63
64 re_usage_count = defaultdict(int)
65
66 to_ret = OrderedDict()
67 for name, step in step_odict.iteritems():
68 field_set = includes.pop(name, None)
69 if field_set is None:
70 for exp, (_, _, fset) in re_data.iteritems():
71 if exp.match(name):
72 re_usage_count[exp] += 1
73 field_set = fset
74 break
75 if field_set is None:
76 continue
77 if len(field_set) == 0:
78 to_ret[name] = step
79 else:
80 to_ret[name] = {
81 k: v for k, v in step.iteritems()
82 if k in field_set or k == 'name'
83 }
84
85 check('all includes were used', len(includes) == 0)
86
87 for regex, (at_least, at_most, _) in re_data.iteritems():
88 check(re_usage_count[regex] >= at_least)
89 if at_most is not None:
90 check(re_usage_count[regex] <= at_most)
91
92 return to_ret
93
94 def include(self, step_name, *fields):
95 new_data = self.data.copy()
96 new_data[step_name] = frozenset(fields)
97 return _filterImpl(new_data, self.re_data)
98
99 def include_re(self, step_name_re, at_least=1, at_most=None, *fields):
100 new_re_data = self.re_data.copy()
101 new_re_data[re.compile(step_name_re)] = _filterImpl._reEntry(
102 (at_least, at_most, frozenset(fields)))
103 return _filterImpl(self.data, new_re_data)
104
105
106 def NewFilter(*steps):
107 """NewFilter returns a new Filter object. It may be optionally prepopulated by
108 specifying steps.
109
110 Usage:
111 f = NewFilter('step_a', 'step_b')
112 yield TEST + api.post_process(f)
113
114 f = f.include('other_step')
115 yield TEST + api.post_process(f)
116
117 yield TEST + api.post_process(NewFilter, 'step_a', 'step_b', 'other_step')
118 """
119 return _filterImpl({name: () for name in steps}, {})
120
121
122 def DoesNotRun(check, step_odict, *steps):
123 """Asserts that the given steps to not run.
martiniss 2016/10/10 18:53:12 "to not" -> "don't"
124
125 Usage:
126 yield TEST + api.post_process(DoesNotRun, 'step_a', 'step_b')
127
128 """
129 banSet = set(steps)
130 for step_name in step_odict:
131 check(step_name not in banSet)
132
133
134 def DoesNotRunRE(check, step_odict, *step_regexes):
135 """Asserts that no steps matching any of the regexes have run.
136
137 Args:
138 step_regexes (str) - The step name regexes to ban.
139
140 Usage:
141 yield TEST + api.post_process(DoesNotRunRE, '.*with_patch.*', '.*compile.*')
142
143 """
144 step_regexes = [re.compile(r) for r in step_regexes]
145 for step_name in step_odict:
146 for r in step_regexes:
147 check(not r.match(step_name))
148
149
150 def MustRun(check, step_odict, *steps):
151 """Asserts that steps with the given names are in the expectations.
152
153 Args:
154 steps (str) - The steps that must have run.
155
156 Usage:
157 yield TEST + api.post_process(MustRun, 'step_a', 'step_b')
158 """
159 for step_name in steps:
160 check(step_name in step_odict)
161
162
163 def MustRunRE(check, step_odict, step_regex, at_least=1, at_most=None):
164 """Assert that steps matching the given regex completely are in the
165 exepectations.
166
167 Args:
168 step_regex (str, compiled regex) - The regular expression to match.
169 at_least (int) - Match at least this many steps. Matching fewer than this
170 is a CHECK failure.
171 at_most (int) - Optional upper bound on the number of matches. Matching
172 more than this is a CHECK failure.
173
174 Usage:
175 yield TEST + api.post_process(MustRunRE, r'.*with_patch.*', at_most=2)
176 """
177 step_regex = re.compile(step_regex)
178 matches = 0
179 for step_name in step_odict:
180 if step_regex.match(step_name):
181 matches += 1
182 check(matches >= at_least)
183 if at_most is not None:
184 check(matches <= at_most)
185
186
187 def DropExpectation(_check, _step_odict):
188 """Using this post-process hook will drop the expectations for this test
189 completely.
190
191 Usage:
192 yield TEST + api.post_process(DropExpectation)
193
194 """
195 return {}
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698