Merge lp:~kissiel/checkbox/fix-1391774-required-field-in-tp into lp:checkbox

Proposed by Maciej Kisielewski
Status: Work in progress
Proposed branch: lp:~kissiel/checkbox/fix-1391774-required-field-in-tp
Merge into: lp:checkbox
Diff against target: 206 lines (+82/-7)
4 files modified
checkbox-ng/checkbox_ng/commands/newcli.py (+5/-1)
plainbox/docs/manpages/plainbox-test-plan-units.rst (+9/-0)
plainbox/plainbox/impl/unit/test_testplan.py (+33/-2)
plainbox/plainbox/impl/unit/testplan.py (+35/-4)
To merge this branch: bzr merge lp:~kissiel/checkbox/fix-1391774-required-field-in-tp
Reviewer Review Type Date Requested Status
Checkbox Developers Pending
Review via email: mp+261860@code.launchpad.net

Description of the change

This MR brings 'required' field to test plan unit.

Jobs matching any pattern from the 'required' field will get always get executed REGARDLESS of the selection made. This change set doesn't make any changes in existing providers, but it paves the way for whitelist -> test plan conversion.

b2b06a3 plainbox:impl:unit:testplan: add 'required' field
0b621a3 plainbox:impl:unit:testplan: add get_required_jobs_qualifier to testplan
bd0325e checkbox-ng:commands: make 'normal run' run required jobs
1c555e5 plainbxo:docs:manpages: update testplan - add 'required' field info

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

General comment, don't include the "impl" part in plainbox commit messages.
Push stuff _earlier_ before stuff is done as any re-design changes are cheaper.
Please fix typos in commit messages below.

Please patch plainbox to run the mandatory part if plainbox run -T is used, then you probably won't have to patch checkbox-ng at all.

I want to see a design decision on what happens if a job is selected by both normal and mandatory parts. I think it should trigger a warning from the test plan validator and that this job should be impossible to de-select in checkbox-ng/gui/touch later.

Looking at the get_$newthing_qualifier() it seems like a copy-paste of the other one, perhaps we want a new private method and have all the old methods and the new method call that instead.

Please expand the manual page to include some examples. I think we should include a very useful certification example that says "if you want a test plan that works for certification purpose, please add this snippet "required: ..."

Oh, and the word required is better but I think I still prefer mandatory as it's used similarly in other products. I'd put my $0.90 on mandatory-include instead.

More reviews next week. Thanks.

Unmerged revisions

3831. By Maciej Kisielewski

plainbxo:docs:manpages: update testplan - add 'required' field info

Signed-off-by: Maciej Kisielewski <email address hidden>

3830. By Maciej Kisielewski

checkbox-ng:commands: make 'normal run' run required jobs

This patch makes checkbox-ng run required jobs when doing normal sequence.

Fixes: https://bugs.launchpad.net/plainbox-provider-checkbox/+bug/1463921

Signed-off-by: Maciej Kisielewski <email address hidden>

3829. By Maciej Kisielewski

plainbox:impl:unit:testplan: add get_required_jobs_qualifier to testplan

This patch adds testplan method to get qualifier that corresponds to jobs
pointed by the 'required' field.

Signed-off-by: Maciej Kisielewski <email address hidden>

3828. By Maciej Kisielewski

plainbox:impl:unit:testplan: add 'required' field

This patch adds 'required' field to the testplan that specifies jobs that
should ALWAYS be run, regardless of what user selects.

