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