salt/0008-Cleaner-deprecation-process-with-decorators.patch
Klaus Kämpf 56c946e70f Accepting request 391560 from systemsmanagement:saltstack:testing
- Prevent crash if pygit2 package requests recompilation.
  Add:
  * 0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch
- Align OS grains from older SLES with the current one (bsc#975757)
  Add:
  * 0014-align-OS-grains-from-older-SLES-with-current-one-326.patch

- remove patches which produce duplicate functions:
  Remove:
  * 0004-implement-version_cmp-for-zypper.patch
  * 0005-pylint-changes.patch
  * 0006-Check-if-rpm-python-can-be-imported.patch
- remove patches which add and revert the same file
  Remove:
  * 0007-Initial-Zypper-Unit-Tests-and-bugfixes.patch
  * 0009-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch
- rename patches:
  0008-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch to
     0004-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch
  0010-Use-SHA256-hash-type-by-default.patch to
     0005-Use-SHA256-hash-type-by-default.patch
  0011-Update-to-2015.8.8.2.patch to
     0006-Update-to-2015.8.8.2.patch
  0012-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch to
     0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch
  0013-Cleaner-deprecation-process-with-decorators.patch  to
     0008-Cleaner-deprecation-process-with-decorators.patch
- fix sorting by latest package
  Add:
  * 0009-fix-sorting-by-latest-version-when-called-with-an-at.patch
- Prevent metadata download when getting installed products
  Add:
  * 0010-Prevent-metadata-download-when-getting-installed-pro.patch
- Check if EOL is available in a particular product (bsc#975093)
  Add:
  * 0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch
- Bugfix: salt-key crashes if tries to generate keys 
  to the directory w/o write access (bsc#969320)
  Add:
  * 0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch

- Deprecation process using decorators and re-implementation
  of status.update function.
  Add:
  * 0013-Cleaner-deprecation-process-with-decorators.patch

- Reverted the fake 2015.8.8.2 patch, with the right one,
- this patch only contains:
  - https://github.com/saltstack/salt/pull/32135 
  - https://github.com/saltstack/salt/pull/32023 
  - https://github.com/saltstack/salt/pull/32117

- Ensure that in case of multi-packages installed on the system,
  the latest is reported by pkg.info_installed (bsc#972490)
  Add:
  * 0012-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch

- Update to the fake 2015.8.8.2 release
  upstream released a bunch of fixes on top of 2015.8.8, without creating a new
  tag and proper release. This commit includes all the changes between tag
  v2015.8.8 and commit ID 596444e2b447b7378dbcdfeb9fc9610b90057745 which
  introduces the fake 2015.8.8.2 release.
  see https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html#salt-2015-8-8-2

- Update to 2015.8.8
  see https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html
  Patches renamed:
  * 0004-implement-version_cmp-for-zypper.patch
  * 0005-pylint-changes.patch
  * 0006-Check-if-rpm-python-can-be-imported.patch
  * 0007-Initial-Zypper-Unit-Tests-and-bugfixes.patch
  * 0008-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch
  * 0009-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch
  * 0010-Use-SHA256-hash-type-by-default.patch
  Patches removed:
  * 0004-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch
  * 0005-Fix-package-status-filtering-on-latest-version-and-i.patch
  * 0006-add_key-reject_key-do-not-crash-w-Permission-denied-.patch
  * 0007-Force-kill-websocket-s-child-processes-faster-than-d.patch
  * 0008-Fix-types-in-the-output-data-and-return-just-a-list-.patch
  * 0009-The-functions-in-the-state-module-that-return-a-retc.patch
  * 0010-add-handling-for-OEM-products.patch
  * 0011-improve-doc-for-list_pkgs.patch
  * 0012-implement-version_cmp-for-zypper.patch
  * 0013-pylint-changes.patch
  * 0014-Check-if-rpm-python-can-be-imported.patch
  * 0015-call-zypper-with-option-non-interactive-everywhere.patch
  * 0016-write-a-zypper-command-builder-function.patch
  * 0017-Fix-crash-with-scheduler-and-runners-31106.patch
  * 0018-unify-behavior-of-refresh.patch
  * 0019-add-refresh-option-to-more-functions.patch
  * 0020-simplify-checking-the-refresh-paramater.patch
  * 0021-do-not-change-kwargs-in-refresh-while-checking-a-val.patch
  * 0022-fix-argument-handling-for-pkg.download.patch
  * 0023-Initial-Zypper-Unit-Tests-and-bugfixes.patch
  * 0024-proper-checking-if-zypper-exit-codes-and-handling-of.patch
  * 0025-adapt-tests-to-new-zypper_check_result-output.patch
  * 0026-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch
  * 0027-make-suse-check-consistent-with-rh_service.patch
  * 0028-fix-numerical-check-of-osrelease.patch
  * 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch
  * 0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch
  * 0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch
  * 0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch
  * 0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch
  * 0034-Fix-git_pillar-race-condition.patch
  * 0035-Fix-the-always-false-behavior-on-checking-state.patch
  * 0036-Use-SHA256-hash-type-by-default.patch

OBS-URL: https://build.opensuse.org/request/show/391560
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=66
2016-04-28 07:26:14 +00:00

923 lines
32 KiB
Diff

From 2dcc979ab2897619baebfef5779120a98284d408 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@maryniuk.net>
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