Merge lp:~roadmr/checkbox/cdts-report-2-exporter-non-boolean-options-xml-client-name-option into lp:checkbox

Proposed by Daniel Manrique
Status: Merged
Approved by: Zygmunt Krynicki
Approved revision: 2866
Merged at revision: 2864
Proposed branch: lp:~roadmr/checkbox/cdts-report-2-exporter-non-boolean-options-xml-client-name-option
Merge into: lp:checkbox
Diff against target: 254 lines (+144/-20)
5 files modified
plainbox/plainbox/impl/commands/test_run.py (+1/-1)
plainbox/plainbox/impl/exporter/__init__.py (+52/-5)
plainbox/plainbox/impl/exporter/test_init.py (+40/-0)
plainbox/plainbox/impl/exporter/test_xml.py (+36/-5)
plainbox/plainbox/impl/exporter/xml.py (+15/-9)
To merge this branch: bzr merge lp:~roadmr/checkbox/cdts-report-2-exporter-non-boolean-options-xml-client-name-option
Reviewer Review Type Date Requested Status
Zygmunt Krynicki (community) Approve
Review via email: mp+213723@code.launchpad.net

Commit message

 plainbox:exporter:xml: Add client-name option. Add support to exporters for non-boolean options using "=".

Description of the change

Adds support for non-boolean options in the option-string for exporters. This is leveraged to add the client-name setting to the xml exporter, which in turn results in this information showing in the HTML report. I did this in three small steps:

16f8fa4 plainbox:exporter:xml: Add client-name option.
763b4ab plainbox:exporter: Add support for non-boolean options using "=".
1606e15 plainbox:exporter: Change _option_list into a property.

Other than the test suite, this can be verified manually quite easily. Set up a working venv, then run:

plainbox run -w plainbox-provider-checkbox/provider_whitelists/hwsubmit.whitelist -f html -p client-name=CUSTOM_CLIENT_NAME -o /tmp/test.html

Even if no tests are run (due to running from source) the html report should show "This report was created using CUSTOM_CLIENT_NAME 0.6.0" at the top.

To post a comment you must log in.
2864. By Daniel Manrique

plainbox:exporter: Change _option_list into a property.

Added tests to ensure current behavior is maintained

2865. By Daniel Manrique

plainbox:exporter: Add support for non-boolean options using "=".

The old "API" of directly querying and setting an exporter's
_option_list is maintained for backwards compatibility.

Options that have "=" are parsed into option=value by the constructor.

New methods to query those values, as well as set an option to either
just True or a string value, were added.

2866. By Daniel Manrique

plainbox:exporter:xml: Add client-name option.

A non-boolean option (client-name=something) meant to pass the client
name to put in the report to the xml exporter. If set, this will take
precedence over the client_name parameter to the constructor.

Tests were updated accordingly.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

