diff options
| author | Robert Collins <robertc@robertcollins.net> | 2009-10-10 14:42:32 +1100 |
|---|---|---|
| committer | Robert Collins <robertc@robertcollins.net> | 2009-10-10 14:42:32 +1100 |
| commit | 40ae70b04c7c88ed80a5e5b3f340f0c523b95e59 (patch) | |
| tree | bcf7bb54e48b98a714dd12f2bd7d59e685eefdb8 /python | |
| parent | 65ddbd561a22e15e1ae5cdb3383c64bfd78e7d95 (diff) | |
| download | subunit-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__.py | 15 | ||||
| -rw-r--r-- | python/subunit/chunked.py | 63 | ||||
| -rw-r--r-- | python/subunit/tests/__init__.py | 2 | ||||
| -rw-r--r-- | python/subunit/tests/test_chunked.py | 65 | ||||
| -rw-r--r-- | python/subunit/tests/test_test_protocol.py | 18 |
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(): |
