test_versions.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. # -*- coding: utf-8 -*-
  2. """
  3. tests.unit.version_test
  4. ~~~~~~~~~~~~~~~~~~~~~~~
  5. These tests are copied from python's source `Lib/distutils/tests/test_version.py`
  6. Some new examples were added and some adjustments were made to run tests in python 2 and 3
  7. """
  8. # pylint: disable=string-substitution-usage-error
  9. # Import python libs
  10. from __future__ import absolute_import, print_function, unicode_literals
  11. import datetime
  12. import os
  13. import sys
  14. import warnings
  15. import pytest
  16. # Import Salt libs
  17. import salt.modules.cmdmod
  18. import salt.utils.platform
  19. import salt.utils.versions
  20. import salt.version
  21. # Import 3rd-party libs
  22. from salt.ext import six
  23. from salt.utils.versions import LooseVersion, StrictVersion
  24. from tests.support.mock import patch
  25. from tests.support.paths import CODE_DIR
  26. # Import Salt Testing libs
  27. from tests.support.unit import TestCase, skipIf
  28. if six.PY2:
  29. cmp_method = "__cmp__"
  30. else:
  31. cmp_method = "_cmp"
  32. class VersionTestCase(TestCase):
  33. def test_prerelease(self):
  34. version = StrictVersion("1.2.3a1")
  35. self.assertEqual(version.version, (1, 2, 3))
  36. self.assertEqual(version.prerelease, ("a", 1))
  37. self.assertEqual(six.text_type(version), "1.2.3a1")
  38. version = StrictVersion("1.2.0")
  39. self.assertEqual(six.text_type(version), "1.2")
  40. def test_cmp_strict(self):
  41. versions = (
  42. ("1.5.1", "1.5.2b2", -1),
  43. ("161", "3.10a", ValueError),
  44. ("8.02", "8.02", 0),
  45. ("3.4j", "1996.07.12", ValueError),
  46. ("3.2.pl0", "3.1.1.6", ValueError),
  47. ("2g6", "11g", ValueError),
  48. ("0.9", "2.2", -1),
  49. ("1.2.1", "1.2", 1),
  50. ("1.1", "1.2.2", -1),
  51. ("1.2", "1.1", 1),
  52. ("1.2.1", "1.2.2", -1),
  53. ("1.2.2", "1.2", 1),
  54. ("1.2", "1.2.2", -1),
  55. ("0.4.0", "0.4", 0),
  56. ("1.13++", "5.5.kw", ValueError),
  57. # Added by us
  58. ("1.1.1a1", "1.1.1", -1),
  59. )
  60. for v1, v2, wanted in versions:
  61. try:
  62. res = getattr(StrictVersion(v1), cmp_method)(StrictVersion(v2))
  63. except ValueError:
  64. if wanted is ValueError:
  65. continue
  66. else:
  67. raise AssertionError(
  68. ("cmp(%s, %s) " "shouldn't raise ValueError") % (v1, v2)
  69. )
  70. self.assertEqual(
  71. res, wanted, "cmp(%s, %s) should be %s, got %s" % (v1, v2, wanted, res)
  72. )
  73. def test_cmp(self):
  74. versions = (
  75. ("1.5.1", "1.5.2b2", -1),
  76. ("161", "3.10a", 1),
  77. ("8.02", "8.02", 0),
  78. ("3.4j", "1996.07.12", -1),
  79. ("3.2.pl0", "3.1.1.6", 1),
  80. ("2g6", "11g", -1),
  81. ("0.960923", "2.2beta29", -1),
  82. ("1.13++", "5.5.kw", -1),
  83. # Added by us
  84. ("3.10.0-514.el7", "3.10.0-514.6.1.el7", 1),
  85. ("2.2.2", "2.12.1", -1),
  86. )
  87. for v1, v2, wanted in versions:
  88. res = getattr(LooseVersion(v1), cmp_method)(LooseVersion(v2))
  89. self.assertEqual(
  90. res, wanted, "cmp(%s, %s) should be %s, got %s" % (v1, v2, wanted, res)
  91. )
  92. @skipIf(not salt.utils.platform.is_linux(), "only need to run on linux")
  93. @pytest.mark.slow_test(seconds=5) # Test takes >1 and <=5 seconds
  94. def test_spelling_version_name(self):
  95. """
  96. check the spelling of the version name for the release
  97. names in the salt.utils.versions.warn_until call
  98. """
  99. query = "salt.utils.versions.warn_until("
  100. names = salt.version.SaltStackVersion.NAMES
  101. cmd = "grep -lr {} -A 1 {}".format(query, os.path.join(CODE_DIR, "salt"))
  102. grep_call = salt.modules.cmdmod.run_stdout(cmd=cmd).split(os.linesep)
  103. for line in grep_call:
  104. num_cmd = salt.modules.cmdmod.run_stdout(
  105. "grep -c {0} {1}".format(query, line)
  106. )
  107. ver_cmd = salt.modules.cmdmod.run_stdout(
  108. "grep {0} {1} -A 1".format(query, line)
  109. )
  110. if "pyc" in line:
  111. break
  112. match = 0
  113. for key in names:
  114. if key in ver_cmd:
  115. match = match + (ver_cmd.count(key))
  116. if "utils/__init__.py" in line:
  117. # work around for utils/__init__.py because
  118. # it includes the warn_utils function
  119. match = match + 1
  120. self.assertEqual(
  121. match,
  122. int(num_cmd),
  123. msg="The file: {0} has an "
  124. "incorrect spelling for the release name in the warn_utils "
  125. "call: {1}. Expecting one of these release names: "
  126. "{2}".format(line, ver_cmd, names),
  127. )
  128. class VersionFuncsTestCase(TestCase):
  129. def test_compare(self):
  130. ret = salt.utils.versions.compare("1.0", "==", "1.0")
  131. self.assertTrue(ret)
  132. ret = salt.utils.versions.compare("1.0", "!=", "1.0")
  133. self.assertFalse(ret)
  134. with patch.object(salt.utils.versions, "log") as log_mock:
  135. ret = salt.utils.versions.compare(
  136. "1.0", "HAH I AM NOT A COMP OPERATOR! I AM YOUR FATHER!", "1.0"
  137. )
  138. self.assertTrue(log_mock.error.called)
  139. def test_kwargs_warn_until(self):
  140. # Test invalid version arg
  141. self.assertRaises(RuntimeError, salt.utils.versions.kwargs_warn_until, {}, [])
  142. def test_warn_until_warning_raised(self):
  143. # We *always* want *all* warnings thrown on this module
  144. warnings.filterwarnings("always", "", DeprecationWarning, __name__)
  145. def raise_warning(_version_info_=(0, 16, 0)):
  146. salt.utils.versions.warn_until(
  147. (0, 17), "Deprecation Message!", _version_info_=_version_info_
  148. )
  149. def raise_named_version_warning(_version_info_=(0, 16, 0)):
  150. salt.utils.versions.warn_until(
  151. "Hydrogen", "Deprecation Message!", _version_info_=_version_info_
  152. )
  153. # raise_warning should show warning until version info is >= (0, 17)
  154. with warnings.catch_warnings(record=True) as recorded_warnings:
  155. raise_warning()
  156. self.assertEqual(
  157. "Deprecation Message!", six.text_type(recorded_warnings[0].message)
  158. )
  159. # raise_warning should show warning until version info is >= (0, 17)
  160. with warnings.catch_warnings(record=True) as recorded_warnings:
  161. raise_named_version_warning()
  162. self.assertEqual(
  163. "Deprecation Message!", six.text_type(recorded_warnings[0].message)
  164. )
  165. # the deprecation warning is not issued because we passed
  166. # _dont_call_warning
  167. with warnings.catch_warnings(record=True) as recorded_warnings:
  168. salt.utils.versions.warn_until(
  169. (0, 17), "Foo", _dont_call_warnings=True, _version_info_=(0, 16)
  170. )
  171. self.assertEqual(0, len(recorded_warnings))
  172. # Let's set version info to (0, 17), a RuntimeError should be raised
  173. with self.assertRaisesRegex(
  174. RuntimeError,
  175. r"The warning triggered on filename \'(.*)test_versions.py\', "
  176. r"line number ([\d]+), is supposed to be shown until version "
  177. r"0.17.0 is released. Current version is now 0.17.0. "
  178. r"Please remove the warning.",
  179. ):
  180. raise_warning(_version_info_=(0, 17, 0))
  181. # Let's set version info to (0, 17), a RuntimeError should be raised
  182. with self.assertRaisesRegex(
  183. RuntimeError,
  184. r"The warning triggered on filename \'(.*)test_versions.py\', "
  185. r"line number ([\d]+), is supposed to be shown until version "
  186. r"(.*) is released. Current version is now "
  187. r"([\d.]+). Please remove the warning.",
  188. ):
  189. raise_named_version_warning(
  190. _version_info_=(
  191. getattr(sys, "maxint", None) or getattr(sys, "maxsize"),
  192. 16,
  193. 0,
  194. )
  195. )
  196. # Even though we're calling warn_until, we pass _dont_call_warnings
  197. # because we're only after the RuntimeError
  198. with self.assertRaisesRegex(
  199. RuntimeError,
  200. r"The warning triggered on filename \'(.*)test_versions.py\', "
  201. r"line number ([\d]+), is supposed to be shown until version "
  202. r"0.17.0 is released. Current version is now "
  203. r"(.*). Please remove the warning.",
  204. ):
  205. salt.utils.versions.warn_until((0, 17), "Foo", _dont_call_warnings=True)
  206. with self.assertRaisesRegex(
  207. RuntimeError,
  208. r"The warning triggered on filename \'(.*)test_versions.py\', "
  209. r"line number ([\d]+), is supposed to be shown until version "
  210. r"(.*) is released. Current version is now "
  211. r"(.*). Please remove the warning.",
  212. ):
  213. salt.utils.versions.warn_until(
  214. "Hydrogen",
  215. "Foo",
  216. _dont_call_warnings=True,
  217. _version_info_=(
  218. getattr(sys, "maxint", None) or getattr(sys, "maxsize"),
  219. 16,
  220. 0,
  221. ),
  222. )
  223. # version on the deprecation message gets properly formatted
  224. with warnings.catch_warnings(record=True) as recorded_warnings:
  225. vrs = salt.version.SaltStackVersion.from_name("Helium")
  226. salt.utils.versions.warn_until(
  227. "Helium",
  228. "Deprecation Message until {version}!",
  229. _version_info_=(vrs.major - 1, 0),
  230. )
  231. self.assertEqual(
  232. "Deprecation Message until {0}!".format(vrs.formatted_version),
  233. six.text_type(recorded_warnings[0].message),
  234. )
  235. def test_kwargs_warn_until_warning_raised(self):
  236. # We *always* want *all* warnings thrown on this module
  237. warnings.filterwarnings("always", "", DeprecationWarning, __name__)
  238. def raise_warning(**kwargs):
  239. _version_info_ = kwargs.pop("_version_info_", (0, 16, 0))
  240. salt.utils.versions.kwargs_warn_until(
  241. kwargs, (0, 17), _version_info_=_version_info_
  242. )
  243. # raise_warning({...}) should show warning until version info is >= (0, 17)
  244. with warnings.catch_warnings(record=True) as recorded_warnings:
  245. raise_warning(foo=42) # with a kwarg
  246. self.assertEqual(
  247. "The following parameter(s) have been deprecated and "
  248. "will be removed in '0.17.0': 'foo'.",
  249. six.text_type(recorded_warnings[0].message),
  250. )
  251. # With no **kwargs, should not show warning until version info is >= (0, 17)
  252. with warnings.catch_warnings(record=True) as recorded_warnings:
  253. salt.utils.versions.kwargs_warn_until(
  254. {}, (0, 17), _version_info_=(0, 16, 0) # no kwargs
  255. )
  256. self.assertEqual(0, len(recorded_warnings))
  257. # Let's set version info to (0, 17), a RuntimeError should be raised
  258. # regardless of whether or not we pass any **kwargs.
  259. with self.assertRaisesRegex(
  260. RuntimeError,
  261. r"The warning triggered on filename \'(.*)test_versions.py\', "
  262. r"line number ([\d]+), is supposed to be shown until version "
  263. r"0.17.0 is released. Current version is now 0.17.0. "
  264. r"Please remove the warning.",
  265. ):
  266. raise_warning(_version_info_=(0, 17)) # no kwargs
  267. with self.assertRaisesRegex(
  268. RuntimeError,
  269. r"The warning triggered on filename \'(.*)test_versions.py\', "
  270. r"line number ([\d]+), is supposed to be shown until version "
  271. r"0.17.0 is released. Current version is now 0.17.0. "
  272. r"Please remove the warning.",
  273. ):
  274. raise_warning(bar="baz", qux="quux", _version_info_=(0, 17)) # some kwargs
  275. def test_warn_until_date_warning_raised(self):
  276. # We *always* want *all* warnings thrown on this module
  277. warnings.filterwarnings("always", "", DeprecationWarning, __name__)
  278. _current_date = datetime.date(2000, 1, 1)
  279. # Test warning with datetime.date instance
  280. with warnings.catch_warnings(record=True) as recorded_warnings:
  281. salt.utils.versions.warn_until_date(
  282. datetime.date(2000, 1, 2),
  283. "Deprecation Message!",
  284. _current_date=_current_date,
  285. )
  286. self.assertEqual(
  287. "Deprecation Message!", six.text_type(recorded_warnings[0].message)
  288. )
  289. # Test warning with datetime.datetime instance
  290. with warnings.catch_warnings(record=True) as recorded_warnings:
  291. salt.utils.versions.warn_until_date(
  292. datetime.datetime(2000, 1, 2),
  293. "Deprecation Message!",
  294. _current_date=_current_date,
  295. )
  296. self.assertEqual(
  297. "Deprecation Message!", six.text_type(recorded_warnings[0].message)
  298. )
  299. # Test warning with date as a string
  300. with warnings.catch_warnings(record=True) as recorded_warnings:
  301. salt.utils.versions.warn_until_date(
  302. "20000102", "Deprecation Message!", _current_date=_current_date
  303. )
  304. self.assertEqual(
  305. "Deprecation Message!", six.text_type(recorded_warnings[0].message)
  306. )
  307. # the deprecation warning is not issued because we passed
  308. # _dont_call_warning
  309. with warnings.catch_warnings(record=True) as recorded_warnings:
  310. salt.utils.versions.warn_until_date(
  311. "20000102",
  312. "Deprecation Message!",
  313. _dont_call_warnings=True,
  314. _current_date=_current_date,
  315. )
  316. self.assertEqual(0, len(recorded_warnings))
  317. # Let's test for RuntimeError raise
  318. with self.assertRaisesRegex(
  319. RuntimeError,
  320. r"Deprecation Message! This warning\(now exception\) triggered on "
  321. r"filename \'(.*)test_versions.py\', line number ([\d]+), is "
  322. r"supposed to be shown until ([\d-]+). Today is ([\d-]+). "
  323. r"Please remove the warning.",
  324. ):
  325. salt.utils.versions.warn_until_date("20000101", "Deprecation Message!")
  326. # Even though we're calling warn_until_date, we pass _dont_call_warnings
  327. # because we're only after the RuntimeError
  328. with self.assertRaisesRegex(
  329. RuntimeError,
  330. r"Deprecation Message! This warning\(now exception\) triggered on "
  331. r"filename \'(.*)test_versions.py\', line number ([\d]+), is "
  332. r"supposed to be shown until ([\d-]+). Today is ([\d-]+). "
  333. r"Please remove the warning.",
  334. ):
  335. salt.utils.versions.warn_until_date(
  336. "20000101",
  337. "Deprecation Message!",
  338. _dont_call_warnings=True,
  339. _current_date=_current_date,
  340. )
  341. def test_warn_until_date_bad_strptime_format(self):
  342. # We *always* want *all* warnings thrown on this module
  343. warnings.filterwarnings("always", "", DeprecationWarning, __name__)
  344. # Let's test for RuntimeError raise
  345. with self.assertRaisesRegex(
  346. ValueError, "time data '0022' does not match format '%Y%m%d'"
  347. ):
  348. salt.utils.versions.warn_until_date("0022", "Deprecation Message!")