diff options
author | Keith Wall <kwall@apache.org> | 2014-09-06 22:06:41 +0000 |
---|---|---|
committer | Keith Wall <kwall@apache.org> | 2014-09-06 22:06:41 +0000 |
commit | f85b65fc9947af2924c9791bcdcf68cd84dbf278 (patch) | |
tree | 681ee53732f2ed51b4fdedf6e09039027805565d | |
parent | 3ab3a3016756d11b68120d102365aed05b02cc27 (diff) | |
download | qpid-python-f85b65fc9947af2924c9791bcdcf68cd84dbf278.tar.gz |
QPID-6085: [Python client] 08..091 implement sending/receiving of additional property types
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1622952 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | python/qpid/codec.py | 143 | ||||
-rw-r--r-- | python/qpid/tests/codec.py | 128 | ||||
-rw-r--r-- | tests/src/py/qpid_tests/broker_0_9/messageheader.py | 26 |
3 files changed, 281 insertions, 16 deletions
diff --git a/python/qpid/codec.py b/python/qpid/codec.py index 8026b209dc..a4c542415c 100644 --- a/python/qpid/codec.py +++ b/python/qpid/codec.py @@ -26,14 +26,18 @@ fields. The unit test for this module is located in tests/codec.py """ -import re, qpid, spec08 +import re, qpid, spec08, os from cStringIO import StringIO from struct import * from reference import ReferenceId +from logging import getLogger + +log = getLogger("qpid.codec") class EOF(Exception): pass +# This code appears to be dead TYPE_ALIASES = { "long_string": "longstr", "unsigned_int": "long" @@ -56,36 +60,81 @@ class Codec: self.incoming_bits = [] self.outgoing_bits = [] + # Before 0-91, the AMQP's set of types did not include the boolean type. However, + # the 0-8 and 0-9 Java client uses this type so we encode/decode it too. However, this + # can be turned off by setting the followng environment value. + if "QPID_CODEC_DISABLE_0_91_BOOLEAN" in os.environ: + self.understand_boolean = False + else: + self.understand_boolean = True + + log.debug("AMQP 0-91 boolean supported : %r", self.understand_boolean) + self.types = {} self.codes = {} + self.integertypes = [int, long] self.encodings = { + float: "double", # python uses 64bit floats, send them as doubles basestring: "longstr", - int: "long", - long: "long", None.__class__:"void", list: "sequence", tuple: "sequence", dict: "table" } + if self.understand_boolean: + self.encodings[bool] = "boolean" + for constant in self.spec.constants: + # This code appears to be dead if constant.klass == "field-table-type": type = constant.name.replace("field_table_", "") self.typecode(constant.id, TYPE_ALIASES.get(type, type)) if not self.types: + # long-string 'S' self.typecode(ord('S'), "longstr") - self.typecode(ord('I'), "long") + # void 'V' + self.typecode(ord('V'), "void") + # long-int 'I' (32bit signed) + self.typecode(ord('I'), "signed_int") + # long-long-int 'l' (64bit signed) + # This is a long standing pre-0-91-spec type used by the Java + # client, 0-9-1 says it should be unsigned or use 'L') + self.typecode(ord('l'), "signed_long") + # double 'd' + self.typecode(ord('d'), "double") + # float 'f' + self.typecode(ord('f'), "float") + + if self.understand_boolean: + self.typecode(ord('t'), "boolean") + + ## The following are supported for decoding only ## + + # short-short-uint 'b' (8bit signed) + self.types[ord('b')] = "signed_octet" + # short-int 's' (16bit signed) + # This is a long standing pre-0-91-spec type code used by the Java + # client to send shorts, it should really be a short-string, or for 0-9-1 use 'U' + self.types[ord('s')] = "signed_short" def typecode(self, code, type): self.types[code] = type self.codes[type] = code - def resolve(self, klass): + def resolve(self, klass, value): + if(klass in self.integertypes): + if (value >= -2147483648 and value <= 2147483647): + return "signed_int" + elif (value >= -9223372036854775808 and value <= 9223372036854775807): + return "signed_long" + else: + raise ValueError('Integer value is outwith the supported 64bit signed range') if self.encodings.has_key(klass): return self.encodings[klass] for base in klass.__bases__: - result = self.resolve(base) + result = self.resolve(base, value) if result != None: return result @@ -168,6 +217,7 @@ class Codec: if isinstance(type, spec08.Struct): return self.decode_struct(type) else: + log.debug("Decoding using method: decode_" + type) return getattr(self, "decode_" + type)() def encode_bit(self, o): @@ -191,7 +241,7 @@ class Codec: def encode_octet(self, o): """ - encodes octet (8 bits) data 'o' in network byte order + encodes an UNSIGNED octet (8 bits) data 'o' in network byte order """ # octet's valid range is [0,255] @@ -202,13 +252,20 @@ class Codec: def decode_octet(self): """ - decodes a octet (8 bits) encoded in network byte order + decodes an UNSIGNED octet (8 bits) encoded in network byte order """ return self.unpack("!B") + def decode_signed_octet(self): + """ + decodes a signed octet (8 bits) encoded in network byte order + """ + return self.unpack("!b") + def encode_short(self, o): """ - encodes short (16 bits) data 'o' in network byte order + encodes an UNSIGNED short (16 bits) data 'o' in network byte order + AMQP 0-9-1 type: short-uint """ # short int's valid range is [0,65535] @@ -219,13 +276,22 @@ class Codec: def decode_short(self): """ - decodes a short (16 bits) in network byte order + decodes an UNSIGNED short (16 bits) in network byte order + AMQP 0-9-1 type: short-uint """ return self.unpack("!H") + def decode_signed_short(self): + """ + decodes a signed short (16 bits) in network byte order + AMQP 0-9-1 type: short-int + """ + return self.unpack("!h") + def encode_long(self, o): """ - encodes long (32 bits) data 'o' in network byte order + encodes an UNSIGNED long (32 bits) data 'o' in network byte order + AMQP 0-9-1 type: long-uint """ # we need to check both bounds because on 64 bit platforms @@ -237,31 +303,50 @@ class Codec: def decode_long(self): """ - decodes a long (32 bits) in network byte order + decodes an UNSIGNED long (32 bits) in network byte order + AMQP 0-9-1 type: long-uint """ return self.unpack("!L") def encode_signed_long(self, o): + """ + encodes a signed long (64 bits) in network byte order + AMQP 0-9-1 type: long-long-int + """ self.pack("!q", o) def decode_signed_long(self): + """ + decodes a signed long (64 bits) in network byte order + AMQP 0-9-1 type: long-long-int + """ return self.unpack("!q") def encode_signed_int(self, o): + """ + encodes a signed int (32 bits) in network byte order + AMQP 0-9-1 type: long-int + """ self.pack("!l", o) def decode_signed_int(self): + """ + decodes a signed int (32 bits) in network byte order + AMQP 0-9-1 type: long-int + """ return self.unpack("!l") def encode_longlong(self, o): """ - encodes long long (64 bits) data 'o' in network byte order + encodes an UNSIGNED long long (64 bits) data 'o' in network byte order + AMQP 0-9-1 type: long-long-uint """ self.pack("!Q", o) def decode_longlong(self): """ - decodes a long long (64 bits) in network byte order + decodes an UNSIGNED long long (64 bits) in network byte order + AMQP 0-9-1 type: long-long-uint """ return self.unpack("!Q") @@ -354,9 +439,9 @@ class Codec: for key, value in tbl.items(): if self.spec.major == 8 and self.spec.minor == 0 and len(key) > 128: raise ValueError("field table key too long: '%s'" % key) - type = self.resolve(value.__class__) + type = self.resolve(value.__class__, value) if type == None: - raise ValueError("no encoding for: " + value.__class__) + raise ValueError("no encoding for: " + str(value.__class__)) codec.encode_shortstr(key) codec.encode_octet(self.codes[type]) codec.encode(type, value) @@ -373,7 +458,9 @@ class Codec: result = {} while self.nread - start < size: key = self.decode_shortstr() + log.debug("Field table entry key: %r", key) code = self.decode_octet() + log.debug("Field table entry type code: %r", code) if self.types.has_key(code): value = self.decode(self.types[code]) else: @@ -383,6 +470,7 @@ class Codec: else: value = self.read(self.dec_num(w)) result[key] = value + log.debug("Field table entry value: %r", value) return result def encode_timestamp(self, t): @@ -451,6 +539,13 @@ class Codec: def decode_uuid(self): return self.unpack("16s") + def encode_void(self,o): + #NO-OP, value is implicit in the type. + return + + def decode_void(self): + return None + def enc_num(self, width, n): if width == 1: self.encode_octet(n) @@ -565,6 +660,22 @@ class Codec: result.append(value) return result + def encode_boolean(self, s): + if (s): + self.pack("!c", "\x01") + else: + self.pack("!c", "\x00") + + def decode_boolean(self): + b = self.unpack("!c") + if b == "\x00": + return False + else: + # AMQP spec says anything else is True + return True + + + def fixed(code): return (code >> 6) != 2 diff --git a/python/qpid/tests/codec.py b/python/qpid/tests/codec.py index 8fd0528636..8017f794fe 100644 --- a/python/qpid/tests/codec.py +++ b/python/qpid/tests/codec.py @@ -487,6 +487,134 @@ class ContentTestCase(BaseDataTypes): """ self.failUnlessEqual(self.readFunc('decode_content', '\x01\x00\x00\x00\x07dummyId').id, 'dummyId', 'reference content decode FAILED...') +# ----------------------------------- +# ----------------------------------- +class BooleanTestCase(BaseDataTypes): + + # ------------------- + def test_true_encode(self): + self.failUnlessEqual(self.callFunc('encode_boolean', True), '\x01', 'True encoding FAILED...') + + # ------------------- + def test_true_decode(self): + self.failUnlessEqual(self.readFunc('decode_boolean', '\x01'), True, 'True decoding FAILED...') + self.failUnlessEqual(self.readFunc('decode_boolean', '\x02'), True, 'True decoding FAILED...') + self.failUnlessEqual(self.readFunc('decode_boolean', '\xFF'), True, 'True decoding FAILED...') + + # ------------------- + def test_false_encode(self): + self.failUnlessEqual(self.callFunc('encode_boolean', False), '\x00', 'False encoding FAILED...') + + # ------------------- + def test_false_decode(self): + self.failUnlessEqual(self.readFunc('decode_boolean', '\x00'), False, 'False decoding FAILED...') + +# ----------------------------------- +# ----------------------------------- +class ResolveTestCase(BaseDataTypes): + + # ------------------- + # Test resolving the value 1, which should implicitly be a python int + def test_resolve_int_1(self): + value = 1 + expected = "signed_int" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving the value -1, which should implicitly be a python int + def test_resolve_int_negative_1(self): + value = -1 + expected = "signed_int" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving the min signed 32bit integer value, -2^31 + def test_resolve_int_min(self): + value = -2147483648 #-2^31 + expected = "signed_int" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving the max signed 32bit integer value, 2^31 -1 + def test_resolve_int_max(self): + value = 2147483647 #2^31 -1 + expected = "signed_int" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving above the max signed 32bit integer value of 2^31 -1 + # Should be a python long, but should be classed as a signed 64bit long on the wire either way + def test_resolve_int_above_signed_32bit_max(self): + value = 2147483648 #2^31, i.e 1 above the 32bit signed max + expected = "signed_long" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving above the max signed 32bit integer value of 2^31 -1 + # As above except use an explicitly cast python long + def test_resolve_long_above_signed_32bit_max(self): + value = 2147483648L #2^31, i.e 1 above the 32bit signed max + expected = "signed_long" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving an explicitly cast python long of value 1, i.e less than the max signed 32bit integer value + # Should be encoded as a 32bit signed int on the wire + def test_resolve_long_1(self): + value = 1L + expected = "signed_int" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving the max signed 64bit integer value of 2^63 -1 + # Should be a python long, but should be classed as a signed 64bit long on the wire either way + def test_resolve_64bit_signed_max(self): + value = 9223372036854775807 #2^63 -1 + expected = "signed_long" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving the min signed 64bit integer value of -2^63 + # Should be a python long, but should be classed as a signed 64bit long on the wire either way + def test_resolve_64bit_signed_min(self): + value = -9223372036854775808 # -2^63 + expected = "signed_long" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving a value of 2^63, i.e more than the max a signed 64bit integer value can hold. + # Should throw an exception indicating the value can't be encoded. + def test_resolve_above_64bit_signed_max(self): + value = 9223372036854775808L #2^63 + self.failUnlessRaises(Exception, self.codec.resolve, value.__class__, value) + # ------------------- + # Test resolving a value of -2^63 -1, i.e less than the min a signed 64bit integer value can hold. + # Should throw an exception indicating the value can't be encoded. + def test_resolve_below_64bit_signed_min(self): + value = 9223372036854775808L # -2^63 -1 + self.failUnlessRaises(Exception, self.codec.resolve, value.__class__, value) + # ------------------- + # Test resolving a float. Should indicate use of double as python uses 64bit floats + def test_resolve_float(self): + value = 1.1 + expected = "double" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving a string. Should indicate use of long string encoding + def test_resolve_string(self): + value = "myString" + expected = "longstr" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------- + # Test resolving None. Should indicate use of a void encoding. + def test_resolve_None(self): + value = None + expected = "void" + resolved = self.codec.resolve(value.__class__, value) + self.failUnlessEqual(resolved, expected, "resolve FAILED...expected %s got %s" % (expected, resolved)) + # ------------------------ # # Pre - existing test code # # ------------------------ # diff --git a/tests/src/py/qpid_tests/broker_0_9/messageheader.py b/tests/src/py/qpid_tests/broker_0_9/messageheader.py index 3526cf37af..3d64adfcf0 100644 --- a/tests/src/py/qpid_tests/broker_0_9/messageheader.py +++ b/tests/src/py/qpid_tests/broker_0_9/messageheader.py @@ -33,3 +33,29 @@ class MessageHeaderTests(TestBase): self.queue_declare(queue="q") q = self.consume("q") self.assertPublishGet(q, routing_key="q", properties=props) + + def test_message_with_boolean_header(self): + """The AMQP boolean type is not officially supported until 0-91 but the 0-8/9 Java client use its field value typecode. + Note: If you run this test with QPID_CODEC_DISABLE_0_91_BOOLEAN set, this test will still pass as the booleans are + coerced into integer.""" + + props={"headers":{"trueHeader":True, "falseHeader":False}} + self.queue_declare(queue="q") + q = self.consume("q") + self.assertPublishGet(q, routing_key="q", properties=props) + + def test_message_with_negatives_longints_floats_and_None(self): + """ Tests sending and then receiving negative integers, longs, the None (void) value, and doubles.""" + props={"headers":{"myIntMin": -2147483648, + "myIntMax": 2147483647, + "myLongMax": 9223372036854775807, + "myLongMin": -9223372036854775808, + "myNullString": None, + "myDouble1.1": 1.1, + "myDoubleMin": 4.9E-324, + "myDoubleMax": 1.7976931348623157E308}} + + self.queue_declare(queue="q") + q = self.consume("q") + self.assertPublishGet(q, routing_key="q", properties=props) + |