test_dictupdate.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import copy
  5. # Import Salt Testing libs
  6. from tests.support.unit import TestCase
  7. # Import Salt libs
  8. import salt.utils.dictupdate as dictupdate
  9. from salt.utils.odict import OrderedDict
  10. from salt.exceptions import SaltInvocationError
  11. class UtilDictupdateTestCase(TestCase):
  12. dict1 = {'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J'}}}
  13. def test_update(self):
  14. # level 1 value changes
  15. mdict = copy.deepcopy(self.dict1)
  16. mdict['A'] = 'Z'
  17. res = dictupdate.update(copy.deepcopy(self.dict1), {'A': 'Z'})
  18. self.assertEqual(res, mdict)
  19. # level 1 value changes (list replacement)
  20. mdict = copy.deepcopy(self.dict1)
  21. mdict['A'] = [1, 2]
  22. res = dictupdate.update(copy.deepcopy(mdict), {'A': [2, 3]},
  23. merge_lists=False)
  24. mdict['A'] = [2, 3]
  25. self.assertEqual(res, mdict)
  26. # level 1 value changes (list merge)
  27. mdict = copy.deepcopy(self.dict1)
  28. mdict['A'] = [1, 2]
  29. res = dictupdate.update(copy.deepcopy(mdict), {'A': [3, 4]},
  30. merge_lists=True)
  31. mdict['A'] = [1, 2, 3, 4]
  32. self.assertEqual(res, mdict)
  33. # level 1 value changes (list merge, remove duplicates, preserve order)
  34. mdict = copy.deepcopy(self.dict1)
  35. mdict['A'] = [1, 2]
  36. res = dictupdate.update(copy.deepcopy(mdict), {'A': [4, 3, 2, 1]},
  37. merge_lists=True)
  38. mdict['A'] = [1, 2, 4, 3]
  39. self.assertEqual(res, mdict)
  40. # level 2 value changes
  41. mdict = copy.deepcopy(self.dict1)
  42. mdict['C']['D'] = 'Z'
  43. res = dictupdate.update(copy.deepcopy(self.dict1), {'C': {'D': 'Z'}})
  44. self.assertEqual(res, mdict)
  45. # level 2 value changes (list replacement)
  46. mdict = copy.deepcopy(self.dict1)
  47. mdict['C']['D'] = ['a', 'b']
  48. res = dictupdate.update(copy.deepcopy(mdict), {'C': {'D': ['c', 'd']}},
  49. merge_lists=False)
  50. mdict['C']['D'] = ['c', 'd']
  51. self.assertEqual(res, mdict)
  52. # level 2 value changes (list merge)
  53. mdict = copy.deepcopy(self.dict1)
  54. mdict['C']['D'] = ['a', 'b']
  55. res = dictupdate.update(copy.deepcopy(mdict), {'C': {'D': ['c', 'd']}},
  56. merge_lists=True)
  57. mdict['C']['D'] = ['a', 'b', 'c', 'd']
  58. self.assertEqual(res, mdict)
  59. # level 2 value changes (list merge, remove duplicates, preserve order)
  60. mdict = copy.deepcopy(self.dict1)
  61. mdict['C']['D'] = ['a', 'b']
  62. res = dictupdate.update(copy.deepcopy(mdict),
  63. {'C': {'D': ['d', 'c', 'b', 'a']}},
  64. merge_lists=True)
  65. mdict['C']['D'] = ['a', 'b', 'd', 'c']
  66. self.assertEqual(res, mdict)
  67. # level 3 value changes
  68. mdict = copy.deepcopy(self.dict1)
  69. mdict['C']['F']['G'] = 'Z'
  70. res = dictupdate.update(
  71. copy.deepcopy(self.dict1),
  72. {'C': {'F': {'G': 'Z'}}}
  73. )
  74. self.assertEqual(res, mdict)
  75. # level 3 value changes (list replacement)
  76. mdict = copy.deepcopy(self.dict1)
  77. mdict['C']['F']['G'] = ['a', 'b']
  78. res = dictupdate.update(copy.deepcopy(mdict),
  79. {'C': {'F': {'G': ['c', 'd']}}},
  80. merge_lists=False)
  81. mdict['C']['F']['G'] = ['c', 'd']
  82. self.assertEqual(res, mdict)
  83. # level 3 value changes (list merge)
  84. mdict = copy.deepcopy(self.dict1)
  85. mdict['C']['F']['G'] = ['a', 'b']
  86. res = dictupdate.update(copy.deepcopy(mdict),
  87. {'C': {'F': {'G': ['c', 'd']}}},
  88. merge_lists=True)
  89. mdict['C']['F']['G'] = ['a', 'b', 'c', 'd']
  90. self.assertEqual(res, mdict)
  91. # level 3 value changes (list merge, remove duplicates, preserve order)
  92. mdict = copy.deepcopy(self.dict1)
  93. mdict['C']['F']['G'] = ['a', 'b']
  94. res = dictupdate.update(copy.deepcopy(mdict),
  95. {'C': {'F': {'G': ['d', 'c', 'b', 'a']}}},
  96. merge_lists=True)
  97. mdict['C']['F']['G'] = ['a', 'b', 'd', 'c']
  98. self.assertEqual(res, mdict)
  99. # replace a sub-dictionary
  100. mdict = copy.deepcopy(self.dict1)
  101. mdict['C'] = 'Z'
  102. res = dictupdate.update(copy.deepcopy(self.dict1), {'C': 'Z'})
  103. self.assertEqual(res, mdict)
  104. # add a new scalar value
  105. mdict = copy.deepcopy(self.dict1)
  106. mdict['Z'] = 'Y'
  107. res = dictupdate.update(copy.deepcopy(self.dict1), {'Z': 'Y'})
  108. self.assertEqual(res, mdict)
  109. # add a dictionary
  110. mdict = copy.deepcopy(self.dict1)
  111. mdict['Z'] = {'Y': 'X'}
  112. res = dictupdate.update(copy.deepcopy(self.dict1), {'Z': {'Y': 'X'}})
  113. self.assertEqual(res, mdict)
  114. # add a nested dictionary
  115. mdict = copy.deepcopy(self.dict1)
  116. mdict['Z'] = {'Y': {'X': 'W'}}
  117. res = dictupdate.update(
  118. copy.deepcopy(self.dict1),
  119. {'Z': {'Y': {'X': 'W'}}}
  120. )
  121. self.assertEqual(res, mdict)
  122. class UtilDictMergeTestCase(TestCase):
  123. dict1 = {'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J'}}}
  124. def test_merge_overwrite_traditional(self):
  125. '''
  126. Test traditional overwrite, wherein a key in the second dict overwrites a key in the first
  127. '''
  128. mdict = copy.deepcopy(self.dict1)
  129. mdict['A'] = 'b'
  130. ret = dictupdate.merge_overwrite(copy.deepcopy(self.dict1), {'A': 'b'})
  131. self.assertEqual(mdict, ret)
  132. def test_merge_overwrite_missing_source_key(self):
  133. '''
  134. Test case wherein the overwrite strategy is used but a key in the second dict is
  135. not present in the first
  136. '''
  137. mdict = copy.deepcopy(self.dict1)
  138. mdict['D'] = 'new'
  139. ret = dictupdate.merge_overwrite(copy.deepcopy(self.dict1), {'D': 'new'})
  140. self.assertEqual(mdict, ret)
  141. def test_merge_aggregate_traditional(self):
  142. '''
  143. Test traditional aggregation, where a val from dict2 overwrites one
  144. present in dict1
  145. '''
  146. mdict = copy.deepcopy(self.dict1)
  147. mdict['A'] = 'b'
  148. ret = dictupdate.merge_overwrite(copy.deepcopy(self.dict1), {'A': 'b'})
  149. self.assertEqual(mdict, ret)
  150. def test_merge_list_traditional(self):
  151. '''
  152. Test traditional list merge, where a key present in dict2 will be converted
  153. to a list
  154. '''
  155. mdict = copy.deepcopy(self.dict1)
  156. mdict['A'] = ['B', 'b']
  157. ret = dictupdate.merge_list(copy.deepcopy(self.dict1), {'A': 'b'})
  158. self.assertEqual(mdict, ret)
  159. def test_merge_list_append(self):
  160. '''
  161. This codifies the intended behaviour that items merged into a dict val that is already
  162. a list that those items will *appended* to the list, and not magically merged in
  163. '''
  164. mdict = copy.deepcopy(self.dict1)
  165. mdict['A'] = ['B', 'b', 'c']
  166. # Prepare a modified copy of dict1 that has a list as a val for the key of 'A'
  167. mdict1 = copy.deepcopy(self.dict1)
  168. mdict1['A'] = ['B']
  169. ret = dictupdate.merge_list(mdict1, {'A': ['b', 'c']})
  170. self.assertEqual({'A': [['B'], ['b', 'c']], 'C': {'D': 'E', 'F': {'I': 'J', 'G': 'H'}}}, ret)
  171. class UtilDeepDictUpdateTestCase(TestCase):
  172. dict1 = {'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J'}}}
  173. def test_deep_set_overwrite(self):
  174. '''
  175. Test overwriting an existing value.
  176. '''
  177. mdict = copy.deepcopy(self.dict1)
  178. res = dictupdate.set_dict_key_value(mdict, 'C:F', 'foo')
  179. self.assertEqual({'A': 'B', 'C': {'D': 'E', 'F': 'foo'}}, res)
  180. # Verify modify-in-place
  181. self.assertEqual({'A': 'B', 'C': {'D': 'E', 'F': 'foo'}}, mdict)
  182. # Test using alternative delimiter
  183. res = dictupdate.set_dict_key_value(mdict, 'C/F', {'G': 'H', 'I': 'J'}, delimiter='/')
  184. self.assertEqual(self.dict1, res)
  185. # Test without using a delimiter in the keys
  186. res = dictupdate.set_dict_key_value(mdict, 'C', None)
  187. self.assertEqual({'A': 'B', 'C': None}, res)
  188. def test_deep_set_create(self):
  189. '''
  190. Test creating new nested keys.
  191. '''
  192. mdict = copy.deepcopy(self.dict1)
  193. res = dictupdate.set_dict_key_value(mdict, 'K:L:M', 'Q')
  194. self.assertEqual({'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J'}}, 'K': {'L': {'M': 'Q'}}}, res)
  195. def test_deep_set_ordered_dicts(self):
  196. '''
  197. Test creating new nested ordereddicts.
  198. '''
  199. res = dictupdate.set_dict_key_value({}, 'A:B', 'foo', ordered_dict=True)
  200. self.assertEqual({'A': OrderedDict([('B', 'foo')])}, res)
  201. def test_deep_append(self):
  202. '''
  203. Test appending to a list.
  204. '''
  205. sdict = {'bar': {'baz': [1, 2]}}
  206. res = dictupdate.append_dict_key_value(sdict, 'bar:baz', 42)
  207. self.assertEqual({'bar': {'baz': [1, 2, 42]}}, res)
  208. # Append with alternate delimiter
  209. res = dictupdate.append_dict_key_value(sdict, 'bar~baz', 43, delimiter='~')
  210. self.assertEqual({'bar': {'baz': [1, 2, 42, 43]}}, res)
  211. # Append to a not-yet existing list
  212. res = dictupdate.append_dict_key_value({}, 'foo:bar:baz', 42)
  213. self.assertEqual({'foo': {'bar': {'baz': [42]}}}, res)
  214. def test_deep_extend(self):
  215. '''
  216. Test extending a list.
  217. Note that the provided value (to extend with) will be coerced to a list
  218. if this is not already a list. This can cause unexpected behaviour.
  219. '''
  220. sdict = {'bar': {'baz': [1, 2]}}
  221. res = dictupdate.extend_dict_key_value(sdict, 'bar:baz', [42, 42])
  222. self.assertEqual({'bar': {'baz': [1, 2, 42, 42]}}, res)
  223. # Extend a not-yet existing list
  224. res = dictupdate.extend_dict_key_value({}, 'bar:baz:qux', [42])
  225. self.assertEqual({'bar': {'baz': {'qux': [42]}}}, res)
  226. # Extend with a dict (remember, foo has been updated in the first test)
  227. res = dictupdate.extend_dict_key_value(sdict, 'bar:baz', {'qux': 'quux'})
  228. self.assertEqual({'bar': {'baz': [1, 2, 42, 42, 'qux']}}, res)
  229. def test_deep_extend_illegal_addition(self):
  230. '''
  231. Test errorhandling extending lists with illegal types.
  232. '''
  233. # Extend with an illegal type
  234. for extend_with in [42, None]:
  235. with self.assertRaisesRegex(SaltInvocationError,
  236. r"Cannot extend {} with a {}."
  237. "".format(type([]), type(extend_with))):
  238. dictupdate.extend_dict_key_value({}, 'foo', extend_with)
  239. def test_deep_extend_illegal_source(self):
  240. '''
  241. Test errorhandling extending things that are not a list.
  242. '''
  243. # Extend an illegal type
  244. for extend_this in [{}, 42, 'bar']:
  245. with self.assertRaisesRegex(SaltInvocationError,
  246. r"The last key contains a {}, which cannot extend."
  247. "".format(type(extend_this))):
  248. dictupdate.extend_dict_key_value({'foo': extend_this}, 'foo', [42])
  249. def test_deep_update(self):
  250. '''
  251. Test updating a (sub)dict.
  252. '''
  253. mdict = copy.deepcopy(self.dict1)
  254. res = dictupdate.update_dict_key_value(mdict, 'C:F', {'foo': 'bar', 'qux': 'quux'})
  255. self.assertEqual({'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J', 'foo': 'bar', 'qux': 'quux'}}}, res)
  256. # Test updating a non-existing subkey
  257. res = dictupdate.update_dict_key_value({}, 'foo:bar:baz', {'qux': 'quux'})
  258. self.assertEqual({'foo': {'bar': {'baz': {'qux': 'quux'}}}}, res)
  259. # Test updating a non-existing subkey, with a different delimiter
  260. res = dictupdate.update_dict_key_value({}, 'foo bar baz', {'qux': 'quux'}, delimiter=' ')
  261. self.assertEqual({'foo': {'bar': {'baz': {'qux': 'quux'}}}}, res)
  262. def test_deep_update_illegal_update(self):
  263. '''
  264. Test errorhandling updating a (sub)dict with illegal types.
  265. '''
  266. # Update with an illegal type
  267. for update_with in [42, None, [42], 'bar']:
  268. with self.assertRaisesRegex(SaltInvocationError,
  269. r"Cannot update {} with a {}."
  270. "".format(type({}), type(update_with))):
  271. dictupdate.update_dict_key_value({}, 'foo', update_with)
  272. # Again, but now using OrderedDicts
  273. for update_with in [42, None, [42], 'bar']:
  274. with self.assertRaisesRegex(SaltInvocationError,
  275. r"Cannot update {} with a {}."
  276. "".format(type(OrderedDict()), type(update_with))):
  277. dictupdate.update_dict_key_value({}, 'foo', update_with, ordered_dict=True)