2014-10-24 17:13:47 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2014-11-21 19:05:13 +00:00
|
|
|
# Import python libs
|
2018-01-12 21:03:04 +00:00
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
2014-10-24 17:13:47 +00:00
|
|
|
import copy
|
|
|
|
|
|
|
|
# Import Salt Testing libs
|
2017-02-27 13:58:07 +00:00
|
|
|
from tests.support.unit import TestCase
|
2014-10-24 17:13:47 +00:00
|
|
|
|
|
|
|
# Import Salt libs
|
Use explicit unicode strings + break up salt.utils
This PR is part of what will be an ongoing effort to use explicit
unicode strings in Salt. Because Python 3 does not suport Python 2's raw
unicode string syntax (i.e. `ur'\d+'`), we must use
`salt.utils.locales.sdecode()` to ensure that the raw string is unicode.
However, because of how `salt/utils/__init__.py` has evolved into the
hulking monstrosity it is today, this means importing a large module in
places where it is not needed, which could negatively impact
performance. For this reason, this PR also breaks out some of the
functions from `salt/utils/__init__.py` into new/existing modules under
`salt/utils/`. The long term goal will be that the modules within this
directory do not depend on importing `salt.utils`.
A summary of the changes in this PR is as follows:
* Moves the following functions from `salt.utils` to new locations
(including a deprecation warning if invoked from `salt.utils`):
`to_bytes`, `to_str`, `to_unicode`, `str_to_num`, `is_quoted`,
`dequote`, `is_hex`, `is_bin_str`, `rand_string`,
`contains_whitespace`, `clean_kwargs`, `invalid_kwargs`, `which`,
`which_bin`, `path_join`, `shlex_split`, `rand_str`, `is_windows`,
`is_proxy`, `is_linux`, `is_darwin`, `is_sunos`, `is_smartos`,
`is_smartos_globalzone`, `is_smartos_zone`, `is_freebsd`, `is_netbsd`,
`is_openbsd`, `is_aix`
* Moves the functions already deprecated by @rallytime to the bottom of
`salt/utils/__init__.py` for better organization, so we can keep the
deprecated ones separate from the ones yet to be deprecated as we
continue to break up `salt.utils`
* Updates `salt/*.py` and all files under `salt/client/` to use explicit
unicode string literals.
* Gets rid of implicit imports of `salt.utils` (e.g. `from salt.utils
import foo` becomes `import salt.utils.foo as foo`).
* Renames the `test.rand_str` function to `test.random_hash` to more
accurately reflect what it does
* Modifies `salt.utils.stringutils.random()` (née `salt.utils.rand_string()`)
such that it returns a string matching the passed size. Previously
this function would get `size` bytes from `os.urandom()`,
base64-encode it, and return the result, which would in most cases not
be equal to the passed size.
2017-07-25 01:47:15 +00:00
|
|
|
import salt.utils.dictupdate as dictupdate
|
2014-10-24 17:13:47 +00:00
|
|
|
|
2014-10-24 17:32:12 +00:00
|
|
|
|
2014-10-24 17:13:47 +00:00
|
|
|
class UtilDictupdateTestCase(TestCase):
|
|
|
|
|
|
|
|
dict1 = {'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J'}}}
|
|
|
|
|
|
|
|
def test_update(self):
|
|
|
|
|
|
|
|
# level 1 value changes
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = 'Z'
|
|
|
|
res = dictupdate.update(copy.deepcopy(self.dict1), {'A': 'Z'})
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2015-10-13 04:59:39 +00:00
|
|
|
# level 1 value changes (list replacement)
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = [1, 2]
|
2015-12-29 17:22:54 +00:00
|
|
|
res = dictupdate.update(copy.deepcopy(mdict), {'A': [2, 3]},
|
|
|
|
merge_lists=False)
|
2015-10-13 04:59:39 +00:00
|
|
|
mdict['A'] = [2, 3]
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
|
|
|
# level 1 value changes (list merge)
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = [1, 2]
|
2015-10-13 15:15:12 +00:00
|
|
|
res = dictupdate.update(copy.deepcopy(mdict), {'A': [3, 4]},
|
2015-10-13 04:59:39 +00:00
|
|
|
merge_lists=True)
|
|
|
|
mdict['A'] = [1, 2, 3, 4]
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2017-04-27 15:42:42 +00:00
|
|
|
# level 1 value changes (list merge, remove duplicates, preserve order)
|
2017-04-27 13:41:37 +00:00
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = [1, 2]
|
2017-04-27 15:42:42 +00:00
|
|
|
res = dictupdate.update(copy.deepcopy(mdict), {'A': [4, 3, 2, 1]},
|
2017-04-27 13:41:37 +00:00
|
|
|
merge_lists=True)
|
2017-04-27 15:42:42 +00:00
|
|
|
mdict['A'] = [1, 2, 4, 3]
|
2015-10-13 04:59:39 +00:00
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2014-10-24 17:13:47 +00:00
|
|
|
# level 2 value changes
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['D'] = 'Z'
|
|
|
|
res = dictupdate.update(copy.deepcopy(self.dict1), {'C': {'D': 'Z'}})
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2015-10-13 04:59:39 +00:00
|
|
|
# level 2 value changes (list replacement)
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['D'] = ['a', 'b']
|
2015-12-29 17:22:54 +00:00
|
|
|
res = dictupdate.update(copy.deepcopy(mdict), {'C': {'D': ['c', 'd']}},
|
|
|
|
merge_lists=False)
|
2015-10-13 04:59:39 +00:00
|
|
|
mdict['C']['D'] = ['c', 'd']
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
|
|
|
# level 2 value changes (list merge)
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['D'] = ['a', 'b']
|
|
|
|
res = dictupdate.update(copy.deepcopy(mdict), {'C': {'D': ['c', 'd']}},
|
|
|
|
merge_lists=True)
|
|
|
|
mdict['C']['D'] = ['a', 'b', 'c', 'd']
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2017-04-27 15:42:42 +00:00
|
|
|
# level 2 value changes (list merge, remove duplicates, preserve order)
|
2017-04-27 13:41:37 +00:00
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['D'] = ['a', 'b']
|
|
|
|
res = dictupdate.update(copy.deepcopy(mdict),
|
2017-04-27 15:42:42 +00:00
|
|
|
{'C': {'D': ['d', 'c', 'b', 'a']}},
|
2017-04-27 13:41:37 +00:00
|
|
|
merge_lists=True)
|
2017-04-27 15:42:42 +00:00
|
|
|
mdict['C']['D'] = ['a', 'b', 'd', 'c']
|
2015-10-13 04:59:39 +00:00
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2014-10-24 17:13:47 +00:00
|
|
|
# level 3 value changes
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['F']['G'] = 'Z'
|
|
|
|
res = dictupdate.update(
|
|
|
|
copy.deepcopy(self.dict1),
|
|
|
|
{'C': {'F': {'G': 'Z'}}}
|
|
|
|
)
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2015-10-13 04:59:39 +00:00
|
|
|
# level 3 value changes (list replacement)
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['F']['G'] = ['a', 'b']
|
|
|
|
res = dictupdate.update(copy.deepcopy(mdict),
|
2015-12-29 17:22:54 +00:00
|
|
|
{'C': {'F': {'G': ['c', 'd']}}}, merge_lists=False)
|
2015-10-13 04:59:39 +00:00
|
|
|
mdict['C']['F']['G'] = ['c', 'd']
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
|
|
|
# level 3 value changes (list merge)
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['F']['G'] = ['a', 'b']
|
|
|
|
res = dictupdate.update(copy.deepcopy(mdict),
|
|
|
|
{'C': {'F': {'G': ['c', 'd']}}}, merge_lists=True)
|
|
|
|
mdict['C']['F']['G'] = ['a', 'b', 'c', 'd']
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2017-04-27 15:42:42 +00:00
|
|
|
# level 3 value changes (list merge, remove duplicates, preserve order)
|
2017-04-27 13:41:37 +00:00
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C']['F']['G'] = ['a', 'b']
|
|
|
|
res = dictupdate.update(copy.deepcopy(mdict),
|
2017-04-27 15:42:42 +00:00
|
|
|
{'C': {'F': {'G': ['d', 'c', 'b', 'a']}}}, merge_lists=True)
|
|
|
|
mdict['C']['F']['G'] = ['a', 'b', 'd', 'c']
|
2015-10-13 04:59:39 +00:00
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2014-10-24 17:13:47 +00:00
|
|
|
# replace a sub-dictionary
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['C'] = 'Z'
|
|
|
|
res = dictupdate.update(copy.deepcopy(self.dict1), {'C': 'Z'})
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
|
|
|
# add a new scalar value
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['Z'] = 'Y'
|
|
|
|
res = dictupdate.update(copy.deepcopy(self.dict1), {'Z': 'Y'})
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
|
|
|
# add a dictionary
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['Z'] = {'Y': 'X'}
|
|
|
|
res = dictupdate.update(copy.deepcopy(self.dict1), {'Z': {'Y': 'X'}})
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
|
|
|
# add a nested dictionary
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['Z'] = {'Y': {'X': 'W'}}
|
|
|
|
res = dictupdate.update(
|
|
|
|
copy.deepcopy(self.dict1),
|
|
|
|
{'Z': {'Y': {'X': 'W'}}}
|
|
|
|
)
|
|
|
|
self.assertEqual(res, mdict)
|
|
|
|
|
2015-06-15 17:30:14 +00:00
|
|
|
|
|
|
|
class UtilDictMergeTestCase(TestCase):
|
|
|
|
|
|
|
|
dict1 = {'A': 'B', 'C': {'D': 'E', 'F': {'G': 'H', 'I': 'J'}}}
|
|
|
|
|
|
|
|
def test_merge_overwrite_traditional(self):
|
|
|
|
'''
|
|
|
|
Test traditional overwrite, wherein a key in the second dict overwrites a key in the first
|
|
|
|
'''
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = 'b'
|
|
|
|
ret = dictupdate.merge_overwrite(copy.deepcopy(self.dict1), {'A': 'b'})
|
|
|
|
self.assertEqual(mdict, ret)
|
|
|
|
|
|
|
|
def test_merge_overwrite_missing_source_key(self):
|
|
|
|
'''
|
|
|
|
Test case wherein the overwrite strategy is used but a key in the second dict is
|
|
|
|
not present in the first
|
|
|
|
'''
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['D'] = 'new'
|
|
|
|
ret = dictupdate.merge_overwrite(copy.deepcopy(self.dict1), {'D': 'new'})
|
|
|
|
self.assertEqual(mdict, ret)
|
|
|
|
|
|
|
|
def test_merge_aggregate_traditional(self):
|
|
|
|
'''
|
|
|
|
Test traditional aggregation, where a val from dict2 overwrites one
|
|
|
|
present in dict1
|
|
|
|
'''
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = 'b'
|
|
|
|
ret = dictupdate.merge_overwrite(copy.deepcopy(self.dict1), {'A': 'b'})
|
|
|
|
self.assertEqual(mdict, ret)
|
|
|
|
|
|
|
|
def test_merge_list_traditional(self):
|
|
|
|
'''
|
|
|
|
Test traditional list merge, where a key present in dict2 will be converted
|
|
|
|
to a list
|
|
|
|
'''
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = ['B', 'b']
|
|
|
|
ret = dictupdate.merge_list(copy.deepcopy(self.dict1), {'A': 'b'})
|
|
|
|
self.assertEqual(mdict, ret)
|
|
|
|
|
|
|
|
def test_merge_list_append(self):
|
|
|
|
'''
|
|
|
|
This codifies the intended behaviour that items merged into a dict val that is already
|
|
|
|
a list that those items will *appended* to the list, and not magically merged in
|
|
|
|
'''
|
|
|
|
mdict = copy.deepcopy(self.dict1)
|
|
|
|
mdict['A'] = ['B', 'b', 'c']
|
|
|
|
|
|
|
|
# Prepare a modified copy of dict1 that has a list as a val for the key of 'A'
|
|
|
|
mdict1 = copy.deepcopy(self.dict1)
|
|
|
|
mdict1['A'] = ['B']
|
|
|
|
ret = dictupdate.merge_list(mdict1, {'A': ['b', 'c']})
|
|
|
|
self.assertEqual({'A': [['B'], ['b', 'c']], 'C': {'D': 'E', 'F': {'I': 'J', 'G': 'H'}}}, ret)
|