1############################################################################### 2# 3# The MIT License (MIT) 4# 5# Copyright (c) Crossbar.io Technologies GmbH 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the "Software"), to deal 9# in the Software without restriction, including without limitation the rights 10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11# copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23# THE SOFTWARE. 24# 25############################################################################### 26 27from __future__ import absolute_import 28 29import os 30import unittest 31import six 32 33from autobahn.wamp import message 34from autobahn.wamp import role 35from autobahn.wamp import serializer 36 37 38# FIXME: autobahn.wamp.serializer.JsonObjectSerializer uses a patched JSON 39# encoder/decoder - however, the patching currently only works on Python 3! 40def must_skip(ser, contains_binary): 41 if contains_binary and ser.SERIALIZER_ID.startswith(u'json') and six.PY2: 42 return True 43 else: 44 return False 45 46 47def generate_test_messages(): 48 """ 49 List of WAMP test message used for serializers. Expand this if you add more 50 options or messages. 51 52 This list of WAMP message does not contain any binary app payloads! 53 """ 54 msgs = [ 55 message.Hello(u"realm1", {u'subscriber': role.RoleSubscriberFeatures()}), 56 message.Goodbye(), 57 message.Yield(123456), 58 message.Yield(123456, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 59 message.Yield(123456, args=[u'hello']), 60 message.Yield(123456, progress=True), 61 message.Interrupt(123456), 62 message.Interrupt(123456, mode=message.Interrupt.KILL), 63 message.Invocation(123456, 789123), 64 message.Invocation(123456, 789123, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 65 message.Invocation(123456, 789123, timeout=10000), 66 message.Result(123456), 67 message.Result(123456, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 68 message.Result(123456, progress=True), 69 message.Cancel(123456), 70 message.Cancel(123456, mode=message.Cancel.KILL), 71 message.Call(123456, u'com.myapp.procedure1'), 72 message.Call(123456, u'com.myapp.procedure1', args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 73 message.Call(123456, u'com.myapp.procedure1', timeout=10000), 74 message.Unregistered(123456), 75 message.Unregister(123456, 789123), 76 message.Registered(123456, 789123), 77 message.Register(123456, u'com.myapp.procedure1'), 78 message.Register(123456, u'com.myapp.procedure1', match=u'prefix'), 79 message.Register(123456, u'com.myapp.procedure1', invoke=u'roundrobin'), 80 message.Event(123456, 789123), 81 message.Event(123456, 789123, args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 82 message.Event(123456, 789123, publisher=300), 83 message.Published(123456, 789123), 84 message.Publish(123456, u'com.myapp.topic1'), 85 message.Publish(123456, u'com.myapp.topic1', args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 86 message.Publish(123456, u'com.myapp.topic1', exclude_me=False, exclude=[300], eligible=[100, 200, 300]), 87 message.Unsubscribed(123456), 88 message.Unsubscribe(123456, 789123), 89 message.Subscribed(123456, 789123), 90 message.Subscribe(123456, u'com.myapp.topic1'), 91 message.Subscribe(123456, u'com.myapp.topic1', match=message.Subscribe.MATCH_PREFIX), 92 message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1'), 93 message.Error(message.Call.MESSAGE_TYPE, 123456, u'com.myapp.error1', args=[1, 2, 3], kwargs={u'foo': 23, u'bar': u'hello'}), 94 message.Call(123456, u'com.myapp.\u4f60\u597d\u4e16\u754c', args=[1, 2, 3]), 95 message.Result(123456, args=[1, 2, 3], kwargs={u'en': u'Hello World', u'jp': u'\u3053\u3093\u306b\u3061\u306f\u4e16\u754c'}) 96 ] 97 return [(False, msg) for msg in msgs] 98 99 100def generate_test_messages_binary(): 101 """ 102 Generate WAMP test messages which contain binary app payloads. 103 104 With the JSON serializer, this currently only works on Python 3 (both CPython3 and PyPy3), 105 because even on Python 3, we need to patch the stdlib JSON, and on Python 2, the patching 106 would be even hackier. 107 """ 108 msgs = [] 109 for binary in [b'', 110 b'\x00', 111 b'\30', 112 os.urandom(4), 113 os.urandom(16), 114 os.urandom(128), 115 os.urandom(256), 116 os.urandom(512), 117 os.urandom(1024)]: 118 msgs.append(message.Event(123456, 789123, args=[binary])) 119 msgs.append(message.Event(123456, 789123, args=[binary], kwargs={u'foo': binary})) 120 return [(True, msg) for msg in msgs] 121 122 123def create_serializers(): 124 _serializers = [] 125 126 _serializers.append(serializer.JsonSerializer()) 127 _serializers.append(serializer.JsonSerializer(batched=True)) 128 129 _serializers.append(serializer.MsgPackSerializer()) 130 _serializers.append(serializer.MsgPackSerializer(batched=True)) 131 132 _serializers.append(serializer.CBORSerializer()) 133 _serializers.append(serializer.CBORSerializer(batched=True)) 134 135 _serializers.append(serializer.UBJSONSerializer()) 136 _serializers.append(serializer.UBJSONSerializer(batched=True)) 137 138 # FIXME: implement full FlatBuffers serializer for WAMP 139 if six.PY3: 140 # WAMP-FlatBuffers currently only supports Python 3 141 # _serializers.append(serializer.FlatBuffersSerializer()) 142 # _serializers.append(serializer.FlatBuffersSerializer(batched=True)) 143 pass 144 145 return _serializers 146 147 148@unittest.skipIf(not six.PY3, 'WAMP-FlatBuffers currently only supports Python 3') 149class TestFlatBuffersSerializer(unittest.TestCase): 150 151 def test_basic(self): 152 messages = [ 153 message.Event(123456, 154 789123, 155 args=[1, 2, 3], 156 kwargs={u'foo': 23, u'bar': u'hello'}, 157 publisher=666, 158 retained=True), 159 message.Publish(123456, 160 'com.example.topic1', 161 args=[1, 2, 3], 162 kwargs={u'foo': 23, u'bar': u'hello'}, 163 retain=True) 164 ] 165 166 ser = serializer.FlatBuffersSerializer() 167 168 # from pprint import pprint 169 170 for msg in messages: 171 172 # serialize message 173 payload, binary = ser.serialize(msg) 174 175 # unserialize message again 176 msg2 = ser.unserialize(payload, binary)[0] 177 178 # pprint(msg.marshal()) 179 # pprint(msg2.marshal()) 180 181 # must be equal: message roundtrips via the serializer 182 self.assertEqual(msg, msg2) 183 # self.assertEqual(msg.subscription, msg2.subscription) 184 # self.assertEqual(msg.publication, msg2.publication) 185 186 187class TestSerializer(unittest.TestCase): 188 189 def setUp(self): 190 self._test_messages = generate_test_messages() + generate_test_messages_binary() 191 self._test_serializers = create_serializers() 192 # print('Testing WAMP serializers {} with {} WAMP test messages'.format([ser.SERIALIZER_ID for ser in self._test_serializers], len(self._test_messages))) 193 194 def test_deep_equal_msg(self): 195 """ 196 Test deep object equality assert (because I am paranoid). 197 """ 198 v = os.urandom(10) 199 o1 = [1, 2, {u'foo': u'bar', u'bar': v, u'baz': [9, 3, 2], u'goo': {u'moo': [1, 2, 3]}}, v] 200 o2 = [1, 2, {u'goo': {u'moo': [1, 2, 3]}, u'bar': v, u'baz': [9, 3, 2], u'foo': u'bar'}, v] 201 self.assertEqual(o1, o2) 202 203 def test_roundtrip_msg(self): 204 """ 205 Test round-tripping over each serializer. 206 """ 207 for ser in self._test_serializers: 208 209 for contains_binary, msg in self._test_messages: 210 211 if not must_skip(ser, contains_binary): 212 # serialize message 213 payload, binary = ser.serialize(msg) 214 215 # unserialize message again 216 msg2 = ser.unserialize(payload, binary) 217 218 # must be equal: message roundtrips via the serializer 219 self.assertEqual([msg], msg2) 220 221 def test_crosstrip_msg(self): 222 """ 223 Test cross-tripping over 2 serializers (as is done by WAMP routers). 224 """ 225 for ser1 in self._test_serializers: 226 227 for contains_binary, msg in self._test_messages: 228 229 if not must_skip(ser1, contains_binary): 230 # serialize message 231 payload, binary = ser1.serialize(msg) 232 233 # unserialize message again 234 msg1 = ser1.unserialize(payload, binary) 235 msg1 = msg1[0] 236 237 for ser2 in self._test_serializers: 238 239 if not must_skip(ser2, contains_binary): 240 # serialize message 241 payload, binary = ser2.serialize(msg1) 242 243 # unserialize message again 244 msg2 = ser2.unserialize(payload, binary) 245 246 # must be equal: message crosstrips via 247 # the serializers ser1 -> ser2 248 self.assertEqual([msg], msg2) 249 250 def test_cache_msg(self): 251 """ 252 Test message serialization caching. 253 """ 254 for contains_binary, msg in self._test_messages: 255 256 # message serialization cache is initially empty 257 self.assertEqual(msg._serialized, {}) 258 259 for ser in self._test_serializers: 260 261 if not must_skip(ser, contains_binary): 262 263 # verify message serialization is not yet cached 264 self.assertFalse(ser._serializer in msg._serialized) 265 payload, binary = ser.serialize(msg) 266 267 # now the message serialization must be cached 268 self.assertTrue(ser._serializer in msg._serialized) 269 self.assertEqual(msg._serialized[ser._serializer], payload) 270 271 # and after resetting the serialization cache, message 272 # serialization is gone 273 msg.uncache() 274 self.assertFalse(ser._serializer in msg._serialized) 275