~testing-cabal/ubuntu/oneiric/tribunal/daily-build-packaging

« back to all changes in this revision

Viewing changes to tribunal-subunit

  • Committer: Martin Pool
  • Date: 2010-08-09 07:41:31 UTC
  • mfrom: (153.1.5 trunk)
  • Revision ID: mbp@sourcefrog.net-20100809074131-hekxht7y8eyflygr
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
45
45
from tribunal import (
46
46
    tree,
47
47
    util,
 
48
    window,
48
49
    )
49
50
from tribunal.eventloop import GtkLoop
50
51
from tribunal.inputdriver import (
64
65
    def write(self, a):
65
66
        pass
66
67
 
67
 
 
68
 
class SubunitWindow(object):
69
 
    """Per-window application logic.
70
 
 
71
 
    Contains references to many gtk controls, in particular
72
 
 
73
 
    :ivar subunit_window: the top level gtk window
74
 
    :ivar model_filter: TreeModelFilter controlling what's actually visible
75
 
        in the list of tests.
76
 
    :ivar _outcome_filter: dict from outcome strings (eg 'passed') to bool
77
 
        saying whether they should be shown
78
 
    """
79
 
    
80
 
    def __init__(self, glade_xml):
81
 
        self.glade = glade_xml
82
 
        for i in [
83
 
            'result_frame',
84
 
            'result_frame_label',
85
 
            'result_textview', 
86
 
            'show_failed_tb',
87
 
            'subunit_window', 
88
 
            'statusbar',
89
 
            'test_treeview',
90
 
            ]:
91
 
            w = glade_xml.get_widget(i)
92
 
            assert w is not None, \
93
 
                "couldn't load %r" % i
94
 
            setattr(self, i, w)
95
 
        # acts like a TestSuite and feeds into a gtk tree; also sets some
96
 
        # parameters on the view
97
 
        self.test_result = tree.TableTestResults(self)
98
 
        self._connect_tree()
99
 
        glade_xml.signal_autoconnect(self)
100
 
        self._outcome_filter = dict(
101
 
            failed=True,
102
 
            skipped=False,
103
 
            passed=False,
104
 
            xfail=False)
105
 
        self._glade_xml = glade_xml
106
 
        self._get_widget = glade_xml.get_widget
107
 
        # XXX: send misunderstood subunit data to stderr for the sake of
108
 
        # debugging; eventually we'd like to show it in a window  -- mbp
109
 
        # 20100127
110
 
        self._subunit_passthrough = sys.stderr # NullOutputStream()
111
 
        self._running_test_statusbar_context = \
112
 
            self.statusbar.get_context_id("current test")
113
 
        self.source = None
114
 
 
115
 
    def _connect_tree(self):
116
 
        """Connect the tree model to the view in table view."""
117
 
        view = self.test_treeview
118
 
        self.model_filter = self.test_result.store.filter_new()
119
 
        self.model_filter.set_visible_func(self._row_visible_func)
120
 
        view.set_model(self.model_filter)
121
 
 
122
 
        view.set_headers_visible(True)
123
 
        view.set_headers_clickable(True)
124
 
        view.set_show_expanders(False)
125
 
 
126
 
        outcome_col = gtk.TreeViewColumn("Outcome",
127
 
            gtk.CellRendererText(),
128
 
            text=self.test_result.COL_OUTCOME)
129
 
        view.append_column(outcome_col)
130
 
 
131
 
        test_name_renderer = gtk.CellRendererText()
132
 
        test_name_renderer.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE)
133
 
 
134
 
        test_name_col = gtk.TreeViewColumn("Test",
135
 
            test_name_renderer,
136
 
            text=self.test_result.COL_TEST_ID)
137
 
        view.append_column(test_name_col)
138
 
 
139
 
    def load_from_subunit_stream(self, input_file):
140
 
        # feed things from the subunit stream into self.test_result
141
 
        #
142
 
        # this currently works by running the subunit stream as if it were an
143
 
        # actually running test, through the python unittest protocol.  This
144
 
        # squashes some of the interesting data, such as folding the
145
 
        # attachments into a _StringException.  We probably actually need to
146
 
        # load it through a Subunit-specific protocol.
147
 
        context = self.statusbar.get_context_id("Loading")
148
 
        self.statusbar.push(context, "Loading stream...")
149
 
        self.test_result._clear()
150
 
        # probably a good guess that the user now wants to look through the
151
 
        # test results tree
152
 
        self.test_treeview.grab_focus()
153
 
        def on_finish():
