Merge lp:~lifeless/subunit/progress-gtk into lp:~subunit/subunit/trunk
- progress-gtk
- Merge into trunk
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 |
Related bugs: |
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.
Commit message
Description of the change
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal | # |
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?
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
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) |
This builds on progress-core to do a GTK progress bar. Woo. Shiny.