1
0

test_versions.py 15 KB

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