1
0

unit.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. ============================
  5. Unittest Compatibility Layer
  6. ============================
  7. Compatibility layer to use :mod:`unittest <python2:unittest>` under Python
  8. 2.7 or `unittest2`_ under Python 2.6 without having to worry about which is
  9. in use.
  10. .. attention::
  11. Please refer to Python's :mod:`unittest <python2:unittest>`
  12. documentation as the ultimate source of information, this is just a
  13. compatibility layer.
  14. .. _`unittest2`: https://pypi.python.org/pypi/unittest2
  15. '''
  16. # pylint: disable=unused-import,blacklisted-module,deprecated-method
  17. # Import python libs
  18. from __future__ import absolute_import, print_function, unicode_literals
  19. import os
  20. import sys
  21. import logging
  22. from salt.ext import six
  23. try:
  24. import psutil
  25. HAS_PSUTIL = True
  26. except ImportError:
  27. HAS_PSUTIL = False
  28. log = logging.getLogger(__name__)
  29. # Set SHOW_PROC to True to show
  30. # process details when running in verbose mode
  31. # i.e. [CPU:15.1%|MEM:48.3%|Z:0]
  32. SHOW_PROC = 'NO_SHOW_PROC' not in os.environ
  33. LOREM_IPSUM = '''\
  34. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eget urna a arcu lacinia sagittis.
  35. Sed scelerisque, lacus eget malesuada vestibulum, justo diam facilisis tortor, in sodales dolor
  36. nibh eu urna. Aliquam iaculis massa risus, sed elementum risus accumsan id. Suspendisse mattis,
  37. metus sed lacinia dictum, leo orci dapibus sapien, at porttitor sapien nulla ac velit.
  38. Duis ac cursus leo, non varius metus. Sed laoreet felis magna, vel tempor diam malesuada nec.
  39. Quisque cursus odio tortor. In consequat augue nisl, eget lacinia odio vestibulum eget.
  40. Donec venenatis elementum arcu at rhoncus. Nunc pharetra erat in lacinia convallis. Ut condimentum
  41. eu mauris sit amet convallis. Morbi vulputate vel odio non laoreet. Nullam in suscipit tellus.
  42. Sed quis posuere urna.'''
  43. # support python < 2.7 via unittest2
  44. if sys.version_info < (2, 7):
  45. try:
  46. # pylint: disable=import-error
  47. from unittest2 import (
  48. TestLoader as __TestLoader,
  49. TextTestRunner as __TextTestRunner,
  50. TestCase as __TestCase,
  51. expectedFailure,
  52. TestSuite as __TestSuite,
  53. skip,
  54. skipIf,
  55. TestResult as _TestResult,
  56. TextTestResult as __TextTestResult
  57. )
  58. from unittest2.case import _id
  59. # pylint: enable=import-error
  60. class NewStyleClassMixin(object):
  61. '''
  62. Simple new style class to make pylint shut up!
  63. And also to avoid errors like:
  64. 'Cannot create a consistent method resolution order (MRO) for bases'
  65. '''
  66. class _TestLoader(__TestLoader, NewStyleClassMixin):
  67. pass
  68. class _TextTestRunner(__TextTestRunner, NewStyleClassMixin):
  69. pass
  70. class _TestCase(__TestCase, NewStyleClassMixin):
  71. pass
  72. class _TestSuite(__TestSuite, NewStyleClassMixin):
  73. pass
  74. class TestResult(_TestResult, NewStyleClassMixin):
  75. pass
  76. class _TextTestResult(__TextTestResult, NewStyleClassMixin):
  77. pass
  78. except ImportError:
  79. raise SystemExit('You need to install unittest2 to run the salt tests')
  80. else:
  81. from unittest import (
  82. TestLoader as _TestLoader,
  83. TextTestRunner as _TextTestRunner,
  84. TestCase as _TestCase,
  85. expectedFailure,
  86. TestSuite as _TestSuite,
  87. skip,
  88. skipIf,
  89. TestResult,
  90. TextTestResult as _TextTestResult,
  91. SkipTest,
  92. )
  93. from unittest.case import _id
  94. class TestSuite(_TestSuite):
  95. def _handleClassSetUp(self, test, result):
  96. previousClass = getattr(result, '_previousTestClass', None)
  97. currentClass = test.__class__
  98. if currentClass == previousClass or getattr(currentClass, 'setUpClass', None) is None:
  99. return super(TestSuite, self)._handleClassSetUp(test, result)
  100. # Store a reference to all class attributes before running the setUpClass method
  101. initial_class_attributes = dir(test.__class__)
  102. ret = super(TestSuite, self)._handleClassSetUp(test, result)
  103. # Store the difference in in a variable in order to check later if they were deleted
  104. test.__class__._prerun_class_attributes = [
  105. attr for attr in dir(test.__class__) if attr not in initial_class_attributes]
  106. return ret
  107. def _tearDownPreviousClass(self, test, result):
  108. # Run any tearDownClass code defined
  109. super(TestSuite, self)._tearDownPreviousClass(test, result)
  110. previousClass = getattr(result, '_previousTestClass', None)
  111. currentClass = test.__class__
  112. if currentClass == previousClass:
  113. return
  114. # See if the previous class attributes have been cleaned
  115. if previousClass and getattr(previousClass, 'tearDownClass', None):
  116. prerun_class_attributes = getattr(previousClass, '_prerun_class_attributes', None)
  117. if prerun_class_attributes is not None:
  118. previousClass._prerun_class_attributes = None
  119. del previousClass._prerun_class_attributes
  120. for attr in prerun_class_attributes:
  121. if hasattr(previousClass, attr):
  122. attr_value = getattr(previousClass, attr, None)
  123. if attr_value is None:
  124. continue
  125. if isinstance(attr_value, (bool,) + six.string_types + six.integer_types):
  126. setattr(previousClass, attr, None)
  127. continue
  128. log.warning('Deleting extra class attribute after test run: %s.%s(%s). '
  129. 'Please consider using \'del self.%s\' on the test class '
  130. '\'tearDownClass()\' method', previousClass.__name__, attr,
  131. str(getattr(previousClass, attr))[:100], attr)
  132. delattr(previousClass, attr)
  133. class TestLoader(_TestLoader):
  134. # We're just subclassing to make sure tha tour TestSuite class is the one used
  135. suiteClass = TestSuite
  136. class TestCase(_TestCase):
  137. # pylint: disable=expected-an-indented-block-comment,too-many-leading-hastag-for-block-comment
  138. ## Commented out because it may be causing tests to hang
  139. ## at the end of the run
  140. #
  141. # _cwd = os.getcwd()
  142. # _chdir_counter = 0
  143. # @classmethod
  144. # def tearDownClass(cls):
  145. # '''
  146. # Overriden method for tearing down all classes in salttesting
  147. #
  148. # This hard-resets the environment between test classes
  149. # '''
  150. # # Compare where we are now compared to where we were when we began this family of tests
  151. # if not cls._cwd == os.getcwd() and cls._chdir_counter > 0:
  152. # os.chdir(cls._cwd)
  153. # print('\nWARNING: A misbehaving test has modified the working directory!\nThe test suite has reset the working directory '
  154. # 'on tearDown() to {0}\n'.format(cls._cwd))
  155. # cls._chdir_counter += 1
  156. # pylint: enable=expected-an-indented-block-comment,too-many-leading-hastag-for-block-comment
  157. def run(self, result=None):
  158. self._prerun_instance_attributes = dir(self)
  159. self.maxDiff = None
  160. outcome = super(TestCase, self).run(result=result)
  161. for attr in dir(self):
  162. if attr == '_prerun_instance_attributes':
  163. continue
  164. if attr in getattr(self.__class__, '_prerun_class_attributes', ()):
  165. continue
  166. if attr not in self._prerun_instance_attributes:
  167. attr_value = getattr(self, attr, None)
  168. if attr_value is None:
  169. continue
  170. if isinstance(attr_value, (bool,) + six.string_types + six.integer_types):
  171. setattr(self, attr, None)
  172. continue
  173. log.warning('Deleting extra class attribute after test run: %s.%s(%s). '
  174. 'Please consider using \'del self.%s\' on the test case '
  175. '\'tearDown()\' method', self.__class__.__name__, attr,
  176. getattr(self, attr), attr)
  177. delattr(self, attr)
  178. self._prerun_instance_attributes = None
  179. del self._prerun_instance_attributes
  180. return outcome
  181. def shortDescription(self):
  182. desc = _TestCase.shortDescription(self)
  183. if HAS_PSUTIL and SHOW_PROC:
  184. show_zombie_processes = 'SHOW_PROC_ZOMBIES' in os.environ
  185. proc_info = '[CPU:{0}%|MEM:{1}%'.format(psutil.cpu_percent(),
  186. psutil.virtual_memory().percent)
  187. if show_zombie_processes:
  188. found_zombies = 0
  189. try:
  190. for proc in psutil.process_iter():
  191. if proc.status == psutil.STATUS_ZOMBIE:
  192. found_zombies += 1
  193. except Exception:
  194. pass
  195. proc_info += '|Z:{0}'.format(found_zombies)
  196. proc_info += '] {short_desc}'.format(short_desc=desc if desc else '')
  197. return proc_info
  198. else:
  199. return _TestCase.shortDescription(self)
  200. def assertEquals(self, *args, **kwargs):
  201. raise DeprecationWarning(
  202. 'The {0}() function is deprecated. Please start using {1}() '
  203. 'instead.'.format('assertEquals', 'assertEqual')
  204. )
  205. # return _TestCase.assertEquals(self, *args, **kwargs)
  206. def assertNotEquals(self, *args, **kwargs):
  207. raise DeprecationWarning(
  208. 'The {0}() function is deprecated. Please start using {1}() '
  209. 'instead.'.format('assertNotEquals', 'assertNotEqual')
  210. )
  211. # return _TestCase.assertNotEquals(self, *args, **kwargs)
  212. def assert_(self, *args, **kwargs):
  213. # The unittest2 library uses this deprecated method, we can't raise
  214. # the exception.
  215. raise DeprecationWarning(
  216. 'The {0}() function is deprecated. Please start using {1}() '
  217. 'instead.'.format('assert_', 'assertTrue')
  218. )
  219. # return _TestCase.assert_(self, *args, **kwargs)
  220. def assertAlmostEquals(self, *args, **kwargs):
  221. raise DeprecationWarning(
  222. 'The {0}() function is deprecated. Please start using {1}() '
  223. 'instead.'.format('assertAlmostEquals', 'assertAlmostEqual')
  224. )
  225. # return _TestCase.assertAlmostEquals(self, *args, **kwargs)
  226. def assertNotAlmostEquals(self, *args, **kwargs):
  227. raise DeprecationWarning(
  228. 'The {0}() function is deprecated. Please start using {1}() '
  229. 'instead.'.format('assertNotAlmostEquals', 'assertNotAlmostEqual')
  230. )
  231. # return _TestCase.assertNotAlmostEquals(self, *args, **kwargs)
  232. def repack_state_returns(self, state_ret):
  233. '''
  234. Accepts a state return dict and returns it back with the top level key
  235. names rewritten such that the ID declaration is the key instead of the
  236. State's unique tag. For example: 'foo' instead of
  237. 'file_|-foo_|-/etc/foo.conf|-managed'
  238. This makes it easier to work with state returns when crafting asserts
  239. after running states.
  240. '''
  241. assert isinstance(state_ret, dict), state_ret
  242. return {x.split('_|-')[1]: y for x, y in six.iteritems(state_ret)}
  243. def failUnlessEqual(self, *args, **kwargs):
  244. raise DeprecationWarning(
  245. 'The {0}() function is deprecated. Please start using {1}() '
  246. 'instead.'.format('failUnlessEqual', 'assertEqual')
  247. )
  248. # return _TestCase.failUnlessEqual(self, *args, **kwargs)
  249. def failIfEqual(self, *args, **kwargs):
  250. raise DeprecationWarning(
  251. 'The {0}() function is deprecated. Please start using {1}() '
  252. 'instead.'.format('failIfEqual', 'assertNotEqual')
  253. )
  254. # return _TestCase.failIfEqual(self, *args, **kwargs)
  255. def failUnless(self, *args, **kwargs):
  256. raise DeprecationWarning(
  257. 'The {0}() function is deprecated. Please start using {1}() '
  258. 'instead.'.format('failUnless', 'assertTrue')
  259. )
  260. # return _TestCase.failUnless(self, *args, **kwargs)
  261. def failIf(self, *args, **kwargs):
  262. raise DeprecationWarning(
  263. 'The {0}() function is deprecated. Please start using {1}() '
  264. 'instead.'.format('failIf', 'assertFalse')
  265. )
  266. # return _TestCase.failIf(self, *args, **kwargs)
  267. def failUnlessRaises(self, *args, **kwargs):
  268. raise DeprecationWarning(
  269. 'The {0}() function is deprecated. Please start using {1}() '
  270. 'instead.'.format('failUnlessRaises', 'assertRaises')
  271. )
  272. # return _TestCase.failUnlessRaises(self, *args, **kwargs)
  273. def failUnlessAlmostEqual(self, *args, **kwargs):
  274. raise DeprecationWarning(
  275. 'The {0}() function is deprecated. Please start using {1}() '
  276. 'instead.'.format('failUnlessAlmostEqual', 'assertAlmostEqual')
  277. )
  278. # return _TestCase.failUnlessAlmostEqual(self, *args, **kwargs)
  279. def failIfAlmostEqual(self, *args, **kwargs):
  280. raise DeprecationWarning(
  281. 'The {0}() function is deprecated. Please start using {1}() '
  282. 'instead.'.format('failIfAlmostEqual', 'assertNotAlmostEqual')
  283. )
  284. # return _TestCase.failIfAlmostEqual(self, *args, **kwargs)
  285. @staticmethod
  286. def assert_called_once(mock):
  287. '''
  288. mock.assert_called_once only exists in PY3 in 3.6 and newer
  289. '''
  290. try:
  291. mock.assert_called_once()
  292. except AttributeError:
  293. log.warning('assert_called_once invoked, but not available')
  294. if six.PY2:
  295. def assertRegexpMatches(self, *args, **kwds):
  296. raise DeprecationWarning(
  297. 'The {0}() function will be deprecated in python 3. Please start '
  298. 'using {1}() instead.'.format(
  299. 'assertRegexpMatches',
  300. 'assertRegex'
  301. )
  302. )
  303. def assertRegex(self, text, regex, msg=None):
  304. # In python 2, alias to the future python 3 function
  305. return _TestCase.assertRegexpMatches(self, text, regex, msg=msg)
  306. def assertNotRegexpMatches(self, *args, **kwds):
  307. raise DeprecationWarning(
  308. 'The {0}() function will be deprecated in python 3. Please start '
  309. 'using {1}() instead.'.format(
  310. 'assertNotRegexpMatches',
  311. 'assertNotRegex'
  312. )
  313. )
  314. def assertNotRegex(self, text, regex, msg=None):
  315. # In python 2, alias to the future python 3 function
  316. return _TestCase.assertNotRegexpMatches(self, text, regex, msg=msg)
  317. def assertRaisesRegexp(self, *args, **kwds):
  318. raise DeprecationWarning(
  319. 'The {0}() function will be deprecated in python 3. Please start '
  320. 'using {1}() instead.'.format(
  321. 'assertRaisesRegexp',
  322. 'assertRaisesRegex'
  323. )
  324. )
  325. def assertRaisesRegex(self, exception, regexp, *args, **kwds):
  326. # In python 2, alias to the future python 3 function
  327. return _TestCase.assertRaisesRegexp(self, exception, regexp, *args, **kwds)
  328. else:
  329. def assertRegexpMatches(self, *args, **kwds):
  330. raise DeprecationWarning(
  331. 'The {0}() function is deprecated. Please start using {1}() '
  332. 'instead.'.format(
  333. 'assertRegexpMatches',
  334. 'assertRegex'
  335. )
  336. )
  337. def assertNotRegexpMatches(self, *args, **kwds):
  338. raise DeprecationWarning(
  339. 'The {0}() function is deprecated. Please start using {1}() '
  340. 'instead.'.format(
  341. 'assertNotRegexpMatches',
  342. 'assertNotRegex'
  343. )
  344. )
  345. def assertRaisesRegexp(self, *args, **kwds):
  346. raise DeprecationWarning(
  347. 'The {0}() function is deprecated. Please start using {1}() '
  348. 'instead.'.format(
  349. 'assertRaisesRegexp',
  350. 'assertRaisesRegex'
  351. )
  352. )
  353. class TextTestResult(_TextTestResult):
  354. '''
  355. Custom TestResult class whith logs the start and the end of a test
  356. '''
  357. def startTest(self, test):
  358. log.debug('>>>>> START >>>>> {0}'.format(test.id()))
  359. return super(TextTestResult, self).startTest(test)
  360. def stopTest(self, test):
  361. log.debug('<<<<< END <<<<<<< {0}'.format(test.id()))
  362. return super(TextTestResult, self).stopTest(test)
  363. class TextTestRunner(_TextTestRunner):
  364. '''
  365. Custom Text tests runner to log the start and the end of a test case
  366. '''
  367. resultclass = TextTestResult
  368. __all__ = [
  369. 'TestLoader',
  370. 'TextTestRunner',
  371. 'TestCase',
  372. 'expectedFailure',
  373. 'TestSuite',
  374. 'skipIf',
  375. 'TestResult'
  376. ]