Merge lp:~lifeless/subunit/progress-gtk into lp:~subunit/subunit/trunk

Proposed by Jelmer Vernooij
Status: Merged
Merge reported by: Robert Collins
Merged at revision: not available
Proposed branch: lp:~lifeless/subunit/progress-gtk
Merge into: lp:~subunit/subunit/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~lifeless/subunit/progress-gtk
Reviewer Review Type Date Requested Status
Jelmer Vernooij Needs Fixing
Review via email: mp+9487@code.launchpad.net

This proposal supersedes a proposal from 2009-07-29.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal

This builds on progress-core to do a GTK progress bar. Woo. Shiny.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

bb:tweak (is that equivalent to Needs Fixing) ?

Two minor points:

It seems more sensible to use subunit.SEEK_SET rather than os.SEEK_SET, even though they happen to have the same value at the moment.

Please use getattr() rather than catching AttributeError, and perhaps comment why we need to do so (I suspect it's because the standard TestResult class doesn't know about skips?)

Is the copyright header for the GTK+ tutorial still relevant? Is there any code from the original still in your code?

review: Needs Fixing
Revision history for this message
Robert Collins (lifeless) wrote :

On Sat, 2009-08-01 at 21:50 +0000, Jelmer Vernooij wrote:
>
>
> It seems more sensible to use subunit.SEEK_SET rather than
> os.SEEK_SET, even though they happen to have the same value at the
> moment.

Ack.

> Please use getattr() rather than catching AttributeError, and perhaps
> comment why we need to do so (I suspect it's because the standard
> TestResult class doesn't know about skips?)

I'll document why (its because they are new features in python 2.7 and
3.1). Can you enlarge on why you don't want AttributeError caught?

> Is the copyright header for the GTK+ tutorial still relevant? Is there
> any code from the original still in your code?

3 or 4 lines probably. I'll admit its a grey area, but it's the right
thing in a moral sense - I really did start with the tutorial and chop
and change, rather than starting with a clean slate and the manual.

