test_data.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Tests for salt.utils.data
  4. '''
  5. # Import Python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import logging
  8. # Import Salt libs
  9. import salt.utils.data
  10. import salt.utils.stringutils
  11. from salt.utils.odict import OrderedDict
  12. from tests.support.unit import TestCase, LOREM_IPSUM
  13. from tests.support.mock import patch
  14. from salt.ext.six.moves import builtins # pylint: disable=import-error,redefined-builtin
  15. from salt.ext import six
  16. log = logging.getLogger(__name__)
  17. _b = lambda x: x.encode('utf-8')
  18. _s = lambda x: salt.utils.stringutils.to_str(x, normalize=True)
  19. # Some randomized data that will not decode
  20. BYTES = b'1\x814\x10'
  21. # This is an example of a unicode string with й constructed using two separate
  22. # code points. Do not modify it.
  23. EGGS = '\u044f\u0438\u0306\u0446\u0430'
  24. class DataTestCase(TestCase):
  25. test_data = [
  26. 'unicode_str',
  27. _b('питон'),
  28. 123,
  29. 456.789,
  30. True,
  31. False,
  32. None,
  33. EGGS,
  34. BYTES,
  35. [123, 456.789, _b('спам'), True, False, None, EGGS, BYTES],
  36. (987, 654.321, _b('яйца'), EGGS, None, (True, EGGS, BYTES)),
  37. {_b('str_key'): _b('str_val'),
  38. None: True,
  39. 123: 456.789,
  40. EGGS: BYTES,
  41. _b('subdict'): {'unicode_key': EGGS,
  42. _b('tuple'): (123, 'hello', _b('world'), True, EGGS, BYTES),
  43. _b('list'): [456, _b('спам'), False, EGGS, BYTES]}},
  44. OrderedDict([(_b('foo'), 'bar'), (123, 456), (EGGS, BYTES)])
  45. ]
  46. def test_sorted_ignorecase(self):
  47. test_list = ['foo', 'Foo', 'bar', 'Bar']
  48. expected_list = ['bar', 'Bar', 'foo', 'Foo']
  49. self.assertEqual(
  50. salt.utils.data.sorted_ignorecase(test_list), expected_list)
  51. def test_mysql_to_dict(self):
  52. test_mysql_output = ['+----+------+-----------+------+---------+------+-------+------------------+',
  53. '| Id | User | Host | db | Command | Time | State | Info |',
  54. '+----+------+-----------+------+---------+------+-------+------------------+',
  55. '| 7 | root | localhost | NULL | Query | 0 | init | show processlist |',
  56. '+----+------+-----------+------+---------+------+-------+------------------+']
  57. ret = salt.utils.data.mysql_to_dict(test_mysql_output, 'Info')
  58. expected_dict = {
  59. 'show processlist': {'Info': 'show processlist', 'db': 'NULL', 'State': 'init', 'Host': 'localhost',
  60. 'Command': 'Query', 'User': 'root', 'Time': 0, 'Id': 7}}
  61. self.assertDictEqual(ret, expected_dict)
  62. def test_subdict_match(self):
  63. test_two_level_dict = {'foo': {'bar': 'baz'}}
  64. test_two_level_comb_dict = {'foo': {'bar': 'baz:woz'}}
  65. test_two_level_dict_and_list = {
  66. 'abc': ['def', 'ghi', {'lorem': {'ipsum': [{'dolor': 'sit'}]}}],
  67. }
  68. test_three_level_dict = {'a': {'b': {'c': 'v'}}}
  69. self.assertTrue(
  70. salt.utils.data.subdict_match(
  71. test_two_level_dict, 'foo:bar:baz'
  72. )
  73. )
  74. # In test_two_level_comb_dict, 'foo:bar' corresponds to 'baz:woz', not
  75. # 'baz'. This match should return False.
  76. self.assertFalse(
  77. salt.utils.data.subdict_match(
  78. test_two_level_comb_dict, 'foo:bar:baz'
  79. )
  80. )
  81. # This tests matching with the delimiter in the value part (in other
  82. # words, that the path 'foo:bar' corresponds to the string 'baz:woz').
  83. self.assertTrue(
  84. salt.utils.data.subdict_match(
  85. test_two_level_comb_dict, 'foo:bar:baz:woz'
  86. )
  87. )
  88. # This would match if test_two_level_comb_dict['foo']['bar'] was equal
  89. # to 'baz:woz:wiz', or if there was more deep nesting. But it does not,
  90. # so this should return False.
  91. self.assertFalse(
  92. salt.utils.data.subdict_match(
  93. test_two_level_comb_dict, 'foo:bar:baz:woz:wiz'
  94. )
  95. )
  96. # This tests for cases when a key path corresponds to a list. The
  97. # value part 'ghi' should be successfully matched as it is a member of
  98. # the list corresponding to key path 'abc'. It is somewhat a
  99. # duplication of a test within test_traverse_dict_and_list, but
  100. # salt.utils.data.subdict_match() does more than just invoke
  101. # salt.utils.traverse_list_and_dict() so this particular assertion is a
  102. # sanity check.
  103. self.assertTrue(
  104. salt.utils.data.subdict_match(
  105. test_two_level_dict_and_list, 'abc:ghi'
  106. )
  107. )
  108. # This tests the use case of a dict embedded in a list, embedded in a
  109. # list, embedded in a dict. This is a rather absurd case, but it
  110. # confirms that match recursion works properly.
  111. self.assertTrue(
  112. salt.utils.data.subdict_match(
  113. test_two_level_dict_and_list, 'abc:lorem:ipsum:dolor:sit'
  114. )
  115. )
  116. # Test four level dict match for reference
  117. self.assertTrue(
  118. salt.utils.data.subdict_match(
  119. test_three_level_dict, 'a:b:c:v'
  120. )
  121. )
  122. # Test regression in 2015.8 where 'a:c:v' would match 'a:b:c:v'
  123. self.assertFalse(
  124. salt.utils.data.subdict_match(
  125. test_three_level_dict, 'a:c:v'
  126. )
  127. )
  128. # Test wildcard match
  129. self.assertTrue(
  130. salt.utils.data.subdict_match(
  131. test_three_level_dict, 'a:*:c:v'
  132. )
  133. )
  134. def test_subdict_match_with_wildcards(self):
  135. '''
  136. Tests subdict matching when wildcards are used in the expression
  137. '''
  138. data = {
  139. 'a': {
  140. 'b': {
  141. 'ç': 'd',
  142. 'é': ['eff', 'gee', '8ch'],
  143. 'ĩ': {'j': 'k'}
  144. }
  145. }
  146. }
  147. assert salt.utils.data.subdict_match(data, '*:*:*:*')
  148. assert salt.utils.data.subdict_match(data, 'a:*:*:*')
  149. assert salt.utils.data.subdict_match(data, 'a:b:*:*')
  150. assert salt.utils.data.subdict_match(data, 'a:b:ç:*')
  151. assert salt.utils.data.subdict_match(data, 'a:b:*:d')
  152. assert salt.utils.data.subdict_match(data, 'a:*:ç:d')
  153. assert salt.utils.data.subdict_match(data, '*:b:ç:d')
  154. assert salt.utils.data.subdict_match(data, '*:*:ç:d')
  155. assert salt.utils.data.subdict_match(data, '*:*:*:d')
  156. assert salt.utils.data.subdict_match(data, 'a:*:*:d')
  157. assert salt.utils.data.subdict_match(data, 'a:b:*:ef*')
  158. assert salt.utils.data.subdict_match(data, 'a:b:*:g*')
  159. assert salt.utils.data.subdict_match(data, 'a:b:*:j:*')
  160. assert salt.utils.data.subdict_match(data, 'a:b:*:j:k')
  161. assert salt.utils.data.subdict_match(data, 'a:b:*:*:k')
  162. assert salt.utils.data.subdict_match(data, 'a:b:*:*:*')
  163. def test_traverse_dict(self):
  164. test_two_level_dict = {'foo': {'bar': 'baz'}}
  165. self.assertDictEqual(
  166. {'not_found': 'nope'},
  167. salt.utils.data.traverse_dict(
  168. test_two_level_dict, 'foo:bar:baz', {'not_found': 'nope'}
  169. )
  170. )
  171. self.assertEqual(
  172. 'baz',
  173. salt.utils.data.traverse_dict(
  174. test_two_level_dict, 'foo:bar', {'not_found': 'not_found'}
  175. )
  176. )
  177. def test_traverse_dict_and_list(self):
  178. test_two_level_dict = {'foo': {'bar': 'baz'}}
  179. test_two_level_dict_and_list = {
  180. 'foo': ['bar', 'baz', {'lorem': {'ipsum': [{'dolor': 'sit'}]}}]
  181. }
  182. # Check traversing too far: salt.utils.data.traverse_dict_and_list() returns
  183. # the value corresponding to a given key path, and baz is a value
  184. # corresponding to the key path foo:bar.
  185. self.assertDictEqual(
  186. {'not_found': 'nope'},
  187. salt.utils.data.traverse_dict_and_list(
  188. test_two_level_dict, 'foo:bar:baz', {'not_found': 'nope'}
  189. )
  190. )
  191. # Now check to ensure that foo:bar corresponds to baz
  192. self.assertEqual(
  193. 'baz',
  194. salt.utils.data.traverse_dict_and_list(
  195. test_two_level_dict, 'foo:bar', {'not_found': 'not_found'}
  196. )
  197. )
  198. # Check traversing too far
  199. self.assertDictEqual(
  200. {'not_found': 'nope'},
  201. salt.utils.data.traverse_dict_and_list(
  202. test_two_level_dict_and_list, 'foo:bar', {'not_found': 'nope'}
  203. )
  204. )
  205. # Check index 1 (2nd element) of list corresponding to path 'foo'
  206. self.assertEqual(
  207. 'baz',
  208. salt.utils.data.traverse_dict_and_list(
  209. test_two_level_dict_and_list, 'foo:1', {'not_found': 'not_found'}
  210. )
  211. )
  212. # Traverse a couple times into dicts embedded in lists
  213. self.assertEqual(
  214. 'sit',
  215. salt.utils.data.traverse_dict_and_list(
  216. test_two_level_dict_and_list,
  217. 'foo:lorem:ipsum:dolor',
  218. {'not_found': 'not_found'}
  219. )
  220. )
  221. def test_compare_dicts(self):
  222. ret = salt.utils.data.compare_dicts(old={'foo': 'bar'}, new={'foo': 'bar'})
  223. self.assertEqual(ret, {})
  224. ret = salt.utils.data.compare_dicts(old={'foo': 'bar'}, new={'foo': 'woz'})
  225. expected_ret = {'foo': {'new': 'woz', 'old': 'bar'}}
  226. self.assertDictEqual(ret, expected_ret)
  227. def test_compare_lists_no_change(self):
  228. ret = salt.utils.data.compare_lists(old=[1, 2, 3, 'a', 'b', 'c'],
  229. new=[1, 2, 3, 'a', 'b', 'c'])
  230. expected = {}
  231. self.assertDictEqual(ret, expected)
  232. def test_compare_lists_changes(self):
  233. ret = salt.utils.data.compare_lists(old=[1, 2, 3, 'a', 'b', 'c'],
  234. new=[1, 2, 4, 'x', 'y', 'z'])
  235. expected = {'new': [4, 'x', 'y', 'z'], 'old': [3, 'a', 'b', 'c']}
  236. self.assertDictEqual(ret, expected)
  237. def test_compare_lists_changes_new(self):
  238. ret = salt.utils.data.compare_lists(old=[1, 2, 3],
  239. new=[1, 2, 3, 'x', 'y', 'z'])
  240. expected = {'new': ['x', 'y', 'z']}
  241. self.assertDictEqual(ret, expected)
  242. def test_compare_lists_changes_old(self):
  243. ret = salt.utils.data.compare_lists(old=[1, 2, 3, 'a', 'b', 'c'],
  244. new=[1, 2, 3])
  245. expected = {'old': ['a', 'b', 'c']}
  246. self.assertDictEqual(ret, expected)
  247. def test_decode(self):
  248. '''
  249. Companion to test_decode_to_str, they should both be kept up-to-date
  250. with one another.
  251. NOTE: This uses the lambda "_b" defined above in the global scope,
  252. which encodes a string to a bytestring, assuming utf-8.
  253. '''
  254. expected = [
  255. 'unicode_str',
  256. 'питон',
  257. 123,
  258. 456.789,
  259. True,
  260. False,
  261. None,
  262. 'яйца',
  263. BYTES,
  264. [123, 456.789, 'спам', True, False, None, 'яйца', BYTES],
  265. (987, 654.321, 'яйца', 'яйца', None, (True, 'яйца', BYTES)),
  266. {'str_key': 'str_val',
  267. None: True,
  268. 123: 456.789,
  269. 'яйца': BYTES,
  270. 'subdict': {'unicode_key': 'яйца',
  271. 'tuple': (123, 'hello', 'world', True, 'яйца', BYTES),
  272. 'list': [456, 'спам', False, 'яйца', BYTES]}},
  273. OrderedDict([('foo', 'bar'), (123, 456), ('яйца', BYTES)])
  274. ]
  275. ret = salt.utils.data.decode(
  276. self.test_data,
  277. keep=True,
  278. normalize=True,
  279. preserve_dict_class=True,
  280. preserve_tuples=True)
  281. self.assertEqual(ret, expected)
  282. # The binary data in the data structure should fail to decode, even
  283. # using the fallback, and raise an exception.
  284. self.assertRaises(
  285. UnicodeDecodeError,
  286. salt.utils.data.decode,
  287. self.test_data,
  288. keep=False,
  289. normalize=True,
  290. preserve_dict_class=True,
  291. preserve_tuples=True)
  292. # Now munge the expected data so that we get what we would expect if we
  293. # disable preservation of dict class and tuples
  294. expected[10] = [987, 654.321, 'яйца', 'яйца', None, [True, 'яйца', BYTES]]
  295. expected[11]['subdict']['tuple'] = [123, 'hello', 'world', True, 'яйца', BYTES]
  296. expected[12] = {'foo': 'bar', 123: 456, 'яйца': BYTES}
  297. ret = salt.utils.data.decode(
  298. self.test_data,
  299. keep=True,
  300. normalize=True,
  301. preserve_dict_class=False,
  302. preserve_tuples=False)
  303. self.assertEqual(ret, expected)
  304. # Now test single non-string, non-data-structure items, these should
  305. # return the same value when passed to this function
  306. for item in (123, 4.56, True, False, None):
  307. log.debug('Testing decode of %s', item)
  308. self.assertEqual(salt.utils.data.decode(item), item)
  309. # Test single strings (not in a data structure)
  310. self.assertEqual(salt.utils.data.decode('foo'), 'foo')
  311. self.assertEqual(salt.utils.data.decode(_b('bar')), 'bar')
  312. self.assertEqual(salt.utils.data.decode(EGGS, normalize=True), 'яйца')
  313. self.assertEqual(salt.utils.data.decode(EGGS, normalize=False), EGGS)
  314. # Test binary blob
  315. self.assertEqual(salt.utils.data.decode(BYTES, keep=True), BYTES)
  316. self.assertRaises(
  317. UnicodeDecodeError,
  318. salt.utils.data.decode,
  319. BYTES,
  320. keep=False)
  321. def test_decode_to_str(self):
  322. '''
  323. Companion to test_decode, they should both be kept up-to-date with one
  324. another.
  325. NOTE: This uses the lambda "_s" defined above in the global scope,
  326. which converts the string/bytestring to a str type.
  327. '''
  328. expected = [
  329. _s('unicode_str'),
  330. _s('питон'),
  331. 123,
  332. 456.789,
  333. True,
  334. False,
  335. None,
  336. _s('яйца'),
  337. BYTES,
  338. [123, 456.789, _s('спам'), True, False, None, _s('яйца'), BYTES],
  339. (987, 654.321, _s('яйца'), _s('яйца'), None, (True, _s('яйца'), BYTES)),
  340. {
  341. _s('str_key'): _s('str_val'),
  342. None: True,
  343. 123: 456.789,
  344. _s('яйца'): BYTES,
  345. _s('subdict'): {
  346. _s('unicode_key'): _s('яйца'),
  347. _s('tuple'): (123, _s('hello'), _s('world'), True, _s('яйца'), BYTES),
  348. _s('list'): [456, _s('спам'), False, _s('яйца'), BYTES]
  349. }
  350. },
  351. OrderedDict([(_s('foo'), _s('bar')), (123, 456), (_s('яйца'), BYTES)])
  352. ]
  353. ret = salt.utils.data.decode(
  354. self.test_data,
  355. keep=True,
  356. normalize=True,
  357. preserve_dict_class=True,
  358. preserve_tuples=True,
  359. to_str=True)
  360. self.assertEqual(ret, expected)
  361. if six.PY3:
  362. # The binary data in the data structure should fail to decode, even
  363. # using the fallback, and raise an exception.
  364. self.assertRaises(
  365. UnicodeDecodeError,
  366. salt.utils.data.decode,
  367. self.test_data,
  368. keep=False,
  369. normalize=True,
  370. preserve_dict_class=True,
  371. preserve_tuples=True,
  372. to_str=True)
  373. # Now munge the expected data so that we get what we would expect if we
  374. # disable preservation of dict class and tuples
  375. expected[10] = [987, 654.321, _s('яйца'), _s('яйца'), None, [True, _s('яйца'), BYTES]]
  376. expected[11][_s('subdict')][_s('tuple')] = [123, _s('hello'), _s('world'), True, _s('яйца'), BYTES]
  377. expected[12] = {_s('foo'): _s('bar'), 123: 456, _s('яйца'): BYTES}
  378. ret = salt.utils.data.decode(
  379. self.test_data,
  380. keep=True,
  381. normalize=True,
  382. preserve_dict_class=False,
  383. preserve_tuples=False,
  384. to_str=True)
  385. self.assertEqual(ret, expected)
  386. # Now test single non-string, non-data-structure items, these should
  387. # return the same value when passed to this function
  388. for item in (123, 4.56, True, False, None):
  389. log.debug('Testing decode of %s', item)
  390. self.assertEqual(salt.utils.data.decode(item, to_str=True), item)
  391. # Test single strings (not in a data structure)
  392. self.assertEqual(salt.utils.data.decode('foo', to_str=True), _s('foo'))
  393. self.assertEqual(salt.utils.data.decode(_b('bar'), to_str=True), _s('bar'))
  394. # Test binary blob
  395. self.assertEqual(
  396. salt.utils.data.decode(BYTES, keep=True, to_str=True),
  397. BYTES
  398. )
  399. if six.PY3:
  400. self.assertRaises(
  401. UnicodeDecodeError,
  402. salt.utils.data.decode,
  403. BYTES,
  404. keep=False,
  405. to_str=True)
  406. def test_decode_fallback(self):
  407. '''
  408. Test fallback to utf-8
  409. '''
  410. with patch.object(builtins, '__salt_system_encoding__', 'ascii'):
  411. self.assertEqual(salt.utils.data.decode(_b('яйца')), 'яйца')
  412. def test_encode(self):
  413. '''
  414. NOTE: This uses the lambda "_b" defined above in the global scope,
  415. which encodes a string to a bytestring, assuming utf-8.
  416. '''
  417. expected = [
  418. _b('unicode_str'),
  419. _b('питон'),
  420. 123,
  421. 456.789,
  422. True,
  423. False,
  424. None,
  425. _b(EGGS),
  426. BYTES,
  427. [123, 456.789, _b('спам'), True, False, None, _b(EGGS), BYTES],
  428. (987, 654.321, _b('яйца'), _b(EGGS), None, (True, _b(EGGS), BYTES)),
  429. {
  430. _b('str_key'): _b('str_val'),
  431. None: True,
  432. 123: 456.789,
  433. _b(EGGS): BYTES,
  434. _b('subdict'): {
  435. _b('unicode_key'): _b(EGGS),
  436. _b('tuple'): (123, _b('hello'), _b('world'), True, _b(EGGS), BYTES),
  437. _b('list'): [456, _b('спам'), False, _b(EGGS), BYTES]
  438. }
  439. },
  440. OrderedDict([(_b('foo'), _b('bar')), (123, 456), (_b(EGGS), BYTES)])
  441. ]
  442. # Both keep=True and keep=False should work because the BYTES data is
  443. # already bytes.
  444. ret = salt.utils.data.encode(
  445. self.test_data,
  446. keep=True,
  447. preserve_dict_class=True,
  448. preserve_tuples=True)
  449. self.assertEqual(ret, expected)
  450. ret = salt.utils.data.encode(
  451. self.test_data,
  452. keep=False,
  453. preserve_dict_class=True,
  454. preserve_tuples=True)
  455. self.assertEqual(ret, expected)
  456. # Now munge the expected data so that we get what we would expect if we
  457. # disable preservation of dict class and tuples
  458. expected[10] = [987, 654.321, _b('яйца'), _b(EGGS), None, [True, _b(EGGS), BYTES]]
  459. expected[11][_b('subdict')][_b('tuple')] = [
  460. 123, _b('hello'), _b('world'), True, _b(EGGS), BYTES
  461. ]
  462. expected[12] = {_b('foo'): _b('bar'), 123: 456, _b(EGGS): BYTES}
  463. ret = salt.utils.data.encode(
  464. self.test_data,
  465. keep=True,
  466. preserve_dict_class=False,
  467. preserve_tuples=False)
  468. self.assertEqual(ret, expected)
  469. ret = salt.utils.data.encode(
  470. self.test_data,
  471. keep=False,
  472. preserve_dict_class=False,
  473. preserve_tuples=False)
  474. self.assertEqual(ret, expected)
  475. # Now test single non-string, non-data-structure items, these should
  476. # return the same value when passed to this function
  477. for item in (123, 4.56, True, False, None):
  478. log.debug('Testing encode of %s', item)
  479. self.assertEqual(salt.utils.data.encode(item), item)
  480. # Test single strings (not in a data structure)
  481. self.assertEqual(salt.utils.data.encode('foo'), _b('foo'))
  482. self.assertEqual(salt.utils.data.encode(_b('bar')), _b('bar'))
  483. # Test binary blob, nothing should happen even when keep=False since
  484. # the data is already bytes
  485. self.assertEqual(salt.utils.data.encode(BYTES, keep=True), BYTES)
  486. self.assertEqual(salt.utils.data.encode(BYTES, keep=False), BYTES)
  487. def test_encode_keep(self):
  488. '''
  489. Whereas we tested the keep argument in test_decode, it is much easier
  490. to do a more comprehensive test of keep in its own function where we
  491. can force the encoding.
  492. '''
  493. unicode_str = 'питон'
  494. encoding = 'ascii'
  495. # Test single string
  496. self.assertEqual(
  497. salt.utils.data.encode(unicode_str, encoding, keep=True),
  498. unicode_str)
  499. self.assertRaises(
  500. UnicodeEncodeError,
  501. salt.utils.data.encode,
  502. unicode_str,
  503. encoding,
  504. keep=False)
  505. data = [
  506. unicode_str,
  507. [b'foo', [unicode_str], {b'key': unicode_str}, (unicode_str,)],
  508. {b'list': [b'foo', unicode_str],
  509. b'dict': {b'key': unicode_str},
  510. b'tuple': (b'foo', unicode_str)},
  511. ([b'foo', unicode_str], {b'key': unicode_str}, (unicode_str,))
  512. ]
  513. # Since everything was a bytestring aside from the bogus data, the
  514. # return data should be identical. We don't need to test recursive
  515. # decoding, that has already been tested in test_encode.
  516. self.assertEqual(
  517. salt.utils.data.encode(data, encoding,
  518. keep=True, preserve_tuples=True),
  519. data
  520. )
  521. self.assertRaises(
  522. UnicodeEncodeError,
  523. salt.utils.data.encode,
  524. data,
  525. encoding,
  526. keep=False,
  527. preserve_tuples=True)
  528. for index, _ in enumerate(data):
  529. self.assertEqual(
  530. salt.utils.data.encode(data[index], encoding,
  531. keep=True, preserve_tuples=True),
  532. data[index]
  533. )
  534. self.assertRaises(
  535. UnicodeEncodeError,
  536. salt.utils.data.encode,
  537. data[index],
  538. encoding,
  539. keep=False,
  540. preserve_tuples=True)
  541. def test_encode_fallback(self):
  542. '''
  543. Test fallback to utf-8
  544. '''
  545. with patch.object(builtins, '__salt_system_encoding__', 'ascii'):
  546. self.assertEqual(salt.utils.data.encode('яйца'), _b('яйца'))
  547. with patch.object(builtins, '__salt_system_encoding__', 'CP1252'):
  548. self.assertEqual(salt.utils.data.encode('Ψ'), _b('Ψ'))
  549. def test_repack_dict(self):
  550. list_of_one_element_dicts = [{'dict_key_1': 'dict_val_1'},
  551. {'dict_key_2': 'dict_val_2'},
  552. {'dict_key_3': 'dict_val_3'}]
  553. expected_ret = {'dict_key_1': 'dict_val_1',
  554. 'dict_key_2': 'dict_val_2',
  555. 'dict_key_3': 'dict_val_3'}
  556. ret = salt.utils.data.repack_dictlist(list_of_one_element_dicts)
  557. self.assertDictEqual(ret, expected_ret)
  558. # Try with yaml
  559. yaml_key_val_pair = '- key1: val1'
  560. ret = salt.utils.data.repack_dictlist(yaml_key_val_pair)
  561. self.assertDictEqual(ret, {'key1': 'val1'})
  562. # Make sure we handle non-yaml junk data
  563. ret = salt.utils.data.repack_dictlist(LOREM_IPSUM)
  564. self.assertDictEqual(ret, {})
  565. def test_stringify(self):
  566. self.assertRaises(TypeError, salt.utils.data.stringify, 9)
  567. self.assertEqual(
  568. salt.utils.data.stringify(['one', 'two', str('three'), 4, 5]), # future lint: disable=blacklisted-function
  569. ['one', 'two', 'three', '4', '5']
  570. )
  571. class FilterFalseyTestCase(TestCase):
  572. '''
  573. Test suite for salt.utils.data.filter_falsey
  574. '''
  575. def test_nop(self):
  576. '''
  577. Test cases where nothing will be done.
  578. '''
  579. # Test with dictionary without recursion
  580. old_dict = {'foo': 'bar', 'bar': {'baz': {'qux': 'quux'}}, 'baz': ['qux', {'foo': 'bar'}]}
  581. new_dict = salt.utils.data.filter_falsey(old_dict)
  582. self.assertEqual(old_dict, new_dict)
  583. # Check returned type equality
  584. self.assertIs(type(old_dict), type(new_dict))
  585. # Test dictionary with recursion
  586. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=3)
  587. self.assertEqual(old_dict, new_dict)
  588. # Test with list
  589. old_list = ['foo', 'bar']
  590. new_list = salt.utils.data.filter_falsey(old_list)
  591. self.assertEqual(old_list, new_list)
  592. # Check returned type equality
  593. self.assertIs(type(old_list), type(new_list))
  594. # Test with set
  595. old_set = set(['foo', 'bar'])
  596. new_set = salt.utils.data.filter_falsey(old_set)
  597. self.assertEqual(old_set, new_set)
  598. # Check returned type equality
  599. self.assertIs(type(old_set), type(new_set))
  600. # Test with OrderedDict
  601. old_dict = OrderedDict([
  602. ('foo', 'bar'),
  603. ('bar', OrderedDict([('qux', 'quux')])),
  604. ('baz', ['qux', OrderedDict([('foo', 'bar')])])
  605. ])
  606. new_dict = salt.utils.data.filter_falsey(old_dict)
  607. self.assertEqual(old_dict, new_dict)
  608. self.assertIs(type(old_dict), type(new_dict))
  609. # Test excluding int
  610. old_list = [0]
  611. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type(0)])
  612. self.assertEqual(old_list, new_list)
  613. # Test excluding str (or unicode) (or both)
  614. old_list = ['']
  615. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type('')])
  616. self.assertEqual(old_list, new_list)
  617. # Test excluding list
  618. old_list = [[]]
  619. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type([])])
  620. self.assertEqual(old_list, new_list)
  621. # Test excluding dict
  622. old_list = [{}]
  623. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type({})])
  624. self.assertEqual(old_list, new_list)
  625. def test_filter_dict_no_recurse(self):
  626. '''
  627. Test filtering a dictionary without recursing.
  628. This will only filter out key-values where the values are falsey.
  629. '''
  630. old_dict = {'foo': None,
  631. 'bar': {'baz': {'qux': None, 'quux': '', 'foo': []}},
  632. 'baz': ['qux'],
  633. 'qux': {},
  634. 'quux': []}
  635. new_dict = salt.utils.data.filter_falsey(old_dict)
  636. expect_dict = {'bar': {'baz': {'qux': None, 'quux': '', 'foo': []}}, 'baz': ['qux']}
  637. self.assertEqual(expect_dict, new_dict)
  638. self.assertIs(type(expect_dict), type(new_dict))
  639. def test_filter_dict_recurse(self):
  640. '''
  641. Test filtering a dictionary with recursing.
  642. This will filter out any key-values where the values are falsey or when
  643. the values *become* falsey after filtering their contents (in case they
  644. are lists or dicts).
  645. '''
  646. old_dict = {'foo': None,
  647. 'bar': {'baz': {'qux': None, 'quux': '', 'foo': []}},
  648. 'baz': ['qux'],
  649. 'qux': {},
  650. 'quux': []}
  651. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=3)
  652. expect_dict = {'baz': ['qux']}
  653. self.assertEqual(expect_dict, new_dict)
  654. self.assertIs(type(expect_dict), type(new_dict))
  655. def test_filter_list_no_recurse(self):
  656. '''
  657. Test filtering a list without recursing.
  658. This will only filter out items which are falsey.
  659. '''
  660. old_list = ['foo', None, [], {}, 0, '']
  661. new_list = salt.utils.data.filter_falsey(old_list)
  662. expect_list = ['foo']
  663. self.assertEqual(expect_list, new_list)
  664. self.assertIs(type(expect_list), type(new_list))
  665. # Ensure nested values are *not* filtered out.
  666. old_list = [
  667. 'foo',
  668. ['foo'],
  669. ['foo', None],
  670. {'foo': 0},
  671. {'foo': 'bar', 'baz': []},
  672. [{'foo': ''}],
  673. ]
  674. new_list = salt.utils.data.filter_falsey(old_list)
  675. self.assertEqual(old_list, new_list)
  676. self.assertIs(type(old_list), type(new_list))
  677. def test_filter_list_recurse(self):
  678. '''
  679. Test filtering a list with recursing.
  680. This will filter out any items which are falsey, or which become falsey
  681. after filtering their contents (in case they are lists or dicts).
  682. '''
  683. old_list = [
  684. 'foo',
  685. ['foo'],
  686. ['foo', None],
  687. {'foo': 0},
  688. {'foo': 'bar', 'baz': []},
  689. [{'foo': ''}]
  690. ]
  691. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=3)
  692. expect_list = ['foo', ['foo'], ['foo'], {'foo': 'bar'}]
  693. self.assertEqual(expect_list, new_list)
  694. self.assertIs(type(expect_list), type(new_list))
  695. def test_filter_set_no_recurse(self):
  696. '''
  697. Test filtering a set without recursing.
  698. Note that a set cannot contain unhashable types, so recursion is not possible.
  699. '''
  700. old_set = set([
  701. 'foo',
  702. None,
  703. 0,
  704. '',
  705. ])
  706. new_set = salt.utils.data.filter_falsey(old_set)
  707. expect_set = set(['foo'])
  708. self.assertEqual(expect_set, new_set)
  709. self.assertIs(type(expect_set), type(new_set))
  710. def test_filter_ordereddict_no_recurse(self):
  711. '''
  712. Test filtering an OrderedDict without recursing.
  713. '''
  714. old_dict = OrderedDict([
  715. ('foo', None),
  716. ('bar', OrderedDict([('baz', OrderedDict([('qux', None), ('quux', ''), ('foo', [])]))])),
  717. ('baz', ['qux']),
  718. ('qux', {}),
  719. ('quux', [])
  720. ])
  721. new_dict = salt.utils.data.filter_falsey(old_dict)
  722. expect_dict = OrderedDict([
  723. ('bar', OrderedDict([('baz', OrderedDict([('qux', None), ('quux', ''), ('foo', [])]))])),
  724. ('baz', ['qux']),
  725. ])
  726. self.assertEqual(expect_dict, new_dict)
  727. self.assertIs(type(expect_dict), type(new_dict))
  728. def test_filter_ordereddict_recurse(self):
  729. '''
  730. Test filtering an OrderedDict with recursing.
  731. '''
  732. old_dict = OrderedDict([
  733. ('foo', None),
  734. ('bar', OrderedDict([('baz', OrderedDict([('qux', None), ('quux', ''), ('foo', [])]))])),
  735. ('baz', ['qux']),
  736. ('qux', {}),
  737. ('quux', [])
  738. ])
  739. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=3)
  740. expect_dict = OrderedDict([
  741. ('baz', ['qux']),
  742. ])
  743. self.assertEqual(expect_dict, new_dict)
  744. self.assertIs(type(expect_dict), type(new_dict))
  745. def test_filter_list_recurse_limit(self):
  746. '''
  747. Test filtering a list with recursing, but with a limited depth.
  748. Note that the top-level is always processed, so a recursion depth of 2
  749. means that two *additional* levels are processed.
  750. '''
  751. old_list = [None, [None, [None, [None]]]]
  752. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=2)
  753. self.assertEqual([[[[None]]]], new_list)
  754. def test_filter_dict_recurse_limit(self):
  755. '''
  756. Test filtering a dict with recursing, but with a limited depth.
  757. Note that the top-level is always processed, so a recursion depth of 2
  758. means that two *additional* levels are processed.
  759. '''
  760. old_dict = {'one': None,
  761. 'foo': {'two': None, 'bar': {'three': None, 'baz': {'four': None}}}}
  762. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=2)
  763. self.assertEqual({'foo': {'bar': {'baz': {'four': None}}}}, new_dict)
  764. def test_filter_exclude_types(self):
  765. '''
  766. Test filtering a list recursively, but also ignoring (i.e. not filtering)
  767. out certain types that can be falsey.
  768. '''
  769. # Ignore int, unicode
  770. old_list = ['foo', ['foo'], ['foo', None], {'foo': 0}, {'foo': 'bar', 'baz': []}, [{'foo': ''}]]
  771. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=3, ignore_types=[type(0), type('')])
  772. self.assertEqual(['foo', ['foo'], ['foo'], {'foo': 0}, {'foo': 'bar'}, [{'foo': ''}]], new_list)
  773. # Ignore list
  774. old_list = ['foo', ['foo'], ['foo', None], {'foo': 0}, {'foo': 'bar', 'baz': []}, [{'foo': ''}]]
  775. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=3, ignore_types=[type([])])
  776. self.assertEqual(['foo', ['foo'], ['foo'], {'foo': 'bar', 'baz': []}, []], new_list)
  777. # Ignore dict
  778. old_list = ['foo', ['foo'], ['foo', None], {'foo': 0}, {'foo': 'bar', 'baz': []}, [{'foo': ''}]]
  779. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=3, ignore_types=[type({})])
  780. self.assertEqual(['foo', ['foo'], ['foo'], {}, {'foo': 'bar'}, [{}]], new_list)
  781. # Ignore NoneType
  782. old_list = ['foo', ['foo'], ['foo', None], {'foo': 0}, {'foo': 'bar', 'baz': []}, [{'foo': ''}]]
  783. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=3, ignore_types=[type(None)])
  784. self.assertEqual(['foo', ['foo'], ['foo', None], {'foo': 'bar'}], new_list)