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
1=== modified file 'checkbox-ng/checkbox_ng/commands/newcli.py'
2--- checkbox-ng/checkbox_ng/commands/newcli.py 2015-06-10 14:53:49 +0000
3+++ checkbox-ng/checkbox_ng/commands/newcli.py 2015-06-12 15:06:42 +0000
4@@ -329,8 +329,12 @@
5 # the original ordering we should just treat it as a mask and
6 # use it to filter jobs from desired_job_list.
7 wanted_set = frozenset(tree.selection)
8+ qualifier_list = [
9+ tp.get_required_jobs_qualifier() for tp in self._testplan_list]
10+ required_job_list = select_jobs(
11+ self.manager.state.job_list, qualifier_list)
12 job_list = [job for job in self.manager.state.run_list
13- if job in wanted_set]
14+ if job in wanted_set] + required_job_list
15 self._update_desired_job_list(job_list)
16
17 def export_and_send_results(self):
18
19=== modified file 'plainbox/docs/manpages/plainbox-test-plan-units.rst'
20--- plainbox/docs/manpages/plainbox-test-plan-units.rst 2015-03-09 13:38:05 +0000
21+++ plainbox/docs/manpages/plainbox-test-plan-units.rst 2015-06-12 15:06:42 +0000
22@@ -131,6 +131,15 @@
23
24 When a job is both included and excluded, exclusion always takes priority.
25
26+``required``:
27+ A multi-line list of job identifiers or patterns matching such identifiers
28+ that should always be executed.
29+
30+ This optional field can be used to specify the jobs that should always run.
31+ This is particularly useful for specifying jobs that gather vital
32+ info about the tested system, as it renders imposible to generate a report
33+ with no information about system under test.
34+
35 ``category-overrides``:
36 A multi-line list of category override statements.
37
38
39=== modified file 'plainbox/plainbox/impl/unit/test_testplan.py'
40--- plainbox/plainbox/impl/unit/test_testplan.py 2015-06-10 15:40:16 +0000
41+++ plainbox/plainbox/impl/unit/test_testplan.py 2015-06-12 15:06:42 +0000
42@@ -104,6 +104,17 @@
43 }, provider=self.provider)
44 self.assertEqual(unit.exclude, "exclude")
45
46+ def test_required_default(self):
47+ unit = TestPlanUnit({
48+ }, provider=self.provider)
49+ self.assertEqual(unit.required, None)
50+
51+ def test_required_normal(self):
52+ unit = TestPlanUnit({
53+ 'required': 'required'
54+ }, provider=self.provider)
55+ self.assertEqual(unit.required, "required")
56+
57 def test_category_override__default(self):
58 unit = TestPlanUnit({
59 }, provider=self.provider)
60@@ -224,6 +235,7 @@
61 # +4 # nothing
62 # +5 b.*
63 # +6 exclude: bar
64+ # +7 required: baz
65 # Let's also assume that it is at a +10 offset in the file it comes
66 # from so that the first line +0 is actually the 10th Line
67 src = mock.Mock(name='source', spec_set=ITextSource)
68@@ -232,7 +244,8 @@
69 'unit': 0,
70 'name': 1,
71 'include': 3,
72- 'exclude': 6
73+ 'exclude': 6,
74+ 'required': 7
75 }
76 unit = TestPlanUnit({
77 'unit': 'test-plan',
78@@ -242,7 +255,8 @@
79 '# nothing\n'
80 'b.*\n'
81 ),
82- 'exclude': 'bar\n'
83+ 'exclude': 'bar\n',
84+ 'required': 'baz\n'
85 }, provider=self.provider, origin=origin,
86 field_offset_map=field_offset_map)
87 qual_list = unit.get_qualifier().get_primitive_qualifiers()
88@@ -261,6 +275,11 @@
89 self.assertEqual(qual_list[2].matcher.value, 'ns::bar')
90 self.assertEqual(qual_list[2].origin, Origin(src, 16, 16))
91 self.assertEqual(qual_list[2].inclusive, False)
92+ self.assertEqual(qual_list[3].field, 'id')
93+ self.assertIsInstance(qual_list[3].matcher, OperatorMatcher)
94+ self.assertEqual(qual_list[3].matcher.value, 'ns::baz')
95+ self.assertEqual(qual_list[3].origin, Origin(src, 17, 17))
96+ self.assertEqual(qual_list[3].inclusive, True)
97
98 def test_get_qualifier__only_comments(self):
99 unit = TestPlanUnit({
100@@ -315,3 +334,15 @@
101 unit = TestPlanUnit({}, provider=self.provider)
102 with self.assertRaisesRegex(ValueError, "expected override value"):
103 unit.parse_category_overrides('apply')
104+
105+ def test_get_required_jobs_qualifier__when_required_excluded(self):
106+ tp = TestPlanUnit({
107+ 'required': 'required',
108+ 'exclude': 'required'
109+ }, provider=self.provider)
110+ src = mock.Mock(name='source', spec_set=ITextSource)
111+ qualifiers = (tp.get_required_jobs_qualifier()
112+ .get_primitive_qualifiers())
113+ self.assertEqual(qualifiers[0].field, 'id')
114+ self.assertIsInstance(qualifiers[0].matcher, OperatorMatcher)
115+ self.assertEqual(qualifiers[0].matcher.value, 'ns::required')
116
117=== modified file 'plainbox/plainbox/impl/unit/testplan.py'
118--- plainbox/plainbox/impl/unit/testplan.py 2015-04-14 12:17:11 +0000
119+++ plainbox/plainbox/impl/unit/testplan.py 2015-06-12 15:06:42 +0000
120@@ -159,6 +159,10 @@
121 return self.get_record_value('exclude')
122
123 @property
124+ def required(self):
125+ return self.get_record_value('required')
126+
127+ @property
128 def icon(self):
129 return self.get_record_value('icon')
130
131@@ -202,13 +206,34 @@
132
133 :returns:
134 A CompositeQualifier corresponding to the contents of both
135- the include and exclude fields.
136+ the include, exclude and required fields.
137 """
138 qual_list = []
139 qual_list.extend(self._gen_qualifiers('include', self.include, True))
140 qual_list.extend(self._gen_qualifiers('exclude', self.exclude, False))
141+ qual_list.extend(self._gen_qualifiers('required', self.required, True))
142 return CompositeQualifier(qual_list)
143
144+ def get_required_jobs_qualifier(self):
145+ """
146+ Convert this test plan to qualifier that picks only required jobs.
147+
148+ :returns:
149+ A FieldQualifier corresponding to the 'required' field.
150+ """
151+ field_qualifier_list = []
152+ if self.required is not None:
153+ field_origin = self.origin.just_line().with_offset(
154+ self.field_offset_map['required'])
155+ matchers_gen = self.parse_matchers(self.required)
156+ for lineno_offset, matcher_field, matcher, error in matchers_gen:
157+ if error is not None:
158+ raise error
159+ offset = field_origin.with_offset(lineno_offset)
160+ field_qualifier_list.append(FieldQualifier(
161+ matcher_field, matcher, offset, True))
162+ return CompositeQualifier(field_qualifier_list)
163+
164 def _gen_qualifiers(self, field_name, field_value, inclusive):
165 if field_value is not None:
166 field_origin = self.origin.just_line().with_offset(
167@@ -399,6 +424,7 @@
168 description = 'description'
169 include = 'include'
170 exclude = 'exclude'
171+ required = 'required'
172 estimated_duration = 'estimated_duration'
173 icon = 'icon'
174 category_overrides = 'category-overrides'
175@@ -434,6 +460,9 @@
176 fields.exclude: [
177 NonEmptyPatternIntersectionValidator,
178 ],
179+ fields.required: [
180+ NonEmptyPatternIntersectionValidator,
181+ ],
182 fields.estimated_duration: [
183 UntranslatableFieldValidator,
184 TemplateInvariantFieldValidator,
185@@ -470,9 +499,9 @@
186 methods on the test plan unit class itself can be implemented in an easier
187 way.
188
189- The key data to handle are obviously the ``include`` and ``exclude``
190- fields. Those are used to come up with a qualifier object suitable for
191- selecting jobs.
192+ The key data to handle are obviously the ``include``, ``exclude`` and
193+ ``required`` fields. Those are used to come up with a qualifier object
194+ suitable for selecting jobs.
195
196 The second key piece of data is obtained from the ``include`` field and
197 from the ``category-overrides`` and ``certification-status-overrides``
198@@ -527,6 +556,8 @@
199 self._get_qualifier_for(testplan, 'include', True))
200 qual_list.extend(
201 self._get_qualifier_for(testplan, 'exclude', False))
202+ qual_list.extend(
203+ self._get_qualifier_for(testplan, 'required', True))
204 return CompositeQualifier(qual_list)
205
206 def _get_qualifier_for(self, testplan, field_name, inclusive):

Subscribers

People subscribed via source and target branches