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

Side by Side Diff: recipe_engine/post_process.py

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

Powered by Google App Engine
This is Rietveld 408576698