154
 
            self.statusbar.pop(context)
155
 
 
156
 
        # dummy_result = testtools.TestResult()
157
 
 
158
 
        protocol_server = TestProtocolServer(
159
 
            self.test_result,
160
 
            self._subunit_passthrough,
161
 
            None)
162
 
        # if it's a real file, we should do nonblocking callback io
163
 
        if getattr(input_file, 'fileno', None):
164
 
            driver_class = FileInputDriver
165
 
        else:
166
 
            driver_class = InProcessInputDriver
167
 
        self.input_driver = driver_class(input_file,
168
 
            on_finish=on_finish,
169
 
            protocol_server=protocol_server)
170
 
        self.input_driver.start_reading()
171
 
 
172
 
    def on_about_menuitem_activate(self, menuitem):
173
 
        aboutdialog = gtk.AboutDialog()
174
 
        aboutdialog.set_authors((
175
 
            'Jonathan Lange',
176
 
            'Martin Pool',
177
 
            'Jelmer Vernooij',
178
 
            ))
179
 
        aboutdialog.set_copyright(u"Copyright \u00a9 2008-2010 Contributors")
180
 
        aboutdialog.set_comments(
181
 
            "A graphical inspector for Subunit test results")
182
 
        aboutdialog.set_license("""
183
 
Licensed under the Apache License, Version 2.0 (the "License");
184
 
you may not use this program except in compliance with the License.
185
 
You may obtain a copy of the License at
186
 
 
187
 
   http://www.apache.org/licenses/LICENSE-2.0
188
 
 
189
 
Unless required by applicable law or agreed to in writing, software 
190
 
distributed under the License is distributed on an "AS IS" BASIS, 
191
 
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
192
 
See the License for the specific language governing permissions and
193
 
limitations under the License.""")
194
 
        aboutdialog.set_website("http://launchpad.net/tribunal")
195
 
        aboutdialog.run()
196
 
        aboutdialog.destroy()
197
 
 
198
 
    def on_filter_failed_menuitem_toggled(self, menuitem):
199
 
        self.set_outcome_filter('failed', menuitem.get_active())
200
 
 
201
 
    def on_filter_skipped_menuitem_toggled(self, menuitem):
202
 
        self.set_outcome_filter('skipped', menuitem.get_active())
203
 
 
204
 
    def on_filter_passed_menuitem_toggled(self, menuitem):
205
 
        self.set_outcome_filter('passed', menuitem.get_active())
206
 
        
207
 
    def on_filter_xfail_menuitem_toggled(self, menuitem):
208
 
        self.set_outcome_filter('xfail', menuitem.get_active())
209
 
        
210
 
    def on_pause_input_tb_toggled(self, toggle):
211
 
        self.input_driver.set_paused(toggle.get_active())
212
 
 
213
 
    def on_reload_button_clicked(self, reload_button):
214
 
        try:
215
 
            self.reload_from_source()
216
 
        except UnreloadableStream:
