test_data.py 54 KB

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