Merge lp:~sylvain-pineau/checkbox/show_reports into lp:checkbox
- show_reports
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 1273 | ||||
Proposed branch: | lp:~sylvain-pineau/checkbox/show_reports | ||||
Merge into: | lp:checkbox | ||||
Diff against target: |
776 lines (+472/-86) 6 files modified
checkbox_cli/cli_interface.py (+97/-1) checkbox_gtk/gtk_interface.py (+1/-1) checkbox_urwid/urwid_interface.py (+323/-84) debian/changelog (+1/-0) plugins/launchpad_report.py (+48/-0) plugins/report_prompt.py (+2/-0) |
||||
To merge this branch: | bzr merge lp:~sylvain-pineau/checkbox/show_reports | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marc Tardif (community) | Approve | ||
Sylvain Pineau | Needs Resubmitting | ||
Ara Pulido (community) | Needs Fixing | ||
Review via email: mp+93856@code.launchpad.net |
Commit message
Description of the change
This branch provides additional screens for the gtk/cli and urwid ui to display test results.
Used in conjunction with checkbox-oem, the gtk ui also provides a bug report screen.
These changes come from the "patchy" checkbox maintained by oem-qa and worked well with checkbox-oem.
Once this merge proposal is accepted we could get rid of the oem patches branches for precise.
- 1272. By Sylvain Pineau
-
Remove the gtk code of the bug report screen, useless without the corresponding plugin
Sylvain Pineau (sylvain-pineau) wrote : | # |
I've removed the bug report code making the change only related to the test report for all non qt interfaces
Marc Tardif (cr3) wrote : | # |
I'm still looking at the code but, in the meanwhile, I'd appreciate if you could report a bug describing the problem this merge request fixes. Then, add the corresponding changelog entry in your branch and feel free to resubmit. Finally, link the bug to your branch. Thanks!
Marc Tardif (cr3) wrote : | # |
On line 558 of checkbox_
- message_
+ button = getattr(Gtk, "STOCK_%s" % option.upper())
+ message_
The problem is that the message dialog is also used for prompting the user to restart or recover when running checkbox after the first time. However, there is not STOCK_RESTART nor STOCK_RECOVER attribute in Gtk. If you just revert those two lines to the previous single line, that should work.
Marc Tardif (cr3) wrote : | # |
On line 728 of checkbox_
- self._collapse_
+ #self._
This gives the uneasy feeling that maybe the code was commented out by mistake. I'm not a big fan of commenting out code in general so, while you're making changes, I'd appreciate if you could remove the line completely. Thanks!
- 1273. By Sylvain Pineau
-
Updated changelog and fix after reviews
Sylvain Pineau (sylvain-pineau) wrote : | # |
Thanks for your comment Marc, I've fixed the branch.
Marc Tardif (cr3) wrote : | # |
First, it seems that your last commit replaced some symlinks by accident:
bzr status -r 1272..
kind changed:
configs@ (symlink => directory)
data/
modified:
checkbox_
checkbox_
debian/changelog
Second, I'm not seeing the show_report method in the user interface called anywhere. And, when I run the GTK interface, the view report button still shows the old report. Is the purpose that this code will only be called by checkbox-oem rather than checkbox?
- 1274. By Sylvain Pineau
-
Update plugins to take advantage of the report screen
Sylvain Pineau (sylvain-pineau) wrote : | # |
1) Even after rebranching the code, i don't see the symlink errors you mentioned.
2) It was an error, the report code is intended to be rendered by checkbox, not only checkbox-oem. I've updated the plugins to enable it.
- 1275. By Sylvain Pineau
-
Fix missing text parameter and use NotImplementedError to raise exception in show_report
Marc Tardif (cr3) wrote : | # |
The first problem was my mistake, sorry about that. As for the second problem, I now see the report. However, do you think we should remove the "View report" link from the final screen? Otherwise, it seems strange to have two ways to view essentially the same information.
- 1276. By Sylvain Pineau
-
Remove the gtk ui implementation to just keep cli/urwid and avoid duplication with the submission.xml link
Sylvain Pineau (sylvain-pineau) wrote : | # |
The scope of the proposal is now limited to the cli/urwid interface, given that the gtk interface allows the tester to open the submission.xml to review the results.
Marc Tardif (cr3) wrote : | # |
1. I unliked bug #937764 from this merge request which only seems to relate to bug #937657.
2. I would appreciate if you could rephrase bug #937657 as a problem rather than a feature request.
3. I updated the changelog entry to be less intimidating: Display results report in non-graphical interfaces.
4. I changed checking for the show_report attribute in plugins/
Finally, I merged and pushed the rest of your changes.
Preview Diff
1 | === modified file 'checkbox_cli/cli_interface.py' |
2 | --- checkbox_cli/cli_interface.py 2012-02-09 16:03:54 +0000 |
3 | +++ checkbox_cli/cli_interface.py 2012-02-22 17:22:19 +0000 |
4 | @@ -160,6 +160,94 @@ |
5 | self.options.append(option) |
6 | |
7 | |
8 | +class CLIReportDialog(CLIDialog): |
9 | + """ |
10 | + Display test results |
11 | + """ |
12 | + STATUS = {'pass': '[0;32m{0}[0m', |
13 | + 'fail': '[0;31m{0}[0m'} |
14 | + |
15 | + def __init__(self, text, results): |
16 | + super(CLIReportDialog, self).__init__(text) |
17 | + self.results = results |
18 | + |
19 | + def run(self): |
20 | + """ |
21 | + Show root of the tree |
22 | + and provide the ability to further display subtress |
23 | + """ |
24 | + root = self.results |
25 | + title = self.text |
26 | + self._display(title, root) |
27 | + |
28 | + def _is_suite(self, root): |
29 | + """ |
30 | + Return True if root contains a suite |
31 | + that is, a job containing other jobs |
32 | + """ |
33 | + return all(issubclass(type(value), dict) |
34 | + for value in root.itervalues()) |
35 | + |
36 | + def _display(self, title, root): |
37 | + """ |
38 | + Display dialog until user decides to exit |
39 | + (recursively for subtrees) |
40 | + """ |
41 | + while True: |
42 | + self.put_newline() |
43 | + self.put_newline() |
44 | + self.put_line(title) |
45 | + self.put_newline() |
46 | + |
47 | + keys = [] |
48 | + options = [] |
49 | + def add_option(option, key=None): |
50 | + """ |
51 | + Add option to list |
52 | + and generate automatic key value |
53 | + if not provided |
54 | + """ |
55 | + if key is None: |
56 | + key = string.lowercase[len(keys)] |
57 | + keys.append(key) |
58 | + options.append(option) |
59 | + |
60 | + for job_name, job_data in sorted(root.iteritems()): |
61 | + if self._is_suite(job_data): |
62 | + add_option(job_name) |
63 | + self.put_line('{key}: {option}' |
64 | + .format(key=keys[-1], |
65 | + option=options[-1])) |
66 | + else: |
67 | + job_status = job_data.get('status') |
68 | + status_string = (self.STATUS.get(job_status, '{0}') |
69 | + .format(job_status)) |
70 | + self.put_line(' {name} [{status}]' |
71 | + .format(name=job_name, |
72 | + status=status_string)) |
73 | + |
74 | + add_option(_("Space when finished"), " ") |
75 | + self.put_line('{key}: {option}' |
76 | + .format(key=keys[-1], |
77 | + option=options[-1])) |
78 | + |
79 | + response = self.get(_("Please choose (%s): ") % ("/".join(keys))) |
80 | + |
81 | + if response != ' ': |
82 | + try: |
83 | + selected_option = options[keys.index(response)] |
84 | + except ValueError: |
85 | + # Display again menu |
86 | + continue |
87 | + |
88 | + # Display new menu with the contents of the selected option |
89 | + self._display(selected_option, root[selected_option]) |
90 | + else: |
91 | + # Exit from this menu display |
92 | + # (display again parent menu or exit) |
93 | + break |
94 | + |
95 | + |
96 | class CLITextDialog(CLIDialog): |
97 | |
98 | limit = 255 |
99 | @@ -271,7 +359,6 @@ |
100 | |
101 | def show_tree(self, text, options={}, default={}): |
102 | keys = sorted(options.keys()) |
103 | - values = [options[k] for k in keys] |
104 | |
105 | dialog = CLIChoiceDialog(text) |
106 | for option in keys: |
107 | @@ -314,6 +401,15 @@ |
108 | |
109 | return results |
110 | |
111 | + |
112 | + def show_report(self, text, results): |
113 | + """ |
114 | + Show test case results in a tree hierarchy |
115 | + """ |
116 | + dialog = CLIReportDialog(text, results) |
117 | + dialog.run() |
118 | + |
119 | + |
120 | def show_test(self, test, runner): |
121 | options = list([ANSWER_TO_OPTION[a] for a in ALL_ANSWERS]) |
122 | if "command" in test: |
123 | |
124 | === modified file 'checkbox_gtk/gtk_interface.py' |
125 | --- checkbox_gtk/gtk_interface.py 2012-02-09 16:03:54 +0000 |
126 | +++ checkbox_gtk/gtk_interface.py 2012-02-22 17:22:19 +0000 |
127 | @@ -116,7 +116,7 @@ |
128 | for radio_button, value in map.items(): |
129 | if self._get_widget(radio_button).get_active(): |
130 | return value |
131 | - raise Exception, "Failed to map radio_button." |
132 | + raise Exception("Failed to map radio_button.") |
133 | |
134 | def _get_label(self, name): |
135 | widget = self._get_widget(name) |
136 | |
137 | === modified file 'checkbox_urwid/urwid_interface.py' |
138 | --- checkbox_urwid/urwid_interface.py 2012-02-09 16:03:54 +0000 |
139 | +++ checkbox_urwid/urwid_interface.py 2012-02-22 17:22:19 +0000 |
140 | @@ -19,6 +19,7 @@ |
141 | import urwid |
142 | |
143 | import re, string |
144 | +from operator import itemgetter |
145 | from gettext import gettext as _ |
146 | |
147 | from checkbox.user_interface import (UserInterface, NEXT, PREV, |
148 | @@ -37,7 +38,12 @@ |
149 | ('button focused', 'white', 'dark blue'), |
150 | ('highlight', 'black', 'dark cyan'), |
151 | ('highlight focused', 'white', 'dark blue'), |
152 | + ('fail', 'light red', 'dark cyan'), |
153 | + ('pass', 'light green', 'dark cyan'), |
154 | + ('result', 'light gray', 'dark cyan'), |
155 | ) |
156 | + PALETTE_MEMBERS = set(color_spec[0] |
157 | + for color_spec in PALETTE) |
158 | header = None |
159 | footer = None |
160 | |
161 | @@ -368,12 +374,12 @@ |
162 | """ |
163 | Create a tree node and all its children |
164 | """ |
165 | - widget = TreeNodeWidget(name, parent) |
166 | + widget = SelectableTreeNodeWidget(name, parent) |
167 | urwid.signals.connect_signal(widget, 'change', |
168 | widget.changed_cb, self.walker) |
169 | |
170 | if isinstance(data, dict): |
171 | - items = sorted(data.iteritems(), key=lambda item: item[0]) |
172 | + items = sorted(data.iteritems(), key=itemgetter(0)) |
173 | for children_name, children_data in items: |
174 | child_widget = self.create_tree(children_name, children_data, widget) |
175 | widget.append(child_widget) |
176 | @@ -402,14 +408,15 @@ |
177 | |
178 | # Show tree |
179 | self.option_widgets = [] |
180 | - items = sorted(self.options.iteritems(), key=lambda item: item[0]) |
181 | + items = sorted(self.options.iteritems(), |
182 | + key=itemgetter(0)) |
183 | for name, data in items: |
184 | widget = self.create_tree(name, data) |
185 | self.option_widgets.append(widget) |
186 | self.walker.append(widget) |
187 | |
188 | self._set_default([node for node in self.walker |
189 | - if isinstance(node, TreeNodeWidget)], |
190 | + if isinstance(node, SelectableTreeNodeWidget)], |
191 | self.default) |
192 | |
193 | # Show buttons |
194 | @@ -422,9 +429,131 @@ |
195 | self.walker.append(buttons_box) |
196 | |
197 | |
198 | +class ReportDialog(ChoiceDialog): |
199 | + """ |
200 | + Display test results dialog |
201 | + """ |
202 | + footer = urwid.AttrMap(urwid.Columns((urwid.Text('Arrow keys/Page Up/Page Down: Move'), |
203 | + urwid.Text(''), |
204 | + urwid.Text('+/-/Enter/Space: Expand/Collapse'))), |
205 | + 'footer') |
206 | + |
207 | + def __init__(self, text, results): |
208 | + Dialog.__init__(self, text) |
209 | + self.results = results |
210 | + |
211 | + |
212 | + def _get_tree_node(self, node): |
213 | + """ |
214 | + Get tree node even if a column is wrapping it |
215 | + """ |
216 | + if issubclass(type(node), TreeNodeWidget): |
217 | + return node |
218 | + elif issubclass(type(node), urwid.Columns): |
219 | + for widget in child.widget_list: |
220 | + if issubclass(type(widget), TreeNodeWidget): |
221 | + return widget |
222 | + return node |
223 | + |
224 | + |
225 | + def expand_all_clicked_cb(self, button): |
226 | + """ |
227 | + Expand all elements in the tree to see results |
228 | + """ |
229 | + for tree_node in [self._get_tree_node(node) |
230 | + for node in self.root_nodes]: |
231 | + tree_node.expand(expand_all=True) |
232 | + |
233 | + |
234 | + def collapse_all_clicked_cb(self, button): |
235 | + """ |
236 | + Collapse all elements in the tree to see results |
237 | + """ |
238 | + for tree_node in [self._get_tree_node(node) |
239 | + for node in self.root_nodes]: |
240 | + tree_node.collapse(collapse_all=True) |
241 | + |
242 | + |
243 | + def next_button_clicked_cb(self, button): |
244 | + """ |
245 | + Set direction, response and exit |
246 | + """ |
247 | + self.direction = NEXT |
248 | + raise urwid.ExitMainLoop |
249 | + |
250 | + |
251 | + def previous_button_clicked_cb(self, button): |
252 | + """ |
253 | + Set direction, response and exit |
254 | + """ |
255 | + self.direction = PREV |
256 | + raise urwid.ExitMainLoop |
257 | + |
258 | + |
259 | + def create_tree(self, name, data, parent=None): |
260 | + """ |
261 | + Create a tree node and all its children |
262 | + """ |
263 | + widget = TreeNodeWidget(name, parent) |
264 | + urwid.signals.connect_signal(widget, 'change', |
265 | + widget.changed_cb, self.walker) |
266 | + |
267 | + items = sorted(data.iteritems(), key=itemgetter(0)) |
268 | + for child_name, child_data in items: |
269 | + is_suite = all(issubclass(type(value), dict) |
270 | + for value in child_data.itervalues()) |
271 | + |
272 | + if is_suite: |
273 | + child_widget = self.create_tree(child_name, |
274 | + child_data, |
275 | + widget) |
276 | + else: |
277 | + result=child_data['status'] |
278 | + |
279 | + # Use color specification for result |
280 | + # if found or default one |
281 | + attr = (result |
282 | + if result in self.PALETTE_MEMBERS |
283 | + else 'result') |
284 | + |
285 | + child_widget = urwid.Columns( |
286 | + (TreeNodeWidget(child_name, widget), |
287 | + urwid.AttrMap(urwid.Text((attr, result)), |
288 | + 'highlight', 'highlight focused'))) |
289 | + widget.append(child_widget) |
290 | + |
291 | + return widget |
292 | + |
293 | + |
294 | + def show(self): |
295 | + """ |
296 | + Display dialog text, options tree and buttons |
297 | + """ |
298 | + # Show text |
299 | + Dialog.show(self) |
300 | + |
301 | + # Show tree |
302 | + items = sorted(self.results.iteritems(), |
303 | + key=itemgetter(0)) |
304 | + for name, data in items: |
305 | + widget = self.create_tree(name, data) |
306 | + self.walker.append(widget) |
307 | + |
308 | + self.root_nodes = [node for node in self.walker] |
309 | + |
310 | + # Show buttons |
311 | + labels = ((_('Expand All'), self.expand_all_clicked_cb), |
312 | + (_('Collapse All'), self.collapse_all_clicked_cb), |
313 | + (_('Previous'), self.previous_button_clicked_cb), |
314 | + (_('Next'), self.next_button_clicked_cb)) |
315 | + buttons_box = self.create_buttons(labels) |
316 | + self.walker.append(urwid.Divider()) |
317 | + self.walker.append(buttons_box) |
318 | + |
319 | + |
320 | class TreeNodeWidget(urwid.WidgetWrap): |
321 | """ |
322 | - Implementation of a node in a tree that can be selected/deselected |
323 | + Implementation of a node in a tree that can be expanded/unexpanded |
324 | """ |
325 | signals = ['change'] |
326 | |
327 | @@ -436,17 +565,45 @@ |
328 | |
329 | self.expanded = False |
330 | |
331 | - # Use a checkbox as internal representation of the widget |
332 | - self.checkbox = urwid.CheckBox(self._get_label()) |
333 | - w = urwid.AttrMap(self.checkbox, 'highlight', 'highlight focused') |
334 | + w = self._get_widget() |
335 | super(TreeNodeWidget, self).__init__(w) |
336 | |
337 | |
338 | + def _get_widget(self): |
339 | + """ |
340 | + Create widget that is wrapped by this class |
341 | + """ |
342 | + self.widget = urwid.Text(self._get_label()) |
343 | + w = urwid.AttrMap(self.widget, 'highlight', 'highlight focused') |
344 | + return w |
345 | + |
346 | + |
347 | + def _update_label(self): |
348 | + """ |
349 | + Update text label |
350 | + """ |
351 | + self.widget.set_text(self._get_label()) |
352 | + |
353 | + |
354 | + def _get_node(self, child): |
355 | + """ |
356 | + Get TreeNode directly without traversing Columns |
357 | + """ |
358 | + if issubclass(type(child), TreeNodeWidget): |
359 | + return child |
360 | + elif issubclass(type(child), urwid.Columns): |
361 | + for widget in child.widget_list: |
362 | + if issubclass(type(widget), TreeNodeWidget): |
363 | + return widget |
364 | + return child |
365 | + |
366 | + |
367 | def __iter__(self): |
368 | """ |
369 | Iterate over children nodes |
370 | """ |
371 | - return iter(self.children) |
372 | + return iter([self._get_node(child) |
373 | + for child in self.children]) |
374 | |
375 | |
376 | def __len__(self): |
377 | @@ -476,69 +633,17 @@ |
378 | return True |
379 | |
380 | |
381 | - @property |
382 | - def state(self): |
383 | - """ |
384 | - Get state from checkbox widget |
385 | - """ |
386 | - return self.checkbox.get_state() |
387 | - |
388 | - |
389 | - @state.setter |
390 | - def state(self, value): |
391 | - """ |
392 | - Set state to checkbox widget |
393 | - """ |
394 | - self.checkbox.set_state(value) |
395 | - |
396 | - |
397 | - def set_ancestors_state(self, new_state): |
398 | - """ |
399 | - Set the state of all ancestors consistently |
400 | - """ |
401 | - # If child is set, then all ancestors must be set |
402 | - if new_state: |
403 | - parent = self.parent |
404 | - while parent: |
405 | - parent.state = new_state |
406 | - parent = parent.parent |
407 | - # If child is not set, then all ancestors mustn't be set |
408 | - # unless another child of the ancestor is set |
409 | - else: |
410 | - parent = self.parent |
411 | - while parent: |
412 | - if any((child.state |
413 | - for child in parent)): |
414 | - break |
415 | - parent.state = new_state |
416 | - parent = parent.parent |
417 | - |
418 | - |
419 | - def set_children_state(self, new_state): |
420 | - """ |
421 | - Set the state of all children recursively |
422 | - """ |
423 | - self.state = new_state |
424 | - for child in self: |
425 | - child.set_children_state(new_state) |
426 | - |
427 | - |
428 | def keypress(self, size, key): |
429 | """ |
430 | - Use key events to select checkbox and expand tree hierarchy |
431 | + Use key events to expand/collapse tree hierarchy |
432 | """ |
433 | - |
434 | - if key == ' ': |
435 | - new_state = not self.state |
436 | - self.state = new_state |
437 | - self.set_children_state(new_state) |
438 | - self.set_ancestors_state(new_state) |
439 | - return None |
440 | - elif self.children: |
441 | - if key in ('+', 'enter') and self.expanded == False: |
442 | + if self.children: |
443 | + if (key in ('+', 'enter', ' ') |
444 | + and self.expanded == False): |
445 | urwid.signals.emit_signal(self, 'change') |
446 | return None |
447 | - elif key in ('-', 'enter') and self.expanded == True: |
448 | + elif (key in ('-', 'enter', ' ') |
449 | + and self.expanded == True): |
450 | urwid.signals.emit_signal(self, 'change') |
451 | return None |
452 | |
453 | @@ -547,16 +652,10 @@ |
454 | |
455 | def mouse_event(self, size, event, button, col, row, focus): |
456 | """ |
457 | - Use mouse events to select checkbox and expand tree hierarchy |
458 | + Use mouse events to expand/collapse tree hierarchy |
459 | """ |
460 | - # Left click event |
461 | - if button == 1: |
462 | - new_state = not self.state |
463 | - self.state = new_state |
464 | - self.set_children_state(new_state) |
465 | - self.set_ancestors_state(new_state) |
466 | # Ignore button release event |
467 | - elif button == 0: |
468 | + if button == 0: |
469 | pass |
470 | else: |
471 | urwid.signals.emit_signal(self, 'change') |
472 | @@ -581,11 +680,15 @@ |
473 | return label |
474 | |
475 | |
476 | - def _update_label(self): |
477 | - """ |
478 | - Update text label |
479 | - """ |
480 | - self.checkbox.set_label(self._get_label()) |
481 | + def collapse(self, collapse_all=False): |
482 | + """ |
483 | + Collapse node |
484 | + """ |
485 | + if self.expanded == True: |
486 | + urwid.signals.emit_signal(self, 'change') |
487 | + |
488 | + if collapse_all: |
489 | + self._collapse_children() |
490 | |
491 | |
492 | def _collapse_children(self): |
493 | @@ -593,12 +696,24 @@ |
494 | Collapse all children |
495 | """ |
496 | for child in self: |
497 | + child._collapse_children() |
498 | if child.expanded: |
499 | - child._collapse_children() |
500 | child.expanded = False |
501 | child._update_label() |
502 | |
503 | |
504 | + def expand(self, expand_all=False): |
505 | + """ |
506 | + Expand node |
507 | + """ |
508 | + if self.expanded == False: |
509 | + urwid.signals.emit_signal(self, 'change') |
510 | + |
511 | + if expand_all: |
512 | + for child in self: |
513 | + child.expand(expand_all) |
514 | + |
515 | + |
516 | def changed_cb(self, walker): |
517 | """ |
518 | Handle node expansion in the tree |
519 | @@ -610,18 +725,134 @@ |
520 | del_end_position = (del_start_position + |
521 | len(self) - 1) |
522 | del walker[del_start_position:del_end_position] |
523 | - self._collapse_children() |
524 | self.expanded = False |
525 | else: |
526 | insert_position = position + 1 |
527 | |
528 | # Append widgets to the list |
529 | - walker[insert_position:insert_position] = self.children |
530 | + subtree = list(self._get_subtree()) |
531 | + walker[insert_position:insert_position] = subtree |
532 | self.expanded = True |
533 | |
534 | self._update_label() |
535 | |
536 | |
537 | + def _get_subtree(self): |
538 | + """ |
539 | + Return subtree with expanded children |
540 | + """ |
541 | + for child in self.children: |
542 | + yield child |
543 | + |
544 | + child_node = self._get_node(child) |
545 | + if child_node.expanded: |
546 | + for descendant in child._get_subtree(): |
547 | + yield descendant |
548 | + |
549 | + |
550 | +class SelectableTreeNodeWidget(TreeNodeWidget): |
551 | + """ |
552 | + Implementation of a node in a tree that can be selected/deselected |
553 | + """ |
554 | + def __init__(self, name, parent=None): |
555 | + super(SelectableTreeNodeWidget, self).__init__(name, parent) |
556 | + |
557 | + |
558 | + def _get_widget(self): |
559 | + """ |
560 | + Create widget that is wrapped by this class |
561 | + """ |
562 | + # Use a checkbox to preserve widget selection stat |
563 | + self.widget = urwid.CheckBox(self._get_label()) |
564 | + w = urwid.AttrMap(self.widget, 'highlight', 'highlight focused') |
565 | + return w |
566 | + |
567 | + |
568 | + def _update_label(self): |
569 | + """ |
570 | + Update text label |
571 | + """ |
572 | + self.widget.set_label(self._get_label()) |
573 | + |
574 | + |
575 | + @property |
576 | + def state(self): |
577 | + """ |
578 | + Get state from checkbox widget |
579 | + """ |
580 | + return self.widget.get_state() |
581 | + |
582 | + |
583 | + @state.setter |
584 | + def state(self, value): |
585 | + """ |
586 | + Set state to checkbox widget |
587 | + """ |
588 | + self.widget.set_state(value) |
589 | + |
590 | + |
591 | + def set_ancestors_state(self, new_state): |
592 | + """ |
593 | + Set the state of all ancestors consistently |
594 | + """ |
595 | + # If child is set, then all ancestors must be set |
596 | + if new_state: |
597 | + parent = self.parent |
598 | + while parent: |
599 | + parent.state = new_state |
600 | + parent = parent.parent |
601 | + # If child is not set, then all ancestors mustn't be set |
602 | + # unless another child of the ancestor is set |
603 | + else: |
604 | + parent = self.parent |
605 | + while parent: |
606 | + if any((child.state |
607 | + for child in parent)): |
608 | + break |
609 | + parent.state = new_state |
610 | + parent = parent.parent |
611 | + |
612 | + |
613 | + def set_children_state(self, new_state): |
614 | + """ |
615 | + Set the state of all children recursively |
616 | + """ |
617 | + self.state = new_state |
618 | + for child in self: |
619 | + child.set_children_state(new_state) |
620 | + |
621 | + |
622 | + def keypress(self, size, key): |
623 | + """ |
624 | + Use key events to select checkbox and expand tree hierarchy |
625 | + """ |
626 | + |
627 | + if key == ' ': |
628 | + new_state = not self.state |
629 | + self.state = new_state |
630 | + self.set_children_state(new_state) |
631 | + self.set_ancestors_state(new_state) |
632 | + return None |
633 | + |
634 | + return super(SelectableTreeNodeWidget, self).keypress(size, key) |
635 | + |
636 | + |
637 | + def mouse_event(self, size, event, button, col, row, focus): |
638 | + """ |
639 | + Use mouse events to select checkbox and expand tree hierarchy |
640 | + """ |
641 | + # Left click event |
642 | + if button == 1: |
643 | + new_state = not self.state |
644 | + self.state = new_state |
645 | + self.set_children_state(new_state) |
646 | + self.set_ancestors_state(new_state) |
647 | + return True |
648 | + |
649 | + return (super(SelectableTreeNodeWidget, self) |
650 | + .mouse_event(size, event, button, col, row, focus)) |
651 | + |
652 | + |
653 | class ProgressDialog(Dialog): |
654 | """ |
655 | Show progress through a bar |
656 | @@ -753,6 +984,14 @@ |
657 | return dialog.response |
658 | |
659 | |
660 | + def show_report(self, text, results): |
661 | + """ |
662 | + Show test case results in a tree hierarchy |
663 | + """ |
664 | + dialog = ReportDialog(text, results).run() |
665 | + self.direction = dialog.direction |
666 | + |
667 | + |
668 | def show_test(self, test, runner): |
669 | """ |
670 | Show test description, radio buttons to set result |
671 | @@ -792,7 +1031,7 @@ |
672 | test['data'] = dialog.input |
673 | test['status'] = ANSWER_TO_STATUS[answer] |
674 | self.direction = dialog.direction |
675 | - return self.response |
676 | + return dialog.response |
677 | |
678 | |
679 | def show_info(self, text, options=[], default=None): |
680 | |
681 | === modified file 'debian/changelog' |
682 | --- debian/changelog 2012-02-20 08:22:59 +0000 |
683 | +++ debian/changelog 2012-02-22 17:22:19 +0000 |
684 | @@ -5,6 +5,7 @@ |
685 | |
686 | [Sylvain Pineau] |
687 | * Fix depends fields in info and suspend test suites (LP: #934051) |
688 | + * Display results report before upload (LP: #937657) |
689 | |
690 | [Brendan Donegan] |
691 | * Typo in command for for miscellanea/virtualization-check (LP: #934243) |
692 | |
693 | === modified file 'plugins/launchpad_report.py' |
694 | --- plugins/launchpad_report.py 2012-02-07 20:39:15 +0000 |
695 | +++ plugins/launchpad_report.py 2012-02-22 17:22:19 +0000 |
696 | @@ -66,6 +66,8 @@ |
697 | ("report-package", self.report_package), |
698 | ("report-uname", self.report_uname), |
699 | ("report-system_id", self.report_system_id), |
700 | + ("report-suites", self.report_suites), |
701 | + ("report-review", self.report_review), |
702 | ("report-tests", self.report_tests)]: |
703 | self._manager.reactor.call_on(rt, rh) |
704 | |
705 | @@ -136,6 +138,7 @@ |
706 | self._report["summary"]["system_id"] = system_id |
707 | |
708 | def report_tests(self, tests): |
709 | + self.tests = tests |
710 | for test in tests: |
711 | question = { |
712 | "name": test["name"], |
713 | @@ -167,5 +170,50 @@ |
714 | |
715 | self._manager.reactor.fire("launchpad-report", self.filename) |
716 | |
717 | + def report_review(self, interface): |
718 | + """ |
719 | + Show test report in the interface |
720 | + """ |
721 | + report = {} |
722 | + |
723 | + def add_job(job): |
724 | + is_suite = 'type' in job and job['type'] == 'suite' |
725 | + if 'suite' in job: |
726 | + suite_name = job['suite'] |
727 | + parent_node = add_job(self.suites[suite_name]) |
728 | + |
729 | + if is_suite: |
730 | + if job['description'] in parent_node: |
731 | + return parent_node[job['description']] |
732 | + |
733 | + node = {} |
734 | + parent_node[job['description']] = node |
735 | + return node |
736 | + parent_node[job['name']] = job |
737 | + else: |
738 | + if is_suite: |
739 | + field = 'description' |
740 | + else: |
741 | + field = 'name' |
742 | + |
743 | + if job[field] in report: |
744 | + return report[job[field]] |
745 | + |
746 | + node = {} |
747 | + report[job[field]] = node |
748 | + return node |
749 | + |
750 | + for test in self.tests: |
751 | + add_job(test) |
752 | + |
753 | + interface.show_report("Test case results report", report) |
754 | + |
755 | + def report_suites(self, suites): |
756 | + """ |
757 | + Get tests results and store it |
758 | + to display them later |
759 | + """ |
760 | + self.suites = dict([(suite['name'], suite) for suite in suites]) |
761 | + |
762 | |
763 | factory = LaunchpadReport |
764 | |
765 | === modified file 'plugins/report_prompt.py' |
766 | --- plugins/report_prompt.py 2011-09-08 14:52:33 +0000 |
767 | +++ plugins/report_prompt.py 2012-02-22 17:22:19 +0000 |
768 | @@ -40,6 +40,8 @@ |
769 | self._manager.reactor.fire, "report") |
770 | |
771 | self._manager.reactor.cancel_call(event_id) |
772 | + if hasattr(interface, 'show_report'): |
773 | + self._manager.reactor.fire("report-review", interface) |
774 | |
775 | |
776 | factory = ReportPrompt |
There is some code (about the bug report) that needs to be removed as it won't be accepted FFe