Merge lp:~lifeless/subunit/progress-gtk into lp:~subunit/subunit/trunk
- progress-gtk
- Merge into trunk
Proposed by
Robert Collins
Status: | Superseded |
---|---|
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 |
---|---|---|---|
Subunit Developers | Pending | ||
Review via email: mp+9418@code.launchpad.net |
This proposal has been superseded by a proposal from 2009-07-30.
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
- 76. By Robert Collins
-
Handle python 2.5 a bit better in subunit2gtk.
- 77. By Robert Collins
-
Fix gtk support more.
- 78. By Robert Collins
-
Fix returning None from gobject IO callbacks.
Unmerged revisions
- 78. By Robert Collins
-
Fix returning None from gobject IO callbacks.
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-22 09:39:00 +0000 | |||
3 | +++ NEWS 2009-07-29 09:59:36 +0000 | |||
4 | @@ -10,10 +10,26 @@ | |||
5 | 10 | 10 | ||
6 | 11 | IMPROVEMENTS: | 11 | IMPROVEMENTS: |
7 | 12 | 12 | ||
8 | 13 | * Subunit streams can now include optional, incremental lookahead | ||
9 | 14 | information about progress. This allows reporters to make estimates | ||
10 | 15 | about completion, when such information is available. See the README | ||
11 | 16 | under ``progress`` for more details. | ||
12 | 17 | |||
13 | 18 | * ``subunit2gtk`` has been added, a filter that shows a GTK summary of a | ||
14 | 19 | test stream. | ||
15 | 20 | |||
16 | 21 | * ``subunit2pyunit`` has a --progress flag which will cause the bzrlib | ||
17 | 22 | test reporter to be used, which has a textual progress bar. This requires | ||
18 | 23 | a recent bzrlib as a minor bugfix was required in bzrlib to support this. | ||
19 | 24 | |||
20 | 13 | BUG FIXES: | 25 | BUG FIXES: |
21 | 14 | 26 | ||
22 | 15 | API CHANGES: | 27 | API CHANGES: |
23 | 16 | 28 | ||
24 | 29 | * When a progress: directive is encountered in a subunit stream, the | ||
25 | 30 | python bindings now call the ``progress(offset, whence)`` methd on | ||
26 | 31 | ``TestResult``. | ||
27 | 32 | |||
28 | 17 | * When a time: directive is encountered in a subunit stream, the python | 33 | * When a time: directive is encountered in a subunit stream, the python |
29 | 18 | bindings now call the ``time(seconds)`` method on ``TestResult``. | 34 | bindings now call the ``time(seconds)`` method on ``TestResult``. |
30 | 19 | 35 | ||
31 | 20 | 36 | ||
32 | === modified file 'README' | |||
33 | --- README 2009-07-22 09:39:00 +0000 | |||
34 | +++ README 2009-07-29 09:59:36 +0000 | |||
35 | @@ -47,6 +47,7 @@ | |||
36 | 47 | Subunit supplies the following filters: | 47 | Subunit supplies the following filters: |
37 | 48 | * tap2subunit - convert perl's TestAnythingProtocol to subunit. | 48 | * tap2subunit - convert perl's TestAnythingProtocol to subunit. |
38 | 49 | * subunit2pyunit - convert a subunit stream to pyunit test results. | 49 | * subunit2pyunit - convert a subunit stream to pyunit test results. |
39 | 50 | * subunit2gtk - show a subunit stream in GTK. | ||
40 | 50 | * subunit-filter - filter out tests from a subunit stream. | 51 | * subunit-filter - filter out tests from a subunit stream. |
41 | 51 | * subunit-ls - list info about tests present in a subunit stream. | 52 | * subunit-ls - list info about tests present in a subunit stream. |
42 | 52 | * subunit-stats - generate a summary of a subunit stream. | 53 | * subunit-stats - generate a summary of a subunit stream. |
43 | @@ -130,12 +131,17 @@ | |||
44 | 130 | # needed and report to your result object. | 131 | # needed and report to your result object. |
45 | 131 | suite.run(result) | 132 | suite.run(result) |
46 | 132 | 133 | ||
49 | 133 | subunit includes extensions to the python ``TestResult`` protocol. The | 134 | subunit includes extensions to the python ``TestResult`` protocol. |
50 | 134 | ``time(a_datetime)`` method is called (if present) when a ``time:`` | 135 | |
51 | 136 | The ``time(a_datetime)`` method is called (if present) when a ``time:`` | ||
52 | 135 | directive is encountered in a subunit stream. This is used to tell a TestResult | 137 | directive is encountered in a subunit stream. This is used to tell a TestResult |
53 | 136 | about the time that events in the stream occured at, to allow reconstructing | 138 | about the time that events in the stream occured at, to allow reconstructing |
54 | 137 | test timing from a stream. | 139 | test timing from a stream. |
55 | 138 | 140 | ||
56 | 141 | The ``progress(offset, whence)`` method controls progress data for a stream. | ||
57 | 142 | The offset parameter is an int, and whence is one of subunit.SEEK_CUR, | ||
58 | 143 | subunit.SEEK_SET. | ||
59 | 144 | |||
60 | 139 | Finally, subunit.run is a convenience wrapper to run a python test suite via | 145 | Finally, subunit.run is a convenience wrapper to run a python test suite via |
61 | 140 | the command line, reporting via subunit:: | 146 | the command line, reporting via subunit:: |
62 | 141 | 147 | ||
63 | @@ -216,10 +222,12 @@ | |||
64 | 216 | xfail[:] test label | 222 | xfail[:] test label |
65 | 217 | xfail[:] test label [ | 223 | xfail[:] test label [ |
66 | 218 | ] | 224 | ] |
67 | 225 | progress: [+|-]X | ||
68 | 219 | tags: [-]TAG ... | 226 | tags: [-]TAG ... |
69 | 220 | time: YYYY-MM-DD HH:MM:SSZ | 227 | time: YYYY-MM-DD HH:MM:SSZ |
70 | 221 | unexpected output on stdout -> stdout. | 228 | unexpected output on stdout -> stdout. |
72 | 222 | exit w/0 or last test -> error | 229 | exit w/0 or last test completing -> error |
73 | 230 | |||
74 | 223 | Tags given outside a test are applied to all following tests | 231 | Tags given outside a test are applied to all following tests |
75 | 224 | Tags given after a test: line and before the result line for the same test | 232 | Tags given after a test: line and before the result line for the same test |
76 | 225 | apply only to that test, and inheric the current global tags. | 233 | apply only to that test, and inheric the current global tags. |
77 | @@ -228,7 +236,19 @@ | |||
78 | 228 | In Python, tags are assigned to the .tags attribute on the RemoteTest objects | 236 | In Python, tags are assigned to the .tags attribute on the RemoteTest objects |
79 | 229 | created by the TestProtocolServer. | 237 | created by the TestProtocolServer. |
80 | 230 | 238 | ||
82 | 231 | The time element acts as a clock event - it sets the time for all future | 239 | The progress directive is used to provide progress information about a stream |
83 | 240 | so that stream consumer can provide completion estimates, progress bars and so | ||
84 | 241 | on. Stream generators that know how many tests will be present in the stream | ||
85 | 242 | should output "progress: COUNT". Stream filters that add tests should output | ||
86 | 243 | "progress: +COUNT", and those that remove tests should output | ||
87 | 244 | "progress: -COUNT". An absolute count should reset the progress indicators in | ||
88 | 245 | use - it indicates that two separate streams from different generators have | ||
89 | 246 | been trivially concatenated together, and there is no knowledge of how many | ||
90 | 247 | more complete streams are incoming. Smart concatenation could scan each stream | ||
91 | 248 | for their count and sum them, or alternatively translate absolute counts into | ||
92 | 249 | relative counts inline. | ||
93 | 250 | |||
94 | 251 | The time directive acts as a clock event - it sets the time for all future | ||
95 | 232 | events. The value should be a valid ISO8601 time. | 252 | events. The value should be a valid ISO8601 time. |
96 | 233 | 253 | ||
97 | 234 | The skip result is used to indicate a test that was found by the runner but not | 254 | The skip result is used to indicate a test that was found by the runner but not |
98 | 235 | 255 | ||
99 | === added file 'filters/subunit2gtk' | |||
100 | --- filters/subunit2gtk 1970-01-01 00:00:00 +0000 | |||
101 | +++ filters/subunit2gtk 2009-07-29 09:59:36 +0000 | |||
102 | @@ -0,0 +1,229 @@ | |||
103 | 1 | #!/usr/bin/env python | ||
104 | 2 | # subunit: extensions to python unittest to get test results from subprocesses. | ||
105 | 3 | # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> | ||
106 | 4 | # | ||
107 | 5 | # This program is free software; you can redistribute it and/or modify | ||
108 | 6 | # it under the terms of the GNU General Public License as published by | ||
109 | 7 | # the Free Software Foundation; either version 2 of the License, or | ||
110 | 8 | # (at your option) any later version. | ||
111 | 9 | # | ||
112 | 10 | # This program is distributed in the hope that it will be useful, | ||
113 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
114 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
115 | 13 | # GNU General Public License for more details. | ||
116 | 14 | # | ||
117 | 15 | # You should have received a copy of the GNU General Public License | ||
118 | 16 | # along with this program; if not, write to the Free Software | ||
119 | 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
120 | 18 | # | ||
121 | 19 | |||
122 | 20 | ### The GTK progress bar __init__ function is derived from the pygtk tutorial: | ||
123 | 21 | # The PyGTK Tutorial is Copyright (C) 2001-2005 John Finlay. | ||
124 | 22 | # | ||
125 | 23 | # The GTK Tutorial is Copyright (C) 1997 Ian Main. | ||
126 | 24 | # | ||
127 | 25 | # Copyright (C) 1998-1999 Tony Gale. | ||
128 | 26 | # | ||
129 | 27 | # Permission is granted to make and distribute verbatim copies of this manual | ||
130 | 28 | # provided the copyright notice and this permission notice are preserved on all | ||
131 | 29 | # copies. | ||
132 | 30 | # | ||
133 | 31 | # Permission is granted to copy and distribute modified versions of this | ||
134 | 32 | # document under the conditions for verbatim copying, provided that this | ||
135 | 33 | # copyright notice is included exactly as in the original, and that the entire | ||
136 | 34 | # resulting derived work is distributed under the terms of a permission notice | ||
137 | 35 | # identical to this one. | ||
138 | 36 | # | ||
139 | 37 | # Permission is granted to copy and distribute translations of this document | ||
140 | 38 | # into another language, under the above conditions for modified versions. | ||
141 | 39 | # | ||
142 | 40 | # If you are intending to incorporate this document into a published work, | ||
143 | 41 | # please contact the maintainer, and we will make an effort to ensure that you | ||
144 | 42 | # have the most up to date information available. | ||
145 | 43 | # | ||
146 | 44 | # There is no guarantee that this document lives up to its intended purpose. | ||
147 | 45 | # This is simply provided as a free resource. As such, the authors and | ||
148 | 46 | # maintainers of the information provided within can not make any guarantee | ||
149 | 47 | # that the information is even accurate. | ||
150 | 48 | |||
151 | 49 | """Display a subunit stream in a gtk progress window.""" | ||
152 | 50 | |||
153 | 51 | import os | ||
154 | 52 | import sys | ||
155 | 53 | import unittest | ||
156 | 54 | |||
157 | 55 | import pygtk | ||
158 | 56 | pygtk.require('2.0') | ||
159 | 57 | import gtk, gtk.gdk, gobject | ||
160 | 58 | |||
161 | 59 | from subunit import ProtocolTestCase, TestProtocolServer | ||
162 | 60 | |||
163 | 61 | class GTKTestResult(unittest.TestResult): | ||
164 | 62 | |||
165 | 63 | def __init__(self): | ||
166 | 64 | super(GTKTestResult, self).__init__() | ||
167 | 65 | # Instance variables (in addition to TestResult) | ||
168 | 66 | self.window = None | ||
169 | 67 | self.run_label = None | ||
170 | 68 | self.ok_label = None | ||
171 | 69 | self.not_ok_label = None | ||
172 | 70 | self.total_tests = None | ||
173 | 71 | |||
174 | 72 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) | ||
175 | 73 | self.window.set_resizable(True) | ||
176 | 74 | |||
177 | 75 | self.window.connect("destroy", gtk.main_quit) | ||
178 | 76 | self.window.set_title("Tests...") | ||
179 | 77 | self.window.set_border_width(0) | ||
180 | 78 | |||
181 | 79 | vbox = gtk.VBox(False, 5) | ||
182 | 80 | vbox.set_border_width(10) | ||
183 | 81 | self.window.add(vbox) | ||
184 | 82 | vbox.show() | ||
185 | 83 | |||
186 | 84 | # Create a centering alignment object | ||
187 | 85 | align = gtk.Alignment(0.5, 0.5, 0, 0) | ||
188 | 86 | vbox.pack_start(align, False, False, 5) | ||
189 | 87 | align.show() | ||
190 | 88 | |||
191 | 89 | # Create the ProgressBar | ||
192 | 90 | self.pbar = gtk.ProgressBar() | ||
193 | 91 | align.add(self.pbar) | ||
194 | 92 | self.pbar.set_text("Running") | ||
195 | 93 | self.pbar.show() | ||
196 | 94 | |||
197 | 95 | separator = gtk.HSeparator() | ||
198 | 96 | vbox.pack_start(separator, False, False, 0) | ||
199 | 97 | separator.show() | ||
200 | 98 | |||
201 | 99 | # rows, columns, homogeneous | ||
202 | 100 | table = gtk.Table(2, 3, False) | ||
203 | 101 | vbox.pack_start(table, False, True, 0) | ||
204 | 102 | table.show() | ||
205 | 103 | # Show summary details about the run. Could use an expander. | ||
206 | 104 | label = gtk.Label("Run:") | ||
207 | 105 | table.attach(label, 0, 1, 1, 2, gtk.EXPAND | gtk.FILL, | ||
208 | 106 | gtk.EXPAND | gtk.FILL, 5, 5) | ||
209 | 107 | label.show() | ||
210 | 108 | self.run_label = gtk.Label("N/A") | ||
211 | 109 | table.attach(self.run_label, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, | ||
212 | 110 | gtk.EXPAND | gtk.FILL, 5, 5) | ||
213 | 111 | self.run_label.show() | ||
214 | 112 | |||
215 | 113 | label = gtk.Label("OK:") | ||
216 | 114 | table.attach(label, 0, 1, 2, 3, gtk.EXPAND | gtk.FILL, | ||
217 | 115 | gtk.EXPAND | gtk.FILL, 5, 5) | ||
218 | 116 | label.show() | ||
219 | 117 | self.ok_label = gtk.Label("N/A") | ||
220 | 118 | table.attach(self.ok_label, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, | ||
221 | 119 | gtk.EXPAND | gtk.FILL, 5, 5) | ||
222 | 120 | self.ok_label.show() | ||
223 | 121 | |||
224 | 122 | label = gtk.Label("Not OK:") | ||
225 | 123 | table.attach(label, 0, 1, 3, 4, gtk.EXPAND | gtk.FILL, | ||
226 | 124 | gtk.EXPAND | gtk.FILL, 5, 5) | ||
227 | 125 | label.show() | ||
228 | 126 | self.not_ok_label = gtk.Label("N/A") | ||
229 | 127 | table.attach(self.not_ok_label, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, | ||
230 | 128 | gtk.EXPAND | gtk.FILL, 5, 5) | ||
231 | 129 | self.not_ok_label.show() | ||
232 | 130 | |||
233 | 131 | self.window.show() | ||
234 | 132 | # For the demo. | ||
235 | 133 | self.window.set_keep_above(True) | ||
236 | 134 | self.window.present() | ||
237 | 135 | |||
238 | 136 | def stopTest(self, test): | ||
239 | 137 | super(GTKTestResult, self).stopTest(test) | ||
240 | 138 | if not self.total_tests: | ||
241 | 139 | self.pbar.pulse() | ||
242 | 140 | else: | ||
243 | 141 | self.pbar.set_fraction(self.testsRun/float(self.total_tests)) | ||
244 | 142 | |||
245 | 143 | def stopTestRun(self): | ||
246 | 144 | try: | ||
247 | 145 | super(GTKTestResult, self).stopTestRun() | ||
248 | 146 | except AttributeError: | ||
249 | 147 | pass | ||
250 | 148 | self.pbar.set_text('Finished') | ||
251 | 149 | |||
252 | 150 | def addError(self, test, err): | ||
253 | 151 | super(GTKTestResult, self).addError(test, err) | ||
254 | 152 | self.update_counts() | ||
255 | 153 | |||
256 | 154 | def addFailure(self, test, err): | ||
257 | 155 | super(GTKTestResult, self).addFailure(test, err) | ||
258 | 156 | self.update_counts() | ||
259 | 157 | |||
260 | 158 | def addSuccess(self, test): | ||
261 | 159 | super(GTKTestResult, self).addSuccess(test) | ||
262 | 160 | self.update_counts() | ||
263 | 161 | |||
264 | 162 | def addSkip(self, test, reason): | ||
265 | 163 | super(GTKTestResult, self).addSkipSuccess(test, reason) | ||
266 | 164 | self.update_counts() | ||
267 | 165 | |||
268 | 166 | def addExpectedFailure(self, test, err): | ||
269 | 167 | super(GTKTestResult, self).addExpectedFailure(test, err) | ||
270 | 168 | self.update_counts() | ||
271 | 169 | |||
272 | 170 | def addUnexpectedSuccess(self, test): | ||
273 | 171 | super(GTKTestResult, self).addUnexpectedSuccess(test) | ||
274 | 172 | self.update_counts() | ||
275 | 173 | |||
276 | 174 | def progress(self, offset, whence): | ||
277 | 175 | if whence == os.SEEK_SET: | ||
278 | 176 | self.total_tests = offset | ||
279 | 177 | else: | ||
280 | 178 | self.total_tests += offset | ||
281 | 179 | |||
282 | 180 | def time(self, a_datetime): | ||
283 | 181 | # We don't try to estimate completion yet. | ||
284 | 182 | pass | ||
285 | 183 | |||
286 | 184 | def update_counts(self): | ||
287 | 185 | self.run_label.set_text(str(self.testsRun)) | ||
288 | 186 | bad = len(self.failures + self.errors) | ||
289 | 187 | self.ok_label.set_text(str(self.testsRun - bad)) | ||
290 | 188 | self.not_ok_label.set_text(str(bad)) | ||
291 | 189 | |||
292 | 190 | |||
293 | 191 | class GIOProtocolTestCase(object): | ||
294 | 192 | |||
295 | 193 | def __init__(self, stream, result, on_finish): | ||
296 | 194 | self.stream = stream | ||
297 | 195 | self.schedule_read() | ||
298 | 196 | self.hup_id = gobject.io_add_watch(stream, gobject.IO_HUP, self.hup) | ||
299 | 197 | self.protocol = TestProtocolServer(result) | ||
300 | 198 | self.on_finish = on_finish | ||
301 | 199 | |||
302 | 200 | def read(self, source, condition): | ||
303 | 201 | #NB: \o/ actually blocks | ||
304 | 202 | line = source.readline() | ||
305 | 203 | if not line: | ||
306 | 204 | self.protocol.lostConnection() | ||
307 | 205 | self.on_finish() | ||
308 | 206 | return False | ||
309 | 207 | self.protocol.lineReceived(line) | ||
310 | 208 | # schedule more IO shortly - if we say we're willing to do it | ||
311 | 209 | # immediately we starve things. | ||
312 | 210 | source_id = gobject.timeout_add(1, self.schedule_read) | ||
313 | 211 | return False | ||
314 | 212 | |||
315 | 213 | def schedule_read(self): | ||
316 | 214 | self.read_id = gobject.io_add_watch(self.stream, gobject.IO_IN, self.read) | ||
317 | 215 | |||
318 | 216 | def hup(self, source, condition): | ||
319 | 217 | self.protocol.lostConnection() | ||
320 | 218 | gobject.remove(self.read_id) | ||
321 | 219 | self.on_finish() | ||
322 | 220 | |||
323 | 221 | |||
324 | 222 | result = GTKTestResult() | ||
325 | 223 | test = GIOProtocolTestCase(sys.stdin, result, result.stopTestRun) | ||
326 | 224 | gtk.main() | ||
327 | 225 | if result.wasSuccessful(): | ||
328 | 226 | exit_code = 0 | ||
329 | 227 | else: | ||
330 | 228 | exit_code = 1 | ||
331 | 229 | sys.exit(exit_code) | ||
332 | 0 | 230 | ||
333 | === modified file 'filters/subunit2pyunit' | |||
334 | --- filters/subunit2pyunit 2009-02-15 11:55:00 +0000 | |||
335 | +++ filters/subunit2pyunit 2009-07-28 21:44:28 +0000 | |||
336 | @@ -17,15 +17,27 @@ | |||
337 | 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
338 | 18 | # | 18 | # |
339 | 19 | 19 | ||
341 | 20 | """Filter a subunit stream through python's default unittest test runner.""" | 20 | """Display a subunit stream through python's unittest test runner.""" |
342 | 21 | 21 | ||
343 | 22 | from optparse import OptionParser | ||
344 | 22 | import sys | 23 | import sys |
345 | 23 | import unittest | 24 | import unittest |
346 | 24 | 25 | ||
347 | 25 | from subunit import ProtocolTestCase, TestProtocolServer | 26 | from subunit import ProtocolTestCase, TestProtocolServer |
348 | 26 | 27 | ||
350 | 27 | runner = unittest.TextTestRunner(verbosity=2) | 28 | parser = OptionParser(description=__doc__) |
351 | 29 | parser.add_option("--progress", action="store_true", | ||
352 | 30 | help="Use bzrlib's test reporter (requires bzrlib)", | ||
353 | 31 | default=False) | ||
354 | 32 | (options, args) = parser.parse_args() | ||
355 | 28 | test = ProtocolTestCase(sys.stdin) | 33 | test = ProtocolTestCase(sys.stdin) |
356 | 34 | if options.progress: | ||
357 | 35 | from bzrlib.tests import TextTestRunner | ||
358 | 36 | from bzrlib import ui | ||
359 | 37 | ui.ui_factory = ui.make_ui_for_terminal(None, sys.stdout, sys.stderr) | ||
360 | 38 | runner = TextTestRunner() | ||
361 | 39 | else: | ||
362 | 40 | runner = unittest.TextTestRunner(verbosity=2) | ||
363 | 29 | if runner.run(test).wasSuccessful(): | 41 | if runner.run(test).wasSuccessful(): |
364 | 30 | exit_code = 0 | 42 | exit_code = 0 |
365 | 31 | else: | 43 | else: |
366 | 32 | 44 | ||
367 | === modified file 'python/subunit/__init__.py' | |||
368 | --- python/subunit/__init__.py 2009-07-22 08:46:04 +0000 | |||
369 | +++ python/subunit/__init__.py 2009-07-28 13:32:10 +0000 | |||
370 | @@ -28,6 +28,10 @@ | |||
371 | 28 | import iso8601 | 28 | import iso8601 |
372 | 29 | 29 | ||
373 | 30 | 30 | ||
374 | 31 | SEEK_CUR = os.SEEK_CUR | ||
375 | 32 | SEEK_SET = os.SEEK_SET | ||
376 | 33 | |||
377 | 34 | |||
378 | 31 | def test_suite(): | 35 | def test_suite(): |
379 | 32 | import subunit.tests | 36 | import subunit.tests |
380 | 33 | return subunit.tests.test_suite() | 37 | return subunit.tests.test_suite() |
381 | @@ -200,6 +204,18 @@ | |||
382 | 200 | else: | 204 | else: |
383 | 201 | self.stdOutLineReceived(line) | 205 | self.stdOutLineReceived(line) |
384 | 202 | 206 | ||
385 | 207 | def _handleProgress(self, offset, line): | ||
386 | 208 | """Process a progress directive.""" | ||
387 | 209 | line = line[offset:].strip() | ||
388 | 210 | if line[0] in '+-': | ||
389 | 211 | whence = SEEK_CUR | ||
390 | 212 | else: | ||
391 | 213 | whence = SEEK_SET | ||
392 | 214 | delta = int(line) | ||
393 | 215 | progress_method = getattr(self.client, 'progress', None) | ||
394 | 216 | if callable(progress_method): | ||
395 | 217 | progress_method(delta, whence) | ||
396 | 218 | |||
397 | 203 | def _handleTags(self, offset, line): | 219 | def _handleTags(self, offset, line): |
398 | 204 | """Process a tags command.""" | 220 | """Process a tags command.""" |
399 | 205 | tags = line[offset:].split() | 221 | tags = line[offset:].split() |
400 | @@ -243,6 +259,8 @@ | |||
401 | 243 | self._addError(offset, line) | 259 | self._addError(offset, line) |
402 | 244 | elif cmd == 'failure': | 260 | elif cmd == 'failure': |
403 | 245 | self._addFailure(offset, line) | 261 | self._addFailure(offset, line) |
404 | 262 | elif cmd == 'progress': | ||
405 | 263 | self._handleProgress(offset, line) | ||
406 | 246 | elif cmd == 'skip': | 264 | elif cmd == 'skip': |
407 | 247 | self._addSkip(offset, line) | 265 | self._addSkip(offset, line) |
408 | 248 | elif cmd in ('success', 'successful'): | 266 | elif cmd in ('success', 'successful'): |
409 | @@ -355,6 +373,21 @@ | |||
410 | 355 | """Mark a test as starting its test run.""" | 373 | """Mark a test as starting its test run.""" |
411 | 356 | self._stream.write("test: %s\n" % test.id()) | 374 | self._stream.write("test: %s\n" % test.id()) |
412 | 357 | 375 | ||
413 | 376 | def progress(self, offset, whence): | ||
414 | 377 | """Provide indication about the progress/length of the test run. | ||
415 | 378 | |||
416 | 379 | :param offset: Information about the number of tests remaining. If | ||
417 | 380 | whence is SEEK_CUR, then offset increases/decreases the remaining | ||
418 | 381 | test count. If whence is SEEK_SET, then offset specifies exactly | ||
419 | 382 | the remaining test count. | ||
420 | 383 | :param whence: One of SEEK_CUR or SEEK_SET. | ||
421 | 384 | """ | ||
422 | 385 | if whence == SEEK_CUR and offset > -1: | ||
423 | 386 | prefix = "+" | ||
424 | 387 | else: | ||
425 | 388 | prefix = "" | ||
426 | 389 | self._stream.write("progress: %s%s\n" % (prefix, offset)) | ||
427 | 390 | |||
428 | 358 | def time(self, a_datetime): | 391 | def time(self, a_datetime): |
429 | 359 | """Inform the client of the time. | 392 | """Inform the client of the time. |
430 | 360 | 393 | ||
431 | 361 | 394 | ||
432 | === modified file 'python/subunit/test_results.py' | |||
433 | --- python/subunit/test_results.py 2009-07-22 23:17:18 +0000 | |||
434 | +++ python/subunit/test_results.py 2009-07-28 13:32:10 +0000 | |||
435 | @@ -87,6 +87,10 @@ | |||
436 | 87 | self._before_event() | 87 | self._before_event() |
437 | 88 | return self._call_maybe("addUnexpectedSuccess", test) | 88 | return self._call_maybe("addUnexpectedSuccess", test) |
438 | 89 | 89 | ||
439 | 90 | def progress(self, offset, whence): | ||
440 | 91 | self._before_event() | ||
441 | 92 | return self._call_maybe("progress", offset, whence) | ||
442 | 93 | |||
443 | 90 | def wasSuccessful(self): | 94 | def wasSuccessful(self): |
444 | 91 | self._before_event() | 95 | self._before_event() |
445 | 92 | return self.decorated.wasSuccessful() | 96 | return self.decorated.wasSuccessful() |
446 | @@ -124,6 +128,9 @@ | |||
447 | 124 | time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc()) | 128 | time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc()) |
448 | 125 | self._call_maybe("time", time) | 129 | self._call_maybe("time", time) |
449 | 126 | 130 | ||
450 | 131 | def progress(self, offset, whence): | ||
451 | 132 | return self._call_maybe("progress", offset, whence) | ||
452 | 133 | |||
453 | 127 | @property | 134 | @property |
454 | 128 | def shouldStop(self): | 135 | def shouldStop(self): |
455 | 129 | return self.decorated.shouldStop | 136 | return self.decorated.shouldStop |
456 | 130 | 137 | ||
457 | === modified file 'python/subunit/tests/test_test_protocol.py' | |||
458 | --- python/subunit/tests/test_test_protocol.py 2009-07-22 08:46:04 +0000 | |||
459 | +++ python/subunit/tests/test_test_protocol.py 2009-07-28 13:32:10 +0000 | |||
460 | @@ -37,6 +37,7 @@ | |||
461 | 37 | self.skip_calls = [] | 37 | self.skip_calls = [] |
462 | 38 | self.start_calls = [] | 38 | self.start_calls = [] |
463 | 39 | self.success_calls = [] | 39 | self.success_calls = [] |
464 | 40 | self.progress_calls = [] | ||
465 | 40 | self._time = None | 41 | self._time = None |
466 | 41 | super(MockTestProtocolServerClient, self).__init__() | 42 | super(MockTestProtocolServerClient, self).__init__() |
467 | 42 | 43 | ||
468 | @@ -58,6 +59,9 @@ | |||
469 | 58 | def startTest(self, test): | 59 | def startTest(self, test): |
470 | 59 | self.start_calls.append(test) | 60 | self.start_calls.append(test) |
471 | 60 | 61 | ||
472 | 62 | def progress(self, offset, whence): | ||
473 | 63 | self.progress_calls.append((offset, whence)) | ||
474 | 64 | |||
475 | 61 | def time(self, time): | 65 | def time(self, time): |
476 | 62 | self._time = time | 66 | self._time = time |
477 | 63 | 67 | ||
478 | @@ -118,6 +122,11 @@ | |||
479 | 118 | self.assertEqual(protocol.success_calls, []) | 122 | self.assertEqual(protocol.success_calls, []) |
480 | 119 | self.assertEqual(protocol.start_calls, []) | 123 | self.assertEqual(protocol.start_calls, []) |
481 | 120 | 124 | ||
482 | 125 | def test_progress(self): | ||
483 | 126 | protocol = MockTestProtocolServerClient() | ||
484 | 127 | protocol.progress(-1, subunit.SEEK_CUR) | ||
485 | 128 | self.assertEqual(protocol.progress_calls, [(-1, subunit.SEEK_CUR)]) | ||
486 | 129 | |||
487 | 121 | 130 | ||
488 | 122 | class TestTestImports(unittest.TestCase): | 131 | class TestTestImports(unittest.TestCase): |
489 | 123 | 132 | ||
490 | @@ -710,6 +719,36 @@ | |||
491 | 710 | self.success_quoted_bracket("success:") | 719 | self.success_quoted_bracket("success:") |
492 | 711 | 720 | ||
493 | 712 | 721 | ||
494 | 722 | class TestTestProtocolServerProgress(unittest.TestCase): | ||
495 | 723 | """Test receipt of progress: directives.""" | ||
496 | 724 | |||
497 | 725 | def test_progress_accepted_stdlib(self): | ||
498 | 726 | # With a stdlib TestResult, progress events are swallowed. | ||
499 | 727 | self.result = unittest.TestResult() | ||
500 | 728 | self.stream = StringIO() | ||
501 | 729 | self.protocol = subunit.TestProtocolServer(self.result, | ||
502 | 730 | stream=self.stream) | ||
503 | 731 | self.protocol.lineReceived("progress: 23") | ||
504 | 732 | self.protocol.lineReceived("progress: -2") | ||
505 | 733 | self.protocol.lineReceived("progress: +4") | ||
506 | 734 | self.assertEqual("", self.stream.getvalue()) | ||
507 | 735 | |||
508 | 736 | def test_progress_accepted_extended(self): | ||
509 | 737 | # With a progress capable TestResult, progress events are emitted. | ||
510 | 738 | self.result = MockTestProtocolServerClient() | ||
511 | 739 | self.stream = StringIO() | ||
512 | 740 | self.protocol = subunit.TestProtocolServer(self.result, | ||
513 | 741 | stream=self.stream) | ||
514 | 742 | self.protocol.lineReceived("progress: 23") | ||
515 | 743 | self.protocol.lineReceived("progress: -2") | ||
516 | 744 | self.protocol.lineReceived("progress: +4") | ||
517 | 745 | self.assertEqual("", self.stream.getvalue()) | ||
518 | 746 | self.assertEqual( | ||
519 | 747 | [(23, subunit.SEEK_SET), (-2, subunit.SEEK_CUR), | ||
520 | 748 | (4, subunit.SEEK_CUR)], | ||
521 | 749 | self.result.progress_calls) | ||
522 | 750 | |||
523 | 751 | |||
524 | 713 | class TestTestProtocolServerStreamTags(unittest.TestCase): | 752 | class TestTestProtocolServerStreamTags(unittest.TestCase): |
525 | 714 | """Test managing tags on the protocol level.""" | 753 | """Test managing tags on the protocol level.""" |
526 | 715 | 754 | ||
527 | @@ -1005,6 +1044,18 @@ | |||
528 | 1005 | self.io.getvalue(), | 1044 | self.io.getvalue(), |
529 | 1006 | 'skip: %s [\nHas it really?\n]\n' % self.test.id()) | 1045 | 'skip: %s [\nHas it really?\n]\n' % self.test.id()) |
530 | 1007 | 1046 | ||
531 | 1047 | def test_progress_set(self): | ||
532 | 1048 | self.protocol.progress(23, subunit.SEEK_SET) | ||
533 | 1049 | self.assertEqual(self.io.getvalue(), 'progress: 23\n') | ||
534 | 1050 | |||
535 | 1051 | def test_progress_neg_cur(self): | ||
536 | 1052 | self.protocol.progress(-23, subunit.SEEK_CUR) | ||
537 | 1053 | self.assertEqual(self.io.getvalue(), 'progress: -23\n') | ||
538 | 1054 | |||
539 | 1055 | def test_progress_pos_cur(self): | ||
540 | 1056 | self.protocol.progress(23, subunit.SEEK_CUR) | ||
541 | 1057 | self.assertEqual(self.io.getvalue(), 'progress: +23\n') | ||
542 | 1058 | |||
543 | 1008 | def test_time(self): | 1059 | def test_time(self): |
544 | 1009 | # Calling time() outputs a time signal immediately. | 1060 | # Calling time() outputs a time signal immediately. |
545 | 1010 | self.protocol.time( | 1061 | self.protocol.time( |
546 | 1011 | 1062 | ||
547 | === modified file 'python/subunit/tests/test_test_results.py' | |||
548 | --- python/subunit/tests/test_test_results.py 2009-07-22 23:17:18 +0000 | |||
549 | +++ python/subunit/tests/test_test_results.py 2009-07-28 13:32:10 +0000 | |||
550 | @@ -108,6 +108,9 @@ | |||
551 | 108 | def test_addUnexpectedSuccess(self): | 108 | def test_addUnexpectedSuccess(self): |
552 | 109 | self.result.addUnexpectedSuccess(self) | 109 | self.result.addUnexpectedSuccess(self) |
553 | 110 | 110 | ||
554 | 111 | def test_progress(self): | ||
555 | 112 | self.result.progress(1, os.SEEK_SET) | ||
556 | 113 | |||
557 | 111 | def test_wasSuccessful(self): | 114 | def test_wasSuccessful(self): |
558 | 112 | self.result.wasSuccessful() | 115 | self.result.wasSuccessful() |
559 | 113 | 116 | ||
560 | @@ -135,6 +138,10 @@ | |||
561 | 135 | self.assertEqual(1, len(self.result.decorated._calls)) | 138 | self.assertEqual(1, len(self.result.decorated._calls)) |
562 | 136 | self.assertNotEqual(None, self.result.decorated._calls[0]) | 139 | self.assertNotEqual(None, self.result.decorated._calls[0]) |
563 | 137 | 140 | ||
564 | 141 | def test_no_time_from_progress(self): | ||
565 | 142 | self.result.progress(1, os.SEEK_CUR) | ||
566 | 143 | self.assertEqual(0, len(self.result.decorated._calls)) | ||
567 | 144 | |||
568 | 138 | def test_no_time_from_shouldStop(self): | 145 | def test_no_time_from_shouldStop(self): |
569 | 139 | self.result.decorated.stop() | 146 | self.result.decorated.stop() |
570 | 140 | self.result.shouldStop | 147 | self.result.shouldStop |
This builds on progress-core to do a GTK progress bar. Woo. Shiny.