test_data.py 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461
  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.ext import six
  12. # Import 3rd party libs
  13. from salt.ext.six.moves import ( # pylint: disable=import-error,redefined-builtin
  14. builtins,
  15. )
  16. from salt.utils.odict import OrderedDict
  17. from tests.support.mock import patch
  18. from tests.support.unit import LOREM_IPSUM, TestCase
  19. log = logging.getLogger(__name__)
  20. _b = lambda x: x.encode("utf-8")
  21. _s = lambda x: salt.utils.stringutils.to_str(x, normalize=True)
  22. # Some randomized data that will not decode
  23. BYTES = b"1\x814\x10"
  24. # This is an example of a unicode string with й constructed using two separate
  25. # code points. Do not modify it.
  26. EGGS = "\u044f\u0438\u0306\u0446\u0430"
  27. class DataTestCase(TestCase):
  28. test_data = [
  29. "unicode_str",
  30. _b("питон"),
  31. 123,
  32. 456.789,
  33. True,
  34. False,
  35. None,
  36. EGGS,
  37. BYTES,
  38. [123, 456.789, _b("спам"), True, False, None, EGGS, BYTES],
  39. (987, 654.321, _b("яйца"), EGGS, None, (True, EGGS, BYTES)),
  40. {
  41. _b("str_key"): _b("str_val"),
  42. None: True,
  43. 123: 456.789,
  44. EGGS: BYTES,
  45. _b("subdict"): {
  46. "unicode_key": EGGS,
  47. _b("tuple"): (123, "hello", _b("world"), True, EGGS, BYTES),
  48. _b("list"): [456, _b("спам"), False, EGGS, BYTES],
  49. },
  50. },
  51. OrderedDict([(_b("foo"), "bar"), (123, 456), (EGGS, BYTES)]),
  52. ]
  53. def test_sorted_ignorecase(self):
  54. test_list = ["foo", "Foo", "bar", "Bar"]
  55. expected_list = ["bar", "Bar", "foo", "Foo"]
  56. self.assertEqual(salt.utils.data.sorted_ignorecase(test_list), expected_list)
  57. def test_mysql_to_dict(self):
  58. test_mysql_output = [
  59. "+----+------+-----------+------+---------+------+-------+------------------+",
  60. "| Id | User | Host | db | Command | Time | State | Info |",
  61. "+----+------+-----------+------+---------+------+-------+------------------+",
  62. "| 7 | root | localhost | NULL | Query | 0 | init | show processlist |",
  63. "+----+------+-----------+------+---------+------+-------+------------------+",
  64. ]
  65. ret = salt.utils.data.mysql_to_dict(test_mysql_output, "Info")
  66. expected_dict = {
  67. "show processlist": {
  68. "Info": "show processlist",
  69. "db": "NULL",
  70. "State": "init",
  71. "Host": "localhost",
  72. "Command": "Query",
  73. "User": "root",
  74. "Time": 0,
  75. "Id": 7,
  76. }
  77. }
  78. self.assertDictEqual(ret, expected_dict)
  79. def test_subdict_match(self):
  80. test_two_level_dict = {"foo": {"bar": "baz"}}
  81. test_two_level_comb_dict = {"foo": {"bar": "baz:woz"}}
  82. test_two_level_dict_and_list = {
  83. "abc": ["def", "ghi", {"lorem": {"ipsum": [{"dolor": "sit"}]}}],
  84. }
  85. test_three_level_dict = {"a": {"b": {"c": "v"}}}
  86. self.assertTrue(
  87. salt.utils.data.subdict_match(test_two_level_dict, "foo:bar:baz")
  88. )
  89. # In test_two_level_comb_dict, 'foo:bar' corresponds to 'baz:woz', not
  90. # 'baz'. This match should return False.
  91. self.assertFalse(
  92. salt.utils.data.subdict_match(test_two_level_comb_dict, "foo:bar:baz")
  93. )
  94. # This tests matching with the delimiter in the value part (in other
  95. # words, that the path 'foo:bar' corresponds to the string 'baz:woz').
  96. self.assertTrue(
  97. salt.utils.data.subdict_match(test_two_level_comb_dict, "foo:bar:baz:woz")
  98. )
  99. # This would match if test_two_level_comb_dict['foo']['bar'] was equal
  100. # to 'baz:woz:wiz', or if there was more deep nesting. But it does not,
  101. # so this should return False.
  102. self.assertFalse(
  103. salt.utils.data.subdict_match(
  104. test_two_level_comb_dict, "foo:bar:baz:woz:wiz"
  105. )
  106. )
  107. # This tests for cases when a key path corresponds to a list. The
  108. # value part 'ghi' should be successfully matched as it is a member of
  109. # the list corresponding to key path 'abc'. It is somewhat a
  110. # duplication of a test within test_traverse_dict_and_list, but
  111. # salt.utils.data.subdict_match() does more than just invoke
  112. # salt.utils.traverse_list_and_dict() so this particular assertion is a
  113. # sanity check.
  114. self.assertTrue(
  115. salt.utils.data.subdict_match(test_two_level_dict_and_list, "abc:ghi")
  116. )
  117. # This tests the use case of a dict embedded in a list, embedded in a
  118. # list, embedded in a dict. This is a rather absurd case, but it
  119. # confirms that match recursion works properly.
  120. self.assertTrue(
  121. salt.utils.data.subdict_match(
  122. test_two_level_dict_and_list, "abc:lorem:ipsum:dolor:sit"
  123. )
  124. )
  125. # Test four level dict match for reference
  126. self.assertTrue(salt.utils.data.subdict_match(test_three_level_dict, "a:b:c:v"))
  127. # Test regression in 2015.8 where 'a:c:v' would match 'a:b:c:v'
  128. self.assertFalse(salt.utils.data.subdict_match(test_three_level_dict, "a:c:v"))
  129. # Test wildcard match
  130. self.assertTrue(salt.utils.data.subdict_match(test_three_level_dict, "a:*:c:v"))
  131. def test_subdict_match_with_wildcards(self):
  132. """
  133. Tests subdict matching when wildcards are used in the expression
  134. """
  135. data = {"a": {"b": {"ç": "d", "é": ["eff", "gee", "8ch"], "ĩ": {"j": "k"}}}}
  136. assert salt.utils.data.subdict_match(data, "*:*:*:*")
  137. assert salt.utils.data.subdict_match(data, "a:*:*:*")
  138. assert salt.utils.data.subdict_match(data, "a:b:*:*")
  139. assert salt.utils.data.subdict_match(data, "a:b:ç:*")
  140. assert salt.utils.data.subdict_match(data, "a:b:*:d")
  141. assert salt.utils.data.subdict_match(data, "a:*:ç:d")
  142. assert salt.utils.data.subdict_match(data, "*:b:ç:d")
  143. assert salt.utils.data.subdict_match(data, "*:*:ç:d")
  144. assert salt.utils.data.subdict_match(data, "*:*:*:d")
  145. assert salt.utils.data.subdict_match(data, "a:*:*:d")
  146. assert salt.utils.data.subdict_match(data, "a:b:*:ef*")
  147. assert salt.utils.data.subdict_match(data, "a:b:*:g*")
  148. assert salt.utils.data.subdict_match(data, "a:b:*:j:*")
  149. assert salt.utils.data.subdict_match(data, "a:b:*:j:k")
  150. assert salt.utils.data.subdict_match(data, "a:b:*:*:k")
  151. assert salt.utils.data.subdict_match(data, "a:b:*:*:*")
  152. def test_traverse_dict(self):
  153. test_two_level_dict = {"foo": {"bar": "baz"}}
  154. self.assertDictEqual(
  155. {"not_found": "nope"},
  156. salt.utils.data.traverse_dict(
  157. test_two_level_dict, "foo:bar:baz", {"not_found": "nope"}
  158. ),
  159. )
  160. self.assertEqual(
  161. "baz",
  162. salt.utils.data.traverse_dict(
  163. test_two_level_dict, "foo:bar", {"not_found": "not_found"}
  164. ),
  165. )
  166. def test_traverse_dict_and_list(self):
  167. test_two_level_dict = {"foo": {"bar": "baz"}}
  168. test_two_level_dict_and_list = {
  169. "foo": ["bar", "baz", {"lorem": {"ipsum": [{"dolor": "sit"}]}}]
  170. }
  171. # Check traversing too far: salt.utils.data.traverse_dict_and_list() returns
  172. # the value corresponding to a given key path, and baz is a value
  173. # corresponding to the key path foo:bar.
  174. self.assertDictEqual(
  175. {"not_found": "nope"},
  176. salt.utils.data.traverse_dict_and_list(
  177. test_two_level_dict, "foo:bar:baz", {"not_found": "nope"}
  178. ),
  179. )
  180. # Now check to ensure that foo:bar corresponds to baz
  181. self.assertEqual(
  182. "baz",
  183. salt.utils.data.traverse_dict_and_list(
  184. test_two_level_dict, "foo:bar", {"not_found": "not_found"}
  185. ),
  186. )
  187. # Check traversing too far
  188. self.assertDictEqual(
  189. {"not_found": "nope"},
  190. salt.utils.data.traverse_dict_and_list(
  191. test_two_level_dict_and_list, "foo:bar", {"not_found": "nope"}
  192. ),
  193. )
  194. # Check index 1 (2nd element) of list corresponding to path 'foo'
  195. self.assertEqual(
  196. "baz",
  197. salt.utils.data.traverse_dict_and_list(
  198. test_two_level_dict_and_list, "foo:1", {"not_found": "not_found"}
  199. ),
  200. )
  201. # Traverse a couple times into dicts embedded in lists
  202. self.assertEqual(
  203. "sit",
  204. salt.utils.data.traverse_dict_and_list(
  205. test_two_level_dict_and_list,
  206. "foo:lorem:ipsum:dolor",
  207. {"not_found": "not_found"},
  208. ),
  209. )
  210. def test_compare_dicts(self):
  211. ret = salt.utils.data.compare_dicts(old={"foo": "bar"}, new={"foo": "bar"})
  212. self.assertEqual(ret, {})
  213. ret = salt.utils.data.compare_dicts(old={"foo": "bar"}, new={"foo": "woz"})
  214. expected_ret = {"foo": {"new": "woz", "old": "bar"}}
  215. self.assertDictEqual(ret, expected_ret)
  216. def test_compare_lists_no_change(self):
  217. ret = salt.utils.data.compare_lists(
  218. old=[1, 2, 3, "a", "b", "c"], new=[1, 2, 3, "a", "b", "c"]
  219. )
  220. expected = {}
  221. self.assertDictEqual(ret, expected)
  222. def test_compare_lists_changes(self):
  223. ret = salt.utils.data.compare_lists(
  224. old=[1, 2, 3, "a", "b", "c"], new=[1, 2, 4, "x", "y", "z"]
  225. )
  226. expected = {"new": [4, "x", "y", "z"], "old": [3, "a", "b", "c"]}
  227. self.assertDictEqual(ret, expected)
  228. def test_compare_lists_changes_new(self):
  229. ret = salt.utils.data.compare_lists(old=[1, 2, 3], new=[1, 2, 3, "x", "y", "z"])
  230. expected = {"new": ["x", "y", "z"]}
  231. self.assertDictEqual(ret, expected)
  232. def test_compare_lists_changes_old(self):
  233. ret = salt.utils.data.compare_lists(old=[1, 2, 3, "a", "b", "c"], new=[1, 2, 3])
  234. expected = {"old": ["a", "b", "c"]}
  235. self.assertDictEqual(ret, expected)
  236. def test_decode(self):
  237. """
  238. Companion to test_decode_to_str, they should both be kept up-to-date
  239. with one another.
  240. NOTE: This uses the lambda "_b" defined above in the global scope,
  241. which encodes a string to a bytestring, assuming utf-8.
  242. """
  243. expected = [
  244. "unicode_str",
  245. "питон",
  246. 123,
  247. 456.789,
  248. True,
  249. False,
  250. None,
  251. "яйца",
  252. BYTES,
  253. [123, 456.789, "спам", True, False, None, "яйца", BYTES],
  254. (987, 654.321, "яйца", "яйца", None, (True, "яйца", BYTES)),
  255. {
  256. "str_key": "str_val",
  257. None: True,
  258. 123: 456.789,
  259. "яйца": BYTES,
  260. "subdict": {
  261. "unicode_key": "яйца",
  262. "tuple": (123, "hello", "world", True, "яйца", BYTES),
  263. "list": [456, "спам", False, "яйца", BYTES],
  264. },
  265. },
  266. OrderedDict([("foo", "bar"), (123, 456), ("яйца", BYTES)]),
  267. ]
  268. ret = salt.utils.data.decode(
  269. self.test_data,
  270. keep=True,
  271. normalize=True,
  272. preserve_dict_class=True,
  273. preserve_tuples=True,
  274. )
  275. self.assertEqual(ret, expected)
  276. # The binary data in the data structure should fail to decode, even
  277. # using the fallback, and raise an exception.
  278. self.assertRaises(
  279. UnicodeDecodeError,
  280. salt.utils.data.decode,
  281. self.test_data,
  282. keep=False,
  283. normalize=True,
  284. preserve_dict_class=True,
  285. preserve_tuples=True,
  286. )
  287. # Now munge the expected data so that we get what we would expect if we
  288. # disable preservation of dict class and tuples
  289. expected[10] = [987, 654.321, "яйца", "яйца", None, [True, "яйца", BYTES]]
  290. expected[11]["subdict"]["tuple"] = [123, "hello", "world", True, "яйца", BYTES]
  291. expected[12] = {"foo": "bar", 123: 456, "яйца": BYTES}
  292. ret = salt.utils.data.decode(
  293. self.test_data,
  294. keep=True,
  295. normalize=True,
  296. preserve_dict_class=False,
  297. preserve_tuples=False,
  298. )
  299. self.assertEqual(ret, expected)
  300. # Now test single non-string, non-data-structure items, these should
  301. # return the same value when passed to this function
  302. for item in (123, 4.56, True, False, None):
  303. log.debug("Testing decode of %s", item)
  304. self.assertEqual(salt.utils.data.decode(item), item)
  305. # Test single strings (not in a data structure)
  306. self.assertEqual(salt.utils.data.decode("foo"), "foo")
  307. self.assertEqual(salt.utils.data.decode(_b("bar")), "bar")
  308. self.assertEqual(salt.utils.data.decode(EGGS, normalize=True), "яйца")
  309. self.assertEqual(salt.utils.data.decode(EGGS, normalize=False), EGGS)
  310. # Test binary blob
  311. self.assertEqual(salt.utils.data.decode(BYTES, keep=True), BYTES)
  312. self.assertRaises(UnicodeDecodeError, salt.utils.data.decode, BYTES, keep=False)
  313. def test_decode_to_str(self):
  314. """
  315. Companion to test_decode, they should both be kept up-to-date with one
  316. another.
  317. NOTE: This uses the lambda "_s" defined above in the global scope,
  318. which converts the string/bytestring to a str type.
  319. """
  320. expected = [
  321. _s("unicode_str"),
  322. _s("питон"),
  323. 123,
  324. 456.789,
  325. True,
  326. False,
  327. None,
  328. _s("яйца"),
  329. BYTES,
  330. [123, 456.789, _s("спам"), True, False, None, _s("яйца"), BYTES],
  331. (987, 654.321, _s("яйца"), _s("яйца"), None, (True, _s("яйца"), BYTES)),
  332. {
  333. _s("str_key"): _s("str_val"),
  334. None: True,
  335. 123: 456.789,
  336. _s("яйца"): BYTES,
  337. _s("subdict"): {
  338. _s("unicode_key"): _s("яйца"),
  339. _s("tuple"): (
  340. 123,
  341. _s("hello"),
  342. _s("world"),
  343. True,
  344. _s("яйца"),
  345. BYTES,
  346. ),
  347. _s("list"): [456, _s("спам"), False, _s("яйца"), BYTES],
  348. },
  349. },
  350. OrderedDict([(_s("foo"), _s("bar")), (123, 456), (_s("яйца"), BYTES)]),
  351. ]
  352. ret = salt.utils.data.decode(
  353. self.test_data,
  354. keep=True,
  355. normalize=True,
  356. preserve_dict_class=True,
  357. preserve_tuples=True,
  358. to_str=True,
  359. )
  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. )
  374. # Now munge the expected data so that we get what we would expect if we
  375. # disable preservation of dict class and tuples
  376. expected[10] = [
  377. 987,
  378. 654.321,
  379. _s("яйца"),
  380. _s("яйца"),
  381. None,
  382. [True, _s("яйца"), BYTES],
  383. ]
  384. expected[11][_s("subdict")][_s("tuple")] = [
  385. 123,
  386. _s("hello"),
  387. _s("world"),
  388. True,
  389. _s("яйца"),
  390. BYTES,
  391. ]
  392. expected[12] = {_s("foo"): _s("bar"), 123: 456, _s("яйца"): BYTES}
  393. ret = salt.utils.data.decode(
  394. self.test_data,
  395. keep=True,
  396. normalize=True,
  397. preserve_dict_class=False,
  398. preserve_tuples=False,
  399. to_str=True,
  400. )
  401. self.assertEqual(ret, expected)
  402. # Now test single non-string, non-data-structure items, these should
  403. # return the same value when passed to this function
  404. for item in (123, 4.56, True, False, None):
  405. log.debug("Testing decode of %s", item)
  406. self.assertEqual(salt.utils.data.decode(item, to_str=True), item)
  407. # Test single strings (not in a data structure)
  408. self.assertEqual(salt.utils.data.decode("foo", to_str=True), _s("foo"))
  409. self.assertEqual(salt.utils.data.decode(_b("bar"), to_str=True), _s("bar"))
  410. # Test binary blob
  411. self.assertEqual(salt.utils.data.decode(BYTES, keep=True, to_str=True), BYTES)
  412. if six.PY3:
  413. self.assertRaises(
  414. UnicodeDecodeError,
  415. salt.utils.data.decode,
  416. BYTES,
  417. keep=False,
  418. to_str=True,
  419. )
  420. def test_decode_fallback(self):
  421. """
  422. Test fallback to utf-8
  423. """
  424. with patch.object(builtins, "__salt_system_encoding__", "ascii"):
  425. self.assertEqual(salt.utils.data.decode(_b("яйца")), "яйца")
  426. def test_encode(self):
  427. """
  428. NOTE: This uses the lambda "_b" defined above in the global scope,
  429. which encodes a string to a bytestring, assuming utf-8.
  430. """
  431. expected = [
  432. _b("unicode_str"),
  433. _b("питон"),
  434. 123,
  435. 456.789,
  436. True,
  437. False,
  438. None,
  439. _b(EGGS),
  440. BYTES,
  441. [123, 456.789, _b("спам"), True, False, None, _b(EGGS), BYTES],
  442. (987, 654.321, _b("яйца"), _b(EGGS), None, (True, _b(EGGS), BYTES)),
  443. {
  444. _b("str_key"): _b("str_val"),
  445. None: True,
  446. 123: 456.789,
  447. _b(EGGS): BYTES,
  448. _b("subdict"): {
  449. _b("unicode_key"): _b(EGGS),
  450. _b("tuple"): (123, _b("hello"), _b("world"), True, _b(EGGS), BYTES),
  451. _b("list"): [456, _b("спам"), False, _b(EGGS), BYTES],
  452. },
  453. },
  454. OrderedDict([(_b("foo"), _b("bar")), (123, 456), (_b(EGGS), BYTES)]),
  455. ]
  456. # Both keep=True and keep=False should work because the BYTES data is
  457. # already bytes.
  458. ret = salt.utils.data.encode(
  459. self.test_data, keep=True, preserve_dict_class=True, preserve_tuples=True
  460. )
  461. self.assertEqual(ret, expected)
  462. ret = salt.utils.data.encode(
  463. self.test_data, keep=False, preserve_dict_class=True, preserve_tuples=True
  464. )
  465. self.assertEqual(ret, expected)
  466. # Now munge the expected data so that we get what we would expect if we
  467. # disable preservation of dict class and tuples
  468. expected[10] = [
  469. 987,
  470. 654.321,
  471. _b("яйца"),
  472. _b(EGGS),
  473. None,
  474. [True, _b(EGGS), BYTES],
  475. ]
  476. expected[11][_b("subdict")][_b("tuple")] = [
  477. 123,
  478. _b("hello"),
  479. _b("world"),
  480. True,
  481. _b(EGGS),
  482. BYTES,
  483. ]
  484. expected[12] = {_b("foo"): _b("bar"), 123: 456, _b(EGGS): BYTES}
  485. ret = salt.utils.data.encode(
  486. self.test_data, keep=True, preserve_dict_class=False, preserve_tuples=False
  487. )
  488. self.assertEqual(ret, expected)
  489. ret = salt.utils.data.encode(
  490. self.test_data, keep=False, preserve_dict_class=False, preserve_tuples=False
  491. )
  492. self.assertEqual(ret, expected)
  493. # Now test single non-string, non-data-structure items, these should
  494. # return the same value when passed to this function
  495. for item in (123, 4.56, True, False, None):
  496. log.debug("Testing encode of %s", item)
  497. self.assertEqual(salt.utils.data.encode(item), item)
  498. # Test single strings (not in a data structure)
  499. self.assertEqual(salt.utils.data.encode("foo"), _b("foo"))
  500. self.assertEqual(salt.utils.data.encode(_b("bar")), _b("bar"))
  501. # Test binary blob, nothing should happen even when keep=False since
  502. # the data is already bytes
  503. self.assertEqual(salt.utils.data.encode(BYTES, keep=True), BYTES)
  504. self.assertEqual(salt.utils.data.encode(BYTES, keep=False), BYTES)
  505. def test_encode_keep(self):
  506. """
  507. Whereas we tested the keep argument in test_decode, it is much easier
  508. to do a more comprehensive test of keep in its own function where we
  509. can force the encoding.
  510. """
  511. unicode_str = "питон"
  512. encoding = "ascii"
  513. # Test single string
  514. self.assertEqual(
  515. salt.utils.data.encode(unicode_str, encoding, keep=True), unicode_str
  516. )
  517. self.assertRaises(
  518. UnicodeEncodeError,
  519. salt.utils.data.encode,
  520. unicode_str,
  521. encoding,
  522. keep=False,
  523. )
  524. data = [
  525. unicode_str,
  526. [b"foo", [unicode_str], {b"key": unicode_str}, (unicode_str,)],
  527. {
  528. b"list": [b"foo", unicode_str],
  529. b"dict": {b"key": unicode_str},
  530. b"tuple": (b"foo", unicode_str),
  531. },
  532. ([b"foo", unicode_str], {b"key": unicode_str}, (unicode_str,)),
  533. ]
  534. # Since everything was a bytestring aside from the bogus data, the
  535. # return data should be identical. We don't need to test recursive
  536. # decoding, that has already been tested in test_encode.
  537. self.assertEqual(
  538. salt.utils.data.encode(data, encoding, keep=True, preserve_tuples=True),
  539. data,
  540. )
  541. self.assertRaises(
  542. UnicodeEncodeError,
  543. salt.utils.data.encode,
  544. data,
  545. encoding,
  546. keep=False,
  547. preserve_tuples=True,
  548. )
  549. for index, _ in enumerate(data):
  550. self.assertEqual(
  551. salt.utils.data.encode(
  552. data[index], encoding, keep=True, preserve_tuples=True
  553. ),
  554. data[index],
  555. )
  556. self.assertRaises(
  557. UnicodeEncodeError,
  558. salt.utils.data.encode,
  559. data[index],
  560. encoding,
  561. keep=False,
  562. preserve_tuples=True,
  563. )
  564. def test_encode_fallback(self):
  565. """
  566. Test fallback to utf-8
  567. """
  568. with patch.object(builtins, "__salt_system_encoding__", "ascii"):
  569. self.assertEqual(salt.utils.data.encode("яйца"), _b("яйца"))
  570. with patch.object(builtins, "__salt_system_encoding__", "CP1252"):
  571. self.assertEqual(salt.utils.data.encode("Ψ"), _b("Ψ"))
  572. def test_repack_dict(self):
  573. list_of_one_element_dicts = [
  574. {"dict_key_1": "dict_val_1"},
  575. {"dict_key_2": "dict_val_2"},
  576. {"dict_key_3": "dict_val_3"},
  577. ]
  578. expected_ret = {
  579. "dict_key_1": "dict_val_1",
  580. "dict_key_2": "dict_val_2",
  581. "dict_key_3": "dict_val_3",
  582. }
  583. ret = salt.utils.data.repack_dictlist(list_of_one_element_dicts)
  584. self.assertDictEqual(ret, expected_ret)
  585. # Try with yaml
  586. yaml_key_val_pair = "- key1: val1"
  587. ret = salt.utils.data.repack_dictlist(yaml_key_val_pair)
  588. self.assertDictEqual(ret, {"key1": "val1"})
  589. # Make sure we handle non-yaml junk data
  590. ret = salt.utils.data.repack_dictlist(LOREM_IPSUM)
  591. self.assertDictEqual(ret, {})
  592. def test_stringify(self):
  593. self.assertRaises(TypeError, salt.utils.data.stringify, 9)
  594. self.assertEqual(
  595. salt.utils.data.stringify(
  596. ["one", "two", str("three"), 4, 5]
  597. ), # future lint: disable=blacklisted-function
  598. ["one", "two", "three", "4", "5"],
  599. )
  600. def test_json_query(self):
  601. # Raises exception if jmespath module is not found
  602. with patch("salt.utils.data.jmespath", None):
  603. self.assertRaisesRegex(
  604. RuntimeError, "requires jmespath", salt.utils.data.json_query, {}, "@"
  605. )
  606. # Test search
  607. user_groups = {
  608. "user1": {"groups": ["group1", "group2", "group3"]},
  609. "user2": {"groups": ["group1", "group2"]},
  610. "user3": {"groups": ["group3"]},
  611. }
  612. expression = "*.groups[0]"
  613. primary_groups = ["group1", "group1", "group3"]
  614. self.assertEqual(
  615. sorted(salt.utils.data.json_query(user_groups, expression)), primary_groups
  616. )
  617. class FilterFalseyTestCase(TestCase):
  618. """
  619. Test suite for salt.utils.data.filter_falsey
  620. """
  621. def test_nop(self):
  622. """
  623. Test cases where nothing will be done.
  624. """
  625. # Test with dictionary without recursion
  626. old_dict = {
  627. "foo": "bar",
  628. "bar": {"baz": {"qux": "quux"}},
  629. "baz": ["qux", {"foo": "bar"}],
  630. }
  631. new_dict = salt.utils.data.filter_falsey(old_dict)
  632. self.assertEqual(old_dict, new_dict)
  633. # Check returned type equality
  634. self.assertIs(type(old_dict), type(new_dict))
  635. # Test dictionary with recursion
  636. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=3)
  637. self.assertEqual(old_dict, new_dict)
  638. # Test with list
  639. old_list = ["foo", "bar"]
  640. new_list = salt.utils.data.filter_falsey(old_list)
  641. self.assertEqual(old_list, new_list)
  642. # Check returned type equality
  643. self.assertIs(type(old_list), type(new_list))
  644. # Test with set
  645. old_set = set(["foo", "bar"])
  646. new_set = salt.utils.data.filter_falsey(old_set)
  647. self.assertEqual(old_set, new_set)
  648. # Check returned type equality
  649. self.assertIs(type(old_set), type(new_set))
  650. # Test with OrderedDict
  651. old_dict = OrderedDict(
  652. [
  653. ("foo", "bar"),
  654. ("bar", OrderedDict([("qux", "quux")])),
  655. ("baz", ["qux", OrderedDict([("foo", "bar")])]),
  656. ]
  657. )
  658. new_dict = salt.utils.data.filter_falsey(old_dict)
  659. self.assertEqual(old_dict, new_dict)
  660. self.assertIs(type(old_dict), type(new_dict))
  661. # Test excluding int
  662. old_list = [0]
  663. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type(0)])
  664. self.assertEqual(old_list, new_list)
  665. # Test excluding str (or unicode) (or both)
  666. old_list = [""]
  667. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type("")])
  668. self.assertEqual(old_list, new_list)
  669. # Test excluding list
  670. old_list = [[]]
  671. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type([])])
  672. self.assertEqual(old_list, new_list)
  673. # Test excluding dict
  674. old_list = [{}]
  675. new_list = salt.utils.data.filter_falsey(old_list, ignore_types=[type({})])
  676. self.assertEqual(old_list, new_list)
  677. def test_filter_dict_no_recurse(self):
  678. """
  679. Test filtering a dictionary without recursing.
  680. This will only filter out key-values where the values are falsey.
  681. """
  682. old_dict = {
  683. "foo": None,
  684. "bar": {"baz": {"qux": None, "quux": "", "foo": []}},
  685. "baz": ["qux"],
  686. "qux": {},
  687. "quux": [],
  688. }
  689. new_dict = salt.utils.data.filter_falsey(old_dict)
  690. expect_dict = {
  691. "bar": {"baz": {"qux": None, "quux": "", "foo": []}},
  692. "baz": ["qux"],
  693. }
  694. self.assertEqual(expect_dict, new_dict)
  695. self.assertIs(type(expect_dict), type(new_dict))
  696. def test_filter_dict_recurse(self):
  697. """
  698. Test filtering a dictionary with recursing.
  699. This will filter out any key-values where the values are falsey or when
  700. the values *become* falsey after filtering their contents (in case they
  701. are lists or dicts).
  702. """
  703. old_dict = {
  704. "foo": None,
  705. "bar": {"baz": {"qux": None, "quux": "", "foo": []}},
  706. "baz": ["qux"],
  707. "qux": {},
  708. "quux": [],
  709. }
  710. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=3)
  711. expect_dict = {"baz": ["qux"]}
  712. self.assertEqual(expect_dict, new_dict)
  713. self.assertIs(type(expect_dict), type(new_dict))
  714. def test_filter_list_no_recurse(self):
  715. """
  716. Test filtering a list without recursing.
  717. This will only filter out items which are falsey.
  718. """
  719. old_list = ["foo", None, [], {}, 0, ""]
  720. new_list = salt.utils.data.filter_falsey(old_list)
  721. expect_list = ["foo"]
  722. self.assertEqual(expect_list, new_list)
  723. self.assertIs(type(expect_list), type(new_list))
  724. # Ensure nested values are *not* filtered out.
  725. old_list = [
  726. "foo",
  727. ["foo"],
  728. ["foo", None],
  729. {"foo": 0},
  730. {"foo": "bar", "baz": []},
  731. [{"foo": ""}],
  732. ]
  733. new_list = salt.utils.data.filter_falsey(old_list)
  734. self.assertEqual(old_list, new_list)
  735. self.assertIs(type(old_list), type(new_list))
  736. def test_filter_list_recurse(self):
  737. """
  738. Test filtering a list with recursing.
  739. This will filter out any items which are falsey, or which become falsey
  740. after filtering their contents (in case they are lists or dicts).
  741. """
  742. old_list = [
  743. "foo",
  744. ["foo"],
  745. ["foo", None],
  746. {"foo": 0},
  747. {"foo": "bar", "baz": []},
  748. [{"foo": ""}],
  749. ]
  750. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=3)
  751. expect_list = ["foo", ["foo"], ["foo"], {"foo": "bar"}]
  752. self.assertEqual(expect_list, new_list)
  753. self.assertIs(type(expect_list), type(new_list))
  754. def test_filter_set_no_recurse(self):
  755. """
  756. Test filtering a set without recursing.
  757. Note that a set cannot contain unhashable types, so recursion is not possible.
  758. """
  759. old_set = set(["foo", None, 0, ""])
  760. new_set = salt.utils.data.filter_falsey(old_set)
  761. expect_set = set(["foo"])
  762. self.assertEqual(expect_set, new_set)
  763. self.assertIs(type(expect_set), type(new_set))
  764. def test_filter_ordereddict_no_recurse(self):
  765. """
  766. Test filtering an OrderedDict without recursing.
  767. """
  768. old_dict = OrderedDict(
  769. [
  770. ("foo", None),
  771. (
  772. "bar",
  773. OrderedDict(
  774. [
  775. (
  776. "baz",
  777. OrderedDict([("qux", None), ("quux", ""), ("foo", [])]),
  778. )
  779. ]
  780. ),
  781. ),
  782. ("baz", ["qux"]),
  783. ("qux", {}),
  784. ("quux", []),
  785. ]
  786. )
  787. new_dict = salt.utils.data.filter_falsey(old_dict)
  788. expect_dict = OrderedDict(
  789. [
  790. (
  791. "bar",
  792. OrderedDict(
  793. [
  794. (
  795. "baz",
  796. OrderedDict([("qux", None), ("quux", ""), ("foo", [])]),
  797. )
  798. ]
  799. ),
  800. ),
  801. ("baz", ["qux"]),
  802. ]
  803. )
  804. self.assertEqual(expect_dict, new_dict)
  805. self.assertIs(type(expect_dict), type(new_dict))
  806. def test_filter_ordereddict_recurse(self):
  807. """
  808. Test filtering an OrderedDict with recursing.
  809. """
  810. old_dict = OrderedDict(
  811. [
  812. ("foo", None),
  813. (
  814. "bar",
  815. OrderedDict(
  816. [
  817. (
  818. "baz",
  819. OrderedDict([("qux", None), ("quux", ""), ("foo", [])]),
  820. )
  821. ]
  822. ),
  823. ),
  824. ("baz", ["qux"]),
  825. ("qux", {}),
  826. ("quux", []),
  827. ]
  828. )
  829. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=3)
  830. expect_dict = OrderedDict([("baz", ["qux"])])
  831. self.assertEqual(expect_dict, new_dict)
  832. self.assertIs(type(expect_dict), type(new_dict))
  833. def test_filter_list_recurse_limit(self):
  834. """
  835. Test filtering a list with recursing, but with a limited depth.
  836. Note that the top-level is always processed, so a recursion depth of 2
  837. means that two *additional* levels are processed.
  838. """
  839. old_list = [None, [None, [None, [None]]]]
  840. new_list = salt.utils.data.filter_falsey(old_list, recurse_depth=2)
  841. self.assertEqual([[[[None]]]], new_list)
  842. def test_filter_dict_recurse_limit(self):
  843. """
  844. Test filtering a dict with recursing, but with a limited depth.
  845. Note that the top-level is always processed, so a recursion depth of 2
  846. means that two *additional* levels are processed.
  847. """
  848. old_dict = {
  849. "one": None,
  850. "foo": {"two": None, "bar": {"three": None, "baz": {"four": None}}},
  851. }
  852. new_dict = salt.utils.data.filter_falsey(old_dict, recurse_depth=2)
  853. self.assertEqual({"foo": {"bar": {"baz": {"four": None}}}}, new_dict)
  854. def test_filter_exclude_types(self):
  855. """
  856. Test filtering a list recursively, but also ignoring (i.e. not filtering)
  857. out certain types that can be falsey.
  858. """
  859. # Ignore int, unicode
  860. old_list = [
  861. "foo",
  862. ["foo"],
  863. ["foo", None],
  864. {"foo": 0},
  865. {"foo": "bar", "baz": []},
  866. [{"foo": ""}],
  867. ]
  868. new_list = salt.utils.data.filter_falsey(
  869. old_list, recurse_depth=3, ignore_types=[type(0), type("")]
  870. )
  871. self.assertEqual(
  872. ["foo", ["foo"], ["foo"], {"foo": 0}, {"foo": "bar"}, [{"foo": ""}]],
  873. new_list,
  874. )
  875. # Ignore list
  876. old_list = [
  877. "foo",
  878. ["foo"],
  879. ["foo", None],
  880. {"foo": 0},
  881. {"foo": "bar", "baz": []},
  882. [{"foo": ""}],
  883. ]
  884. new_list = salt.utils.data.filter_falsey(
  885. old_list, recurse_depth=3, ignore_types=[type([])]
  886. )
  887. self.assertEqual(
  888. ["foo", ["foo"], ["foo"], {"foo": "bar", "baz": []}, []], new_list
  889. )
  890. # Ignore dict
  891. old_list = [
  892. "foo",
  893. ["foo"],
  894. ["foo", None],
  895. {"foo": 0},
  896. {"foo": "bar", "baz": []},
  897. [{"foo": ""}],
  898. ]
  899. new_list = salt.utils.data.filter_falsey(
  900. old_list, recurse_depth=3, ignore_types=[type({})]
  901. )
  902. self.assertEqual(["foo", ["foo"], ["foo"], {}, {"foo": "bar"}, [{}]], new_list)
  903. # Ignore NoneType
  904. old_list = [
  905. "foo",
  906. ["foo"],
  907. ["foo", None],
  908. {"foo": 0},
  909. {"foo": "bar", "baz": []},
  910. [{"foo": ""}],
  911. ]
  912. new_list = salt.utils.data.filter_falsey(
  913. old_list, recurse_depth=3, ignore_types=[type(None)]
  914. )
  915. self.assertEqual(["foo", ["foo"], ["foo", None], {"foo": "bar"}], new_list)
  916. class FilterRecursiveDiff(TestCase):
  917. """
  918. Test suite for salt.utils.data.recursive_diff
  919. """
  920. def test_list_equality(self):
  921. """
  922. Test cases where equal lists are compared.
  923. """
  924. test_list = [0, 1, 2]
  925. self.assertEqual({}, salt.utils.data.recursive_diff(test_list, test_list))
  926. test_list = [[0], [1], [0, 1, 2]]
  927. self.assertEqual({}, salt.utils.data.recursive_diff(test_list, test_list))
  928. def test_dict_equality(self):
  929. """
  930. Test cases where equal dicts are compared.
  931. """
  932. test_dict = {"foo": "bar", "bar": {"baz": {"qux": "quux"}}, "frop": 0}
  933. self.assertEqual({}, salt.utils.data.recursive_diff(test_dict, test_dict))
  934. def test_ordereddict_equality(self):
  935. """
  936. Test cases where equal OrderedDicts are compared.
  937. """
  938. test_dict = OrderedDict(
  939. [
  940. ("foo", "bar"),
  941. ("bar", OrderedDict([("baz", OrderedDict([("qux", "quux")]))])),
  942. ("frop", 0),
  943. ]
  944. )
  945. self.assertEqual({}, salt.utils.data.recursive_diff(test_dict, test_dict))
  946. def test_mixed_equality(self):
  947. """
  948. Test cases where mixed nested lists and dicts are compared.
  949. """
  950. test_data = {
  951. "foo": "bar",
  952. "baz": [0, 1, 2],
  953. "bar": {"baz": [{"qux": "quux"}, {"froop", 0}]},
  954. }
  955. self.assertEqual({}, salt.utils.data.recursive_diff(test_data, test_data))
  956. def test_set_equality(self):
  957. """
  958. Test cases where equal sets are compared.
  959. """
  960. test_set = set([0, 1, 2, 3, "foo"])
  961. self.assertEqual({}, salt.utils.data.recursive_diff(test_set, test_set))
  962. # This is a bit of an oddity, as python seems to sort the sets in memory
  963. # so both sets end up with the same ordering (0..3).
  964. set_one = set([0, 1, 2, 3])
  965. set_two = set([3, 2, 1, 0])
  966. self.assertEqual({}, salt.utils.data.recursive_diff(set_one, set_two))
  967. def test_tuple_equality(self):
  968. """
  969. Test cases where equal tuples are compared.
  970. """
  971. test_tuple = (0, 1, 2, 3, "foo")
  972. self.assertEqual({}, salt.utils.data.recursive_diff(test_tuple, test_tuple))
  973. def test_list_inequality(self):
  974. """
  975. Test cases where two inequal lists are compared.
  976. """
  977. list_one = [0, 1, 2]
  978. list_two = ["foo", "bar", "baz"]
  979. expected_result = {"old": list_one, "new": list_two}
  980. self.assertEqual(
  981. expected_result, salt.utils.data.recursive_diff(list_one, list_two)
  982. )
  983. expected_result = {"new": list_one, "old": list_two}
  984. self.assertEqual(
  985. expected_result, salt.utils.data.recursive_diff(list_two, list_one)
  986. )
  987. list_one = [0, "foo", 1, "bar"]
  988. list_two = [1, "foo", 1, "qux"]
  989. expected_result = {"old": [0, "bar"], "new": [1, "qux"]}
  990. self.assertEqual(
  991. expected_result, salt.utils.data.recursive_diff(list_one, list_two)
  992. )
  993. expected_result = {"new": [0, "bar"], "old": [1, "qux"]}
  994. self.assertEqual(
  995. expected_result, salt.utils.data.recursive_diff(list_two, list_one)
  996. )
  997. list_one = [0, 1, [2, 3]]
  998. list_two = [0, 1, ["foo", "bar"]]
  999. expected_result = {"old": [[2, 3]], "new": [["foo", "bar"]]}
  1000. self.assertEqual(
  1001. expected_result, salt.utils.data.recursive_diff(list_one, list_two)
  1002. )
  1003. expected_result = {"new": [[2, 3]], "old": [["foo", "bar"]]}
  1004. self.assertEqual(
  1005. expected_result, salt.utils.data.recursive_diff(list_two, list_one)
  1006. )
  1007. def test_dict_inequality(self):
  1008. """
  1009. Test cases where two inequal dicts are compared.
  1010. """
  1011. dict_one = {"foo": 1, "bar": 2, "baz": 3}
  1012. dict_two = {"foo": 2, 1: "bar", "baz": 3}
  1013. expected_result = {"old": {"foo": 1, "bar": 2}, "new": {"foo": 2, 1: "bar"}}
  1014. self.assertEqual(
  1015. expected_result, salt.utils.data.recursive_diff(dict_one, dict_two)
  1016. )
  1017. expected_result = {"new": {"foo": 1, "bar": 2}, "old": {"foo": 2, 1: "bar"}}
  1018. self.assertEqual(
  1019. expected_result, salt.utils.data.recursive_diff(dict_two, dict_one)
  1020. )
  1021. dict_one = {"foo": {"bar": {"baz": 1}}}
  1022. dict_two = {"foo": {"qux": {"baz": 1}}}
  1023. expected_result = {"old": dict_one, "new": dict_two}
  1024. self.assertEqual(
  1025. expected_result, salt.utils.data.recursive_diff(dict_one, dict_two)
  1026. )
  1027. expected_result = {"new": dict_one, "old": dict_two}
  1028. self.assertEqual(
  1029. expected_result, salt.utils.data.recursive_diff(dict_two, dict_one)
  1030. )
  1031. def test_ordereddict_inequality(self):
  1032. """
  1033. Test cases where two inequal OrderedDicts are compared.
  1034. """
  1035. odict_one = OrderedDict([("foo", "bar"), ("bar", "baz")])
  1036. odict_two = OrderedDict([("bar", "baz"), ("foo", "bar")])
  1037. expected_result = {"old": odict_one, "new": odict_two}
  1038. self.assertEqual(
  1039. expected_result, salt.utils.data.recursive_diff(odict_one, odict_two)
  1040. )
  1041. def test_set_inequality(self):
  1042. """
  1043. Test cases where two inequal sets are compared.
  1044. Tricky as the sets are compared zipped, so shuffled sets of equal values
  1045. are considered different.
  1046. """
  1047. set_one = set([0, 1, 2, 4])
  1048. set_two = set([0, 1, 3, 4])
  1049. expected_result = {"old": set([2]), "new": set([3])}
  1050. self.assertEqual(
  1051. expected_result, salt.utils.data.recursive_diff(set_one, set_two)
  1052. )
  1053. expected_result = {"new": set([2]), "old": set([3])}
  1054. self.assertEqual(
  1055. expected_result, salt.utils.data.recursive_diff(set_two, set_one)
  1056. )
  1057. # It is unknown how different python versions will store sets in memory.
  1058. # Python 2.7 seems to sort it (i.e. set_one below becomes {0, 1, 'foo', 'bar'}
  1059. # However Python 3.6.8 stores it differently each run.
  1060. # So just test for "not equal" here.
  1061. set_one = set([0, "foo", 1, "bar"])
  1062. set_two = set(["foo", 1, "bar", 2])
  1063. expected_result = {}
  1064. self.assertNotEqual(
  1065. expected_result, salt.utils.data.recursive_diff(set_one, set_two)
  1066. )
  1067. def test_mixed_inequality(self):
  1068. """
  1069. Test cases where two mixed dicts/iterables that are different are compared.
  1070. """
  1071. dict_one = {"foo": [1, 2, 3]}
  1072. dict_two = {"foo": [3, 2, 1]}
  1073. expected_result = {"old": {"foo": [1, 3]}, "new": {"foo": [3, 1]}}
  1074. self.assertEqual(
  1075. expected_result, salt.utils.data.recursive_diff(dict_one, dict_two)
  1076. )
  1077. expected_result = {"new": {"foo": [1, 3]}, "old": {"foo": [3, 1]}}
  1078. self.assertEqual(
  1079. expected_result, salt.utils.data.recursive_diff(dict_two, dict_one)
  1080. )
  1081. list_one = [1, 2, {"foo": ["bar", {"foo": 1, "bar": 2}]}]
  1082. list_two = [3, 4, {"foo": ["qux", {"foo": 1, "bar": 2}]}]
  1083. expected_result = {
  1084. "old": [1, 2, {"foo": ["bar"]}],
  1085. "new": [3, 4, {"foo": ["qux"]}],
  1086. }
  1087. self.assertEqual(
  1088. expected_result, salt.utils.data.recursive_diff(list_one, list_two)
  1089. )
  1090. expected_result = {
  1091. "new": [1, 2, {"foo": ["bar"]}],
  1092. "old": [3, 4, {"foo": ["qux"]}],
  1093. }
  1094. self.assertEqual(
  1095. expected_result, salt.utils.data.recursive_diff(list_two, list_one)
  1096. )
  1097. mixed_one = {"foo": set([0, 1, 2]), "bar": [0, 1, 2]}
  1098. mixed_two = {"foo": set([1, 2, 3]), "bar": [1, 2, 3]}
  1099. expected_result = {
  1100. "old": {"foo": set([0]), "bar": [0, 1, 2]},
  1101. "new": {"foo": set([3]), "bar": [1, 2, 3]},
  1102. }
  1103. self.assertEqual(
  1104. expected_result, salt.utils.data.recursive_diff(mixed_one, mixed_two)
  1105. )
  1106. expected_result = {
  1107. "new": {"foo": set([0]), "bar": [0, 1, 2]},
  1108. "old": {"foo": set([3]), "bar": [1, 2, 3]},
  1109. }
  1110. self.assertEqual(
  1111. expected_result, salt.utils.data.recursive_diff(mixed_two, mixed_one)
  1112. )
  1113. def test_tuple_inequality(self):
  1114. """
  1115. Test cases where two tuples that are different are compared.
  1116. """
  1117. tuple_one = (1, 2, 3)
  1118. tuple_two = (3, 2, 1)
  1119. expected_result = {"old": (1, 3), "new": (3, 1)}
  1120. self.assertEqual(
  1121. expected_result, salt.utils.data.recursive_diff(tuple_one, tuple_two)
  1122. )
  1123. def test_list_vs_set(self):
  1124. """
  1125. Test case comparing a list with a set, will be compared unordered.
  1126. """
  1127. mixed_one = [1, 2, 3]
  1128. mixed_two = set([3, 2, 1])
  1129. expected_result = {}
  1130. self.assertEqual(
  1131. expected_result, salt.utils.data.recursive_diff(mixed_one, mixed_two)
  1132. )
  1133. self.assertEqual(
  1134. expected_result, salt.utils.data.recursive_diff(mixed_two, mixed_one)
  1135. )
  1136. def test_dict_vs_ordereddict(self):
  1137. """
  1138. Test case comparing a dict with an ordereddict, will be compared unordered.
  1139. """
  1140. test_dict = {"foo": "bar", "bar": "baz"}
  1141. test_odict = OrderedDict([("foo", "bar"), ("bar", "baz")])
  1142. self.assertEqual({}, salt.utils.data.recursive_diff(test_dict, test_odict))
  1143. self.assertEqual({}, salt.utils.data.recursive_diff(test_odict, test_dict))
  1144. test_odict2 = OrderedDict([("bar", "baz"), ("foo", "bar")])
  1145. self.assertEqual({}, salt.utils.data.recursive_diff(test_dict, test_odict2))
  1146. self.assertEqual({}, salt.utils.data.recursive_diff(test_odict2, test_dict))
  1147. def test_list_ignore_ignored(self):
  1148. """
  1149. Test case comparing two lists with ignore-list supplied (which is not used
  1150. when comparing lists).
  1151. """
  1152. list_one = [1, 2, 3]
  1153. list_two = [3, 2, 1]
  1154. expected_result = {"old": [1, 3], "new": [3, 1]}
  1155. self.assertEqual(
  1156. expected_result,
  1157. salt.utils.data.recursive_diff(list_one, list_two, ignore_keys=[1, 3]),
  1158. )
  1159. def test_dict_ignore(self):
  1160. """
  1161. Test case comparing two dicts with ignore-list supplied.
  1162. """
  1163. dict_one = {"foo": 1, "bar": 2, "baz": 3}
  1164. dict_two = {"foo": 3, "bar": 2, "baz": 1}
  1165. expected_result = {"old": {"baz": 3}, "new": {"baz": 1}}
  1166. self.assertEqual(
  1167. expected_result,
  1168. salt.utils.data.recursive_diff(dict_one, dict_two, ignore_keys=["foo"]),
  1169. )
  1170. def test_ordereddict_ignore(self):
  1171. """
  1172. Test case comparing two OrderedDicts with ignore-list supplied.
  1173. """
  1174. odict_one = OrderedDict([("foo", 1), ("bar", 2), ("baz", 3)])
  1175. odict_two = OrderedDict([("baz", 1), ("bar", 2), ("foo", 3)])
  1176. # The key 'foo' will be ignored, which means the key from the other OrderedDict
  1177. # will always be considered "different" since OrderedDicts are compared ordered.
  1178. expected_result = {
  1179. "old": OrderedDict([("baz", 3)]),
  1180. "new": OrderedDict([("baz", 1)]),
  1181. }
  1182. self.assertEqual(
  1183. expected_result,
  1184. salt.utils.data.recursive_diff(odict_one, odict_two, ignore_keys=["foo"]),
  1185. )
  1186. def test_dict_vs_ordereddict_ignore(self):
  1187. """
  1188. Test case comparing a dict with an OrderedDict with ignore-list supplied.
  1189. """
  1190. dict_one = {"foo": 1, "bar": 2, "baz": 3}
  1191. odict_two = OrderedDict([("foo", 3), ("bar", 2), ("baz", 1)])
  1192. expected_result = {"old": {"baz": 3}, "new": OrderedDict([("baz", 1)])}
  1193. self.assertEqual(
  1194. expected_result,
  1195. salt.utils.data.recursive_diff(dict_one, odict_two, ignore_keys=["foo"]),
  1196. )
  1197. def test_mixed_nested_ignore(self):
  1198. """
  1199. Test case comparing mixed, nested items with ignore-list supplied.
  1200. """
  1201. dict_one = {"foo": [1], "bar": {"foo": 1, "bar": 2}, "baz": 3}
  1202. dict_two = {"foo": [2], "bar": {"foo": 3, "bar": 2}, "baz": 1}
  1203. expected_result = {"old": {"baz": 3}, "new": {"baz": 1}}
  1204. self.assertEqual(
  1205. expected_result,
  1206. salt.utils.data.recursive_diff(dict_one, dict_two, ignore_keys=["foo"]),
  1207. )
  1208. def test_ordered_dict_unequal_length(self):
  1209. """
  1210. Test case comparing two OrderedDicts of unequal length.
  1211. """
  1212. odict_one = OrderedDict([("foo", 1), ("bar", 2), ("baz", 3)])
  1213. odict_two = OrderedDict([("foo", 1), ("bar", 2)])
  1214. expected_result = {"old": OrderedDict([("baz", 3)]), "new": {}}
  1215. self.assertEqual(
  1216. expected_result, salt.utils.data.recursive_diff(odict_one, odict_two)
  1217. )
  1218. def test_list_unequal_length(self):
  1219. """
  1220. Test case comparing two lists of unequal length.
  1221. """
  1222. list_one = [1, 2, 3]
  1223. list_two = [1, 2, 3, 4]
  1224. expected_result = {"old": [], "new": [4]}
  1225. self.assertEqual(
  1226. expected_result, salt.utils.data.recursive_diff(list_one, list_two)
  1227. )
  1228. def test_set_unequal_length(self):
  1229. """
  1230. Test case comparing two sets of unequal length.
  1231. This does not do anything special, as it is unordered.
  1232. """
  1233. set_one = set([1, 2, 3])
  1234. set_two = set([4, 3, 2, 1])
  1235. expected_result = {"old": set([]), "new": set([4])}
  1236. self.assertEqual(
  1237. expected_result, salt.utils.data.recursive_diff(set_one, set_two)
  1238. )
  1239. def test_tuple_unequal_length(self):
  1240. """
  1241. Test case comparing two tuples of unequal length.
  1242. This should be the same as comparing two ordered lists.
  1243. """
  1244. tuple_one = (1, 2, 3)
  1245. tuple_two = (1, 2, 3, 4)
  1246. expected_result = {"old": (), "new": (4,)}
  1247. self.assertEqual(
  1248. expected_result, salt.utils.data.recursive_diff(tuple_one, tuple_two)
  1249. )
  1250. def test_list_unordered(self):
  1251. """
  1252. Test case comparing two lists unordered.
  1253. """
  1254. list_one = [1, 2, 3, 4]
  1255. list_two = [4, 3, 2]
  1256. expected_result = {"old": [1], "new": []}
  1257. self.assertEqual(
  1258. expected_result,
  1259. salt.utils.data.recursive_diff(list_one, list_two, ignore_order=True),
  1260. )
  1261. def test_mixed_nested_unordered(self):
  1262. """
  1263. Test case comparing nested dicts/lists unordered.
  1264. """
  1265. dict_one = {"foo": {"bar": [1, 2, 3]}, "bar": [{"foo": 4}, 0]}
  1266. dict_two = {"foo": {"bar": [3, 2, 1]}, "bar": [0, {"foo": 4}]}
  1267. expected_result = {}
  1268. self.assertEqual(
  1269. expected_result,
  1270. salt.utils.data.recursive_diff(dict_one, dict_two, ignore_order=True),
  1271. )
  1272. expected_result = {
  1273. "old": {"foo": {"bar": [1, 3]}, "bar": [{"foo": 4}, 0]},
  1274. "new": {"foo": {"bar": [3, 1]}, "bar": [0, {"foo": 4}]},
  1275. }
  1276. self.assertEqual(
  1277. expected_result, salt.utils.data.recursive_diff(dict_one, dict_two)
  1278. )
  1279. def test_ordered_dict_unordered(self):
  1280. """
  1281. Test case comparing OrderedDicts unordered.
  1282. """
  1283. odict_one = OrderedDict([("foo", 1), ("bar", 2), ("baz", 3)])
  1284. odict_two = OrderedDict([("baz", 3), ("bar", 2), ("foo", 1)])
  1285. expected_result = {}
  1286. self.assertEqual(
  1287. expected_result,
  1288. salt.utils.data.recursive_diff(odict_one, odict_two, ignore_order=True),
  1289. )
  1290. def test_ignore_missing_keys_dict(self):
  1291. """
  1292. Test case ignoring missing keys on a comparison of dicts.
  1293. """
  1294. dict_one = {"foo": 1, "bar": 2, "baz": 3}
  1295. dict_two = {"bar": 3}
  1296. expected_result = {"old": {"bar": 2}, "new": {"bar": 3}}
  1297. self.assertEqual(
  1298. expected_result,
  1299. salt.utils.data.recursive_diff(
  1300. dict_one, dict_two, ignore_missing_keys=True
  1301. ),
  1302. )
  1303. def test_ignore_missing_keys_ordered_dict(self):
  1304. """
  1305. Test case not ignoring missing keys on a comparison of OrderedDicts.
  1306. """
  1307. odict_one = OrderedDict([("foo", 1), ("bar", 2), ("baz", 3)])
  1308. odict_two = OrderedDict([("bar", 3)])
  1309. expected_result = {"old": odict_one, "new": odict_two}
  1310. self.assertEqual(
  1311. expected_result,
  1312. salt.utils.data.recursive_diff(
  1313. odict_one, odict_two, ignore_missing_keys=True
  1314. ),
  1315. )
  1316. def test_ignore_missing_keys_recursive(self):
  1317. """
  1318. Test case ignoring missing keys on a comparison of nested dicts.
  1319. """
  1320. dict_one = {"foo": {"bar": 2, "baz": 3}}
  1321. dict_two = {"foo": {"baz": 3}}
  1322. expected_result = {}
  1323. self.assertEqual(
  1324. expected_result,
  1325. salt.utils.data.recursive_diff(
  1326. dict_one, dict_two, ignore_missing_keys=True
  1327. ),
  1328. )
  1329. # Compare from dict-in-dict
  1330. dict_two = {}
  1331. self.assertEqual(
  1332. expected_result,
  1333. salt.utils.data.recursive_diff(
  1334. dict_one, dict_two, ignore_missing_keys=True
  1335. ),
  1336. )
  1337. # Compare from dict-in-list
  1338. dict_one = {"foo": ["bar", {"baz": 3}]}
  1339. dict_two = {"foo": ["bar", {}]}
  1340. self.assertEqual(
  1341. expected_result,
  1342. salt.utils.data.recursive_diff(
  1343. dict_one, dict_two, ignore_missing_keys=True
  1344. ),
  1345. )