OLD | NEW |
---|---|
(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 {} | |
OLD | NEW |