test_msgpack.py 17 KB

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