1
0

unit.py 17 KB

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