test_msgpack.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Test the MessagePack utility
  4. '''
  5. # Import Python Libs
  6. from __future__ import absolute_import
  7. from io import BytesIO
  8. import inspect
  9. import os
  10. import pprint
  11. import sys
  12. import struct
  13. try:
  14. import msgpack
  15. except ImportError:
  16. import msgpack_pure as msgpack # pylint: disable=import-error
  17. # Import Salt Testing Libs
  18. from tests.support.unit import skipIf
  19. from tests.support.unit import TestCase
  20. # Import Salt Libs
  21. from salt.utils.odict import OrderedDict
  22. from salt.ext.six.moves import range
  23. import salt.utils.msgpack
  24. # A keyword to pass to tests that use `raw`, which was added in msgpack 0.5.2
  25. raw = {'raw': False} if msgpack.version > (0, 5, 2) else {}
  26. @skipIf(not salt.utils.msgpack.HAS_MSGPACK, 'msgpack module required for these tests')
  27. class TestMsgpack(TestCase):
  28. '''
  29. In msgpack, the following aliases exist:
  30. load = unpack
  31. loads = unpackb
  32. dump = pack
  33. dumps = packb
  34. The salt.utils.msgpack versions of these functions are not aliases,
  35. verify that they pass the same relevant tests from:
  36. https://github.com/msgpack/msgpack-python/blob/master/test/
  37. '''
  38. test_data = [
  39. 0,
  40. 1,
  41. 127,
  42. 128,
  43. 255,
  44. 256,
  45. 65535,
  46. 65536,
  47. 4294967295,
  48. 4294967296,
  49. -1,
  50. -32,
  51. -33,
  52. -128,
  53. -129,
  54. -32768,
  55. -32769,
  56. -4294967296,
  57. -4294967297,
  58. 1.0,
  59. b"",
  60. b"a",
  61. b"a" * 31,
  62. b"a" * 32,
  63. None,
  64. True,
  65. False,
  66. (),
  67. ((),),
  68. ((), None,),
  69. {None: 0},
  70. (1 << 23),
  71. ]
  72. def test_version(self):
  73. '''
  74. Verify that the version exists and returns a value in the expected format
  75. '''
  76. version = salt.utils.msgpack.version
  77. self.assertTrue(isinstance(version, tuple))
  78. self.assertGreater(version, (0, 0, 0))
  79. def test_Packer(self):
  80. data = os.urandom(1024)
  81. packer = salt.utils.msgpack.Packer()
  82. unpacker = msgpack.Unpacker(None)
  83. packed = packer.pack(data)
  84. # Sanity Check
  85. self.assertTrue(packed)
  86. self.assertNotEqual(data, packed)
  87. # Reverse the packing and the result should be equivalent to the original data
  88. unpacker.feed(packed)
  89. unpacked = msgpack.unpackb(packed)
  90. self.assertEqual(data, unpacked)
  91. def test_Unpacker(self):
  92. data = os.urandom(1024)
  93. packer = msgpack.Packer()
  94. unpacker = salt.utils.msgpack.Unpacker(None)
  95. packed = packer.pack(data)
  96. # Sanity Check
  97. self.assertTrue(packed)
  98. self.assertNotEqual(data, packed)
  99. # Reverse the packing and the result should be equivalent to the original data
  100. unpacker.feed(packed)
  101. unpacked = msgpack.unpackb(packed)
  102. self.assertEqual(data, unpacked)
  103. def test_array_size(self):
  104. sizes = [0, 5, 50, 1000]
  105. bio = BytesIO()
  106. packer = salt.utils.msgpack.Packer()
  107. for size in sizes:
  108. bio.write(packer.pack_array_header(size))
  109. for i in range(size):
  110. bio.write(packer.pack(i))
  111. bio.seek(0)
  112. unpacker = salt.utils.msgpack.Unpacker(bio, use_list=True)
  113. for size in sizes:
  114. self.assertEqual(unpacker.unpack(), list(range(size)))
  115. def test_manual_reset(self):
  116. sizes = [0, 5, 50, 1000]
  117. packer = salt.utils.msgpack.Packer(autoreset=False)
  118. for size in sizes:
  119. packer.pack_array_header(size)
  120. for i in range(size):
  121. packer.pack(i)
  122. bio = BytesIO(packer.bytes())
  123. unpacker = salt.utils.msgpack.Unpacker(bio, use_list=True)
  124. for size in sizes:
  125. self.assertEqual(unpacker.unpack(), list(range(size)))
  126. packer.reset()
  127. self.assertEqual(packer.bytes(), b'')
  128. def test_map_size(self):
  129. sizes = [0, 5, 50, 1000]
  130. bio = BytesIO()
  131. packer = salt.utils.msgpack.Packer()
  132. for size in sizes:
  133. bio.write(packer.pack_map_header(size))
  134. for i in range(size):
  135. bio.write(packer.pack(i)) # key
  136. bio.write(packer.pack(i * 2)) # value
  137. bio.seek(0)
  138. if salt.utils.msgpack.version > (0, 6, 0):
  139. unpacker = salt.utils.msgpack.Unpacker(bio, strict_map_key=False)
  140. else:
  141. unpacker = salt.utils.msgpack.Unpacker(bio)
  142. for size in sizes:
  143. self.assertEqual(unpacker.unpack(), dict((i, i * 2) for i in range(size)))
  144. def test_exceptions(self):
  145. # Verify that this exception exists
  146. self.assertTrue(salt.utils.msgpack.exceptions.PackValueError)
  147. self.assertTrue(salt.utils.msgpack.exceptions.UnpackValueError)
  148. self.assertTrue(salt.utils.msgpack.exceptions.PackValueError)
  149. self.assertTrue(salt.utils.msgpack.exceptions.UnpackValueError)
  150. def test_function_aliases(self):
  151. '''
  152. Fail if core functionality from msgpack is missing in the utility
  153. '''
  154. def sanitized(item):
  155. if inspect.isfunction(getattr(msgpack, item)):
  156. # Only check objects that exist in the same file as msgpack
  157. return inspect.getfile(getattr(msgpack, item)) == inspect.getfile(msgpack)
  158. msgpack_items = set(x for x in dir(msgpack) if not x.startswith('_') and sanitized(x))
  159. msgpack_util_items = set(dir(salt.utils.msgpack))
  160. self.assertFalse(msgpack_items - msgpack_util_items, 'msgpack functions with no alias in `salt.utils.msgpack`')
  161. def _test_base(self, pack_func, unpack_func):
  162. '''
  163. In msgpack, 'dumps' is an alias for 'packb' and 'loads' is an alias for 'unpackb'.
  164. Verify that both salt.utils.msgpack function variations pass the exact same test
  165. '''
  166. data = os.urandom(1024)
  167. packed = pack_func(data)
  168. # Sanity Check
  169. self.assertTrue(packed)
  170. self.assertIsInstance(packed, bytes)
  171. self.assertNotEqual(data, packed)
  172. # Reverse the packing and the result should be equivalent to the original data
  173. unpacked = unpack_func(packed)
  174. self.assertEqual(data, unpacked)
  175. def _test_buffered_base(self, pack_func, unpack_func):
  176. data = os.urandom(1024).decode(errors='ignore')
  177. buffer = BytesIO()
  178. # Sanity check, we are not borking the BytesIO read function
  179. self.assertNotEqual(BytesIO.read, buffer.read)
  180. buffer.read = buffer.getvalue
  181. pack_func(data, buffer)
  182. # Sanity Check
  183. self.assertTrue(buffer.getvalue())
  184. self.assertIsInstance(buffer.getvalue(), bytes)
  185. self.assertNotEqual(data, buffer.getvalue())
  186. # Reverse the packing and the result should be equivalent to the original data
  187. unpacked = unpack_func(buffer)
  188. self.assertEqual(data, unpacked.decode())
  189. def test_buffered_base_pack(self):
  190. self._test_buffered_base(pack_func=salt.utils.msgpack.pack, unpack_func=msgpack.unpack)
  191. def test_buffered_base_unpack(self):
  192. self._test_buffered_base(pack_func=msgpack.pack, unpack_func=salt.utils.msgpack.unpack)
  193. def _test_unpack_array_header_from_file(self, pack_func, **kwargs):
  194. f = BytesIO(pack_func([1, 2, 3, 4]))
  195. unpacker = salt.utils.msgpack.Unpacker(f)
  196. self.assertEqual(unpacker.read_array_header(), 4)
  197. self.assertEqual(unpacker.unpack(), 1)
  198. self.assertEqual(unpacker.unpack(), 2)
  199. self.assertEqual(unpacker.unpack(), 3)
  200. self.assertEqual(unpacker.unpack(), 4)
  201. self.assertRaises(salt.utils.msgpack.exceptions.OutOfData, unpacker.unpack)
  202. @skipIf(not hasattr(sys, 'getrefcount'), 'sys.getrefcount() is needed to pass this test')
  203. def _test_unpacker_hook_refcnt(self, pack_func, **kwargs):
  204. result = []
  205. def hook(x):
  206. result.append(x)
  207. return x
  208. basecnt = sys.getrefcount(hook)
  209. up = salt.utils.msgpack.Unpacker(object_hook=hook, list_hook=hook)
  210. self.assertGreaterEqual(sys.getrefcount(hook), basecnt + 2)
  211. up.feed(pack_func([{}]))
  212. up.feed(pack_func([{}]))
  213. self.assertEqual(up.unpack(), [{}])
  214. self.assertEqual(up.unpack(), [{}])
  215. self.assertEqual(result, [{}, [{}], {}, [{}]])
  216. del up
  217. self.assertEqual(sys.getrefcount(hook), basecnt)
  218. def _test_unpacker_ext_hook(self, pack_func, **kwargs):
  219. class MyUnpacker(salt.utils.msgpack.Unpacker):
  220. def __init__(self):
  221. my_kwargs = {}
  222. super(MyUnpacker, self).__init__(ext_hook=self._hook, **raw)
  223. def _hook(self, code, data):
  224. if code == 1:
  225. return int(data)
  226. else:
  227. return salt.utils.msgpack.ExtType(code, data)
  228. unpacker = MyUnpacker()
  229. unpacker.feed(pack_func({"a": 1}))
  230. self.assertEqual(unpacker.unpack(), {'a': 1})
  231. unpacker.feed(pack_func({'a': salt.utils.msgpack.ExtType(1, b'123')}))
  232. self.assertEqual(unpacker.unpack(), {'a': 123})
  233. unpacker.feed(pack_func({'a': salt.utils.msgpack.ExtType(2, b'321')}))
  234. self.assertEqual(unpacker.unpack(), {'a': salt.utils.msgpack.ExtType(2, b'321')})
  235. def _check(self, data, pack_func, unpack_func, use_list=False, strict_map_key=False):
  236. my_kwargs = {}
  237. if salt.utils.msgpack.version >= (0, 6, 0):
  238. my_kwargs['strict_map_key'] = strict_map_key
  239. ret = unpack_func(pack_func(data), use_list=use_list, **my_kwargs)
  240. self.assertEqual(ret, data)
  241. def _test_pack_unicode(self, pack_func, unpack_func):
  242. test_data = [u'', u'abcd', [u'defgh'], u'Русский текст']
  243. for td in test_data:
  244. ret = unpack_func(pack_func(td), use_list=True, **raw)
  245. self.assertEqual(ret, td)
  246. packer = salt.utils.msgpack.Packer()
  247. data = packer.pack(td)
  248. ret = salt.utils.msgpack.Unpacker(BytesIO(data), use_list=True, **raw).unpack()
  249. self.assertEqual(ret, td)
  250. def _test_pack_bytes(self, pack_func, unpack_func):
  251. test_data = [
  252. b'',
  253. b'abcd',
  254. (b'defgh',),
  255. ]
  256. for td in test_data:
  257. self._check(td, pack_func, unpack_func)
  258. def _test_pack_byte_arrays(self, pack_func, unpack_func):
  259. test_data = [
  260. bytearray(b''),
  261. bytearray(b'abcd'),
  262. (bytearray(b'defgh'),),
  263. ]
  264. for td in test_data:
  265. self._check(td, pack_func, unpack_func)
  266. @skipIf(sys.version_info < (3, 0), 'Python 2 passes invalid surrogates')
  267. def _test_ignore_unicode_errors(self, pack_func, unpack_func):
  268. ret = unpack_func(
  269. pack_func(b'abc\xeddef', use_bin_type=False), unicode_errors='ignore', **raw
  270. )
  271. self.assertEqual(u'abcdef', ret)
  272. def _test_strict_unicode_unpack(self, pack_func, unpack_func):
  273. packed = pack_func(b'abc\xeddef', use_bin_type=False)
  274. self.assertRaises(UnicodeDecodeError, unpack_func, packed, use_list=True, **raw)
  275. @skipIf(sys.version_info < (3, 0), 'Python 2 passes invalid surrogates')
  276. def _test_ignore_errors_pack(self, pack_func, unpack_func):
  277. ret = unpack_func(
  278. pack_func(u'abc\uDC80\uDCFFdef', use_bin_type=True, unicode_errors='ignore'), use_list=True, **raw
  279. )
  280. self.assertEqual(u'abcdef', ret)
  281. def _test_decode_binary(self, pack_func, unpack_func):
  282. ret = unpack_func(pack_func(b'abc'), use_list=True)
  283. self.assertEqual(b'abc', ret)
  284. @skipIf(salt.utils.msgpack.version < (0, 2, 2), 'use_single_float was added in msgpack==0.2.2')
  285. def _test_pack_float(self, pack_func, **kwargs):
  286. self.assertEqual(b'\xca' + struct.pack(str('>f'), 1.0), pack_func(1.0, use_single_float=True))
  287. self.assertEqual(b'\xcb' + struct.pack(str('>d'), 1.0), pack_func(1.0, use_single_float=False))
  288. def _test_odict(self, pack_func, unpack_func):
  289. seq = [(b'one', 1), (b'two', 2), (b'three', 3), (b'four', 4)]
  290. od = OrderedDict(seq)
  291. self.assertEqual(dict(seq), unpack_func(pack_func(od), use_list=True))
  292. def pair_hook(seq):
  293. return list(seq)
  294. self.assertEqual(seq, unpack_func(pack_func(od), object_pairs_hook=pair_hook, use_list=True))
  295. def _test_pair_list(self, unpack_func, **kwargs):
  296. pairlist = [(b'a', 1), (2, b'b'), (b'foo', b'bar')]
  297. packer = salt.utils.msgpack.Packer()
  298. packed = packer.pack_map_pairs(pairlist)
  299. if salt.utils.msgpack.version > (0, 6, 0):
  300. unpacked = unpack_func(packed, object_pairs_hook=list, strict_map_key=False)
  301. else:
  302. unpacked = unpack_func(packed, object_pairs_hook=list)
  303. self.assertEqual(pairlist, unpacked)
  304. @skipIf(salt.utils.msgpack.version < (0, 6, 0), 'getbuffer() was added to Packer in msgpack 0.6.0')
  305. def _test_get_buffer(self, pack_func, **kwargs):
  306. packer = msgpack.Packer(autoreset=False, use_bin_type=True)
  307. packer.pack([1, 2])
  308. strm = BytesIO()
  309. strm.write(packer.getbuffer())
  310. written = strm.getvalue()
  311. expected = pack_func([1, 2], use_bin_type=True)
  312. self.assertEqual(expected, written)
  313. @staticmethod
  314. def no_fail_run(test, *args, **kwargs):
  315. '''
  316. Run a test without failure and return any exception it raises
  317. '''
  318. try:
  319. test(*args, **kwargs)
  320. except Exception as e: # pylint: disable=broad-except
  321. return e
  322. def test_binary_function_compatibility(self):
  323. functions = [
  324. {'pack_func': salt.utils.msgpack.packb, 'unpack_func': msgpack.unpackb},
  325. {'pack_func': msgpack.packb, 'unpack_func': salt.utils.msgpack.unpackb},
  326. ]
  327. # These functions are equivalent but could potentially be overwritten
  328. if salt.utils.msgpack.dumps is not salt.utils.msgpack.packb:
  329. functions.append({'pack_func': salt.utils.msgpack.dumps, 'unpack_func': msgpack.unpackb})
  330. if salt.utils.msgpack.loads is not salt.utils.msgpack.unpackb:
  331. functions.append({'pack_func': msgpack.packb, 'unpack_func': salt.utils.msgpack.loads})
  332. test_funcs = (
  333. self._test_base,
  334. self._test_unpack_array_header_from_file,
  335. self._test_unpacker_hook_refcnt,
  336. self._test_unpacker_ext_hook,
  337. self._test_pack_unicode,
  338. self._test_pack_bytes,
  339. self._test_pack_byte_arrays,
  340. self._test_ignore_unicode_errors,
  341. self._test_strict_unicode_unpack,
  342. self._test_ignore_errors_pack,
  343. self._test_decode_binary,
  344. self._test_pack_float,
  345. self._test_odict,
  346. self._test_pair_list,
  347. self._test_get_buffer,
  348. )
  349. errors = {}
  350. for test_func in test_funcs:
  351. # Run the test without the salt.utils.msgpack module for comparison
  352. vanilla_run = self.no_fail_run(test_func, **{'pack_func': msgpack.packb, 'unpack_func': msgpack.unpackb})
  353. for func_args in functions:
  354. func_name = func_args['pack_func'] if func_args['pack_func'].__module__.startswith('salt.utils') \
  355. else func_args['unpack_func']
  356. if hasattr(TestCase, 'subTest'):
  357. with self.subTest(test=test_func.__name__, func=func_name.__name__):
  358. # Run the test with the salt.utils.msgpack module
  359. run = self.no_fail_run(test_func, **func_args)
  360. # If the vanilla msgpack module errored, then skip if we got the same error
  361. if run:
  362. if str(vanilla_run) == str(run):
  363. self.skipTest('Failed the same way as the vanilla msgpack module:\n{}'.format(run))
  364. else:
  365. # If subTest isn't available then run the tests collect the errors of all the tests before failing
  366. run = self.no_fail_run(test_func, **func_args)
  367. if run:
  368. # If the vanilla msgpack module errored, then skip if we got the same error
  369. if str(vanilla_run) == str(run):
  370. self.skipTest('Test failed the same way the vanilla msgpack module fails:\n{}'.format(run))
  371. else:
  372. errors[(test_func.__name__, func_name.__name__)] = run
  373. if errors:
  374. self.fail(pprint.pformat(errors))