summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2009-08-08 17:03:38 +1000
committerRobert Collins <robertc@robertcollins.net>2009-08-08 17:03:38 +1000
commit37080a3a8b3b3414e8d6ec1eb68fb6000d926a0a (patch)
tree15d15c7208e43515435d4ed8f6d0a7f4d588b929 /python
parentcc4699a328dd76ecaad6a2e94b17845793bdf51d (diff)
downloadsubunit-git-37080a3a8b3b3414e8d6ec1eb68fb6000d926a0a.tar.gz
Extend the progress model to support a push/pop model.
Diffstat (limited to 'python')
-rw-r--r--python/subunit/__init__.py36
-rw-r--r--python/subunit/progress_model.py109
-rw-r--r--python/subunit/tests/__init__.py2
-rw-r--r--python/subunit/tests/test_progress_model.py121
-rw-r--r--python/subunit/tests/test_test_protocol.py25
-rw-r--r--python/subunit/tests/test_test_results.py7
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):