Merge lp:~sylvain-pineau/checkbox/nested_testplans into lp:checkbox
- nested_testplans
- Merge into trunk
Proposed by
Sylvain Pineau
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Sylvain Pineau | ||||
Approved revision: | 4401 | ||||
Merged at revision: | 4417 | ||||
Proposed branch: | lp:~sylvain-pineau/checkbox/nested_testplans | ||||
Merge into: | lp:checkbox | ||||
Diff against target: |
487 lines (+254/-65) 5 files modified
plainbox/plainbox/impl/commands/inv_special.py (+5/-1) plainbox/plainbox/impl/session/assistant.py (+3/-5) plainbox/plainbox/impl/session/manager.py (+1/-1) plainbox/plainbox/impl/unit/test_testplan.py (+125/-1) plainbox/plainbox/impl/unit/testplan.py (+120/-57) |
||||
To merge this branch: | bzr merge lp:~sylvain-pineau/checkbox/nested_testplans | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sylvain Pineau (community) | Approve | ||
Paul Larson | Approve | ||
Review via email:
|
Commit message
Description of the change
The nested test plans feature is ready for review.
Instead of a CEP, I'll work on updating the documentation in a separate MR
To use and test this new field in your testplans just add:
nested_part:
testplan_id # if it belongs to the same namespace
2013.
Nota: jobs will follow the nested parts ordering but all "include:" jobs of the "master" testplan if this list is non empty will come first.
Tested with both checkbox-cli and checkbox-converged, additional tests ( the ones coming from the nested parts) are properly included.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Sylvain Pineau (sylvain-pineau) wrote : | # |
Let's land it, thanks for the review.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'plainbox/plainbox/impl/commands/inv_special.py' |
2 | --- plainbox/plainbox/impl/commands/inv_special.py 2015-09-25 09:13:53 +0000 |
3 | +++ plainbox/plainbox/impl/commands/inv_special.py 2016-06-09 20:31:24 +0000 |
4 | @@ -57,7 +57,11 @@ |
5 | # specified just operate on the whole set. The ns.special check |
6 | # prevents people starting plainbox from accidentally running _all_ |
7 | # jobs without prompting. |
8 | - if ns.special is not None and not ns.include_pattern_list: |
9 | + if ( |
10 | + ns.special is not None and |
11 | + not ns.include_pattern_list and |
12 | + not ns.test_plan |
13 | + ): |
14 | matching_job_list = job_list |
15 | return matching_job_list |
16 | |
17 | |
18 | === modified file 'plainbox/plainbox/impl/session/assistant.py' |
19 | --- plainbox/plainbox/impl/session/assistant.py 2016-05-24 19:38:24 +0000 |
20 | +++ plainbox/plainbox/impl/session/assistant.py 2016-06-09 20:31:24 +0000 |
21 | @@ -1059,11 +1059,9 @@ |
22 | """ |
23 | UsageExpectation.of(self).enforce() |
24 | test_plan = self._manager.test_plans[0] |
25 | - potential_job_list = select_jobs( |
26 | - self._context.state.job_list, |
27 | - [test_plan.get_qualifier(), test_plan.get_mandatory_qualifier()]) |
28 | return list(set( |
29 | - test_plan.get_effective_category_map(potential_job_list).values())) |
30 | + test_plan.get_effective_category_map( |
31 | + self._context.state.run_list).values())) |
32 | |
33 | @raises(UnexpectedMethodCall) |
34 | def get_mandatory_jobs(self) -> 'Iterable[str]': |
35 | @@ -1432,7 +1430,7 @@ |
36 | UsageExpectation.of(self).enforce() |
37 | exporter = self._manager.create_exporter(exporter_id, option_list, |
38 | strict=False) |
39 | - |
40 | + |
41 | # LP:1585326 maintain isoformat but removing ':' chars that cause |
42 | # issues when copying files. |
43 | isoformat = "%Y-%m-%dT%H.%M.%S.%f" |
44 | |
45 | === modified file 'plainbox/plainbox/impl/session/manager.py' |
46 | --- plainbox/plainbox/impl/session/manager.py 2016-04-29 08:36:20 +0000 |
47 | +++ plainbox/plainbox/impl/session/manager.py 2016-06-09 20:31:24 +0000 |
48 | @@ -550,8 +550,8 @@ |
49 | repo = SessionStorageRepository(tmp) |
50 | if provider_list is None: |
51 | provider_list = get_providers() |
52 | + try: |
53 | manager = cls.create(repo=repo) |
54 | - try: |
55 | manager.add_local_device_context() |
56 | device_context = manager.default_device_context |
57 | for provider in provider_list: |
58 | |
59 | === modified file 'plainbox/plainbox/impl/unit/test_testplan.py' |
60 | --- plainbox/plainbox/impl/unit/test_testplan.py 2015-09-25 06:48:54 +0000 |
61 | +++ plainbox/plainbox/impl/unit/test_testplan.py 2016-06-09 20:31:24 +0000 |
62 | @@ -33,6 +33,7 @@ |
63 | from plainbox.impl.secure.qualifiers import OperatorMatcher |
64 | from plainbox.impl.secure.qualifiers import PatternMatcher |
65 | from plainbox.impl.unit.testplan import TestPlanUnit |
66 | +from plainbox.impl.unit.testplan import TestPlanUnitSupport |
67 | from plainbox.vendor import mock |
68 | |
69 | |
70 | @@ -133,7 +134,7 @@ |
71 | |
72 | def test_category_override__normal(self): |
73 | unit = TestPlanUnit({ |
74 | - 'category-overrides': 'value', |
75 | + 'category_overrides': 'value', |
76 | }, provider=self.provider) |
77 | self.assertEqual(unit.category_overrides, 'value') |
78 | |
79 | @@ -385,3 +386,126 @@ |
80 | }, provider=self.provider) |
81 | self.assertEqual(unit.get_bootstrap_job_ids(), |
82 | set(['ns::Foo', 'ns::Bar'])) |
83 | + |
84 | + |
85 | +class TestNestedTestPlan(TestCase): |
86 | + |
87 | + def setUp(self): |
88 | + self.provider1 = mock.Mock(name='provider1', spec_set=IProvider1) |
89 | + self.provider1.namespace = 'ns1' |
90 | + self.provider2 = mock.Mock(name='provider2', spec_set=IProvider1) |
91 | + self.provider2.namespace = 'ns2' |
92 | + self.tp1 = TestPlanUnit({ |
93 | + 'id': 'tp1', |
94 | + 'unit': 'test-plan', |
95 | + 'name': 'An example test plan 1', |
96 | + 'include': 'Foo', |
97 | + 'nested_part': 'tp2' |
98 | + }, provider=self.provider1) |
99 | + self.tp2 = TestPlanUnit({ |
100 | + 'id': 'tp2', |
101 | + 'unit': 'test-plan', |
102 | + 'name': 'An example test plan 2', |
103 | + 'include': 'Bar', |
104 | + 'mandatory_include': 'Baz', |
105 | + 'bootstrap_include': 'Qux' |
106 | + }, provider=self.provider1) |
107 | + self.tp3 = TestPlanUnit({ |
108 | + 'id': 'tp3', |
109 | + 'unit': 'test-plan', |
110 | + 'name': 'An example test plan 3', |
111 | + 'include': '# nothing\n', |
112 | + 'nested_part': 'tp2', |
113 | + 'certification_status_overrides': 'apply blocker to Bar' |
114 | + }, provider=self.provider1) |
115 | + self.tp4 = TestPlanUnit({ |
116 | + 'id': 'tp4', |
117 | + 'unit': 'test-plan', |
118 | + 'name': 'An example test plan 4', |
119 | + 'include': '# nothing\n', |
120 | + 'nested_part': ( |
121 | + 'tp2\n' |
122 | + 'tp5\n' |
123 | + ) |
124 | + }, provider=self.provider1) |
125 | + self.tp5 = TestPlanUnit({ |
126 | + 'id': 'tp5', |
127 | + 'unit': 'test-plan', |
128 | + 'name': 'An example test plan 5', |
129 | + 'include': 'Baz2', |
130 | + }, provider=self.provider1) |
131 | + self.tp6 = TestPlanUnit({ |
132 | + 'id': 'tp6', |
133 | + 'unit': 'test-plan', |
134 | + 'name': 'An example test plan 6', |
135 | + 'include': 'Foo', |
136 | + 'nested_part': 'ns2::tp7' |
137 | + }, provider=self.provider1) |
138 | + self.tp7 = TestPlanUnit({ |
139 | + 'id': 'tp7', |
140 | + 'unit': 'test-plan', |
141 | + 'name': 'An example test plan 7', |
142 | + 'include': 'Bar' |
143 | + }, provider=self.provider2) |
144 | + self.provider1.unit_list = [] |
145 | + self.provider2.unit_list = [self.tp7] |
146 | + self.tp7.provider_list = [self.provider1, self.provider2] |
147 | + for i in range(1, 7): |
148 | + tp = getattr(self, 'tp{}'.format(i)) |
149 | + tp.provider_list = [self.provider1, self.provider2] |
150 | + self.provider1.unit_list.append(tp) |
151 | + |
152 | + def test_nested_tesplan__qualifiers(self): |
153 | + qual_list = self.tp1.get_qualifier().get_primitive_qualifiers() |
154 | + mandatory_qual_list = \ |
155 | + self.tp1.get_mandatory_qualifier().get_primitive_qualifiers() |
156 | + bootstrap_qual_list = \ |
157 | + self.tp1.get_bootstrap_qualifier().get_primitive_qualifiers() |
158 | + self.assertEqual(qual_list[0].field, 'id') |
159 | + self.assertIsInstance(qual_list[0].matcher, OperatorMatcher) |
160 | + self.assertEqual(qual_list[0].matcher.value, 'ns1::Foo') |
161 | + self.assertEqual(qual_list[0].inclusive, True) |
162 | + self.assertEqual(qual_list[1].field, 'id') |
163 | + self.assertIsInstance(qual_list[1].matcher, OperatorMatcher) |
164 | + self.assertEqual(qual_list[1].matcher.value, 'ns1::Qux') |
165 | + self.assertEqual(qual_list[1].inclusive, False) |
166 | + self.assertEqual(qual_list[2].field, 'id') |
167 | + self.assertIsInstance(qual_list[2].matcher, OperatorMatcher) |
168 | + self.assertEqual(qual_list[2].matcher.value, 'ns1::Bar') |
169 | + self.assertEqual(qual_list[2].inclusive, True) |
170 | + self.assertEqual(mandatory_qual_list[0].field, 'id') |
171 | + self.assertIsInstance(mandatory_qual_list[0].matcher, OperatorMatcher) |
172 | + self.assertEqual(mandatory_qual_list[0].matcher.value, 'ns1::Baz') |
173 | + self.assertEqual(mandatory_qual_list[0].inclusive, True) |
174 | + self.assertEqual(bootstrap_qual_list[0].field, 'id') |
175 | + self.assertIsInstance(bootstrap_qual_list[0].matcher, OperatorMatcher) |
176 | + self.assertEqual(bootstrap_qual_list[0].matcher.value, 'ns1::Qux') |
177 | + self.assertEqual(bootstrap_qual_list[0].inclusive, True) |
178 | + |
179 | + def test_nested_tesplan__certification_status_override(self): |
180 | + support = TestPlanUnitSupport(self.tp3) |
181 | + self.assertEqual( |
182 | + support.override_list, |
183 | + [('^ns1::Bar$', [('certification_status', 'blocker')])]) |
184 | + |
185 | + def test_nested_tesplan__multiple_parts(self): |
186 | + qual_list = self.tp4.get_qualifier().get_primitive_qualifiers() |
187 | + self.assertEqual(qual_list[1].field, 'id') |
188 | + self.assertIsInstance(qual_list[1].matcher, OperatorMatcher) |
189 | + self.assertEqual(qual_list[1].matcher.value, 'ns1::Bar') |
190 | + self.assertEqual(qual_list[1].inclusive, True) |
191 | + self.assertEqual(qual_list[3].field, 'id') |
192 | + self.assertIsInstance(qual_list[3].matcher, OperatorMatcher) |
193 | + self.assertEqual(qual_list[3].matcher.value, 'ns1::Baz2') |
194 | + self.assertEqual(qual_list[3].inclusive, True) |
195 | + |
196 | + def test_nested_tesplan__multiple_namespaces(self): |
197 | + qual_list = self.tp6.get_qualifier().get_primitive_qualifiers() |
198 | + self.assertEqual(qual_list[0].field, 'id') |
199 | + self.assertIsInstance(qual_list[0].matcher, OperatorMatcher) |
200 | + self.assertEqual(qual_list[0].matcher.value, 'ns1::Foo') |
201 | + self.assertEqual(qual_list[0].inclusive, True) |
202 | + self.assertEqual(qual_list[1].field, 'id') |
203 | + self.assertIsInstance(qual_list[1].matcher, OperatorMatcher) |
204 | + self.assertEqual(qual_list[1].matcher.value, 'ns2::Bar') |
205 | + self.assertEqual(qual_list[1].inclusive, True) |
206 | |
207 | === modified file 'plainbox/plainbox/impl/unit/testplan.py' |
208 | --- plainbox/plainbox/impl/unit/testplan.py 2016-05-20 13:54:59 +0000 |
209 | +++ plainbox/plainbox/impl/unit/testplan.py 2016-06-09 20:31:24 +0000 |
210 | @@ -24,6 +24,7 @@ |
211 | import logging |
212 | import operator |
213 | import re |
214 | +from functools import lru_cache |
215 | |
216 | from plainbox.i18n import gettext as _ |
217 | from plainbox.impl.secure.qualifiers import CompositeQualifier |
218 | @@ -237,16 +238,33 @@ |
219 | return self.get_record_value('exclude') |
220 | |
221 | @property |
222 | + def nested_part(self): |
223 | + return self.get_record_value('nested_part') |
224 | + |
225 | + @property |
226 | def icon(self): |
227 | return self.get_record_value('icon') |
228 | |
229 | @property |
230 | def category_overrides(self): |
231 | - return self.get_record_value('category-overrides') |
232 | + return self.get_record_value('category_overrides') |
233 | |
234 | @property |
235 | def certification_status_overrides(self): |
236 | - return self.get_record_value('certification-status-overrides') |
237 | + return self.get_record_value('certification_status_overrides') |
238 | + |
239 | + @property |
240 | + def provider_list(self): |
241 | + """ |
242 | + List of provider to used when calling get_throwaway_manager(). |
243 | + Meant to be used by unit tests only. |
244 | + """ |
245 | + if hasattr(self, "_provider_list"): |
246 | + return self._provider_list |
247 | + |
248 | + @provider_list.setter |
249 | + def provider_list(self, value): |
250 | + self._provider_list = value |
251 | |
252 | @property |
253 | def estimated_duration(self): |
254 | @@ -299,21 +317,51 @@ |
255 | def get_bootstrap_job_ids(self): |
256 | """Compute and return a set of job ids from bootstrap_include field.""" |
257 | job_ids = set() |
258 | - if self.bootstrap_include is None: |
259 | - return job_ids |
260 | - |
261 | - class V(Visitor): |
262 | - |
263 | - def visit_Text_node(visitor, node: Text): |
264 | - job_ids.add(self.qualify_id(node.text)) |
265 | - |
266 | - def visit_Error_node(visitor, node: Error): |
267 | - logger.warning(_( |
268 | - "unable to parse bootstrap_include: %s"), node.msg) |
269 | - |
270 | - V().visit(WordList.parse(self.bootstrap_include)) |
271 | + if self.bootstrap_include is not None: |
272 | + |
273 | + class V(Visitor): |
274 | + |
275 | + def visit_Text_node(visitor, node: Text): |
276 | + job_ids.add(self.qualify_id(node.text)) |
277 | + |
278 | + def visit_Error_node(visitor, node: Error): |
279 | + logger.warning(_( |
280 | + "unable to parse bootstrap_include: %s"), node.msg) |
281 | + |
282 | + V().visit(WordList.parse(self.bootstrap_include)) |
283 | + for tp_unit in self.get_nested_part(): |
284 | + job_ids |= tp_unit.get_bootstrap_job_ids() |
285 | return job_ids |
286 | |
287 | + @lru_cache(maxsize=None) |
288 | + def get_nested_part(self): |
289 | + """Compute and return a set of test plan ids from nested_part field.""" |
290 | + nested_parts = [] |
291 | + if self.nested_part is not None: |
292 | + from plainbox.impl.session import SessionManager |
293 | + with SessionManager.get_throwaway_manager(self.provider_list) as m: |
294 | + context = m.default_device_context |
295 | + testplan_ids = [] |
296 | + |
297 | + class V(Visitor): |
298 | + |
299 | + def visit_Text_node(visitor, node: Text): |
300 | + testplan_ids.append(self.qualify_id(node.text)) |
301 | + |
302 | + def visit_Error_node(visitor, node: Error): |
303 | + logger.warning(_( |
304 | + "unable to parse nested_part: %s"), node.msg) |
305 | + |
306 | + V().visit(WordList.parse(self.nested_part)) |
307 | + for tp_id in testplan_ids: |
308 | + try: |
309 | + nested_parts.append(context.get_unit(tp_id, 'test plan')) |
310 | + except KeyError: |
311 | + logger.warning(_( |
312 | + "unable to find nested part: %s"), tp_id) |
313 | + return nested_parts |
314 | + |
315 | + @lru_cache(maxsize=None) |
316 | def get_qualifier(self): |
317 | """ |
318 | Convert this test plan to an equivalent qualifier for job selection |
319 | @@ -326,8 +374,11 @@ |
320 | qual_list.extend(self._gen_qualifiers('include', self.include, True)) |
321 | qual_list.extend(self._gen_qualifiers('exclude', self.exclude, False)) |
322 | qual_list.extend([self.get_bootstrap_qualifier(excluding=True)]) |
323 | + for tp_unit in self.get_nested_part(): |
324 | + qual_list.extend([tp_unit.get_qualifier()]) |
325 | return CompositeQualifier(qual_list) |
326 | |
327 | + @lru_cache(maxsize=None) |
328 | def get_mandatory_qualifier(self): |
329 | """ |
330 | Convert this test plan to an equivalent qualifier for job selection |
331 | @@ -339,20 +390,24 @@ |
332 | qual_list = [] |
333 | qual_list.extend( |
334 | self._gen_qualifiers('include', self.mandatory_include, True)) |
335 | + for tp_unit in self.get_nested_part(): |
336 | + qual_list.extend([tp_unit.get_mandatory_qualifier()]) |
337 | return CompositeQualifier(qual_list) |
338 | |
339 | + @lru_cache(maxsize=None) |
340 | def get_bootstrap_qualifier(self, excluding=False): |
341 | """ |
342 | Convert this test plan to an equivalent qualifier for job selection |
343 | """ |
344 | qual_list = [] |
345 | - if self.bootstrap_include is None: |
346 | - return CompositeQualifier(qual_list) |
347 | - field_origin = self.origin.just_line().with_offset( |
348 | - self.field_offset_map['bootstrap_include']) |
349 | - qual_list = [FieldQualifier( |
350 | - 'id', OperatorMatcher(operator.eq, target_id), field_origin, |
351 | - not excluding) for target_id in self.get_bootstrap_job_ids()] |
352 | + if self.bootstrap_include is not None: |
353 | + field_origin = self.origin.just_line().with_offset( |
354 | + self.field_offset_map['bootstrap_include']) |
355 | + qual_list = [FieldQualifier( |
356 | + 'id', OperatorMatcher(operator.eq, target_id), field_origin, |
357 | + not excluding) for target_id in self.get_bootstrap_job_ids()] |
358 | + for tp_unit in self.get_nested_part(): |
359 | + qual_list.extend([tp_unit.get_bootstrap_qualifier(excluding)]) |
360 | return CompositeQualifier(qual_list) |
361 | |
362 | def _gen_qualifiers(self, field_name, field_value, inclusive): |
363 | @@ -547,6 +602,7 @@ |
364 | mandatory_include = 'mandatory_include' |
365 | bootstrap_include = 'bootstrap_include' |
366 | exclude = 'exclude' |
367 | + nested_part = 'nested_part' |
368 | estimated_duration = 'estimated_duration' |
369 | icon = 'icon' |
370 | category_overrides = 'category-overrides' |
371 | @@ -602,6 +658,9 @@ |
372 | fields.exclude: [ |
373 | NonEmptyPatternIntersectionValidator, |
374 | ], |
375 | + fields.nested_part: [ |
376 | + NonEmptyPatternIntersectionValidator, |
377 | + ], |
378 | fields.estimated_duration: [ |
379 | UntranslatableFieldValidator, |
380 | TemplateInvariantFieldValidator, |
381 | @@ -649,17 +708,17 @@ |
382 | Some examples of how that works, given this test plan: |
383 | >>> testplan = TestPlanUnit({ |
384 | ... 'include': ''' |
385 | - ... job-a certification-status=blocker, category-id=example |
386 | - ... job-b certification-status=non-blocker |
387 | + ... job-a certification_status=blocker, category-id=example |
388 | + ... job-b certification_status=non-blocker |
389 | ... job-c |
390 | ... ''', |
391 | ... 'exclude': ''' |
392 | ... job-[x-z] |
393 | ... ''', |
394 | - ... 'category-overrides': ''' |
395 | + ... 'category_overrides': ''' |
396 | ... apply other-example to job-[bc] |
397 | ... ''', |
398 | - ... 'certification-status-overrides': ''' |
399 | + ... 'certification_status_overrides': ''' |
400 | ... apply not-part-of-certification to job-c |
401 | ... ''', |
402 | ... }) |
403 | @@ -810,6 +869,8 @@ |
404 | override_list.append((pattern, 'category_id', category_id)) |
405 | |
406 | V().visit(OverrideFieldList.parse(testplan.category_overrides)) |
407 | + for tp_unit in testplan.get_nested_part(): |
408 | + override_list.extend(self._get_category_overrides(tp_unit)) |
409 | return override_list |
410 | |
411 | def _get_blocker_status_overrides( |
412 | @@ -824,20 +885,21 @@ |
413 | is the overridden value. |
414 | """ |
415 | override_list = [] |
416 | - if testplan.certification_status_overrides is None: |
417 | - return override_list |
418 | - |
419 | - class V(Visitor): |
420 | - |
421 | - def visit_FieldOverride_node(self, node: FieldOverride): |
422 | - blocker_status = node.value.text |
423 | - pattern = r"^{}$".format( |
424 | - testplan.qualify_id(node.pattern.text)) |
425 | - override_list.append( |
426 | - (pattern, 'certification_status', blocker_status)) |
427 | - |
428 | - V().visit(OverrideFieldList.parse( |
429 | - testplan.certification_status_overrides)) |
430 | + if testplan.certification_status_overrides is not None: |
431 | + |
432 | + class V(Visitor): |
433 | + |
434 | + def visit_FieldOverride_node(self, node: FieldOverride): |
435 | + blocker_status = node.value.text |
436 | + pattern = r"^{}$".format( |
437 | + testplan.qualify_id(node.pattern.text)) |
438 | + override_list.append( |
439 | + (pattern, 'certification_status', blocker_status)) |
440 | + |
441 | + V().visit(OverrideFieldList.parse( |
442 | + testplan.certification_status_overrides)) |
443 | + for tp_unit in testplan.get_nested_part(): |
444 | + override_list.extend(self._get_blocker_status_overrides(tp_unit)) |
445 | return override_list |
446 | |
447 | def _get_inline_overrides( |
448 | @@ -850,21 +912,22 @@ |
449 | subsequently packed into a tuple ``(pattern, field_value_list)``. |
450 | """ |
451 | override_list = [] |
452 | - if testplan.include is None: |
453 | - return override_list |
454 | - |
455 | - class V(Visitor): |
456 | - |
457 | - def visit_IncludeStmt_node(self, node: IncludeStmt): |
458 | - if not node.overrides: |
459 | - return |
460 | - pattern = r"^{}$".format( |
461 | - testplan.qualify_id(node.pattern.text)) |
462 | - field_value_list = [ |
463 | - (override_exp.field.text.replace('-', '_'), |
464 | - override_exp.value.text) |
465 | - for override_exp in node.overrides] |
466 | - override_list.append((pattern, field_value_list)) |
467 | - |
468 | - V().visit(IncludeStmtList.parse(testplan.include)) |
469 | + if testplan.include is not None: |
470 | + |
471 | + class V(Visitor): |
472 | + |
473 | + def visit_IncludeStmt_node(self, node: IncludeStmt): |
474 | + if not node.overrides: |
475 | + return |
476 | + pattern = r"^{}$".format( |
477 | + testplan.qualify_id(node.pattern.text)) |
478 | + field_value_list = [ |
479 | + (override_exp.field.text.replace('-', '_'), |
480 | + override_exp.value.text) |
481 | + for override_exp in node.overrides] |
482 | + override_list.append((pattern, field_value_list)) |
483 | + |
484 | + V().visit(IncludeStmtList.parse(testplan.include)) |
485 | + for tp_unit in testplan.get_nested_part(): |
486 | + override_list.extend(self._get_inline_overrides(tp_unit)) |
487 | return override_list |
Looks good, and unit tests pass. +1