diff options
| author | Robert Collins <robertc@robertcollins.net> | 2009-08-08 17:03:38 +1000 |
|---|---|---|
| committer | Robert Collins <robertc@robertcollins.net> | 2009-08-08 17:03:38 +1000 |
| commit | 37080a3a8b3b3414e8d6ec1eb68fb6000d926a0a (patch) | |
| tree | 15d15c7208e43515435d4ed8f6d0a7f4d588b929 /python | |
| parent | cc4699a328dd76ecaad6a2e94b17845793bdf51d (diff) | |
| download | subunit-git-37080a3a8b3b3414e8d6ec1eb68fb6000d926a0a.tar.gz | |
Extend the progress model to support a push/pop model.
Diffstat (limited to 'python')
| -rw-r--r-- | python/subunit/__init__.py | 36 | ||||
| -rw-r--r-- | python/subunit/progress_model.py | 109 | ||||
| -rw-r--r-- | python/subunit/tests/__init__.py | 2 | ||||
| -rw-r--r-- | python/subunit/tests/test_progress_model.py | 121 | ||||
| -rw-r--r-- | python/subunit/tests/test_test_protocol.py | 25 | ||||
| -rw-r--r-- | python/subunit/tests/test_test_results.py | 7 |
6 files changed, 280 insertions, 20 deletions
diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index f474bd9..27d9e53 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -28,8 +28,10 @@ import unittest import iso8601 -SEEK_CUR = os.SEEK_CUR -SEEK_SET = os.SEEK_SET +PROGRESS_SET = 0 +PROGRESS_CUR = 1 +PROGRESS_PUSH = 2 +PROGRESS_POP = 3 def test_suite(): @@ -225,10 +227,17 @@ class TestProtocolServer(object): """Process a progress directive.""" line = line[offset:].strip() if line[0] in '+-': - whence = SEEK_CUR + whence = PROGRESS_CUR + delta = int(line) + elif line == "push": + whence = PROGRESS_PUSH + delta = None + elif line == "pop": + whence = PROGRESS_POP + delta = None else: - whence = SEEK_SET - delta = int(line) + whence = PROGRESS_SET + delta = int(line) progress_method = getattr(self.client, 'progress', None) if callable(progress_method): progress_method(delta, whence) @@ -394,13 +403,20 @@ class TestProtocolClient(unittest.TestResult): """Provide indication about the progress/length of the test run. :param offset: Information about the number of tests remaining. If - whence is SEEK_CUR, then offset increases/decreases the remaining - test count. If whence is SEEK_SET, then offset specifies exactly - the remaining test count. - :param whence: One of SEEK_CUR or SEEK_SET. + whence is PROGRESS_CUR, then offset increases/decreases the + remaining test count. If whence is PROGRESS_SET, then offset + specifies exactly the remaining test count. + :param whence: One of PROGRESS_CUR, PROGRESS_SET, PROGRESS_PUSH, + PROGRESS_POP. """ - if whence == SEEK_CUR and offset > -1: + if whence == PROGRESS_CUR and offset > -1: prefix = "+" + elif whence == PROGRESS_PUSH: + prefix = "" + offset = "push" + elif whence == PROGRESS_POP: + prefix = "" + offset = "pop" else: prefix = "" self._stream.write("progress: %s%s\n" % (prefix, offset)) diff --git a/python/subunit/progress_model.py b/python/subunit/progress_model.py new file mode 100644 index 0000000..f1ed7af --- /dev/null +++ b/python/subunit/progress_model.py @@ -0,0 +1,109 @@ +# +# subunit: extensions to Python unittest to get test results from subprocesses. +# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +"""Support for dealing with progress state.""" + +class ProgressModel(object): + """A model of progress indicators as subunit defines it. + + Instances of this class represent a single logical operation that is + progressing. The operation may have many steps, and some of those steps may + supply their own progress information. ProgressModel uses a nested concept + where the overall state can be pushed, creating new starting state, and + later pushed to return to the prior state. Many user interfaces will want + to display an overall summary though, and accordingly the pos() and width() + methods return overall summary information rather than information on the + current subtask. + + The default state is 0/0 - indicating that the overall progress is unknown. + Anytime the denominator of pos/width is 0, rendering of a ProgressModel + should should take this into consideration. + + :ivar: _tasks. This private attribute stores the subtasks. Each is a tuple: + pos, width, overall_numerator, overall_denominator. The overall fields + store the calculated overall numerator and denominator for the state + that was pushed. + """ + + def __init__(self): + """Create a ProgressModel. + + The new model has no progress data at all - it will claim a summary + width of zero and position of 0. + """ + self._tasks = [] + self.push() + + def adjust_width(self, offset): + """Adjust the with of the current subtask.""" + self._tasks[-1][1] += offset + + def advance(self): + """Advance the current subtask.""" + self._tasks[-1][0] += 1 + + def pop(self): + """Pop a subtask off the ProgressModel. + + See push for a description of how push and pop work. + """ + self._tasks.pop() + + def pos(self): + """Return how far through the operation has progressed.""" + if not self._tasks: + return 0 + task = self._tasks[-1] + if len(self._tasks) > 1: + # scale up the overall pos by the current task or preserve it if + # no current width is known. + offset = task[2] * (task[1] or 1) + else: + offset = 0 + return offset + task[0] + + def push(self): + """Push a new subtask. + + After pushing a new subtask, the overall progress hasn't changed. Calls + to adjust_width, advance, set_width will only after the progress within + the range that calling 'advance' would have before - the subtask + represents progressing one step in the earlier task. + + Call pop() to restore the progress model to the state before push was + called. + """ + self._tasks.append([0, 0, self.pos(), self.width()]) + + def set_width(self, width): + """Set the width of the current subtask.""" + self._tasks[-1][1] = width + + def width(self): + """Return the total width of the operation.""" + if not self._tasks: + return 0 + task = self._tasks[-1] + if len(self._tasks) > 1: + # scale up the overall width by the current task or preserve it if + # no current width is known. + return task[3] * (task[1] or 1) + else: + return task[1] + diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index 11f3095..bbe12b1 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -19,6 +19,7 @@ from subunit.tests import ( TestUtil, + test_progress_model, test_subunit_filter, test_subunit_stats, test_subunit_tags, @@ -29,6 +30,7 @@ from subunit.tests import ( def test_suite(): result = TestUtil.TestSuite() + result.addTest(test_progress_model.test_suite()) result.addTest(test_test_results.test_suite()) result.addTest(test_test_protocol.test_suite()) result.addTest(test_tap2subunit.test_suite()) diff --git a/python/subunit/tests/test_progress_model.py b/python/subunit/tests/test_progress_model.py new file mode 100644 index 0000000..60beb41 --- /dev/null +++ b/python/subunit/tests/test_progress_model.py @@ -0,0 +1,121 @@ +# +# subunit: extensions to Python unittest to get test results from subprocesses. +# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import unittest + +import subunit +from subunit.progress_model import ProgressModel + + +class TestProgressModel(unittest.TestCase): + + def assertProgressSummary(self, pos, total, progress): + """Assert that a progress model has reached a particular point.""" + self.assertEqual(pos, progress.pos()) + self.assertEqual(total, progress.width()) + + def test_new_progress_0_0(self): + progress = ProgressModel() + self.assertProgressSummary(0, 0, progress) + + def test_advance_0_0(self): + progress = ProgressModel() + progress.advance() + self.assertProgressSummary(1, 0, progress) + + def test_advance_1_0(self): + progress = ProgressModel() + progress.advance() + self.assertProgressSummary(1, 0, progress) + + def test_set_width_absolute(self): + progress = ProgressModel() + progress.set_width(10) + self.assertProgressSummary(0, 10, progress) + + def test_set_width_absolute_preserves_pos(self): + progress = ProgressModel() + progress.advance() + progress.set_width(2) + self.assertProgressSummary(1, 2, progress) + + def test_adjust_width(self): + progress = ProgressModel() + progress.adjust_width(10) + self.assertProgressSummary(0, 10, progress) + progress.adjust_width(-10) + self.assertProgressSummary(0, 0, progress) + + def test_adjust_width_preserves_pos(self): + progress = ProgressModel() + progress.advance() + progress.adjust_width(10) + self.assertProgressSummary(1, 10, progress) + progress.adjust_width(-10) + self.assertProgressSummary(1, 0, progress) + + def test_push_preserves_progress(self): + progress = ProgressModel() + progress.adjust_width(3) + progress.advance() + progress.push() + self.assertProgressSummary(1, 3, progress) + + def test_advance_advances_substack(self): + progress = ProgressModel() + progress.adjust_width(3) + progress.advance() + progress.push() + progress.adjust_width(1) + progress.advance() + self.assertProgressSummary(2, 3, progress) + + def test_adjust_width_adjusts_substack(self): + progress = ProgressModel() + progress.adjust_width(3) + progress.advance() + progress.push() + progress.adjust_width(2) + progress.advance() + self.assertProgressSummary(3, 6, progress) + + def test_set_width_adjusts_substack(self): + progress = ProgressModel() + progress.adjust_width(3) + progress.advance() + progress.push() + progress.set_width(2) + progress.advance() + self.assertProgressSummary(3, 6, progress) + + def test_pop_restores_progress(self): + progress = ProgressModel() + progress.adjust_width(3) + progress.advance() + progress.push() + progress.adjust_width(1) + progress.advance() + progress.pop() + self.assertProgressSummary(1, 3, progress) + + +def test_suite(): + loader = subunit.tests.TestUtil.TestLoader() + result = loader.loadTestsFromName(__name__) + return result diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index a036192..2b90b9e 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -129,8 +129,8 @@ class TestMockTestProtocolServer(unittest.TestCase): def test_progress(self): protocol = MockTestProtocolServerClient() - protocol.progress(-1, subunit.SEEK_CUR) - self.assertEqual(protocol.progress_calls, [(-1, subunit.SEEK_CUR)]) + protocol.progress(-1, subunit.PROGRESS_CUR) + self.assertEqual(protocol.progress_calls, [(-1, subunit.PROGRESS_CUR)]) class TestTestImports(unittest.TestCase): @@ -790,12 +790,15 @@ class TestTestProtocolServerProgress(unittest.TestCase): self.protocol = subunit.TestProtocolServer(self.result, stream=self.stream) self.protocol.lineReceived("progress: 23") + self.protocol.lineReceived("progress: push") self.protocol.lineReceived("progress: -2") + self.protocol.lineReceived("progress: pop") self.protocol.lineReceived("progress: +4") self.assertEqual("", self.stream.getvalue()) self.assertEqual( - [(23, subunit.SEEK_SET), (-2, subunit.SEEK_CUR), - (4, subunit.SEEK_CUR)], + [(23, subunit.PROGRESS_SET), (None, subunit.PROGRESS_PUSH), + (-2, subunit.PROGRESS_CUR), (None, subunit.PROGRESS_POP), + (4, subunit.PROGRESS_CUR)], self.result.progress_calls) @@ -1095,17 +1098,25 @@ class TestTestProtocolClient(unittest.TestCase): 'skip: %s [\nHas it really?\n]\n' % self.test.id()) def test_progress_set(self): - self.protocol.progress(23, subunit.SEEK_SET) + self.protocol.progress(23, subunit.PROGRESS_SET) self.assertEqual(self.io.getvalue(), 'progress: 23\n') def test_progress_neg_cur(self): - self.protocol.progress(-23, subunit.SEEK_CUR) + self.protocol.progress(-23, subunit.PROGRESS_CUR) self.assertEqual(self.io.getvalue(), 'progress: -23\n') def test_progress_pos_cur(self): - self.protocol.progress(23, subunit.SEEK_CUR) + self.protocol.progress(23, subunit.PROGRESS_CUR) self.assertEqual(self.io.getvalue(), 'progress: +23\n') + def test_progress_pop(self): + self.protocol.progress(1234, subunit.PROGRESS_POP) + self.assertEqual(self.io.getvalue(), 'progress: pop\n') + + def test_progress_push(self): + self.protocol.progress(1234, subunit.PROGRESS_PUSH) + self.assertEqual(self.io.getvalue(), 'progress: push\n') + def test_time(self): # Calling time() outputs a time signal immediately. self.protocol.time( diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index 4537304..2f76917 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -21,10 +21,11 @@ import datetime import unittest from StringIO import StringIO import os -import subunit.test_results import sys +import subunit import subunit.iso8601 as iso8601 +import subunit.test_results class LoggingDecorator(subunit.test_results.HookedTestResultDecorator): @@ -109,7 +110,7 @@ class TestHookedTestResultDecorator(unittest.TestCase): self.result.addUnexpectedSuccess(self) def test_progress(self): - self.result.progress(1, os.SEEK_SET) + self.result.progress(1, subunit.PROGRESS_SET) def test_wasSuccessful(self): self.result.wasSuccessful() @@ -139,7 +140,7 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase): self.assertNotEqual(None, self.result.decorated._calls[0]) def test_no_time_from_progress(self): - self.result.progress(1, os.SEEK_CUR) + self.result.progress(1, subunit.PROGRESS_CUR) self.assertEqual(0, len(self.result.decorated._calls)) def test_no_time_from_shouldStop(self): |
