2017-02-27 13:58:07 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
'''
|
|
|
|
:codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
|
|
|
|
|
|
|
|
|
|
|
|
============================
|
|
|
|
Unittest Compatibility Layer
|
|
|
|
============================
|
|
|
|
|
|
|
|
Compatibility layer to use :mod:`unittest <python2:unittest>` under Python
|
|
|
|
2.7 or `unittest2`_ under Python 2.6 without having to worry about which is
|
|
|
|
in use.
|
|
|
|
|
|
|
|
.. attention::
|
|
|
|
|
|
|
|
Please refer to Python's :mod:`unittest <python2:unittest>`
|
|
|
|
documentation as the ultimate source of information, this is just a
|
|
|
|
compatibility layer.
|
|
|
|
|
|
|
|
.. _`unittest2`: https://pypi.python.org/pypi/unittest2
|
|
|
|
'''
|
2017-03-31 11:16:08 +00:00
|
|
|
# pylint: disable=unused-import,blacklisted-module,deprecated-method
|
2017-02-27 13:58:07 +00:00
|
|
|
|
|
|
|
# Import python libs
|
|
|
|
from __future__ import absolute_import
|
2017-03-04 19:23:26 +00:00
|
|
|
import os
|
2017-02-27 13:58:07 +00:00
|
|
|
import sys
|
|
|
|
import logging
|
2017-03-06 16:23:33 +00:00
|
|
|
import salt.ext.six as six
|
2017-02-27 13:58:07 +00:00
|
|
|
try:
|
|
|
|
import psutil
|
|
|
|
HAS_PSUTIL = True
|
|
|
|
except ImportError:
|
|
|
|
HAS_PSUTIL = False
|
|
|
|
|
2017-03-04 10:50:37 +00:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2017-02-27 13:58:07 +00:00
|
|
|
# Set SHOW_PROC to True to show
|
|
|
|
# process details when running in verbose mode
|
|
|
|
# i.e. [CPU:15.1%|MEM:48.3%|Z:0]
|
2017-03-04 19:23:26 +00:00
|
|
|
SHOW_PROC = 'NO_SHOW_PROC' not in os.environ
|
2017-02-27 13:58:07 +00:00
|
|
|
|
|
|
|
# support python < 2.7 via unittest2
|
|
|
|
if sys.version_info < (2, 7):
|
|
|
|
try:
|
|
|
|
# pylint: disable=import-error
|
|
|
|
from unittest2 import (
|
2017-03-04 19:15:20 +00:00
|
|
|
TestLoader as __TestLoader,
|
2017-02-27 13:58:07 +00:00
|
|
|
TextTestRunner as __TextTestRunner,
|
|
|
|
TestCase as __TestCase,
|
|
|
|
expectedFailure,
|
2017-03-04 19:15:20 +00:00
|
|
|
TestSuite as __TestSuite,
|
2017-02-27 13:58:07 +00:00
|
|
|
skip,
|
|
|
|
skipIf,
|
|
|
|
TestResult as _TestResult,
|
|
|
|
TextTestResult as __TextTestResult
|
|
|
|
)
|
|
|
|
from unittest2.case import _id
|
|
|
|
# pylint: enable=import-error
|
|
|
|
|
|
|
|
class NewStyleClassMixin(object):
|
|
|
|
'''
|
|
|
|
Simple new style class to make pylint shut up!
|
|
|
|
|
|
|
|
And also to avoid errors like:
|
|
|
|
|
|
|
|
'Cannot create a consistent method resolution order (MRO) for bases'
|
|
|
|
'''
|
|
|
|
|
2017-03-04 19:15:20 +00:00
|
|
|
class _TestLoader(__TestLoader, NewStyleClassMixin):
|
2017-02-27 13:58:07 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
class _TextTestRunner(__TextTestRunner, NewStyleClassMixin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class _TestCase(__TestCase, NewStyleClassMixin):
|
|
|
|
pass
|
|
|
|
|
2017-03-04 19:15:20 +00:00
|
|
|
class _TestSuite(__TestSuite, NewStyleClassMixin):
|
2017-02-27 13:58:07 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
class TestResult(_TestResult, NewStyleClassMixin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class _TextTestResult(__TextTestResult, NewStyleClassMixin):
|
|
|
|
pass
|
|
|
|
|
|
|
|
except ImportError:
|
|
|
|
raise SystemExit('You need to install unittest2 to run the salt tests')
|
|
|
|
else:
|
|
|
|
from unittest import (
|
2017-03-04 19:15:20 +00:00
|
|
|
TestLoader as _TestLoader,
|
2017-02-27 13:58:07 +00:00
|
|
|
TextTestRunner as _TextTestRunner,
|
|
|
|
TestCase as _TestCase,
|
|
|
|
expectedFailure,
|
2017-03-04 19:15:20 +00:00
|
|
|
TestSuite as _TestSuite,
|
2017-02-27 13:58:07 +00:00
|
|
|
skip,
|
|
|
|
skipIf,
|
|
|
|
TestResult,
|
|
|
|
TextTestResult as _TextTestResult
|
|
|
|
)
|
|
|
|
from unittest.case import _id
|
|
|
|
|
|
|
|
|
2017-03-04 19:15:20 +00:00
|
|
|
class TestSuite(_TestSuite):
|
|
|
|
|
|
|
|
def _handleClassSetUp(self, test, result):
|
|
|
|
previousClass = getattr(result, '_previousTestClass', None)
|
|
|
|
currentClass = test.__class__
|
|
|
|
if currentClass == previousClass or getattr(currentClass, 'setUpClass', None) is None:
|
|
|
|
return super(TestSuite, self)._handleClassSetUp(test, result)
|
|
|
|
|
|
|
|
# Store a reference to all class attributes before running the setUpClass method
|
|
|
|
initial_class_attributes = dir(test.__class__)
|
|
|
|
ret = super(TestSuite, self)._handleClassSetUp(test, result)
|
|
|
|
# Store the difference in in a variable in order to check later if they were deleted
|
|
|
|
test.__class__._prerun_class_attributes = [
|
|
|
|
attr for attr in dir(test.__class__) if attr not in initial_class_attributes]
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def _tearDownPreviousClass(self, test, result):
|
|
|
|
# Run any tearDownClass code defined
|
|
|
|
super(TestSuite, self)._tearDownPreviousClass(test, result)
|
|
|
|
previousClass = getattr(result, '_previousTestClass', None)
|
|
|
|
currentClass = test.__class__
|
|
|
|
if currentClass == previousClass:
|
|
|
|
return
|
|
|
|
# See if the previous class attributes have been cleaned
|
|
|
|
if previousClass and getattr(previousClass, 'tearDownClass', None):
|
|
|
|
prerun_class_attributes = getattr(previousClass, '_prerun_class_attributes', None)
|
|
|
|
if prerun_class_attributes is not None:
|
2017-04-07 17:02:04 +00:00
|
|
|
previousClass._prerun_class_attributes = None
|
2017-03-04 19:15:20 +00:00
|
|
|
del previousClass._prerun_class_attributes
|
|
|
|
for attr in prerun_class_attributes:
|
2017-03-06 16:23:33 +00:00
|
|
|
if hasattr(previousClass, attr):
|
|
|
|
attr_value = getattr(previousClass, attr, None)
|
|
|
|
if attr_value is None:
|
|
|
|
continue
|
|
|
|
if isinstance(attr_value, (bool,) + six.string_types + six.integer_types):
|
2017-04-07 17:02:04 +00:00
|
|
|
setattr(previousClass, attr, None)
|
2017-03-06 16:23:33 +00:00
|
|
|
continue
|
2017-03-04 19:15:20 +00:00
|
|
|
log.warning('Deleting extra class attribute after test run: %s.%s(%s). '
|
|
|
|
'Please consider using \'del self.%s\' on the test class '
|
|
|
|
'\'tearDownClass()\' method', previousClass.__name__, attr,
|
|
|
|
str(getattr(previousClass, attr))[:100], attr)
|
|
|
|
delattr(previousClass, attr)
|
|
|
|
|
|
|
|
|
|
|
|
class TestLoader(_TestLoader):
|
|
|
|
# We're just subclassing to make sure tha tour TestSuite class is the one used
|
|
|
|
suiteClass = TestSuite
|
|
|
|
|
|
|
|
|
2017-02-27 13:58:07 +00:00
|
|
|
class TestCase(_TestCase):
|
|
|
|
|
2017-03-04 19:23:26 +00:00
|
|
|
# pylint: disable=expected-an-indented-block-comment,too-many-leading-hastag-for-block-comment
|
2017-02-27 13:58:07 +00:00
|
|
|
## Commented out because it may be causing tests to hang
|
|
|
|
## at the end of the run
|
|
|
|
#
|
|
|
|
# _cwd = os.getcwd()
|
|
|
|
# _chdir_counter = 0
|
|
|
|
|
|
|
|
# @classmethod
|
|
|
|
# def tearDownClass(cls):
|
|
|
|
# '''
|
|
|
|
# Overriden method for tearing down all classes in salttesting
|
|
|
|
#
|
|
|
|
# This hard-resets the environment between test classes
|
|
|
|
# '''
|
|
|
|
# # Compare where we are now compared to where we were when we began this family of tests
|
|
|
|
# if not cls._cwd == os.getcwd() and cls._chdir_counter > 0:
|
|
|
|
# os.chdir(cls._cwd)
|
|
|
|
# print('\nWARNING: A misbehaving test has modified the working directory!\nThe test suite has reset the working directory '
|
|
|
|
# 'on tearDown() to {0}\n'.format(cls._cwd))
|
|
|
|
# cls._chdir_counter += 1
|
2017-03-04 19:23:26 +00:00
|
|
|
# pylint: enable=expected-an-indented-block-comment,too-many-leading-hastag-for-block-comment
|
2017-02-27 13:58:07 +00:00
|
|
|
|
2017-03-04 10:50:37 +00:00
|
|
|
def run(self, result=None):
|
|
|
|
self._prerun_instance_attributes = dir(self)
|
2017-06-27 13:22:09 +00:00
|
|
|
self.maxDiff = None
|
2017-03-04 10:50:37 +00:00
|
|
|
outcome = super(TestCase, self).run(result=result)
|
|
|
|
for attr in dir(self):
|
|
|
|
if attr == '_prerun_instance_attributes':
|
|
|
|
continue
|
2017-03-04 19:15:20 +00:00
|
|
|
if attr in getattr(self.__class__, '_prerun_class_attributes', ()):
|
|
|
|
continue
|
2017-03-06 16:23:33 +00:00
|
|
|
if attr not in self._prerun_instance_attributes:
|
|
|
|
attr_value = getattr(self, attr, None)
|
|
|
|
if attr_value is None:
|
|
|
|
continue
|
|
|
|
if isinstance(attr_value, (bool,) + six.string_types + six.integer_types):
|
2017-04-07 17:02:04 +00:00
|
|
|
setattr(self, attr, None)
|
2017-03-06 16:23:33 +00:00
|
|
|
continue
|
2017-03-04 10:50:37 +00:00
|
|
|
log.warning('Deleting extra class attribute after test run: %s.%s(%s). '
|
|
|
|
'Please consider using \'del self.%s\' on the test case '
|
|
|
|
'\'tearDown()\' method', self.__class__.__name__, attr,
|
|
|
|
getattr(self, attr), attr)
|
|
|
|
delattr(self, attr)
|
2017-04-07 17:02:04 +00:00
|
|
|
self._prerun_instance_attributes = None
|
2017-03-04 10:50:37 +00:00
|
|
|
del self._prerun_instance_attributes
|
|
|
|
return outcome
|
|
|
|
|
2017-02-27 13:58:07 +00:00
|
|
|
def shortDescription(self):
|
|
|
|
desc = _TestCase.shortDescription(self)
|
|
|
|
if HAS_PSUTIL and SHOW_PROC:
|
2017-03-04 19:23:26 +00:00
|
|
|
show_zombie_processes = 'SHOW_PROC_ZOMBIES' in os.environ
|
|
|
|
proc_info = '[CPU:{0}%|MEM:{1}%'.format(psutil.cpu_percent(),
|
|
|
|
psutil.virtual_memory().percent)
|
|
|
|
if show_zombie_processes:
|
|
|
|
found_zombies = 0
|
|
|
|
try:
|
|
|
|
for proc in psutil.process_iter():
|
|
|
|
if proc.status == psutil.STATUS_ZOMBIE:
|
|
|
|
found_zombies += 1
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
proc_info += '|Z:{0}'.format(found_zombies)
|
|
|
|
proc_info += '] {short_desc}'.format(short_desc=desc if desc else '')
|
2017-02-27 13:58:07 +00:00
|
|
|
return proc_info
|
|
|
|
else:
|
|
|
|
return _TestCase.shortDescription(self)
|
|
|
|
|
|
|
|
def assertEquals(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('assertEquals', 'assertEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.assertEquals(self, *args, **kwargs)
|
|
|
|
|
2017-03-31 11:16:08 +00:00
|
|
|
def assertNotEquals(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('assertNotEquals', 'assertNotEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.assertNotEquals(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def assert_(self, *args, **kwargs):
|
|
|
|
# The unittest2 library uses this deprecated method, we can't raise
|
|
|
|
# the exception.
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('assert_', 'assertTrue')
|
|
|
|
)
|
|
|
|
# return _TestCase.assert_(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def assertAlmostEquals(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('assertAlmostEquals', 'assertAlmostEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.assertAlmostEquals(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def assertNotAlmostEquals(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('assertNotAlmostEquals', 'assertNotAlmostEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.assertNotAlmostEquals(self, *args, **kwargs)
|
|
|
|
|
2017-02-27 13:58:07 +00:00
|
|
|
def failUnlessEqual(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failUnlessEqual', 'assertEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.failUnlessEqual(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def failIfEqual(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failIfEqual', 'assertNotEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.failIfEqual(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def failUnless(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failUnless', 'assertTrue')
|
|
|
|
)
|
|
|
|
# return _TestCase.failUnless(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def failIf(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failIf', 'assertFalse')
|
|
|
|
)
|
|
|
|
# return _TestCase.failIf(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def failUnlessRaises(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failUnlessRaises', 'assertRaises')
|
|
|
|
)
|
|
|
|
# return _TestCase.failUnlessRaises(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def failUnlessAlmostEqual(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failUnlessAlmostEqual', 'assertAlmostEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.failUnlessAlmostEqual(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def failIfAlmostEqual(self, *args, **kwargs):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format('failIfAlmostEqual', 'assertNotAlmostEqual')
|
|
|
|
)
|
|
|
|
# return _TestCase.failIfAlmostEqual(self, *args, **kwargs)
|
|
|
|
|
2017-07-03 14:30:12 +00:00
|
|
|
@staticmethod
|
|
|
|
def assert_called_once(mock):
|
|
|
|
'''
|
|
|
|
mock.assert_called_once only exists in PY3 in 3.6 and newer
|
|
|
|
'''
|
|
|
|
try:
|
|
|
|
mock.assert_called_once()
|
|
|
|
except AttributeError:
|
|
|
|
log.warning('assert_called_once invoked, but not available')
|
|
|
|
|
2017-03-31 11:16:08 +00:00
|
|
|
if six.PY2:
|
|
|
|
def assertRegexpMatches(self, *args, **kwds):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function will be deprecated in python 3. Please start '
|
|
|
|
'using {1}() instead.'.format(
|
|
|
|
'assertRegexpMatches',
|
|
|
|
'assertRegex'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def assertRegex(self, text, regex, msg=None):
|
|
|
|
# In python 2, alias to the future python 3 function
|
|
|
|
return _TestCase.assertRegexpMatches(self, text, regex, msg=msg)
|
|
|
|
|
|
|
|
def assertNotRegexpMatches(self, *args, **kwds):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function will be deprecated in python 3. Please start '
|
|
|
|
'using {1}() instead.'.format(
|
|
|
|
'assertNotRegexpMatches',
|
|
|
|
'assertNotRegex'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def assertNotRegex(self, text, regex, msg=None):
|
|
|
|
# In python 2, alias to the future python 3 function
|
|
|
|
return _TestCase.assertNotRegexpMatches(self, text, regex, msg=msg)
|
|
|
|
|
|
|
|
def assertRaisesRegexp(self, *args, **kwds):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function will be deprecated in python 3. Please start '
|
|
|
|
'using {1}() instead.'.format(
|
|
|
|
'assertRaisesRegexp',
|
|
|
|
'assertRaisesRegex'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2017-04-01 09:48:50 +00:00
|
|
|
def assertRaisesRegex(self, exception, regexp, *args, **kwds):
|
2017-03-31 11:16:08 +00:00
|
|
|
# In python 2, alias to the future python 3 function
|
2017-04-01 09:48:50 +00:00
|
|
|
return _TestCase.assertRaisesRegexp(self, exception, regexp, *args, **kwds)
|
2017-03-31 11:16:08 +00:00
|
|
|
else:
|
|
|
|
def assertRegexpMatches(self, *args, **kwds):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format(
|
|
|
|
'assertRegexpMatches',
|
|
|
|
'assertRegex'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def assertNotRegexpMatches(self, *args, **kwds):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format(
|
|
|
|
'assertNotRegexpMatches',
|
|
|
|
'assertNotRegex'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def assertRaisesRegexp(self, *args, **kwds):
|
|
|
|
raise DeprecationWarning(
|
|
|
|
'The {0}() function is deprecated. Please start using {1}() '
|
|
|
|
'instead.'.format(
|
|
|
|
'assertRaisesRegexp',
|
|
|
|
'assertRaisesRegex'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2017-02-27 13:58:07 +00:00
|
|
|
|
|
|
|
class TextTestResult(_TextTestResult):
|
|
|
|
'''
|
|
|
|
Custom TestResult class whith logs the start and the end of a test
|
|
|
|
'''
|
|
|
|
|
|
|
|
def startTest(self, test):
|
2017-03-04 10:50:37 +00:00
|
|
|
log.debug('>>>>> START >>>>> {0}'.format(test.id()))
|
2017-02-27 13:58:07 +00:00
|
|
|
return super(TextTestResult, self).startTest(test)
|
|
|
|
|
|
|
|
def stopTest(self, test):
|
2017-03-04 10:50:37 +00:00
|
|
|
log.debug('<<<<< END <<<<<<< {0}'.format(test.id()))
|
2017-02-27 13:58:07 +00:00
|
|
|
return super(TextTestResult, self).stopTest(test)
|
|
|
|
|
|
|
|
|
|
|
|
class TextTestRunner(_TextTestRunner):
|
|
|
|
'''
|
|
|
|
Custom Text tests runner to log the start and the end of a test case
|
|
|
|
'''
|
|
|
|
resultclass = TextTestResult
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'TestLoader',
|
|
|
|
'TextTestRunner',
|
|
|
|
'TestCase',
|
|
|
|
'expectedFailure',
|
|
|
|
'TestSuite',
|
|
|
|
'skipIf',
|
|
|
|
'TestResult'
|
|
|
|
]
|