+1

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/test_run.py'
2--- plainbox/plainbox/impl/commands/test_run.py 2014-02-20 21:43:54 +0000
3+++ plainbox/plainbox/impl/commands/test_run.py 2014-04-01 23:36:47 +0000
4@@ -158,7 +158,7 @@
5 json: with-io-log, squash-io-log, flatten-io-log, with-run-list, with-job-list, with-resource-map, with-job-defs, with-attachments, with-comments, with-job-via, with-job-hash, machine-json
6 rfc822: with-io-log, squash-io-log, flatten-io-log, with-run-list, with-job-list, with-resource-map, with-job-defs, with-attachments, with-comments, with-job-via, with-job-hash
7 text: with-io-log, squash-io-log, flatten-io-log, with-run-list, with-job-list, with-resource-map, with-job-defs, with-attachments, with-comments, with-job-via, with-job-hash
8- xml:
9+ xml: client-name
10 """
11 self.assertEqual(io.combined, cleandoc(expected) + "\n")
12
13
14=== modified file 'plainbox/plainbox/impl/exporter/__init__.py'
15--- plainbox/plainbox/impl/exporter/__init__.py 2014-03-26 19:36:44 +0000
16+++ plainbox/plainbox/impl/exporter/__init__.py 2014-04-01 23:36:47 +0000
17@@ -61,9 +61,14 @@
18 preserve everything that the session may hold but instead to present it to
19 the user in the best format possible.
20
21- Each exporter can support a set of options (currently boolean flags) that
22- can alter the way it operates. It's best to keep the list of exporter
23- options under control to keep the user interface from becoming annoying.
24+ Each exporter can support a set of options that can alter the way it
25+ operates. Options can either be set boolean-like, or they can be assigned a
26+ value (a string). If an option contains a "=", the part of the string on
27+ the right of the equal sign will be assigned as the option's value;
28+ otherwise they operate in a boolean fashion.
29+
30+ It's best to keep the list of exporter options under control to keep the
31+ user interface from becoming annoying.
32 """
33
34 OPTION_WITH_IO_LOG = 'with-io-log'
35@@ -96,10 +101,52 @@
36 def __init__(self, option_list=None):
37 if option_list is None:
38 option_list = []
39- for option in option_list:
40+ self._option_dict = {}
41+ for option_string in option_list:
42+ # An option can look like "foo" or like "foo=bar". In the first case
43+ # we assign "True" to that key in the dict, in the second we assign "bar".
44+ key_value = option_string.split("=", 1)
45+ option = key_value[0]
46 if option not in self.supported_option_list:
47 raise ValueError("Unsupported option: {}".format(option))
48- self._option_list = option_list
49+ if len(key_value) == 2:
50+ value = key_value[1]
51+ else:
52+ value = True
53+ self._option_dict[option] = value
54+
55+ @property
56+ def _option_list(self):
57+ """
58+ Returns a list of set options.
59+ Users who only are about whether an option is set, regardless of
60+ the value assigned to it, can use this API.
61+ """
62+ return sorted([option for option in self._option_dict.keys()
63+ if self._option_dict[option]])
64+
65+ @_option_list.setter
66+ def _option_list(self, value):
67+ """
68+ Sets the option list to exactly what is sent as the parameter.
69+ Note that this will obliterate any prior settings in the list.
70+ """
71+ self._option_dict = {}
72+ for key in value:
73+ self._option_dict[key] = True
74+
75+ def get_option_value(self, option):
76+ """
77+ Returns the value assigned to an option.
78+ """
79+ return self._option_dict.get(option, False)
80+
81+ def set_option_value(self, option, value=True):
82+ """
83+ assigns a value to an option. If no value is given, it just
84+ "sets" the option to True
85+ """
86+ self._option_dict[option] = value
87
88 @classproperty
89 def supported_option_list(cls):
90
91=== modified file 'plainbox/plainbox/impl/exporter/test_init.py'
92--- plainbox/plainbox/impl/exporter/test_init.py 2013-11-27 20:40:19 +0000
93+++ plainbox/plainbox/impl/exporter/test_init.py 2014-04-01 23:36:47 +0000
94@@ -83,6 +83,46 @@
95 session.update_job_result(job_b, result_b)
96 return session
97
98+ def test_option_list_setting_boolean(self):
99+ exporter = self.TestSessionStateExporter()
100+ exporter._option_list = [
101+ SessionStateExporterBase.OPTION_WITH_IO_LOG,
102+ SessionStateExporterBase.OPTION_FLATTEN_IO_LOG]
103+ self.assertEqual(exporter._option_list, sorted([
104+ SessionStateExporterBase.OPTION_WITH_IO_LOG,
105+ SessionStateExporterBase.OPTION_FLATTEN_IO_LOG]))
106+
107+ def test_option_list_setting_boolean_all_at_once(self):
108+ # Test every option set, all at once
109+ # Just to be paranoid, ensure the options I set are the ones the
110+ # exporter actually thinks it has
111+ exporter = self.TestSessionStateExporter(
112+ self.TestSessionStateExporter.supported_option_list)
113+ self.assertEqual(exporter._option_list,
114+ sorted(self.TestSessionStateExporter.supported_option_list))
115+
116+ def test_option_list_init_non_boolean(self):
117+ option = SessionStateExporterBase.OPTION_WITH_COMMENTS
118+ exporter = self.TestSessionStateExporter(["{}=detailed".format(option)])
119+ self.assertEqual(exporter.get_option_value(option),
120+ "detailed")
121+
122+ def test_option_list_non_duplicated_options(self):
123+ # Setting the same option twice makes no sense, check it gets squashed
124+ # into only one item in the option_list.
125+ option = SessionStateExporterBase.OPTION_WITH_COMMENTS
126+ exporter = self.TestSessionStateExporter([option, option])
127+ self.assertEqual(exporter._option_list, [option])
128+
129+ def test_option_list_setting_api(self):
130+ exporter = self.TestSessionStateExporter(
131+ [SessionStateExporterBase.OPTION_WITH_IO_LOG])
132+ exporter.set_option_value("with-comments")
133+ self.assertEqual(exporter.get_option_value('with-comments'), True)
134+ exporter.set_option_value("with-comments", "detailed")
135+ self.assertEqual(exporter.get_option_value('with-comments'),
136+ "detailed")
137+
138 def test_defaults(self):
139 # Test all defaults, with all options unset
140 exporter = self.TestSessionStateExporter()
141
142=== modified file 'plainbox/plainbox/impl/exporter/test_xml.py'
143--- plainbox/plainbox/impl/exporter/test_xml.py 2013-11-27 20:40:19 +0000
144+++ plainbox/plainbox/impl/exporter/test_xml.py 2014-04-01 23:36:47 +0000
145@@ -87,18 +87,35 @@
146 class XMLExporterTests(TestCase):
147
148 def setUp(self):
149+ self.prepare_test_exporter()
150+
151+ def prepare_test_exporter(self, client_name="plainbox",
152+ system_id="",
153+ option_list=None,
154+ timestamp="2012-12-21T12:00:00",
155+ client_version="1.0"):
156 data = resource_json(
157 "plainbox", "test-data/xml-exporter/example-data.json",
158 exact=True)
159- exporter = XMLSessionStateExporter(
160- system_id="",
161- timestamp="2012-12-21T12:00:00",
162- client_version="1.0")
163+ self.exporter = XMLSessionStateExporter(
164+ client_name=client_name,
165+ option_list=option_list,
166+ system_id=system_id,
167+ timestamp=timestamp,
168+ client_version=client_version)
169 stream = io.BytesIO()
170- exporter.dump(data, stream)
171+ self.exporter.dump(data, stream)
172 self.actual_result = stream.getvalue() # This is bytes
173 self.assertIsInstance(self.actual_result, bytes)
174
175+ def test_exporter_option(self):
176+ """
177+ Ensure that the previously-optionless xml exporter can have its
178+ single accepted 'client-name' option set properly.
179+ """
180+ self.prepare_test_exporter(option_list=['client-name=verifythis'])
181+ self.assertEqual(self.exporter.get_option_value('client-name'), "verifythis")
182+
183 def test_perfect_match(self):
184 expected_result = resource_string(
185 "plainbox", "test-data/xml-exporter/example-data.xml"
186@@ -113,3 +130,17 @@
187 self.assertTrue(
188 validator.validate_text(
189 self.actual_result))
190+
191+ def test_client_name_option_takes_precedence(self):
192+ # We use trickery to verify the xml final report has the client name
193+ # sent in the option string, rather than the constructor parameter.
194+ # We pass a bogus client-name in the constructor, then the correct expected
195+ # name in the option, and just check as usual.
196+ self.prepare_test_exporter(client_name="bogus",
197+ option_list=['client-name=plainbox'])
198+ expected_result = resource_string(
199+ "plainbox", "test-data/xml-exporter/example-data.xml"
200+ ) # unintuitively, resource_string returns bytes
201+ self.assertEqual(self.actual_result, expected_result)
202+
203+
204
205=== modified file 'plainbox/plainbox/impl/exporter/xml.py'
206--- plainbox/plainbox/impl/exporter/xml.py 2014-04-01 15:30:07 +0000
207+++ plainbox/plainbox/impl/exporter/xml.py 2014-04-01 23:36:47 +0000
208@@ -98,7 +98,8 @@
209
210 NS = '2013.com.canonical.certification::'
211
212- SUPPORTED_OPTION_LIST = ()
213+ OPTION_CLIENT_NAME = 'client-name'
214+ SUPPORTED_OPTION_LIST = (OPTION_CLIENT_NAME, )
215
216 # These are the job statuses allowed by the checkbox parser.
217 # This is a limitation of the certification website, so we
218@@ -148,16 +149,18 @@
219 The name of the exporter to report. Defaults to "plainbox".
220 """
221 # Super-call with empty option list
222- super(XMLSessionStateExporter, self).__init__(())
223+ super(XMLSessionStateExporter, self).__init__((option_list))
224 # All the "options" are simply a required configuration element and are
225 # not optional in any way. There is no way to opt-out.
226- self._option_list = (
227- SessionStateExporterBase.OPTION_WITH_IO_LOG,
228- SessionStateExporterBase.OPTION_FLATTEN_IO_LOG,
229- SessionStateExporterBase.OPTION_WITH_JOB_DEFS,
230- SessionStateExporterBase.OPTION_WITH_RESOURCE_MAP,
231- SessionStateExporterBase.OPTION_WITH_COMMENTS,
232- SessionStateExporterBase.OPTION_WITH_ATTACHMENTS)
233+ xml_options = (SessionStateExporterBase.OPTION_WITH_IO_LOG,
234+ SessionStateExporterBase.OPTION_FLATTEN_IO_LOG,
235+ SessionStateExporterBase.OPTION_WITH_JOB_DEFS,
236+ SessionStateExporterBase.OPTION_WITH_RESOURCE_MAP,
237+ SessionStateExporterBase.OPTION_WITH_COMMENTS,
238+ SessionStateExporterBase.OPTION_WITH_ATTACHMENTS)
239+ for option in xml_options:
240+ self.set_option_value(option)
241+
242 # Generate a dummy system hash if needed
243 if system_id is None:
244 # FIXME: Compute an real system_id for submission to
245@@ -174,6 +177,9 @@
246 self._client_version = client_version
247 # Remember client name
248 self._client_name = client_name
249+ # If a client name was specified as an option, prefer that.
250+ if self.get_option_value('client-name'):
251+ self._client_name = self.get_option_value('client-name')
252
253 def dump(self, data, stream):
254 """

Subscribers

People subscribed via source and target branches