summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRobert Collins <robertc@robertcollins.net>2009-10-10 14:42:32 +1100
committerRobert Collins <robertc@robertcollins.net>2009-10-10 14:42:32 +1100
commit40ae70b04c7c88ed80a5e5b3f340f0c523b95e59 (patch)
treebcf7bb54e48b98a714dd12f2bd7d59e685eefdb8 /python
parent65ddbd561a22e15e1ae5cdb3383c64bfd78e7d95 (diff)
downloadsubunit-git-40ae70b04c7c88ed80a5e5b3f340f0c523b95e59.tar.gz
Move chunking to be \r\n based and create a dedicated module with that logic.
Diffstat (limited to 'python')
-rw-r--r--python/subunit/__init__.py15
-rw-r--r--python/subunit/chunked.py63
-rw-r--r--python/subunit/tests/__init__.py2
-rw-r--r--python/subunit/tests/test_chunked.py65
-rw-r--r--python/subunit/tests/test_test_protocol.py18
5 files changed, 150 insertions, 13 deletions
diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py
index f06437c..a93b6eb 100644
--- a/python/subunit/__init__.py
+++ b/python/subunit/__init__.py
@@ -107,6 +107,13 @@ result object::
# And run your suite as normal, Subunit will exec each external script as
# needed and report to your result object.
suite.run(result)
+
+Utility modules
+---------------
+
+* subunit.chunked contains HTTP chunked encoding/decoding logic.
+* subunit.content contains a minimal assumptions MIME content representation.
+* subunit.content_type contains a MIME Content-Type representation.
"""
import datetime
@@ -119,7 +126,7 @@ import unittest
import iso8601
-import content, content_type
+import chunked, content, content_type
PROGRESS_SET = 0
@@ -618,9 +625,9 @@ class TestProtocolClient(unittest.TestResult):
param_strs.append("%s=%s" % (param, value))
self._stream.write(",".join(param_strs))
self._stream.write("\n%s\n" % name)
- for bytes in content.iter_bytes():
- self._stream.write("%d\n%s" % (len(bytes), bytes))
- self._stream.write("0\n")
+ encoder = chunked.Encoder(self._stream)
+ map(encoder.write, content.iter_bytes())
+ encoder.close()
def done(self):
"""Obey the testtools result.done() interface."""
diff --git a/python/subunit/chunked.py b/python/subunit/chunked.py
new file mode 100644
index 0000000..2cfc7ff
--- /dev/null
+++ b/python/subunit/chunked.py
@@ -0,0 +1,63 @@
+#
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+"""Encoder/decoder for http style chunked encoding."""
+
+class Encoder(object):
+ """Encode content to a stream using HTTP Chunked coding."""
+
+ def __init__(self, output):
+ """Create an encoder encoding to output.
+
+ :param output: A file-like object. Bytes written to the Encoder
+ will be encoded using HTTP chunking. Small writes may be buffered
+ and the ``close`` method must be called to finish the stream.
+ """
+ self.output = output
+ self.buffered_bytes = []
+ self.buffer_size = 0
+
+ def flush(self, extra_len=0):
+ """Flush the encoder to the output stream.
+
+ :param extra_len: Increase the size of the chunk by this many bytes
+ to allow for a subsequent write.
+ """
+ if not self.buffer_size and not extra_len:
+ return
+ buffered_bytes = self.buffered_bytes
+ buffer_size = self.buffer_size
+ self.buffered_bytes = []
+ self.buffer_size = 0
+ self.output.write("%X\r\n" % (buffer_size + extra_len))
+ if buffer_size:
+ self.output.write(''.join(buffered_bytes))
+ return True
+
+ def write(self, bytes):
+ """Encode bytes to the output stream."""
+ bytes_len = len(bytes)
+ if self.buffer_size + bytes_len >= 65536:
+ self.flush(bytes_len)
+ self.output.write(bytes)
+ else:
+ self.buffered_bytes.append(bytes)
+ self.buffer_size += bytes_len
+
+ def close(self):
+ """Finish the stream. This does not close the output stream."""
+ self.flush()
+ self.output.write("0\r\n")
diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py
index d842c7e..8869425 100644
--- a/python/subunit/tests/__init__.py
+++ b/python/subunit/tests/__init__.py
@@ -16,6 +16,7 @@
from subunit.tests import (
TestUtil,
+ test_chunked,
test_content_type,
test_content,
test_progress_model,
@@ -29,6 +30,7 @@ from subunit.tests import (
def test_suite():
result = TestUtil.TestSuite()
+ result.addTest(test_chunked.test_suite())
result.addTest(test_content_type.test_suite())
result.addTest(test_content.test_suite())
result.addTest(test_progress_model.test_suite())
diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py
new file mode 100644
index 0000000..2bf82b2
--- /dev/null
+++ b/python/subunit/tests/test_chunked.py
@@ -0,0 +1,65 @@
+#
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+from cStringIO import StringIO
+import unittest
+
+import subunit.chunked
+
+
+def test_suite():
+ loader = subunit.tests.TestUtil.TestLoader()
+ result = loader.loadTestsFromName(__name__)
+ return result
+
+
+class TestEncode(unittest.TestCase):
+
+ def setUp(self):
+ self.output = StringIO()
+ self.encoder = subunit.chunked.Encoder(self.output)
+
+ def test_encode_nothing(self):
+ self.encoder.close()
+ self.assertEqual('0\r\n', self.output.getvalue())
+
+ def test_encode_empty(self):
+ self.encoder.write('')
+ self.encoder.close()
+ self.assertEqual('0\r\n', self.output.getvalue())
+
+ def test_encode_short(self):
+ self.encoder.write('abc')
+ self.encoder.close()
+ self.assertEqual('3\r\nabc0\r\n', self.output.getvalue())
+
+ def test_encode_combines_short(self):
+ self.encoder.write('abc')
+ self.encoder.write('def')
+ self.encoder.close()
+ self.assertEqual('6\r\nabcdef0\r\n', self.output.getvalue())
+
+ def test_encode_over_9_is_in_hex(self):
+ self.encoder.write('1234567890')
+ self.encoder.close()
+ self.assertEqual('A\r\n12345678900\r\n', self.output.getvalue())
+
+ def test_encode_long_ranges_not_combined(self):
+ self.encoder.write('1' * 65536)
+ self.encoder.write('2' * 65536)
+ self.encoder.close()
+ self.assertEqual('10000\r\n' + '1' * 65536 + '10000\r\n' +
+ '2' * 65536 + '0\r\n', self.output.getvalue())
diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py
index c2c62bc..41fc6b4 100644
--- a/python/subunit/tests/test_test_protocol.py
+++ b/python/subunit/tests/test_test_protocol.py
@@ -1083,7 +1083,7 @@ class TestTestProtocolClient(unittest.TestCase):
self.io.getvalue(), "successful: %s [ multipart\n"
"Content-Type: text/plain\n"
"something\n"
- "15\nserialised\nform0\n]\n" % self.test.id())
+ "F\r\nserialised\nform0\r\n]\n" % self.test.id())
def test_add_failure(self):
"""Test addFailure on a TestProtocolClient."""
@@ -1102,10 +1102,10 @@ class TestTestProtocolClient(unittest.TestCase):
"failure: %s [ multipart\n"
"Content-Type: text/plain\n"
"something\n"
- "15\nserialised\nform0\n"
+ "F\r\nserialised\nform0\r\n"
"Content-Type: text/x-traceback;language=python\n"
"traceback\n"
- "25\nRemoteException: boo qux\n0\n"
+ "19\r\nRemoteException: boo qux\n0\r\n"
"]\n" % self.test.id())
def test_add_error(self):
@@ -1127,10 +1127,10 @@ class TestTestProtocolClient(unittest.TestCase):
"error: %s [ multipart\n"
"Content-Type: text/plain\n"
"something\n"
- "15\nserialised\nform0\n"
+ "F\r\nserialised\nform0\r\n"
"Content-Type: text/x-traceback;language=python\n"
"traceback\n"
- "25\nRemoteException: boo qux\n0\n"
+ "19\r\nRemoteException: boo qux\n0\r\n"
"]\n" % self.test.id())
def test_add_expected_failure(self):
@@ -1152,10 +1152,10 @@ class TestTestProtocolClient(unittest.TestCase):
"xfail: %s [ multipart\n"
"Content-Type: text/plain\n"
"something\n"
- "15\nserialised\nform0\n"
+ "F\r\nserialised\nform0\r\n"
"Content-Type: text/x-traceback;language=python\n"
"traceback\n"
- "25\nRemoteException: boo qux\n0\n"
+ "19\r\nRemoteException: boo qux\n0\r\n"
"]\n" % self.test.id())
def test_add_skip(self):
@@ -1177,7 +1177,7 @@ class TestTestProtocolClient(unittest.TestCase):
"skip: %s [ multipart\n"
"Content-Type: text/plain\n"
"reason\n"
- "14\nHas it really?0\n"
+ "E\r\nHas it really?0\r\n"
"]\n" % self.test.id())
def test_progress_set(self):
@@ -1221,7 +1221,7 @@ class TestTestProtocolClient(unittest.TestCase):
self.io.getvalue(), "successful: %s [ multipart\n"
"Content-Type: text/plain\n"
"something\n"
- "15\nserialised\nform0\n]\n" % self.test.id())
+ "F\r\nserialised\nform0\r\n]\n" % self.test.id())
def test_suite():