217
 
            dialog = gtk.Dialog(title="Stream not reloadable",
218
 
                parent=self.subunit_window,
219
 
                buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
220
 
            label = gtk.Label("This stream is not reloadable. Perhaps it is a pipe?")
221
 
            dialog.vbox.pack_start(label, True, True, 0)
222
 
            label.show()
223
 
            dialog.run()
224
 
            dialog.destroy()
225
 
 
226
 
    def on_show_failed_tb_toggled(self, tb_toggle):
227
 
        self.set_outcome_filter('failed', tb_toggle.get_active())
228
 
 
229
 
    def on_show_skipped_tb_toggled(self, tb_toggle):
230
 
        self.set_outcome_filter('skipped', tb_toggle.get_active())
231
 
 
232
 
    def on_show_passed_tb_toggled(self, tb_toggle):
233
 
        self.set_outcome_filter('passed', tb_toggle.get_active())
234
 
        
235
 
    def on_show_xfail_tb_toggled(self, tb_toggle):
236
 
        self.set_outcome_filter('xfail', tb_toggle.get_active())
237
 
 
238
 
    def on_test_treeview_row_activated(self, treeview, path, view_column):
239
 
        self._show_results_for_tree_path(path)
240
 
        
241
 
    def on_test_concluded(self, test_id, outcome):
242
 
        # called back by TribunalTestResult when a test finishes
243
 
        # self.statusbar.pop(self._running_test_statusbar_context)
244
 
        self._update_outcome_counts()
245
 
 
246
 
    def on_test_started(self, test_id):
247
 
        # self.statusbar.push(self._running_test_statusbar_context,
248
 
        #     "running %s" % test_id)
249
 
        pass
250
 
        
251
 
    def on_test_treeview_cursor_changed(self, treeview):
252
 
        path, column = treeview.get_cursor()
253
 
        # print 'cursor to path %r, column %r' % (path, column)
254
 
        self._show_results_for_tree_path(path)
255
 
 
256
 
    def _row_visible_func(self, model, iter, user_data=None):
257
 
        """Called back from TreeModelFilter to check if a row is visible."""
258
 
        row_outcome = model.get_value(iter, tree.TableTestResults.COL_OUTCOME)
259
 
        return self._outcome_filter.get(row_outcome, True)
260
 
 
261
 
    def set_outcome_filter(self, outcome, show):
262
 
        if show != self._outcome_filter[outcome]:
263
 
            self._outcome_filter[outcome] = show
264
 
            self._get_widget('show_%s_tb' % outcome).set_active(show)
265
 
            self._get_widget('filter_%s_menuitem' % outcome).set_active(show)
266
 
            self.refresh_filter()
267
 
            
268
 
    def set_source(self, test_source):
269
 
        self.source = test_source
270
 
 
271
 
    def show_all(self):
272
 
        # show the top level window and the rest will follow
273
 
        self.subunit_window.show_all()
274
 
    
275
 
    def _show_results_for_tree_path(self, path):
276
 
        # apparent path is given in the filtered view; the actual path
277
 
        # we need is the one in the underlying model
278
 
        real_path = self.model_filter.convert_path_to_child_path(path)
279
 
        test_id = self.test_result.get_test_id(real_path)
280
 
        if test_id:
281
 
            details = self.test_result.get_detail_text_by_test_id(
282
 
                    test_id)
283
 
            outcome = self.test_result.get_outcome_by_test_id(test_id)
284
 
            error = self.test_result.get_error_by_test_id(test_id)
285
 
        else:
286
 
            error = details = outcome = None
287
 
        if test_id:
288
 
            self.result_frame_label.set_label('<b>%s %s:</b>' 
289
 
                % tuple(map(xml_escape, [test_id, outcome])))
290
 
            self.result_frame.set_sensitive(True)
291
 
        else:
292
 
            self.result_frame_label.set_label('Details:')
293
 
            self.result_frame.set_sensitive(False)
294
 
        if details or error:
295
 
            self.result_textview.set_sensitive(True)
296
 
            t = (error or '') + '\n' + details_dict_to_text(details)
297
 
            self.result_textview.get_buffer().set_text(t)
298
 
        else:
299
 
            self.result_textview.set_sensitive(False)
300
 
            self.result_textview.get_buffer().set_text('')
301
 
 
302
 
    def refresh_filter(self):
303
 
        self.model_filter.refilter()
304
 
 
305
 
    def reload_from_source(self):
306
 
        self.load_from_subunit_stream(self.source.reload())
307
 
        
308
 
    def _update_outcome_counts(self):
309
 
        for status in ['failed', 'skipped', 'passed', 'xfail']:
310
 
            tb_button = self._glade_xml.get_widget('show_%s_tb' % status)
311
 
            tb_button.set_label("%d %s" %
312
 
                (self.test_result._counts_by_status[status],
313
 
                    status.capitalize()))
314
 
 
315
 
 
316
68
class ViewSubunitApp(object):
317
69
    """Application object. 
318
70
 
328
80
    def _construct_and_show_window(self):
329
81
        glade_filename = util.sibpath(tribunal.__file__, 'ui.glade')
330
82
        glade_xml = gtk.glade.XML(glade_filename)
331
 
        self._window = SubunitWindow(glade_xml)
 
83
        self._window = window.SubunitWindow(glade_xml)
332
84
        glade_xml.signal_autoconnect(self)
333
85
        self._set_default_icons()
334
86
        self._window.show_all()
399
151
        gtk.window_set_default_icon_list(icon_pixbuf)
400
152
 
401
153
 
402
 
def xml_escape(t):
403
 
    return t.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
404
 
 
405
 
 
406
 
def details_dict_to_text(details):
407
 
    if not details:
408
 
        return ''
409
 
    bits = []
410
 
    for name, content in sorted(details.items()):
411
 
        bits.append(name + ':')
412
 
        bits.append(''.join(content.iter_text()))
413
 
    return '\n'.join(bits)
414
 
 
415
 
 
416
154
if __name__ == '__main__':
417
155
    app = ViewSubunitApp(GtkLoop())
418
156
    app.run(sys.argv[1:])