test_msgpack.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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_sanitize_msgpack_kwargs(self):
  168. """
  169. Test helper function _sanitize_msgpack_kwargs
  170. """
  171. version = salt.utils.msgpack.version
  172. kwargs = {"strict_map_key": True, "raw": True, "use_bin_type": True}
  173. salt.utils.msgpack.version = (0, 6, 0)
  174. self.assertEqual(
  175. salt.utils.msgpack._sanitize_msgpack_kwargs(kwargs),
  176. {"raw": True, "strict_map_key": True, "use_bin_type": True},
  177. )
  178. kwargs = {"strict_map_key": True, "raw": True, "use_bin_type": True}
  179. salt.utils.msgpack.version = (0, 5, 2)
  180. self.assertEqual(
  181. salt.utils.msgpack._sanitize_msgpack_kwargs(kwargs),
  182. {"raw": True, "use_bin_type": True},
  183. )
  184. kwargs = {"strict_map_key": True, "raw": True, "use_bin_type": True}
  185. salt.utils.msgpack.version = (0, 4, 0)
  186. self.assertEqual(
  187. salt.utils.msgpack._sanitize_msgpack_kwargs(kwargs), {"use_bin_type": True}
  188. )
  189. kwargs = {"strict_map_key": True, "raw": True, "use_bin_type": True}
  190. salt.utils.msgpack.version = (0, 3, 0)
  191. self.assertEqual(salt.utils.msgpack._sanitize_msgpack_kwargs(kwargs), {})
  192. salt.utils.msgpack.version = version
  193. def test_sanitize_msgpack_unpack_kwargs(self):
  194. """
  195. Test helper function _sanitize_msgpack_unpack_kwargs
  196. """
  197. version = salt.utils.msgpack.version
  198. kwargs = {"strict_map_key": True, "use_bin_type": True, "encoding": "utf-8"}
  199. salt.utils.msgpack.version = (1, 0, 0)
  200. self.assertEqual(
  201. salt.utils.msgpack._sanitize_msgpack_unpack_kwargs(kwargs.copy()),
  202. {"raw": True, "strict_map_key": True, "use_bin_type": True},
  203. )
  204. salt.utils.msgpack.version = (0, 6, 0)
  205. self.assertEqual(
  206. salt.utils.msgpack._sanitize_msgpack_unpack_kwargs(kwargs.copy()),
  207. {"strict_map_key": True, "use_bin_type": True, "encoding": "utf-8"},
  208. )
  209. salt.utils.msgpack.version = (0, 5, 2)
  210. self.assertEqual(
  211. salt.utils.msgpack._sanitize_msgpack_unpack_kwargs(kwargs.copy()),
  212. {"use_bin_type": True, "encoding": "utf-8"},
  213. )
  214. salt.utils.msgpack.version = (0, 4, 0)
  215. self.assertEqual(
  216. salt.utils.msgpack._sanitize_msgpack_unpack_kwargs(kwargs.copy()),
  217. {"use_bin_type": True, "encoding": "utf-8"},
  218. )
  219. kwargs = {"strict_map_key": True, "use_bin_type": True}
  220. salt.utils.msgpack.version = (0, 3, 0)
  221. self.assertEqual(
  222. salt.utils.msgpack._sanitize_msgpack_unpack_kwargs(kwargs.copy()), {}
  223. )
  224. salt.utils.msgpack.version = version
  225. def _test_base(self, pack_func, unpack_func):
  226. """
  227. In msgpack, 'dumps' is an alias for 'packb' and 'loads' is an alias for 'unpackb'.
  228. Verify that both salt.utils.msgpack function variations pass the exact same test
  229. """
  230. data = os.urandom(1024)
  231. packed = pack_func(data)
  232. # Sanity Check
  233. self.assertTrue(packed)
  234. self.assertIsInstance(packed, bytes)
  235. self.assertNotEqual(data, packed)
  236. # Reverse the packing and the result should be equivalent to the original data
  237. unpacked = unpack_func(packed)
  238. self.assertEqual(data, unpacked)
  239. def _test_buffered_base(self, pack_func, unpack_func):
  240. data = os.urandom(1024).decode(errors="ignore")
  241. buffer = BytesIO()
  242. # Sanity check, we are not borking the BytesIO read function
  243. self.assertNotEqual(BytesIO.read, buffer.read)
  244. buffer.read = buffer.getvalue
  245. pack_func(data, buffer)
  246. # Sanity Check
  247. self.assertTrue(buffer.getvalue())
  248. self.assertIsInstance(buffer.getvalue(), bytes)
  249. self.assertNotEqual(data, buffer.getvalue())
  250. # Reverse the packing and the result should be equivalent to the original data
  251. unpacked = unpack_func(buffer)
  252. if isinstance(unpacked, bytes):
  253. unpacked = unpacked.decode()
  254. self.assertEqual(data, unpacked)
  255. def test_buffered_base_pack(self):
  256. self._test_buffered_base(
  257. pack_func=salt.utils.msgpack.pack, unpack_func=msgpack.unpack
  258. )
  259. def test_buffered_base_unpack(self):
  260. self._test_buffered_base(
  261. pack_func=msgpack.pack, unpack_func=salt.utils.msgpack.unpack
  262. )
  263. def _test_unpack_array_header_from_file(self, pack_func, **kwargs):
  264. f = BytesIO(pack_func([1, 2, 3, 4]))
  265. unpacker = salt.utils.msgpack.Unpacker(f)
  266. self.assertEqual(unpacker.read_array_header(), 4)
  267. self.assertEqual(unpacker.unpack(), 1)
  268. self.assertEqual(unpacker.unpack(), 2)
  269. self.assertEqual(unpacker.unpack(), 3)
  270. self.assertEqual(unpacker.unpack(), 4)
  271. self.assertRaises(salt.utils.msgpack.exceptions.OutOfData, unpacker.unpack)
  272. @skipIf(
  273. not hasattr(sys, "getrefcount"), "sys.getrefcount() is needed to pass this test"
  274. )
  275. def _test_unpacker_hook_refcnt(self, pack_func, **kwargs):
  276. result = []
  277. def hook(x):
  278. result.append(x)
  279. return x
  280. basecnt = sys.getrefcount(hook)
  281. up = salt.utils.msgpack.Unpacker(object_hook=hook, list_hook=hook)
  282. self.assertGreaterEqual(sys.getrefcount(hook), basecnt + 2)
  283. up.feed(pack_func([{}]))
  284. up.feed(pack_func([{}]))
  285. self.assertEqual(up.unpack(), [{}])
  286. self.assertEqual(up.unpack(), [{}])
  287. self.assertEqual(result, [{}, [{}], {}, [{}]])
  288. del up
  289. self.assertEqual(sys.getrefcount(hook), basecnt)
  290. def _test_unpacker_ext_hook(self, pack_func, **kwargs):
  291. class MyUnpacker(salt.utils.msgpack.Unpacker):
  292. def __init__(self):
  293. my_kwargs = {}
  294. super(MyUnpacker, self).__init__(ext_hook=self._hook, **raw)
  295. def _hook(self, code, data):
  296. if code == 1:
  297. return int(data)
  298. else:
  299. return salt.utils.msgpack.ExtType(code, data)
  300. unpacker = MyUnpacker()
  301. unpacker.feed(pack_func({"a": 1}))
  302. self.assertEqual(unpacker.unpack(), {"a": 1})
  303. unpacker.feed(pack_func({"a": salt.utils.msgpack.ExtType(1, b"123")}))
  304. self.assertEqual(unpacker.unpack(), {"a": 123})
  305. unpacker.feed(pack_func({"a": salt.utils.msgpack.ExtType(2, b"321")}))
  306. self.assertEqual(
  307. unpacker.unpack(), {"a": salt.utils.msgpack.ExtType(2, b"321")}
  308. )
  309. def _check(
  310. self, data, pack_func, unpack_func, use_list=False, strict_map_key=False
  311. ):
  312. my_kwargs = {}
  313. if salt.utils.msgpack.version >= (0, 6, 0):
  314. my_kwargs["strict_map_key"] = strict_map_key
  315. ret = unpack_func(pack_func(data), use_list=use_list, **my_kwargs)
  316. self.assertEqual(ret, data)
  317. def _test_pack_unicode(self, pack_func, unpack_func):
  318. test_data = [u"", u"abcd", [u"defgh"], u"Русский текст"]
  319. for td in test_data:
  320. ret = unpack_func(pack_func(td), use_list=True, **raw)
  321. self.assertEqual(ret, td)
  322. packer = salt.utils.msgpack.Packer()
  323. data = packer.pack(td)
  324. ret = salt.utils.msgpack.Unpacker(
  325. BytesIO(data), use_list=True, **raw
  326. ).unpack()
  327. self.assertEqual(ret, td)
  328. def _test_pack_bytes(self, pack_func, unpack_func):
  329. test_data = [
  330. b"",
  331. b"abcd",
  332. (b"defgh",),
  333. ]
  334. for td in test_data:
  335. self._check(td, pack_func, unpack_func)
  336. def _test_pack_byte_arrays(self, pack_func, unpack_func):
  337. test_data = [
  338. bytearray(b""),
  339. bytearray(b"abcd"),
  340. (bytearray(b"defgh"),),
  341. ]
  342. for td in test_data:
  343. self._check(td, pack_func, unpack_func)
  344. @skipIf(sys.version_info < (3, 0), "Python 2 passes invalid surrogates")
  345. def _test_ignore_unicode_errors(self, pack_func, unpack_func):
  346. ret = unpack_func(
  347. pack_func(b"abc\xeddef", use_bin_type=False), unicode_errors="ignore", **raw
  348. )
  349. self.assertEqual(u"abcdef", ret)
  350. def _test_strict_unicode_unpack(self, pack_func, unpack_func):
  351. packed = pack_func(b"abc\xeddef", use_bin_type=False)
  352. self.assertRaises(UnicodeDecodeError, unpack_func, packed, use_list=True, **raw)
  353. @skipIf(sys.version_info < (3, 0), "Python 2 passes invalid surrogates")
  354. def _test_ignore_errors_pack(self, pack_func, unpack_func):
  355. ret = unpack_func(
  356. pack_func(
  357. u"abc\uDC80\uDCFFdef", use_bin_type=True, unicode_errors="ignore"
  358. ),
  359. use_list=True,
  360. **raw
  361. )
  362. self.assertEqual(u"abcdef", ret)
  363. def _test_decode_binary(self, pack_func, unpack_func):
  364. ret = unpack_func(pack_func(b"abc"), use_list=True)
  365. self.assertEqual(b"abc", ret)
  366. @skipIf(
  367. salt.utils.msgpack.version < (0, 2, 2),
  368. "use_single_float was added in msgpack==0.2.2",
  369. )
  370. def _test_pack_float(self, pack_func, **kwargs):
  371. self.assertEqual(
  372. b"\xca" + struct.pack(str(">f"), 1.0), pack_func(1.0, use_single_float=True)
  373. )
  374. self.assertEqual(
  375. b"\xcb" + struct.pack(str(">d"), 1.0),
  376. pack_func(1.0, use_single_float=False),
  377. )
  378. def _test_odict(self, pack_func, unpack_func):
  379. seq = [(b"one", 1), (b"two", 2), (b"three", 3), (b"four", 4)]
  380. od = OrderedDict(seq)
  381. self.assertEqual(dict(seq), unpack_func(pack_func(od), use_list=True))
  382. def pair_hook(seq):
  383. return list(seq)
  384. self.assertEqual(
  385. seq, unpack_func(pack_func(od), object_pairs_hook=pair_hook, use_list=True)
  386. )
  387. def _test_pair_list(self, unpack_func, **kwargs):
  388. pairlist = [(b"a", 1), (2, b"b"), (b"foo", b"bar")]
  389. packer = salt.utils.msgpack.Packer()
  390. packed = packer.pack_map_pairs(pairlist)
  391. if salt.utils.msgpack.version > (0, 6, 0):
  392. unpacked = unpack_func(packed, object_pairs_hook=list, strict_map_key=False)
  393. else:
  394. unpacked = unpack_func(packed, object_pairs_hook=list)
  395. self.assertEqual(pairlist, unpacked)
  396. @skipIf(
  397. salt.utils.msgpack.version < (0, 6, 0),
  398. "getbuffer() was added to Packer in msgpack 0.6.0",
  399. )
  400. def _test_get_buffer(self, pack_func, **kwargs):
  401. packer = msgpack.Packer(autoreset=False, use_bin_type=True)
  402. packer.pack([1, 2])
  403. strm = BytesIO()
  404. strm.write(packer.getbuffer())
  405. written = strm.getvalue()
  406. expected = pack_func([1, 2], use_bin_type=True)
  407. self.assertEqual(expected, written)
  408. @staticmethod
  409. def no_fail_run(test, *args, **kwargs):
  410. """
  411. Run a test without failure and return any exception it raises
  412. """
  413. try:
  414. test(*args, **kwargs)
  415. except Exception as e: # pylint: disable=broad-except
  416. return e
  417. def test_binary_function_compatibility(self):
  418. functions = [
  419. {"pack_func": salt.utils.msgpack.packb, "unpack_func": msgpack.unpackb},
  420. {"pack_func": msgpack.packb, "unpack_func": salt.utils.msgpack.unpackb},
  421. ]
  422. # These functions are equivalent but could potentially be overwritten
  423. if salt.utils.msgpack.dumps is not salt.utils.msgpack.packb:
  424. functions.append(
  425. {"pack_func": salt.utils.msgpack.dumps, "unpack_func": msgpack.unpackb}
  426. )
  427. if salt.utils.msgpack.loads is not salt.utils.msgpack.unpackb:
  428. functions.append(
  429. {"pack_func": msgpack.packb, "unpack_func": salt.utils.msgpack.loads}
  430. )
  431. test_funcs = (
  432. self._test_base,
  433. self._test_unpack_array_header_from_file,
  434. self._test_unpacker_hook_refcnt,
  435. self._test_unpacker_ext_hook,
  436. self._test_pack_unicode,
  437. self._test_pack_bytes,
  438. self._test_pack_byte_arrays,
  439. self._test_ignore_unicode_errors,
  440. self._test_strict_unicode_unpack,
  441. self._test_ignore_errors_pack,
  442. self._test_decode_binary,
  443. self._test_pack_float,
  444. self._test_odict,
  445. self._test_pair_list,
  446. self._test_get_buffer,
  447. )
  448. errors = {}
  449. for test_func in test_funcs:
  450. # Run the test without the salt.utils.msgpack module for comparison
  451. vanilla_run = self.no_fail_run(
  452. test_func,
  453. **{"pack_func": msgpack.packb, "unpack_func": msgpack.unpackb}
  454. )
  455. for func_args in functions:
  456. func_name = (
  457. func_args["pack_func"]
  458. if func_args["pack_func"].__module__.startswith("salt.utils")
  459. else func_args["unpack_func"]
  460. )
  461. if hasattr(TestCase, "subTest"):
  462. with self.subTest(test=test_func.__name__, func=func_name.__name__):
  463. # Run the test with the salt.utils.msgpack module
  464. run = self.no_fail_run(test_func, **func_args)
  465. # If the vanilla msgpack module errored, then skip if we got the same error
  466. if run:
  467. if str(vanilla_run) == str(run):
  468. self.skipTest(
  469. "Failed the same way as the vanilla msgpack module:\n{}".format(
  470. run
  471. )
  472. )
  473. else:
  474. # If subTest isn't available then run the tests collect the errors of all the tests before failing
  475. run = self.no_fail_run(test_func, **func_args)
  476. if run:
  477. # If the vanilla msgpack module errored, then skip if we got the same error
  478. if str(vanilla_run) == str(run):
  479. self.skipTest(
  480. "Test failed the same way the vanilla msgpack module fails:\n{}".format(
  481. run
  482. )
  483. )
  484. else:
  485. errors[(test_func.__name__, func_name.__name__)] = run
  486. if errors:
  487. self.fail(pprint.pformat(errors))