Signed-off-by: Maciej Kisielewski <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'checkbox-ng/checkbox_ng/commands/newcli.py'
--- checkbox-ng/checkbox_ng/commands/newcli.py 2015-06-10 14:53:49 +0000
+++ checkbox-ng/checkbox_ng/commands/newcli.py 2015-06-12 15:06:42 +0000
@@ -329,8 +329,12 @@
329 # the original ordering we should just treat it as a mask and329 # the original ordering we should just treat it as a mask and
330 # use it to filter jobs from desired_job_list.330 # use it to filter jobs from desired_job_list.
331 wanted_set = frozenset(tree.selection)331 wanted_set = frozenset(tree.selection)
332 qualifier_list = [
333 tp.get_required_jobs_qualifier() for tp in self._testplan_list]
334 required_job_list = select_jobs(
335 self.manager.state.job_list, qualifier_list)
332 job_list = [job for job in self.manager.state.run_list336 job_list = [job for job in self.manager.state.run_list
333 if job in wanted_set]337 if job in wanted_set] + required_job_list
334 self._update_desired_job_list(job_list)338 self._update_desired_job_list(job_list)
335339
336 def export_and_send_results(self):340 def export_and_send_results(self):
337341
=== modified file 'plainbox/docs/manpages/plainbox-test-plan-units.rst'
--- plainbox/docs/manpages/plainbox-test-plan-units.rst 2015-03-09 13:38:05 +0000
+++ plainbox/docs/manpages/plainbox-test-plan-units.rst 2015-06-12 15:06:42 +0000
@@ -131,6 +131,15 @@
131131
132 When a job is both included and excluded, exclusion always takes priority.132 When a job is both included and excluded, exclusion always takes priority.
133133
134``required``:
135 A multi-line list of job identifiers or patterns matching such identifiers
136 that should always be executed.
137
138 This optional field can be used to specify the jobs that should always run.
139 This is particularly useful for specifying jobs that gather vital
140 info about the tested system, as it renders imposible to generate a report
141 with no information about system under test.
142
134``category-overrides``:143``category-overrides``:
135 A multi-line list of category override statements.144 A multi-line list of category override statements.
136145
137146
=== modified file 'plainbox/plainbox/impl/unit/test_testplan.py'
--- plainbox/plainbox/impl/unit/test_testplan.py 2015-06-10 15:40:16 +0000
+++ plainbox/plainbox/impl/unit/test_testplan.py 2015-06-12 15:06:42 +0000
@@ -104,6 +104,17 @@
104 }, provider=self.provider)104 }, provider=self.provider)
105 self.assertEqual(unit.exclude, "exclude")105 self.assertEqual(unit.exclude, "exclude")
106106
107 def test_required_default(self):
108 unit = TestPlanUnit({
109 }, provider=self.provider)
110 self.assertEqual(unit.required, None)
111
112 def test_required_normal(self):
113 unit = TestPlanUnit({
114 'required': 'required'
115 }, provider=self.provider)
116 self.assertEqual(unit.required, "required")
117
107 def test_category_override__default(self):118 def test_category_override__default(self):
108 unit = TestPlanUnit({119 unit = TestPlanUnit({
109 }, provider=self.provider)120 }, provider=self.provider)
@@ -224,6 +235,7 @@
224 # +4 # nothing235 # +4 # nothing
225 # +5 b.*236 # +5 b.*
226 # +6 exclude: bar237 # +6 exclude: bar
238 # +7 required: baz
227 # Let's also assume that it is at a +10 offset in the file it comes239 # Let's also assume that it is at a +10 offset in the file it comes
228 # from so that the first line +0 is actually the 10th Line240 # from so that the first line +0 is actually the 10th Line
229 src = mock.Mock(name='source', spec_set=ITextSource)241 src = mock.Mock(name='source', spec_set=ITextSource)
@@ -232,7 +244,8 @@
232 'unit': 0,244 'unit': 0,
233 'name': 1,245 'name': 1,
234 'include': 3,246 'include': 3,
235 'exclude': 6247 'exclude': 6,
248 'required': 7
236 }249 }
237 unit = TestPlanUnit({250 unit = TestPlanUnit({
238 'unit': 'test-plan',251 'unit': 'test-plan',
@@ -242,7 +255,8 @@
242 '# nothing\n'255 '# nothing\n'
243 'b.*\n'256 'b.*\n'
244 ),257 ),
245 'exclude': 'bar\n'258 'exclude': 'bar\n',
259 'required': 'baz\n'
246 }, provider=self.provider, origin=origin,260 }, provider=self.provider, origin=origin,
247 field_offset_map=field_offset_map)261 field_offset_map=field_offset_map)
248 qual_list = unit.get_qualifier().get_primitive_qualifiers()262 qual_list = unit.get_qualifier().get_primitive_qualifiers()
@@ -261,6 +275,11 @@
261 self.assertEqual(qual_list[2].matcher.value, 'ns::bar')275 self.assertEqual(qual_list[2].matcher.value, 'ns::bar')
262 self.assertEqual(qual_list[2].origin, Origin(src, 16, 16))276 self.assertEqual(qual_list[2].origin, Origin(src, 16, 16))
263 self.assertEqual(qual_list[2].inclusive, False)277 self.assertEqual(qual_list[2].inclusive, False)
278 self.assertEqual(qual_list[3].field, 'id')
279 self.assertIsInstance(qual_list[3].matcher, OperatorMatcher)
280 self.assertEqual(qual_list[3].matcher.value, 'ns::baz')
281 self.assertEqual(qual_list[3].origin, Origin(src, 17, 17))
282 self.assertEqual(qual_list[3].inclusive, True)
264283
265 def test_get_qualifier__only_comments(self):284 def test_get_qualifier__only_comments(self):
266 unit = TestPlanUnit({285 unit = TestPlanUnit({
@@ -315,3 +334,15 @@
315 unit = TestPlanUnit({}, provider=self.provider)334 unit = TestPlanUnit({}, provider=self.provider)
316 with self.assertRaisesRegex(ValueError, "expected override value"):335 with self.assertRaisesRegex(ValueError, "expected override value"):
317 unit.parse_category_overrides('apply')336 unit.parse_category_overrides('apply')
337
338 def test_get_required_jobs_qualifier__when_required_excluded(self):
339 tp = TestPlanUnit({
340 'required': 'required',
341 'exclude': 'required'
342 }, provider=self.provider)
343 src = mock.Mock(name='source', spec_set=ITextSource)
344 qualifiers = (tp.get_required_jobs_qualifier()
345 .get_primitive_qualifiers())
346 self.assertEqual(qualifiers[0].field, 'id')
347 self.assertIsInstance(qualifiers[0].matcher, OperatorMatcher)
348 self.assertEqual(qualifiers[0].matcher.value, 'ns::required')
318349
=== modified file 'plainbox/plainbox/impl/unit/testplan.py'
--- plainbox/plainbox/impl/unit/testplan.py 2015-04-14 12:17:11 +0000
+++ plainbox/plainbox/impl/unit/testplan.py 2015-06-12 15:06:42 +0000
@@ -159,6 +159,10 @@
159 return self.get_record_value('exclude')159 return self.get_record_value('exclude')
160160
161 @property161 @property
162 def required(self):
163 return self.get_record_value('required')
164
165 @property
162 def icon(self):166 def icon(self):
163 return self.get_record_value('icon')167 return self.get_record_value('icon')
164168
@@ -202,13 +206,34 @@
202206
203 :returns:207 :returns:
204 A CompositeQualifier corresponding to the contents of both208 A CompositeQualifier corresponding to the contents of both
205 the include and exclude fields.209 the include, exclude and required fields.
206 """210 """
207 qual_list = []211 qual_list = []
208 qual_list.extend(self._gen_qualifiers('include', self.include, True))212 qual_list.extend(self._gen_qualifiers('include', self.include, True))
209 qual_list.extend(self._gen_qualifiers('exclude', self.exclude, False))213 qual_list.extend(self._gen_qualifiers('exclude', self.exclude, False))
214 qual_list.extend(self._gen_qualifiers('required', self.required, True))
210 return CompositeQualifier(qual_list)215 return CompositeQualifier(qual_list)
211216
217 def get_required_jobs_qualifier(self):
218 """
219 Convert this test plan to qualifier that picks only required jobs.
220
221 :returns:
222 A FieldQualifier corresponding to the 'required' field.
223 """
224 field_qualifier_list = []
225 if self.required is not None:
226 field_origin = self.origin.just_line().with_offset(
227 self.field_offset_map['required'])
228 matchers_gen = self.parse_matchers(self.required)
229 for lineno_offset, matcher_field, matcher, error in matchers_gen:
230 if error is not None:
231 raise error
232 offset = field_origin.with_offset(lineno_offset)
233 field_qualifier_list.append(FieldQualifier(
234 matcher_field, matcher, offset, True))
235 return CompositeQualifier(field_qualifier_list)
236
212 def _gen_qualifiers(self, field_name, field_value, inclusive):237 def _gen_qualifiers(self, field_name, field_value, inclusive):
213 if field_value is not None:238 if field_value is not None:
214 field_origin = self.origin.just_line().with_offset(239 field_origin = self.origin.just_line().with_offset(
@@ -399,6 +424,7 @@
399 description = 'description'424 description = 'description'
400 include = 'include'425 include = 'include'
401 exclude = 'exclude'426 exclude = 'exclude'
427 required = 'required'
402 estimated_duration = 'estimated_duration'428 estimated_duration = 'estimated_duration'
403 icon = 'icon'429 icon = 'icon'
404 category_overrides = 'category-overrides'430 category_overrides = 'category-overrides'
@@ -434,6 +460,9 @@
434 fields.exclude: [460 fields.exclude: [
435 NonEmptyPatternIntersectionValidator,461 NonEmptyPatternIntersectionValidator,
436 ],462 ],
463 fields.required: [
464 NonEmptyPatternIntersectionValidator,
465 ],
437 fields.estimated_duration: [466 fields.estimated_duration: [
438 UntranslatableFieldValidator,467 UntranslatableFieldValidator,
439 TemplateInvariantFieldValidator,468 TemplateInvariantFieldValidator,
@@ -470,9 +499,9 @@
470 methods on the test plan unit class itself can be implemented in an easier499 methods on the test plan unit class itself can be implemented in an easier
471 way.500 way.
472501
473 The key data to handle are obviously the ``include`` and ``exclude``502 The key data to handle are obviously the ``include``, ``exclude`` and
474 fields. Those are used to come up with a qualifier object suitable for503 ``required`` fields. Those are used to come up with a qualifier object
475 selecting jobs.504 suitable for selecting jobs.
476505
477 The second key piece of data is obtained from the ``include`` field and506 The second key piece of data is obtained from the ``include`` field and
478 from the ``category-overrides`` and ``certification-status-overrides``507 from the ``category-overrides`` and ``certification-status-overrides``
@@ -527,6 +556,8 @@
527 self._get_qualifier_for(testplan, 'include', True))556 self._get_qualifier_for(testplan, 'include', True))
528 qual_list.extend(557 qual_list.extend(
529 self._get_qualifier_for(testplan, 'exclude', False))558 self._get_qualifier_for(testplan, 'exclude', False))
559 qual_list.extend(
560 self._get_qualifier_for(testplan, 'required', True))
530 return CompositeQualifier(qual_list)561 return CompositeQualifier(qual_list)
531562
532 def _get_qualifier_for(self, testplan, field_name, inclusive):563 def _get_qualifier_for(self, testplan, field_name, inclusive):

Subscribers

People subscribed via source and target branches