From 2dcc979ab2897619baebfef5779120a98284d408 Mon Sep 17 00:00:00 2001 From: Bo Maryniuk Date: Wed, 6 Apr 2016 20:55:45 +0200 Subject: [PATCH 08/12] Cleaner deprecation process with decorators * Add deprecation decorator scaffold * Capture type error and unhandled exceptions while function calls * Aware of the current and future version of deprecation * Implement initially is_deprecated decorator * Add an alias for the capitalization * Fix capitalization easier way * Remove an extra line * Add successor name to the deprecation decorator. * Granulate logging and error messages. * Implement function swapper * Raise later the caught exception * Clarify exception message * Save function original name * Remove an extra line * Hide an alternative hidden function name in the error message, preserving the error itself * Rename variable as private * Add a method to detect if a function is using its previous version * Message to the log and/or raise an exception accordingly to the status of used function * Log an error along with the exception * Add internal method documentation * Add documentation and usage process for decorator "is_deprecated" * Add documentation and process usage for the decorator "with_deprecated" * Hide private method name * Fix PEP8, re-word the error message * Deprecate basic uptime function * Add initial decorator unit test * Rename old/new functions, mock versions * Move frequent data to the test setup * Add logging on EOL exception * Rename and document high to low version test on is_deprecated * Implement a test on low to high version of is_deprecated decorator * Add a correction to the test description * Remove a dead code * Implement a test for high to low version on is_deprecated, using with_successor param * Correct typso adn mistaeks * Implement high to low version with successor param on is_deprecated * Setup a virtual name for the module * Implement test for with_deprecated should raise an exception if same deprecated function not found * Implement test for with_deprecated an old function is picked up if configured * Correct test description purpose * Implement test with_deprecated when no deprecation is requested * Add logging test to the configured deprecation request * Add logging testing when deprecated version wasn't requested * Implement test EOL for with_deprecated decorator * Correct test explanation * Rename the test * Implement with_deprecated no EOL, deprecated other function name * Implement with_deprecated, deprecated other function name, EOL reached * Add test description for the with_deprecated + with_name + EOL * Fix confusing test names * Add logging test to the is_deprecated decorator when function as not found. * Add more test point to each test, remove empty lines * Bugfix: at certain conditions a wrong alias name is reported to the log * Fix a typo in a comment * Add test for the logging * Disable a pylint: None will _never_ be raised * Fix test for the deprecated "status.uptime" version * Bugfix: Do not yank raised exceptions * Remove unnecessary decorator * Add test for the new uptime * Add test for the new uptime fails when /proc/uptime does not exists * Rename old test case * Skip test for the UTC time, unless freeze time is used. * Fix pylint * Fix documentation * Bugfix: proxy-pass the docstring of the decorated function * Lint fix --- salt/modules/status.py | 40 ++++- salt/utils/decorators/__init__.py | 345 +++++++++++++++++++++++++++++++++++- tests/unit/modules/status_test.py | 48 ++++- tests/unit/utils/decorators_test.py | 232 ++++++++++++++++++++++++ 4 files changed, 649 insertions(+), 16 deletions(-) create mode 100644 tests/unit/utils/decorators_test.py diff --git a/salt/modules/status.py b/salt/modules/status.py index 1e80b36..04c6204 100644 --- a/salt/modules/status.py +++ b/salt/modules/status.py @@ -11,6 +11,8 @@ import os import re import fnmatch import collections +import time +import datetime # Import 3rd-party libs import salt.ext.six as six @@ -23,6 +25,8 @@ import salt.utils.event from salt.utils.network import host_to_ip as _host_to_ip from salt.utils.network import remote_port_tcp as _remote_port_tcp from salt.ext.six.moves import zip +from salt.utils.decorators import with_deprecated +from salt.exceptions import CommandExecutionError __virtualname__ = 'status' __opts__ = {} @@ -30,7 +34,8 @@ __opts__ = {} def __virtual__(): if salt.utils.is_windows(): - return (False, 'Cannot load status module on windows') + return False, 'Windows platform is not supported by this module' + return __virtualname__ @@ -120,7 +125,38 @@ def custom(): return ret -def uptime(human_readable=True): +@with_deprecated(globals(), "Boron") +def uptime(): + ''' + Return the uptime for this system. + + CLI Example: + + .. code-block:: bash + + salt '*' status.uptime + ''' + ut_path = "/proc/uptime" + if not os.path.exists(ut_path): + raise CommandExecutionError("File {ut_path} was not found.".format(ut_path=ut_path)) + + ut_ret = { + 'seconds': int(float(open(ut_path).read().strip().split()[0])) + } + + utc_time = datetime.datetime.utcfromtimestamp(time.time() - ut_ret['seconds']) + ut_ret['since_iso'] = utc_time.isoformat() + ut_ret['since_t'] = time.mktime(utc_time.timetuple()) + ut_ret['days'] = ut_ret['seconds'] / 60 / 60 / 24 + hours = (ut_ret['seconds'] - (ut_ret['days'] * 24 * 60 * 60)) / 60 / 60 + minutes = ((ut_ret['seconds'] - (ut_ret['days'] * 24 * 60 * 60)) / 60) - hours * 60 + ut_ret['time'] = '{0}:{1}'.format(hours, minutes) + ut_ret['users'] = len(__salt__['cmd.run']("who -s").split(os.linesep)) + + return ut_ret + + +def _uptime(human_readable=True): ''' Return the uptime for this minion diff --git a/salt/utils/decorators/__init__.py b/salt/utils/decorators/__init__.py index 45d3bd6..3b43504 100644 --- a/salt/utils/decorators/__init__.py +++ b/salt/utils/decorators/__init__.py @@ -13,7 +13,8 @@ from collections import defaultdict # Import salt libs import salt.utils -from salt.exceptions import CommandNotFoundError +from salt.exceptions import CommandNotFoundError, CommandExecutionError +from salt.version import SaltStackVersion, __saltstack_version__ from salt.log import LOG_LEVELS # Import 3rd-party libs @@ -144,10 +145,7 @@ class Depends(object): continue -class depends(Depends): # pylint: disable=C0103 - ''' - Wrapper of Depends for capitalization - ''' +depends = Depends def timing(function): @@ -248,3 +246,340 @@ def memoize(func): cache[args] = func(*args) return cache[args] return _memoize + + +class _DeprecationDecorator(object): + ''' + Base mix-in class for the deprecation decorator. + Takes care of a common functionality, used in its derivatives. + ''' + + def __init__(self, globals, version): + ''' + Constructor. + + :param globals: Module globals. Important for finding out replacement functions + :param version: Expiration version + :return: + ''' + + self._globals = globals + self._exp_version_name = version + self._exp_version = SaltStackVersion.from_name(self._exp_version_name) + self._curr_version = __saltstack_version__.info + self._options = self._globals['__opts__'] + self._raise_later = None + self._function = None + self._orig_f_name = None + + def _get_args(self, kwargs): + ''' + Extract function-specific keywords from all of the kwargs. + + :param kwargs: + :return: + ''' + _args = list() + _kwargs = dict() + + for arg_item in kwargs.get('__pub_arg', list()): + if type(arg_item) == dict: + _kwargs.update(arg_item.copy()) + else: + _args.append(arg_item) + return _args, _kwargs + + def _call_function(self, kwargs): + ''' + Call target function that has been decorated. + + :return: + ''' + if self._raise_later: + raise self._raise_later # pylint: disable=E0702 + + if self._function: + args, kwargs = self._get_args(kwargs) + try: + return self._function(*args, **kwargs) + except TypeError as error: + error = str(error).replace(self._function.func_name, self._orig_f_name) # Hide hidden functions + log.error('Function "{f_name}" was not properly called: {error}'.format(f_name=self._orig_f_name, + error=error)) + return self._function.__doc__ + except Exception as error: + log.error('Unhandled exception occurred in ' + 'function "{f_name}: {error}'.format(f_name=self._function.func_name, + error=error)) + raise error + else: + raise CommandExecutionError("Function is deprecated, but the successor function was not found.") + + def __call__(self, function): + ''' + Callable method of the decorator object when + the decorated function is gets called. + + :param function: + :return: + ''' + self._function = function + self._orig_f_name = self._function.func_name + + +class _IsDeprecated(_DeprecationDecorator): + ''' + This decorator should be used only with the deprecated functions + to mark them as deprecated and alter its behavior a corresponding way. + The usage is only suitable if deprecation process is renaming + the function from one to another. In case function name or even function + signature stays the same, please use 'with_deprecated' decorator instead. + + It has the following functionality: + + 1. Put a warning level message to the log, informing that + the deprecated function has been in use. + + 2. Raise an exception, if deprecated function is being called, + but the lifetime of it already expired. + + 3. Point to the successor of the deprecated function in the + log messages as well during the blocking it, once expired. + + Usage of this decorator as follows. In this example no successor + is mentioned, hence the function "foo()" will be logged with the + warning each time is called and blocked completely, once EOF of + it is reached: + + from salt.util.decorators import is_deprecated + + @is_deprecated(globals(), "Beryllium") + def foo(): + pass + + In the following example a successor function is mentioned, hence + every time the function "bar()" is called, message will suggest + to use function "baz()" instead. Once EOF is reached of the function + "bar()", an exception will ask to use function "baz()", in order + to continue: + + from salt.util.decorators import is_deprecated + + @is_deprecated(globals(), "Beryllium", with_successor="baz") + def bar(): + pass + + def baz(): + pass + ''' + + def __init__(self, globals, version, with_successor=None): + ''' + Constructor of the decorator 'is_deprecated'. + + :param globals: Module globals + :param version: Version to be deprecated + :param with_successor: Successor function (optional) + :return: + ''' + _DeprecationDecorator.__init__(self, globals, version) + self._successor = with_successor + + def __call__(self, function): + ''' + Callable method of the decorator object when + the decorated function is gets called. + + :param function: + :return: + ''' + _DeprecationDecorator.__call__(self, function) + + def _decorate(*args, **kwargs): + ''' + Decorator function. + + :param args: + :param kwargs: + :return: + ''' + if self._curr_version < self._exp_version: + msg = ['The function "{f_name}" is deprecated and will ' + 'expire in version "{version_name}".'.format(f_name=self._function.func_name, + version_name=self._exp_version_name)] + if self._successor: + msg.append('Use successor "{successor}" instead.'.format(successor=self._successor)) + log.warning(' '.join(msg)) + else: + msg = ['The lifetime of the function "{f_name}" expired.'.format(f_name=self._function.func_name)] + if self._successor: + msg.append('Please use its successor "{successor}" instead.'.format(successor=self._successor)) + log.warning(' '.join(msg)) + raise CommandExecutionError(' '.join(msg)) + return self._call_function(kwargs) + return _decorate + + +is_deprecated = _IsDeprecated + + +class _WithDeprecated(_DeprecationDecorator): + ''' + This decorator should be used with the successor functions + to mark them as a new and alter its behavior in a corresponding way. + It is used alone if a function content or function signature + needs to be replaced, leaving the name of the function same. + In case function needs to be renamed or just dropped, it has + to be used in pair with 'is_deprecated' decorator. + + It has the following functionality: + + 1. Put a warning level message to the log, in case a component + is using its deprecated version. + + 2. Switch between old and new function in case an older version + is configured for the desired use. + + 3. Raise an exception, if deprecated version reached EOL and + point out for the new version. + + Usage of this decorator as follows. If 'with_name' is not specified, + then the name of the deprecated function is assumed with the "_" prefix. + In this case, in order to deprecate a function, it is required: + + - Add a prefix "_" to an existing function. E.g.: "foo()" to "_foo()". + + - Implement a new function with exactly the same name, just without + the prefix "_". + + Example: + + from salt.util.decorators import with_deprecated + + @with_deprecated(globals(), "Beryllium") + def foo(): + "This is a new function" + + def _foo(): + "This is a deprecated function" + + + In case there is a need to deprecate a function and rename it, + the decorator shuld be used with the 'with_name' parameter. This + parameter is pointing to the existing deprecated function. In this + case deprecation process as follows: + + - Leave a deprecated function without changes, as is. + + - Implement a new function and decorate it with this decorator. + + - Set a parameter 'with_name' to the deprecated function. + + - If a new function has a different name than a deprecated, + decorate a deprecated function with the 'is_deprecated' decorator + in order to let the function have a deprecated behavior. + + Example: + + from salt.util.decorators import with_deprecated + + @with_deprecated(globals(), "Beryllium", with_name="an_old_function") + def a_new_function(): + "This is a new function" + + @is_deprecated(globals(), "Beryllium", with_successor="a_new_function") + def an_old_function(): + "This is a deprecated function" + + ''' + MODULE_NAME = '__virtualname__' + CFG_KEY = 'use_deprecated' + + def __init__(self, globals, version, with_name=None): + ''' + Constructor of the decorator 'with_deprecated' + + :param globals: + :param version: + :param with_name: + :return: + ''' + _DeprecationDecorator.__init__(self, globals, version) + self._with_name = with_name + + def _set_function(self, function): + ''' + Based on the configuration, set to execute an old or a new function. + :return: + ''' + full_name = "{m_name}.{f_name}".format(m_name=self._globals.get(self.MODULE_NAME, ''), + f_name=function.func_name) + if full_name.startswith("."): + self._raise_later = CommandExecutionError('Module not found for function "{f_name}"'.format( + f_name=function.func_name)) + + if full_name in self._options.get(self.CFG_KEY, list()): + self._function = self._globals.get(self._with_name or "_{0}".format(function.func_name)) + + def _is_used_deprecated(self): + ''' + Returns True, if a component configuration explicitly is + asking to use an old version of the deprecated function. + + :return: + ''' + return "{m_name}.{f_name}".format(m_name=self._globals.get(self.MODULE_NAME, ''), + f_name=self._orig_f_name) in self._options.get(self.CFG_KEY, list()) + + def __call__(self, function): + ''' + Callable method of the decorator object when + the decorated function is gets called. + + :param function: + :return: + ''' + _DeprecationDecorator.__call__(self, function) + + def _decorate(*args, **kwargs): + ''' + Decorator function. + + :param args: + :param kwargs: + :return: + ''' + self._set_function(function) + if self._is_used_deprecated(): + if self._curr_version < self._exp_version: + msg = list() + if self._with_name: + msg.append('The function "{f_name}" is deprecated and will ' + 'expire in version "{version_name}".'.format( + f_name=self._with_name.startswith("_") and self._orig_f_name or self._with_name, + version_name=self._exp_version_name)) + else: + msg.append('The function is using its deprecated version and will ' + 'expire in version "{version_name}".'.format(version_name=self._exp_version_name)) + msg.append('Use its successor "{successor}" instead.'.format(successor=self._orig_f_name)) + log.warning(' '.join(msg)) + else: + msg_patt = 'The lifetime of the function "{f_name}" expired.' + if '_' + self._orig_f_name == self._function.func_name: + msg = [msg_patt.format(f_name=self._orig_f_name), + 'Please turn off its deprecated version in the configuration'] + else: + msg = ['Although function "{f_name}" is called, an alias "{f_alias}" ' + 'is configured as its deprecated version.'.format( + f_name=self._orig_f_name, f_alias=self._with_name or self._orig_f_name), + msg_patt.format(f_name=self._with_name or self._orig_f_name), + 'Please use its successor "{successor}" instead.'.format(successor=self._orig_f_name)] + log.error(' '.join(msg)) + raise CommandExecutionError(' '.join(msg)) + return self._call_function(kwargs) + + _decorate.__doc__ = self._function.__doc__ + return _decorate + + +with_deprecated = _WithDeprecated diff --git a/tests/unit/modules/status_test.py b/tests/unit/modules/status_test.py index 191da09..b5cee4f 100644 --- a/tests/unit/modules/status_test.py +++ b/tests/unit/modules/status_test.py @@ -5,15 +5,14 @@ from __future__ import absolute_import # Import Salt Libs from salt.modules import status +from salt.exceptions import CommandExecutionError # Import Salt Testing Libs -from salttesting import skipIf, TestCase +from salttesting import TestCase from salttesting.helpers import ensure_in_syspath from salttesting.mock import ( MagicMock, patch, - NO_MOCK, - NO_MOCK_REASON ) ensure_in_syspath('../../') @@ -22,36 +21,67 @@ ensure_in_syspath('../../') status.__salt__ = {} -@skipIf(NO_MOCK, NO_MOCK_REASON) class StatusTestCase(TestCase): ''' test modules.status functions ''' + def test_uptime(self): ''' - test modules.status.uptime function + Test modules.status.uptime function, new version + :return: + ''' + class ProcUptime(object): + def __init__(self, *args, **kwargs): + self.data = "773865.18 1003405.46" + + def read(self): + return self.data + + with patch.dict(status.__salt__, {'cmd.run': MagicMock(return_value="1\n2\n3")}): + with patch('os.path.exists', MagicMock(return_value=True)): + with patch('time.time', MagicMock(return_value=1458821523.72)): + status.open = ProcUptime + u_time = status.uptime() + self.assertEqual(u_time['users'], 3) + self.assertEqual(u_time['seconds'], 773865) + self.assertEqual(u_time['days'], 8) + self.assertEqual(u_time['time'], '22:57') + + def test_uptime_failure(self): + ''' + Test modules.status.uptime function should raise an exception if /proc/uptime does not exists. + :return: + ''' + with patch('os.path.exists', MagicMock(return_value=False)): + with self.assertRaises(CommandExecutionError): + status.uptime() + + def test_deprecated_uptime(self): + ''' + test modules.status.uptime function, deprecated version ''' mock_uptime = 'very often' mock_run = MagicMock(return_value=mock_uptime) with patch.dict(status.__salt__, {'cmd.run': mock_run}): - self.assertEqual(status.uptime(), mock_uptime) + self.assertEqual(status._uptime(), mock_uptime) mock_uptime = 'very idle' mock_run = MagicMock(return_value=mock_uptime) with patch.dict(status.__salt__, {'cmd.run': mock_run}): with patch('os.path.exists', MagicMock(return_value=True)): - self.assertEqual(status.uptime(human_readable=False), mock_uptime.split()[0]) + self.assertEqual(status._uptime(human_readable=False), mock_uptime.split()[0]) mock_uptime = '' mock_return = 'unexpected format in /proc/uptime' mock_run = MagicMock(return_value=mock_uptime) with patch.dict(status.__salt__, {'cmd.run': mock_run}): with patch('os.path.exists', MagicMock(return_value=True)): - self.assertEqual(status.uptime(human_readable=False), mock_return) + self.assertEqual(status._uptime(human_readable=False), mock_return) mock_return = 'cannot find /proc/uptime' with patch('os.path.exists', MagicMock(return_value=False)): - self.assertEqual(status.uptime(human_readable=False), mock_return) + self.assertEqual(status._uptime(human_readable=False), mock_return) if __name__ == '__main__': diff --git a/tests/unit/utils/decorators_test.py b/tests/unit/utils/decorators_test.py new file mode 100644 index 0000000..4078340 --- /dev/null +++ b/tests/unit/utils/decorators_test.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Bo Maryniuk (bo@suse.de)` + unit.utils.decorators_test +''' + +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing libs +from salttesting import TestCase +from salttesting.helpers import ensure_in_syspath +from salt.utils import decorators +from salt.version import SaltStackVersion +from salt.exceptions import CommandExecutionError + +ensure_in_syspath('../../') + + +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) + + +class DecoratorsTest(TestCase): + ''' + Testing decorators. + ''' + def old_function(self): + return "old" + + def new_function(self): + return "new" + + 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__': {}, + 'old_function': self.old_function, + 'new_function': self.new_function, + } + self.messages = list() + decorators.log = DummyLogger(self.messages) + + 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: + ''' + 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 is using its deprecated version and will expire in version "Beryllium". ' + 'Use its successor "new_function" instead.']) + + 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 is using its deprecated version and will expire in version "Beryllium". ' + 'Use its successor "new_function" instead.'] + 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_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.']) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(DecoratorsTest, needs_daemon=False) -- 2.1.4