salt/tests/unit/utils/test_decorators.py
Erik Johnson 3184168365 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-08-08 13:33:43 -05:00

355 lines
14 KiB
Python

# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Bo Maryniuk (bo@suse.de)`
unit.utils.decorators_test
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt libs
import salt.utils.decorators as decorators
from salt.version import SaltStackVersion
from salt.exceptions import CommandExecutionError, SaltConfigurationError
from tests.support.unit import skipIf, TestCase
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch
class DummyLogger(object):
'''
Dummy logger accepts everything and simply logs
'''
def __init__(self, messages):
self._messages = messages
def __getattr__(self, item):
return self._log
def _log(self, msg):
self._messages.append(msg)
@skipIf(NO_MOCK, NO_MOCK_REASON)
class DecoratorsTest(TestCase):
'''
Testing decorators.
'''
def old_function(self):
return "old"
def new_function(self):
return "new"
def _new_function(self):
return "old"
def _mk_version(self, name):
'''
Make a version
:return:
'''
return name, SaltStackVersion.from_name(name)
def setUp(self):
'''
Setup a test
:return:
'''
self.globs = {
'__virtualname__': 'test',
'__opts__': {},
'__pillar__': {},
'old_function': self.old_function,
'new_function': self.new_function,
'_new_function': self._new_function,
}
self.addCleanup(delattr, self, 'globs')
self.messages = list()
self.addCleanup(delattr, self, 'messages')
patcher = patch.object(decorators, 'log', DummyLogger(self.messages))
patcher.start()
self.addCleanup(patcher.stop)
def test_is_deprecated_version_eol(self):
'''
Use of is_deprecated will result to the exception,
if the expiration version is lower than the current version.
A successor function is not pointed out.
:return:
'''
depr = decorators.is_deprecated(self.globs, "Helium")
depr._curr_version = self._mk_version("Beryllium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.old_function)()
self.assertEqual(self.messages,
['The lifetime of the function "old_function" expired.'])
def test_is_deprecated_with_successor_eol(self):
'''
Use of is_deprecated will result to the exception,
if the expiration version is lower than the current version.
A successor function is pointed out.
:return:
'''
depr = decorators.is_deprecated(self.globs, "Helium", with_successor="new_function")
depr._curr_version = self._mk_version("Beryllium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.old_function)()
self.assertEqual(self.messages,
['The lifetime of the function "old_function" expired. '
'Please use its successor "new_function" instead.'])
def test_is_deprecated(self):
'''
Use of is_deprecated will result to the log message,
if the expiration version is higher than the current version.
A successor function is not pointed out.
:return:
'''
depr = decorators.is_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.old_function)(), self.old_function())
self.assertEqual(self.messages,
['The function "old_function" is deprecated '
'and will expire in version "Beryllium".'])
def test_is_deprecated_with_successor(self):
'''
Use of is_deprecated will result to the log message,
if the expiration version is higher than the current version.
A successor function is pointed out.
:return:
'''
depr = decorators.is_deprecated(self.globs, "Beryllium", with_successor="old_function")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.old_function)(), self.old_function())
self.assertEqual(self.messages,
['The function "old_function" is deprecated '
'and will expire in version "Beryllium". '
'Use successor "old_function" instead.'])
def test_with_deprecated_notfound(self):
'''
Test with_deprecated should raise an exception, if a same name
function with the "_" prefix not implemented.
:return:
'''
del self.globs['_new_function']
self.globs['__opts__']['use_deprecated'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['The function "test.new_function" is using its deprecated '
'version and will expire in version "Beryllium".'])
def test_with_deprecated_notfound_in_pillar(self):
'''
Test with_deprecated should raise an exception, if a same name
function with the "_" prefix not implemented.
:return:
'''
del self.globs['_new_function']
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['The function "test.new_function" is using its deprecated '
'version and will expire in version "Beryllium".'])
def test_with_deprecated_found(self):
'''
Test with_deprecated should not raise an exception, if a same name
function with the "_" prefix is implemented, but should use
an old version instead, if "use_deprecated" is requested.
:return:
'''
self.globs['__opts__']['use_deprecated'] = ['test.new_function']
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.new_function)(), self.old_function())
log_msg = ['The function "test.new_function" is using its deprecated version '
'and will expire in version "Beryllium".']
self.assertEqual(self.messages, log_msg)
def test_with_deprecated_found_in_pillar(self):
'''
Test with_deprecated should not raise an exception, if a same name
function with the "_" prefix is implemented, but should use
an old version instead, if "use_deprecated" is requested.
:return:
'''
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.new_function)(), self.old_function())
log_msg = ['The function "test.new_function" is using its deprecated version '
'and will expire in version "Beryllium".']
self.assertEqual(self.messages, log_msg)
def test_with_deprecated_found_eol(self):
'''
Test with_deprecated should raise an exception, if a same name
function with the "_" prefix is implemented, "use_deprecated" is requested
and EOL is reached.
:return:
'''
self.globs['__opts__']['use_deprecated'] = ['test.new_function']
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Helium")
depr._curr_version = self._mk_version("Beryllium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['Although function "new_function" is called, an alias "new_function" '
'is configured as its deprecated version. The lifetime of the function '
'"new_function" expired. Please use its successor "new_function" instead.'])
def test_with_deprecated_found_eol_in_pillar(self):
'''
Test with_deprecated should raise an exception, if a same name
function with the "_" prefix is implemented, "use_deprecated" is requested
and EOL is reached.
:return:
'''
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Helium")
depr._curr_version = self._mk_version("Beryllium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['Although function "new_function" is called, an alias "new_function" '
'is configured as its deprecated version. The lifetime of the function '
'"new_function" expired. Please use its successor "new_function" instead.'])
def test_with_deprecated_no_conf(self):
'''
Test with_deprecated should not raise an exception, if a same name
function with the "_" prefix is implemented, but should use
a new version instead, if "use_deprecated" is not requested.
:return:
'''
self.globs['_new_function'] = self.old_function
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.new_function)(), self.new_function())
self.assertFalse(self.messages)
def test_with_deprecated_with_name(self):
'''
Test with_deprecated should not raise an exception, if a different name
function is implemented and specified with the "with_name" parameter,
but should use an old version instead and log a warning log message.
:return:
'''
self.globs['__opts__']['use_deprecated'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium", with_name="old_function")
depr._curr_version = self._mk_version("Helium")[1]
self.assertEqual(depr(self.new_function)(), self.old_function())
self.assertEqual(self.messages,
['The function "old_function" is deprecated and will expire in version "Beryllium". '
'Use its successor "new_function" instead.'])
def test_with_deprecated_with_name_eol(self):
'''
Test with_deprecated should raise an exception, if a different name
function is implemented and specified with the "with_name" parameter
and EOL is reached.
:return:
'''
self.globs['__opts__']['use_deprecated'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Helium", with_name="old_function")
depr._curr_version = self._mk_version("Beryllium")[1]
with self.assertRaises(CommandExecutionError):
depr(self.new_function)()
self.assertEqual(self.messages,
['Although function "new_function" is called, '
'an alias "old_function" is configured as its deprecated version. '
'The lifetime of the function "old_function" expired. '
'Please use its successor "new_function" instead.'])
def test_with_deprecated_opt_in_default(self):
'''
Test with_deprecated using opt-in policy,
where newer function is not used, unless configured.
:return:
'''
depr = decorators.with_deprecated(self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN)
depr._curr_version = self._mk_version("Helium")[1]
assert depr(self.new_function)() == self.old_function()
assert self.messages == ['The function "test.new_function" is using its '
'deprecated version and will expire in version "Beryllium".']
def test_with_deprecated_opt_in_use_superseded(self):
'''
Test with_deprecated using opt-in policy,
where newer function is used as per configuration.
:return:
'''
self.globs['__opts__']['use_superseded'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN)
depr._curr_version = self._mk_version("Helium")[1]
assert depr(self.new_function)() == self.new_function()
assert not self.messages
def test_with_deprecated_opt_in_use_superseded_in_pillar(self):
'''
Test with_deprecated using opt-in policy,
where newer function is used as per configuration.
:return:
'''
self.globs['__pillar__']['use_superseded'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN)
depr._curr_version = self._mk_version("Helium")[1]
assert depr(self.new_function)() == self.new_function()
assert not self.messages
def test_with_deprecated_opt_in_use_superseded_and_deprecated(self):
'''
Test with_deprecated misconfiguration.
:return:
'''
self.globs['__opts__']['use_deprecated'] = ['test.new_function']
self.globs['__opts__']['use_superseded'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(SaltConfigurationError):
assert depr(self.new_function)() == self.new_function()
def test_with_deprecated_opt_in_use_superseded_and_deprecated_in_pillar(self):
'''
Test with_deprecated misconfiguration.
:return:
'''
self.globs['__pillar__']['use_deprecated'] = ['test.new_function']
self.globs['__pillar__']['use_superseded'] = ['test.new_function']
depr = decorators.with_deprecated(self.globs, "Beryllium")
depr._curr_version = self._mk_version("Helium")[1]
with self.assertRaises(SaltConfigurationError):
assert depr(self.new_function)() == self.new_function()