* Memoryview and struct were mutually incompatible in 2.7.3 and 3.2.
authorRobert Collins <robertc@robertcollins.net>
Sat, 24 Aug 2013 14:07:59 +0000 (02:07 +1200)
committerRobert Collins <robertc@robertcollins.net>
Sat, 24 Aug 2013 14:07:59 +0000 (02:07 +1200)
  (Robert Collins, #1216163)

NEWS
python/subunit/v2.py

diff --git a/NEWS b/NEWS
index 391a6c0c475c9452365da0566a1f85a49a6f6796..58c565aba0019d4b9457ccf7733cd7c0617b1f37 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,9 @@ BUG FIXES
 * Clients of subunit did not expect memoryview objects in StreamResult events.
   (Robert Collins)
 
+* Memoryview and struct were mutually incompatible in 2.7.3 and 3.2.
+  (Robert Collins, #1216163)
+
 0.0.14
 ------
 
index 1f5bfc1277ee9ab99cd8585a220567446b05bcb0..e71034697cc605a700fe8c04fb4d3cefbe5241bd 100644 (file)
@@ -331,26 +331,35 @@ class ByteStreamToStreamResult(object):
                 eof=True, file_name="Parser Error",
                 file_bytes=(error.args[0]).encode('utf8'))
 
+    def _to_bytes(self, data, pos, length):
+        """Return a slice of data from pos for length as bytes."""
+        # memoryview in 2.7.3 and 3.2 isn't directly usable with struct :(.
+        # see https://bugs.launchpad.net/subunit/+bug/1216163
+        result = data[pos:pos+length]
+        if type(result) is not bytes:
+            return result.tobytes()
+        return result
+
     def _parse_varint(self, data, pos, max_3_bytes=False):
         # because the only incremental IO we do is at the start, and the 32 bit
         # CRC means we can always safely read enough to cover any varint, we
         # can be sure that there should be enough data - and if not it is an
         # error not a normal situation.
-        data_0 = struct.unpack(FMT_8, data[pos:pos+1])[0]
+        data_0 = struct.unpack(FMT_8, self._to_bytes(data, pos, 1))[0]
         typeenum = data_0 & 0xc0
         value_0 = data_0 & 0x3f
         if typeenum == 0x00:
             return value_0, 1
         elif typeenum == 0x40:
-            data_1 = struct.unpack(FMT_8, data[pos+1:pos+2])[0]
+            data_1 = struct.unpack(FMT_8, self._to_bytes(data, pos+1, 1))[0]
             return (value_0 << 8) | data_1, 2
         elif typeenum == 0x80:
-            data_1 = struct.unpack(FMT_16, data[pos+1:pos+3])[0]
+            data_1 = struct.unpack(FMT_16, self._to_bytes(data, pos+1, 2))[0]
             return (value_0 << 16) | data_1, 3
         else:
             if max_3_bytes:
                 raise ParseError('3 byte maximum given but 4 byte value found.')
-            data_1, data_2 = struct.unpack(FMT_24, data[pos+1:pos+4])
+            data_1, data_2 = struct.unpack(FMT_24, self._to_bytes(data, pos+1, 3))
             result = (value_0 << 24) | data_1 << 8 | data_2
             return result, 4
 
@@ -385,16 +394,14 @@ class ByteStreamToStreamResult(object):
                         % (crc, packet_crc))
             if safe_hasattr(builtins, 'memoryview'):
                 body = memoryview(packet[-1])
-                view = True
             else:
                 body = packet[-1]
-                view = False
             # Discard CRC-32
             body = body[:-4]
             # One packet could have both file and status data; the Python API
             # presents these separately (perhaps it shouldn't?)
             if flags & FLAG_TIMESTAMP:
-                seconds = struct.unpack(FMT_32, body[pos:pos+4])[0]
+                seconds = struct.unpack(FMT_32, self._to_bytes(body, pos, 4))[0]
                 nanoseconds, consumed = self._parse_varint(body, pos+4)
                 pos = pos + 4 + consumed
                 timestamp = EPOCH + datetime.timedelta(
@@ -422,9 +429,7 @@ class ByteStreamToStreamResult(object):
                 file_name, pos = self._read_utf8(body, pos)
                 content_length, consumed = self._parse_varint(body, pos)
                 pos += consumed
-                file_bytes = body[pos:pos+content_length]
-                if view:
-                    file_bytes = file_bytes.tobytes()
+                file_bytes = self._to_bytes(body, pos, content_length)
                 if len(file_bytes) != content_length:
                     raise ParseError('File content extends past end of packet: '
                         'claimed %d bytes, %d available' % (