test_decorators.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Bo Maryniuk (bo@suse.de)
  4. unit.utils.decorators_test
  5. """
  6. # Import Python libs
  7. from __future__ import absolute_import, print_function, unicode_literals
  8. import inspect
  9. # Import Salt libs
  10. import salt.utils.decorators as decorators
  11. from salt.exceptions import CommandExecutionError, SaltConfigurationError
  12. from salt.version import SaltStackVersion
  13. from tests.support.mock import MagicMock, patch
  14. from tests.support.unit import TestCase
  15. class DummyLogger(object):
  16. """
  17. Dummy logger accepts everything and simply logs
  18. """
  19. def __init__(self, messages):
  20. self._messages = messages
  21. def __getattr__(self, item):
  22. return self._log
  23. def _log(self, msg):
  24. self._messages.append(msg)
  25. class DecoratorsTest(TestCase):
  26. """
  27. Testing decorators.
  28. """
  29. def old_function(self):
  30. return "old"
  31. def new_function(self):
  32. return "new"
  33. def _new_function(self):
  34. return "old"
  35. def _mk_version(self, name):
  36. """
  37. Make a version
  38. :return:
  39. """
  40. return name, SaltStackVersion.from_name(name)
  41. def setUp(self):
  42. """
  43. Setup a test
  44. :return:
  45. """
  46. self.globs = {
  47. "__virtualname__": "test",
  48. "__opts__": {},
  49. "__pillar__": {},
  50. "old_function": self.old_function,
  51. "new_function": self.new_function,
  52. "_new_function": self._new_function,
  53. }
  54. self.addCleanup(delattr, self, "globs")
  55. self.messages = list()
  56. self.addCleanup(delattr, self, "messages")
  57. patcher = patch.object(decorators, "log", DummyLogger(self.messages))
  58. patcher.start()
  59. self.addCleanup(patcher.stop)
  60. def test_is_deprecated_version_eol(self):
  61. """
  62. Use of is_deprecated will result to the exception,
  63. if the expiration version is lower than the current version.
  64. A successor function is not pointed out.
  65. :return:
  66. """
  67. depr = decorators.is_deprecated(self.globs, "Helium")
  68. depr._curr_version = self._mk_version("Beryllium")[1]
  69. with self.assertRaises(CommandExecutionError):
  70. depr(self.old_function)()
  71. self.assertEqual(
  72. self.messages, ['The lifetime of the function "old_function" expired.']
  73. )
  74. def test_is_deprecated_with_successor_eol(self):
  75. """
  76. Use of is_deprecated will result to the exception,
  77. if the expiration version is lower than the current version.
  78. A successor function is pointed out.
  79. :return:
  80. """
  81. depr = decorators.is_deprecated(
  82. self.globs, "Helium", with_successor="new_function"
  83. )
  84. depr._curr_version = self._mk_version("Beryllium")[1]
  85. with self.assertRaises(CommandExecutionError):
  86. depr(self.old_function)()
  87. self.assertEqual(
  88. self.messages,
  89. [
  90. 'The lifetime of the function "old_function" expired. '
  91. 'Please use its successor "new_function" instead.'
  92. ],
  93. )
  94. def test_is_deprecated(self):
  95. """
  96. Use of is_deprecated will result to the log message,
  97. if the expiration version is higher than the current version.
  98. A successor function is not pointed out.
  99. :return:
  100. """
  101. depr = decorators.is_deprecated(self.globs, "Beryllium")
  102. depr._curr_version = self._mk_version("Helium")[1]
  103. self.assertEqual(depr(self.old_function)(), self.old_function())
  104. self.assertEqual(
  105. self.messages,
  106. [
  107. 'The function "old_function" is deprecated '
  108. 'and will expire in version "Beryllium".'
  109. ],
  110. )
  111. def test_is_deprecated_with_successor(self):
  112. """
  113. Use of is_deprecated will result to the log message,
  114. if the expiration version is higher than the current version.
  115. A successor function is pointed out.
  116. :return:
  117. """
  118. depr = decorators.is_deprecated(
  119. self.globs, "Beryllium", with_successor="old_function"
  120. )
  121. depr._curr_version = self._mk_version("Helium")[1]
  122. self.assertEqual(depr(self.old_function)(), self.old_function())
  123. self.assertEqual(
  124. self.messages,
  125. [
  126. 'The function "old_function" is deprecated '
  127. 'and will expire in version "Beryllium". '
  128. 'Use successor "old_function" instead.'
  129. ],
  130. )
  131. def test_with_deprecated_notfound(self):
  132. """
  133. Test with_deprecated should raise an exception, if a same name
  134. function with the "_" prefix not implemented.
  135. :return:
  136. """
  137. del self.globs["_new_function"]
  138. self.globs["__opts__"]["use_deprecated"] = ["test.new_function"]
  139. depr = decorators.with_deprecated(self.globs, "Beryllium")
  140. depr._curr_version = self._mk_version("Helium")[1]
  141. with self.assertRaises(CommandExecutionError):
  142. depr(self.new_function)()
  143. self.assertEqual(
  144. self.messages,
  145. [
  146. 'The function "test.new_function" is using its deprecated '
  147. 'version and will expire in version "Beryllium".'
  148. ],
  149. )
  150. def test_with_deprecated_notfound_in_pillar(self):
  151. """
  152. Test with_deprecated should raise an exception, if a same name
  153. function with the "_" prefix not implemented.
  154. :return:
  155. """
  156. del self.globs["_new_function"]
  157. self.globs["__pillar__"]["use_deprecated"] = ["test.new_function"]
  158. depr = decorators.with_deprecated(self.globs, "Beryllium")
  159. depr._curr_version = self._mk_version("Helium")[1]
  160. with self.assertRaises(CommandExecutionError):
  161. depr(self.new_function)()
  162. self.assertEqual(
  163. self.messages,
  164. [
  165. 'The function "test.new_function" is using its deprecated '
  166. 'version and will expire in version "Beryllium".'
  167. ],
  168. )
  169. def test_with_deprecated_found(self):
  170. """
  171. Test with_deprecated should not raise an exception, if a same name
  172. function with the "_" prefix is implemented, but should use
  173. an old version instead, if "use_deprecated" is requested.
  174. :return:
  175. """
  176. self.globs["__opts__"]["use_deprecated"] = ["test.new_function"]
  177. self.globs["_new_function"] = self.old_function
  178. depr = decorators.with_deprecated(self.globs, "Beryllium")
  179. depr._curr_version = self._mk_version("Helium")[1]
  180. self.assertEqual(depr(self.new_function)(), self.old_function())
  181. log_msg = [
  182. 'The function "test.new_function" is using its deprecated version '
  183. 'and will expire in version "Beryllium".'
  184. ]
  185. self.assertEqual(self.messages, log_msg)
  186. def test_with_deprecated_found_in_pillar(self):
  187. """
  188. Test with_deprecated should not raise an exception, if a same name
  189. function with the "_" prefix is implemented, but should use
  190. an old version instead, if "use_deprecated" is requested.
  191. :return:
  192. """
  193. self.globs["__pillar__"]["use_deprecated"] = ["test.new_function"]
  194. self.globs["_new_function"] = self.old_function
  195. depr = decorators.with_deprecated(self.globs, "Beryllium")
  196. depr._curr_version = self._mk_version("Helium")[1]
  197. self.assertEqual(depr(self.new_function)(), self.old_function())
  198. log_msg = [
  199. 'The function "test.new_function" is using its deprecated version '
  200. 'and will expire in version "Beryllium".'
  201. ]
  202. self.assertEqual(self.messages, log_msg)
  203. def test_with_deprecated_found_eol(self):
  204. """
  205. Test with_deprecated should raise an exception, if a same name
  206. function with the "_" prefix is implemented, "use_deprecated" is requested
  207. and EOL is reached.
  208. :return:
  209. """
  210. self.globs["__opts__"]["use_deprecated"] = ["test.new_function"]
  211. self.globs["_new_function"] = self.old_function
  212. depr = decorators.with_deprecated(self.globs, "Helium")
  213. depr._curr_version = self._mk_version("Beryllium")[1]
  214. with self.assertRaises(CommandExecutionError):
  215. depr(self.new_function)()
  216. self.assertEqual(
  217. self.messages,
  218. [
  219. 'Although function "new_function" is called, an alias "new_function" '
  220. "is configured as its deprecated version. The lifetime of the function "
  221. '"new_function" expired. Please use its successor "new_function" instead.'
  222. ],
  223. )
  224. def test_with_deprecated_found_eol_in_pillar(self):
  225. """
  226. Test with_deprecated should raise an exception, if a same name
  227. function with the "_" prefix is implemented, "use_deprecated" is requested
  228. and EOL is reached.
  229. :return:
  230. """
  231. self.globs["__pillar__"]["use_deprecated"] = ["test.new_function"]
  232. self.globs["_new_function"] = self.old_function
  233. depr = decorators.with_deprecated(self.globs, "Helium")
  234. depr._curr_version = self._mk_version("Beryllium")[1]
  235. with self.assertRaises(CommandExecutionError):
  236. depr(self.new_function)()
  237. self.assertEqual(
  238. self.messages,
  239. [
  240. 'Although function "new_function" is called, an alias "new_function" '
  241. "is configured as its deprecated version. The lifetime of the function "
  242. '"new_function" expired. Please use its successor "new_function" instead.'
  243. ],
  244. )
  245. def test_with_deprecated_no_conf(self):
  246. """
  247. Test with_deprecated should not raise an exception, if a same name
  248. function with the "_" prefix is implemented, but should use
  249. a new version instead, if "use_deprecated" is not requested.
  250. :return:
  251. """
  252. self.globs["_new_function"] = self.old_function
  253. depr = decorators.with_deprecated(self.globs, "Beryllium")
  254. depr._curr_version = self._mk_version("Helium")[1]
  255. self.assertEqual(depr(self.new_function)(), self.new_function())
  256. self.assertFalse(self.messages)
  257. def test_with_deprecated_with_name(self):
  258. """
  259. Test with_deprecated should not raise an exception, if a different name
  260. function is implemented and specified with the "with_name" parameter,
  261. but should use an old version instead and log a warning log message.
  262. :return:
  263. """
  264. self.globs["__opts__"]["use_deprecated"] = ["test.new_function"]
  265. depr = decorators.with_deprecated(
  266. self.globs, "Beryllium", with_name="old_function"
  267. )
  268. depr._curr_version = self._mk_version("Helium")[1]
  269. self.assertEqual(depr(self.new_function)(), self.old_function())
  270. self.assertEqual(
  271. self.messages,
  272. [
  273. 'The function "old_function" is deprecated and will expire in version "Beryllium". '
  274. 'Use its successor "new_function" instead.'
  275. ],
  276. )
  277. def test_with_deprecated_with_name_eol(self):
  278. """
  279. Test with_deprecated should raise an exception, if a different name
  280. function is implemented and specified with the "with_name" parameter
  281. and EOL is reached.
  282. :return:
  283. """
  284. self.globs["__opts__"]["use_deprecated"] = ["test.new_function"]
  285. depr = decorators.with_deprecated(
  286. self.globs, "Helium", with_name="old_function"
  287. )
  288. depr._curr_version = self._mk_version("Beryllium")[1]
  289. with self.assertRaises(CommandExecutionError):
  290. depr(self.new_function)()
  291. self.assertEqual(
  292. self.messages,
  293. [
  294. 'Although function "new_function" is called, '
  295. 'an alias "old_function" is configured as its deprecated version. '
  296. 'The lifetime of the function "old_function" expired. '
  297. 'Please use its successor "new_function" instead.'
  298. ],
  299. )
  300. def test_with_deprecated_opt_in_default(self):
  301. """
  302. Test with_deprecated using opt-in policy,
  303. where newer function is not used, unless configured.
  304. :return:
  305. """
  306. depr = decorators.with_deprecated(
  307. self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN
  308. )
  309. depr._curr_version = self._mk_version("Helium")[1]
  310. assert depr(self.new_function)() == self.old_function()
  311. assert self.messages == [
  312. 'The function "test.new_function" is using its '
  313. 'deprecated version and will expire in version "Beryllium".'
  314. ]
  315. def test_with_deprecated_opt_in_use_superseded(self):
  316. """
  317. Test with_deprecated using opt-in policy,
  318. where newer function is used as per configuration.
  319. :return:
  320. """
  321. self.globs["__opts__"]["use_superseded"] = ["test.new_function"]
  322. depr = decorators.with_deprecated(
  323. self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN
  324. )
  325. depr._curr_version = self._mk_version("Helium")[1]
  326. assert depr(self.new_function)() == self.new_function()
  327. assert not self.messages
  328. def test_with_deprecated_opt_in_use_superseded_in_pillar(self):
  329. """
  330. Test with_deprecated using opt-in policy,
  331. where newer function is used as per configuration.
  332. :return:
  333. """
  334. self.globs["__pillar__"]["use_superseded"] = ["test.new_function"]
  335. depr = decorators.with_deprecated(
  336. self.globs, "Beryllium", policy=decorators._DeprecationDecorator.OPT_IN
  337. )
  338. depr._curr_version = self._mk_version("Helium")[1]
  339. assert depr(self.new_function)() == self.new_function()
  340. assert not self.messages
  341. def test_with_deprecated_opt_in_use_superseded_and_deprecated(self):
  342. """
  343. Test with_deprecated misconfiguration.
  344. :return:
  345. """
  346. self.globs["__opts__"]["use_deprecated"] = ["test.new_function"]
  347. self.globs["__opts__"]["use_superseded"] = ["test.new_function"]
  348. depr = decorators.with_deprecated(self.globs, "Beryllium")
  349. depr._curr_version = self._mk_version("Helium")[1]
  350. with self.assertRaises(SaltConfigurationError):
  351. assert depr(self.new_function)() == self.new_function()
  352. def test_with_deprecated_opt_in_use_superseded_and_deprecated_in_pillar(self):
  353. """
  354. Test with_deprecated misconfiguration.
  355. :return:
  356. """
  357. self.globs["__pillar__"]["use_deprecated"] = ["test.new_function"]
  358. self.globs["__pillar__"]["use_superseded"] = ["test.new_function"]
  359. depr = decorators.with_deprecated(self.globs, "Beryllium")
  360. depr._curr_version = self._mk_version("Helium")[1]
  361. with self.assertRaises(SaltConfigurationError):
  362. assert depr(self.new_function)() == self.new_function()
  363. def test_with_depreciated_should_wrap_function(self):
  364. wrapped = decorators.with_deprecated({}, "Beryllium")(self.old_function)
  365. assert wrapped.__module__ == self.old_function.__module__
  366. def test_is_deprecated_should_wrap_function(self):
  367. wrapped = decorators.is_deprecated({}, "Beryllium")(self.old_function)
  368. assert wrapped.__module__ == self.old_function.__module__
  369. def test_ensure_unicode_args_should_wrap_function(self):
  370. wrapped = decorators.ensure_unicode_args(self.old_function)
  371. assert wrapped.__module__ == self.old_function.__module__
  372. def test_ignores_kwargs_should_wrap_function(self):
  373. wrapped = decorators.ignores_kwargs("foo", "bar")(self.old_function)
  374. assert wrapped.__module__ == self.old_function.__module__
  375. def test_memoize_should_wrap_function(self):
  376. wrapped = decorators.memoize(self.old_function)
  377. assert wrapped.__module__ == self.old_function.__module__
  378. def timing_should_wrap_function(self):
  379. wrapped = decorators.timing(self.old_function)
  380. assert wrapped.__module__ == self.old_function.__module__
  381. class DependsDecoratorTest(TestCase):
  382. def function(self):
  383. return "foo"
  384. def test_depends_get_previous_frame(self):
  385. """
  386. Confirms that we're not grabbing the entire stack every time the
  387. depends decorator is invoked.
  388. """
  389. # Simply using True as a conditon; we aren't testing the dependency,
  390. # but rather the functions called within the decorator.
  391. dep = decorators.depends(True)
  392. # By mocking both inspect.stack and inspect.currentframe with
  393. # MagicMocks that return themselves, we don't affect normal operation
  394. # of the decorator, and at the same time we get to peek at whether or
  395. # not either was called.
  396. stack_mock = MagicMock(return_value=inspect.stack)
  397. currentframe_mock = MagicMock(return_value=inspect.currentframe)
  398. with patch.object(inspect, "stack", stack_mock), patch.object(
  399. inspect, "currentframe", currentframe_mock
  400. ):
  401. dep(self.function)()
  402. stack_mock.assert_not_called()
  403. currentframe_mock.assert_called_once_with()