-Rob

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-07-28 21:44:28 +0000
3+++ NEWS 2009-07-29 09:59:36 +0000
4@@ -15,6 +15,9 @@
5 about completion, when such information is available. See the README
6 under ``progress`` for more details.
7
8+ * ``subunit2gtk`` has been added, a filter that shows a GTK summary of a
9+ test stream.
10+
11 * ``subunit2pyunit`` has a --progress flag which will cause the bzrlib
12 test reporter to be used, which has a textual progress bar. This requires
13 a recent bzrlib as a minor bugfix was required in bzrlib to support this.
14
15=== modified file 'README'
16--- README 2009-07-28 13:32:10 +0000
17+++ README 2009-07-29 09:59:36 +0000
18@@ -47,6 +47,7 @@
19 Subunit supplies the following filters:
20 * tap2subunit - convert perl's TestAnythingProtocol to subunit.
21 * subunit2pyunit - convert a subunit stream to pyunit test results.
22+ * subunit2gtk - show a subunit stream in GTK.
23 * subunit-filter - filter out tests from a subunit stream.
24 * subunit-ls - list info about tests present in a subunit stream.
25 * subunit-stats - generate a summary of a subunit stream.
26
27=== added file 'filters/subunit2gtk'
28--- filters/subunit2gtk 1970-01-01 00:00:00 +0000
29+++ filters/subunit2gtk 2009-07-30 07:16:31 +0000
30@@ -0,0 +1,234 @@
31+#!/usr/bin/env python
32+# subunit: extensions to python unittest to get test results from subprocesses.
33+# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
34+#
35+# This program is free software; you can redistribute it and/or modify
36+# it under the terms of the GNU General Public License as published by
37+# the Free Software Foundation; either version 2 of the License, or
38+# (at your option) any later version.
39+#
40+# This program is distributed in the hope that it will be useful,
41+# but WITHOUT ANY WARRANTY; without even the implied warranty of
42+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43+# GNU General Public License for more details.
44+#
45+# You should have received a copy of the GNU General Public License
46+# along with this program; if not, write to the Free Software
47+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
48+#
49+
50+### The GTK progress bar __init__ function is derived from the pygtk tutorial:
51+# The PyGTK Tutorial is Copyright (C) 2001-2005 John Finlay.
52+#
53+# The GTK Tutorial is Copyright (C) 1997 Ian Main.
54+#
55+# Copyright (C) 1998-1999 Tony Gale.
56+#
57+# Permission is granted to make and distribute verbatim copies of this manual
58+# provided the copyright notice and this permission notice are preserved on all
59+# copies.
60+#
61+# Permission is granted to copy and distribute modified versions of this
62+# document under the conditions for verbatim copying, provided that this
63+# copyright notice is included exactly as in the original, and that the entire
64+# resulting derived work is distributed under the terms of a permission notice
65+# identical to this one.
66+#
67+# Permission is granted to copy and distribute translations of this document
68+# into another language, under the above conditions for modified versions.
69+#
70+# If you are intending to incorporate this document into a published work,
71+# please contact the maintainer, and we will make an effort to ensure that you
72+# have the most up to date information available.
73+#
74+# There is no guarantee that this document lives up to its intended purpose.
75+# This is simply provided as a free resource. As such, the authors and
76+# maintainers of the information provided within can not make any guarantee
77+# that the information is even accurate.
78+
79+"""Display a subunit stream in a gtk progress window."""
80+
81+import os
82+import sys
83+import unittest
84+
85+import pygtk
86+pygtk.require('2.0')
87+import gtk, gtk.gdk, gobject
88+
89+from subunit import ProtocolTestCase, TestProtocolServer
90+
91+class GTKTestResult(unittest.TestResult):
92+
93+ def __init__(self):
94+ super(GTKTestResult, self).__init__()
95+ # Instance variables (in addition to TestResult)
96+ self.window = None
97+ self.run_label = None
98+ self.ok_label = None
99+ self.not_ok_label = None
100+ self.total_tests = None
101+
102+ self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
103+ self.window.set_resizable(True)
104+
105+ self.window.connect("destroy", gtk.main_quit)
106+ self.window.set_title("Tests...")
107+ self.window.set_border_width(0)
108+
109+ vbox = gtk.VBox(False, 5)
110+ vbox.set_border_width(10)
111+ self.window.add(vbox)
112+ vbox.show()
113+
114+ # Create a centering alignment object
115+ align = gtk.Alignment(0.5, 0.5, 0, 0)
116+ vbox.pack_start(align, False, False, 5)
117+ align.show()
118+
119+ # Create the ProgressBar
120+ self.pbar = gtk.ProgressBar()
121+ align.add(self.pbar)
122+ self.pbar.set_text("Running")
123+ self.pbar.show()
124+
125+ separator = gtk.HSeparator()
126+ vbox.pack_start(separator, False, False, 0)
127+ separator.show()
128+
129+ # rows, columns, homogeneous
130+ table = gtk.Table(2, 3, False)
131+ vbox.pack_start(table, False, True, 0)
132+ table.show()
133+ # Show summary details about the run. Could use an expander.
134+ label = gtk.Label("Run:")
135+ table.attach(label, 0, 1, 1, 2, gtk.EXPAND | gtk.FILL,
136+ gtk.EXPAND | gtk.FILL, 5, 5)
137+ label.show()
138+ self.run_label = gtk.Label("N/A")
139+ table.attach(self.run_label, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL,
140+ gtk.EXPAND | gtk.FILL, 5, 5)
141+ self.run_label.show()
142+
143+ label = gtk.Label("OK:")
144+ table.attach(label, 0, 1, 2, 3, gtk.EXPAND | gtk.FILL,
145+ gtk.EXPAND | gtk.FILL, 5, 5)
146+ label.show()
147+ self.ok_label = gtk.Label("N/A")
148+ table.attach(self.ok_label, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL,
149+ gtk.EXPAND | gtk.FILL, 5, 5)
150+ self.ok_label.show()
151+
152+ label = gtk.Label("Not OK:")
153+ table.attach(label, 0, 1, 3, 4, gtk.EXPAND | gtk.FILL,
154+ gtk.EXPAND | gtk.FILL, 5, 5)
155+ label.show()
156+ self.not_ok_label = gtk.Label("N/A")
157+ table.attach(self.not_ok_label, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL,
158+ gtk.EXPAND | gtk.FILL, 5, 5)
159+ self.not_ok_label.show()
160+
161+ self.window.show()
162+ # For the demo.
163+ self.window.set_keep_above(True)
164+ self.window.present()
165+
166+ def stopTest(self, test):
167+ super(GTKTestResult, self).stopTest(test)
168+ if not self.total_tests:
169+ self.pbar.pulse()
170+ else:
171+ self.pbar.set_fraction(self.testsRun/float(self.total_tests))
172+
173+ def stopTestRun(self):
174+ try:
175+ super(GTKTestResult, self).stopTestRun()
176+ except AttributeError:
177+ pass
178+ self.pbar.set_text('Finished')
179+
180+ def addError(self, test, err):
181+ super(GTKTestResult, self).addError(test, err)
182+ self.update_counts()
183+
184+ def addFailure(self, test, err):
185+ super(GTKTestResult, self).addFailure(test, err)
186+ self.update_counts()
187+
188+ def addSuccess(self, test):
189+ super(GTKTestResult, self).addSuccess(test)
190+ self.update_counts()
191+
192+ def addSkip(self, test, reason):
193+ try:
194+ super(GTKTestResult, self).addSkipSuccess(test, reason)
195+ except AttributeError:
196+ pass
197+ self.update_counts()
198+
199+ def addExpectedFailure(self, test, err):
200+ super(GTKTestResult, self).addExpectedFailure(test, err)
201+ self.update_counts()
202+
203+ def addUnexpectedSuccess(self, test):
204+ super(GTKTestResult, self).addUnexpectedSuccess(test)
205+ self.update_counts()
206+
207+ def progress(self, offset, whence):
208+ if whence == os.SEEK_SET:
209+ self.total_tests = offset
210+ else:
211+ self.total_tests += offset
212+
213+ def time(self, a_datetime):
214+ # We don't try to estimate completion yet.
215+ pass
216+
217+ def update_counts(self):
218+ self.run_label.set_text(str(self.testsRun))
219+ bad = len(self.failures + self.errors)
220+ self.ok_label.set_text(str(self.testsRun - bad))
221+ self.not_ok_label.set_text(str(bad))
222+
223+
224+class GIOProtocolTestCase(object):
225+
226+ def __init__(self, stream, result, on_finish):
227+ self.stream = stream
228+ self.schedule_read()
229+ self.hup_id = gobject.io_add_watch(stream, gobject.IO_HUP, self.hup)
230+ self.protocol = TestProtocolServer(result)
231+ self.on_finish = on_finish
232+
233+ def read(self, source, condition):
234+ #NB: \o/ actually blocks
235+ line = source.readline()
236+ if not line:
237+ self.protocol.lostConnection()
238+ self.on_finish()
239+ return False
240+ self.protocol.lineReceived(line)
241+ # schedule more IO shortly - if we say we're willing to do it
242+ # immediately we starve things.
243+ source_id = gobject.timeout_add(1, self.schedule_read)
244+ return False
245+
246+ def schedule_read(self):
247+ self.read_id = gobject.io_add_watch(self.stream, gobject.IO_IN, self.read)
248+ return False
249+
250+ def hup(self, source, condition):
251+ self.protocol.lostConnection()
252+ gobject.source_remove(self.read_id)
253+ self.on_finish()
254+ return False
255+
256+
257+result = GTKTestResult()
258+test = GIOProtocolTestCase(sys.stdin, result, result.stopTestRun)
259+gtk.main()
260+if result.wasSuccessful():
261+ exit_code = 0
262+else:
263+ exit_code = 1
264+sys.exit(exit_code)

Subscribers

People subscribed via source and target branches