test_packaging.py 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. # Copyright (c) 2013 New Dream Network, LLC (DreamHost)
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. # implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # Copyright (C) 2013 Association of Universities for Research in Astronomy
  17. # (AURA)
  18. #
  19. # Redistribution and use in source and binary forms, with or without
  20. # modification, are permitted provided that the following conditions are met:
  21. #
  22. # 1. Redistributions of source code must retain the above copyright
  23. # notice, this list of conditions and the following disclaimer.
  24. #
  25. # 2. Redistributions in binary form must reproduce the above
  26. # copyright notice, this list of conditions and the following
  27. # disclaimer in the documentation and/or other materials provided
  28. # with the distribution.
  29. #
  30. # 3. The name of AURA and its representatives may not be used to
  31. # endorse or promote products derived from this software without
  32. # specific prior written permission.
  33. #
  34. # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
  35. # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  36. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  37. # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
  38. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  39. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  40. import email
  41. import email.errors
  42. import os
  43. import re
  44. import sysconfig
  45. import tempfile
  46. import textwrap
  47. import fixtures
  48. try:
  49. from unittest import mock
  50. except ImportError:
  51. import mock
  52. import pkg_resources
  53. import six
  54. import testscenarios
  55. import testtools
  56. from testtools import matchers
  57. import virtualenv
  58. from wheel import wheelfile
  59. from pbr import git
  60. from pbr import packaging
  61. from pbr.tests import base
  62. try:
  63. import importlib.machinery
  64. get_suffixes = importlib.machinery.all_suffixes
  65. # NOTE(JayF): ModuleNotFoundError only exists in Python 3.6+, not in 2.7
  66. except ImportError:
  67. import imp
  68. # NOTE(JayF) imp.get_suffixes returns a list of three-tuples;
  69. # we need the first value from each tuple.
  70. def get_suffixes():
  71. return [x[0] for x in imp.get_suffixes]
  72. PBR_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
  73. class TestRepo(fixtures.Fixture):
  74. """A git repo for testing with.
  75. Use of TempHomeDir with this fixture is strongly recommended as due to the
  76. lack of config --local in older gits, it will write to the users global
  77. configuration without TempHomeDir.
  78. """
  79. def __init__(self, basedir):
  80. super(TestRepo, self).__init__()
  81. self._basedir = basedir
  82. def setUp(self):
  83. super(TestRepo, self).setUp()
  84. base._run_cmd(['git', 'init', '.'], self._basedir)
  85. base._config_git()
  86. base._run_cmd(['git', 'add', '.'], self._basedir)
  87. def commit(self, message_content='test commit'):
  88. files = len(os.listdir(self._basedir))
  89. path = self._basedir + '/%d' % files
  90. open(path, 'wt').close()
  91. base._run_cmd(['git', 'add', path], self._basedir)
  92. base._run_cmd(['git', 'commit', '-m', message_content], self._basedir)
  93. def uncommit(self):
  94. base._run_cmd(['git', 'reset', '--hard', 'HEAD^'], self._basedir)
  95. def tag(self, version):
  96. base._run_cmd(
  97. ['git', 'tag', '-sm', 'test tag', version], self._basedir)
  98. class GPGKeyFixture(fixtures.Fixture):
  99. """Creates a GPG key for testing.
  100. It's recommended that this be used in concert with a unique home
  101. directory.
  102. """
  103. def setUp(self):
  104. super(GPGKeyFixture, self).setUp()
  105. tempdir = self.useFixture(fixtures.TempDir())
  106. gnupg_version_re = re.compile(r'^gpg\s.*\s([\d+])\.([\d+])\.([\d+])')
  107. gnupg_version = base._run_cmd(['gpg', '--version'], tempdir.path)
  108. for line in gnupg_version[0].split('\n'):
  109. gnupg_version = gnupg_version_re.match(line)
  110. if gnupg_version:
  111. gnupg_version = (int(gnupg_version.group(1)),
  112. int(gnupg_version.group(2)),
  113. int(gnupg_version.group(3)))
  114. break
  115. else:
  116. if gnupg_version is None:
  117. gnupg_version = (0, 0, 0)
  118. config_file = os.path.join(tempdir.path, 'key-config')
  119. with open(config_file, 'wt') as f:
  120. if gnupg_version[0] == 2 and gnupg_version[1] >= 1:
  121. f.write("""
  122. %no-protection
  123. %transient-key
  124. """)
  125. f.write("""
  126. %no-ask-passphrase
  127. Key-Type: RSA
  128. Name-Real: Example Key
  129. Name-Comment: N/A
  130. Name-Email: example@example.com
  131. Expire-Date: 2d
  132. %commit
  133. """)
  134. # Note that --quick-random (--debug-quick-random in GnuPG 2.x)
  135. # does not have a corresponding preferences file setting and
  136. # must be passed explicitly on the command line instead
  137. if gnupg_version[0] == 1:
  138. gnupg_random = '--quick-random'
  139. elif gnupg_version[0] >= 2:
  140. gnupg_random = '--debug-quick-random'
  141. else:
  142. gnupg_random = ''
  143. base._run_cmd(
  144. ['gpg', '--gen-key', '--batch', gnupg_random, config_file],
  145. tempdir.path)
  146. class Venv(fixtures.Fixture):
  147. """Create a virtual environment for testing with.
  148. :attr path: The path to the environment root.
  149. :attr python: The path to the python binary in the environment.
  150. """
  151. def __init__(self, reason, modules=(), pip_cmd=None):
  152. """Create a Venv fixture.
  153. :param reason: A human readable string to bake into the venv
  154. file path to aid diagnostics in the case of failures.
  155. :param modules: A list of modules to install, defaults to latest
  156. pip, wheel, and the working copy of PBR.
  157. :attr pip_cmd: A list to override the default pip_cmd passed to
  158. python for installing base packages.
  159. """
  160. self._reason = reason
  161. if modules == ():
  162. modules = ['pip', 'wheel', 'build', PBR_ROOT]
  163. self.modules = modules
  164. if pip_cmd is None:
  165. self.pip_cmd = ['-m', 'pip', '-v', 'install']
  166. else:
  167. self.pip_cmd = pip_cmd
  168. def _setUp(self):
  169. path = self.useFixture(fixtures.TempDir()).path
  170. virtualenv.cli_run([path])
  171. python = os.path.join(path, 'bin', 'python')
  172. command = [python] + self.pip_cmd + ['-U']
  173. if self.modules and len(self.modules) > 0:
  174. command.extend(self.modules)
  175. self.useFixture(base.CapturedSubprocess(
  176. 'mkvenv-' + self._reason, command))
  177. self.addCleanup(delattr, self, 'path')
  178. self.addCleanup(delattr, self, 'python')
  179. self.path = path
  180. self.python = python
  181. return path, python
  182. class CreatePackages(fixtures.Fixture):
  183. """Creates packages from dict with defaults
  184. :param package_dirs: A dict of package name to directory strings
  185. {'pkg_a': '/tmp/path/to/tmp/pkg_a', 'pkg_b': '/tmp/path/to/tmp/pkg_b'}
  186. """
  187. defaults = {
  188. 'setup.py': textwrap.dedent(six.u("""\
  189. #!/usr/bin/env python
  190. import setuptools
  191. setuptools.setup(
  192. setup_requires=['pbr'],
  193. pbr=True,
  194. )
  195. """)),
  196. 'setup.cfg': textwrap.dedent(six.u("""\
  197. [metadata]
  198. name = {pkg_name}
  199. """))
  200. }
  201. def __init__(self, packages):
  202. """Creates packages from dict with defaults
  203. :param packages: a dict where the keys are the package name and a
  204. value that is a second dict that may be empty, containing keys of
  205. filenames and a string value of the contents.
  206. {'package-a': {'requirements.txt': 'string', 'setup.cfg': 'string'}
  207. """
  208. self.packages = packages
  209. def _writeFile(self, directory, file_name, contents):
  210. path = os.path.abspath(os.path.join(directory, file_name))
  211. path_dir = os.path.dirname(path)
  212. if not os.path.exists(path_dir):
  213. if path_dir.startswith(directory):
  214. os.makedirs(path_dir)
  215. else:
  216. raise ValueError
  217. with open(path, 'wt') as f:
  218. f.write(contents)
  219. def _setUp(self):
  220. tmpdir = self.useFixture(fixtures.TempDir()).path
  221. package_dirs = {}
  222. for pkg_name in self.packages:
  223. pkg_path = os.path.join(tmpdir, pkg_name)
  224. package_dirs[pkg_name] = pkg_path
  225. os.mkdir(pkg_path)
  226. for cf in ['setup.py', 'setup.cfg']:
  227. if cf in self.packages[pkg_name]:
  228. contents = self.packages[pkg_name].pop(cf)
  229. else:
  230. contents = self.defaults[cf].format(pkg_name=pkg_name)
  231. self._writeFile(pkg_path, cf, contents)
  232. for cf in self.packages[pkg_name]:
  233. self._writeFile(pkg_path, cf, self.packages[pkg_name][cf])
  234. self.useFixture(TestRepo(pkg_path)).commit()
  235. self.addCleanup(delattr, self, 'package_dirs')
  236. self.package_dirs = package_dirs
  237. return package_dirs
  238. class TestPackagingInGitRepoWithCommit(base.BaseTestCase):
  239. scenarios = [
  240. ('preversioned', dict(preversioned=True)),
  241. ('postversioned', dict(preversioned=False)),
  242. ]
  243. def setUp(self):
  244. super(TestPackagingInGitRepoWithCommit, self).setUp()
  245. self.repo = self.useFixture(TestRepo(self.package_dir))
  246. self.repo.commit()
  247. def test_authors(self):
  248. self.run_setup('sdist', allow_fail=False)
  249. # One commit, something should be in the authors list
  250. with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
  251. body = f.read()
  252. self.assertNotEqual(body, '')
  253. def test_changelog(self):
  254. self.run_setup('sdist', allow_fail=False)
  255. with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
  256. body = f.read()
  257. # One commit, something should be in the ChangeLog list
  258. self.assertNotEqual(body, '')
  259. def test_changelog_handles_astrisk(self):
  260. self.repo.commit(message_content="Allow *.openstack.org to work")
  261. self.run_setup('sdist', allow_fail=False)
  262. with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
  263. body = f.read()
  264. self.assertIn(r'\*', body)
  265. def test_changelog_handles_dead_links_in_commit(self):
  266. self.repo.commit(message_content="See os_ for to_do about qemu_.")
  267. self.run_setup('sdist', allow_fail=False)
  268. with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
  269. body = f.read()
  270. self.assertIn(r'os\_', body)
  271. self.assertIn(r'to\_do', body)
  272. self.assertIn(r'qemu\_', body)
  273. def test_changelog_handles_backticks(self):
  274. self.repo.commit(message_content="Allow `openstack.org` to `work")
  275. self.run_setup('sdist', allow_fail=False)
  276. with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
  277. body = f.read()
  278. self.assertIn(r'\`', body)
  279. def test_manifest_exclude_honoured(self):
  280. self.run_setup('sdist', allow_fail=False)
  281. with open(os.path.join(
  282. self.package_dir,
  283. 'pbr_testpackage.egg-info/SOURCES.txt'), 'r') as f:
  284. body = f.read()
  285. self.assertThat(
  286. body, matchers.Not(matchers.Contains('pbr_testpackage/extra.py')))
  287. self.assertThat(body, matchers.Contains('pbr_testpackage/__init__.py'))
  288. def test_install_writes_changelog(self):
  289. stdout, _, _ = self.run_setup(
  290. 'install', '--root', self.temp_dir + 'installed',
  291. allow_fail=False)
  292. self.expectThat(stdout, matchers.Contains('Generating ChangeLog'))
  293. class TestExtrafileInstallation(base.BaseTestCase):
  294. def test_install_glob(self):
  295. stdout, _, _ = self.run_setup(
  296. 'install', '--root', self.temp_dir + 'installed',
  297. allow_fail=False)
  298. self.expectThat(
  299. stdout, matchers.Contains('copying data_files/a.txt'))
  300. self.expectThat(
  301. stdout, matchers.Contains('copying data_files/b.txt'))
  302. class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase):
  303. def setUp(self):
  304. super(TestPackagingInGitRepoWithoutCommit, self).setUp()
  305. self.useFixture(TestRepo(self.package_dir))
  306. self.run_setup('sdist', allow_fail=False)
  307. def test_authors(self):
  308. # No commits, no authors in list
  309. with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
  310. body = f.read()
  311. self.assertEqual('\n', body)
  312. def test_changelog(self):
  313. # No commits, nothing should be in the ChangeLog list
  314. with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
  315. body = f.read()
  316. self.assertEqual('CHANGES\n=======\n\n', body)
  317. class TestPackagingWheels(base.BaseTestCase):
  318. def setUp(self):
  319. super(TestPackagingWheels, self).setUp()
  320. self.useFixture(TestRepo(self.package_dir))
  321. # Build the wheel
  322. self.run_setup('bdist_wheel', allow_fail=False)
  323. # Slowly construct the path to the generated whl
  324. dist_dir = os.path.join(self.package_dir, 'dist')
  325. relative_wheel_filename = os.listdir(dist_dir)[0]
  326. absolute_wheel_filename = os.path.join(
  327. dist_dir, relative_wheel_filename)
  328. wheel_file = wheelfile.WheelFile(absolute_wheel_filename)
  329. wheel_name = wheel_file.parsed_filename.group('namever')
  330. # Create a directory path to unpack the wheel to
  331. self.extracted_wheel_dir = os.path.join(dist_dir, wheel_name)
  332. # Extract the wheel contents to the directory we just created
  333. wheel_file.extractall(self.extracted_wheel_dir)
  334. wheel_file.close()
  335. def test_metadata_directory_has_pbr_json(self):
  336. # Build the path to the scripts directory
  337. pbr_json = os.path.join(
  338. self.extracted_wheel_dir, 'pbr_testpackage-0.0.dist-info/pbr.json')
  339. self.assertTrue(os.path.exists(pbr_json))
  340. def test_data_directory_has_wsgi_scripts(self):
  341. # Build the path to the scripts directory
  342. scripts_dir = os.path.join(
  343. self.extracted_wheel_dir, 'pbr_testpackage-0.0.data/scripts')
  344. self.assertTrue(os.path.exists(scripts_dir))
  345. scripts = os.listdir(scripts_dir)
  346. self.assertIn('pbr_test_wsgi', scripts)
  347. self.assertIn('pbr_test_wsgi_with_class', scripts)
  348. self.assertNotIn('pbr_test_cmd', scripts)
  349. self.assertNotIn('pbr_test_cmd_with_class', scripts)
  350. def test_generates_c_extensions(self):
  351. built_package_dir = os.path.join(
  352. self.extracted_wheel_dir, 'pbr_testpackage')
  353. static_object_filename = 'testext.so'
  354. soabi = get_soabi()
  355. if soabi:
  356. static_object_filename = 'testext.{0}.so'.format(soabi)
  357. static_object_path = os.path.join(
  358. built_package_dir, static_object_filename)
  359. self.assertTrue(os.path.exists(built_package_dir))
  360. self.assertTrue(os.path.exists(static_object_path))
  361. class TestPackagingHelpers(testtools.TestCase):
  362. def test_generate_script(self):
  363. group = 'console_scripts'
  364. entry_point = pkg_resources.EntryPoint(
  365. name='test-ep',
  366. module_name='pbr.packaging',
  367. attrs=('LocalInstallScripts',))
  368. header = '#!/usr/bin/env fake-header\n'
  369. template = ('%(group)s %(module_name)s %(import_target)s '
  370. '%(invoke_target)s')
  371. generated_script = packaging.generate_script(
  372. group, entry_point, header, template)
  373. expected_script = (
  374. '#!/usr/bin/env fake-header\nconsole_scripts pbr.packaging '
  375. 'LocalInstallScripts LocalInstallScripts'
  376. )
  377. self.assertEqual(expected_script, generated_script)
  378. def test_generate_script_validates_expectations(self):
  379. group = 'console_scripts'
  380. entry_point = pkg_resources.EntryPoint(
  381. name='test-ep',
  382. module_name='pbr.packaging')
  383. header = '#!/usr/bin/env fake-header\n'
  384. template = ('%(group)s %(module_name)s %(import_target)s '
  385. '%(invoke_target)s')
  386. self.assertRaises(
  387. ValueError, packaging.generate_script, group, entry_point, header,
  388. template)
  389. entry_point = pkg_resources.EntryPoint(
  390. name='test-ep',
  391. module_name='pbr.packaging',
  392. attrs=('attr1', 'attr2', 'attr3'))
  393. self.assertRaises(
  394. ValueError, packaging.generate_script, group, entry_point, header,
  395. template)
  396. class TestPackagingInPlainDirectory(base.BaseTestCase):
  397. def setUp(self):
  398. super(TestPackagingInPlainDirectory, self).setUp()
  399. def test_authors(self):
  400. self.run_setup('sdist', allow_fail=False)
  401. # Not a git repo, no AUTHORS file created
  402. filename = os.path.join(self.package_dir, 'AUTHORS')
  403. self.assertFalse(os.path.exists(filename))
  404. def test_changelog(self):
  405. self.run_setup('sdist', allow_fail=False)
  406. # Not a git repo, no ChangeLog created
  407. filename = os.path.join(self.package_dir, 'ChangeLog')
  408. self.assertFalse(os.path.exists(filename))
  409. def test_install_no_ChangeLog(self):
  410. stdout, _, _ = self.run_setup(
  411. 'install', '--root', self.temp_dir + 'installed',
  412. allow_fail=False)
  413. self.expectThat(
  414. stdout, matchers.Not(matchers.Contains('Generating ChangeLog')))
  415. class TestPresenceOfGit(base.BaseTestCase):
  416. def testGitIsInstalled(self):
  417. with mock.patch.object(git,
  418. '_run_shell_command') as _command:
  419. _command.return_value = 'git version 1.8.4.1'
  420. self.assertEqual(True, git._git_is_installed())
  421. def testGitIsNotInstalled(self):
  422. with mock.patch.object(git,
  423. '_run_shell_command') as _command:
  424. _command.side_effect = OSError
  425. self.assertEqual(False, git._git_is_installed())
  426. class ParseRequirementsTest(base.BaseTestCase):
  427. def test_empty_requirements(self):
  428. actual = packaging.parse_requirements([])
  429. self.assertEqual([], actual)
  430. def test_default_requirements(self):
  431. """Ensure default files used if no files provided."""
  432. tempdir = tempfile.mkdtemp()
  433. requirements = os.path.join(tempdir, 'requirements.txt')
  434. with open(requirements, 'w') as f:
  435. f.write('pbr')
  436. # the defaults are relative to where pbr is called from so we need to
  437. # override them. This is OK, however, as we want to validate that
  438. # defaults are used - not what those defaults are
  439. with mock.patch.object(packaging, 'REQUIREMENTS_FILES', (
  440. requirements,)):
  441. result = packaging.parse_requirements()
  442. self.assertEqual(['pbr'], result)
  443. def test_override_with_env(self):
  444. """Ensure environment variable used if no files provided."""
  445. _, tmp_file = tempfile.mkstemp(prefix='openstack', suffix='.setup')
  446. with open(tmp_file, 'w') as fh:
  447. fh.write("foo\nbar")
  448. self.useFixture(
  449. fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES', tmp_file))
  450. self.assertEqual(['foo', 'bar'],
  451. packaging.parse_requirements())
  452. def test_override_with_env_multiple_files(self):
  453. _, tmp_file = tempfile.mkstemp(prefix='openstack', suffix='.setup')
  454. with open(tmp_file, 'w') as fh:
  455. fh.write("foo\nbar")
  456. self.useFixture(
  457. fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES',
  458. "no-such-file," + tmp_file))
  459. self.assertEqual(['foo', 'bar'],
  460. packaging.parse_requirements())
  461. def test_index_present(self):
  462. tempdir = tempfile.mkdtemp()
  463. requirements = os.path.join(tempdir, 'requirements.txt')
  464. with open(requirements, 'w') as f:
  465. f.write('-i https://myindex.local\n')
  466. f.write(' --index-url https://myindex.local\n')
  467. f.write(' --extra-index-url https://myindex.local\n')
  468. f.write('--find-links https://myindex.local\n')
  469. f.write('arequirement>=1.0\n')
  470. result = packaging.parse_requirements([requirements])
  471. self.assertEqual(['arequirement>=1.0'], result)
  472. def test_nested_requirements(self):
  473. tempdir = tempfile.mkdtemp()
  474. requirements = os.path.join(tempdir, 'requirements.txt')
  475. nested = os.path.join(tempdir, 'nested.txt')
  476. with open(requirements, 'w') as f:
  477. f.write('-r ' + nested)
  478. with open(nested, 'w') as f:
  479. f.write('pbr')
  480. result = packaging.parse_requirements([requirements])
  481. self.assertEqual(['pbr'], result)
  482. class ParseRequirementsTestScenarios(base.BaseTestCase):
  483. versioned_scenarios = [
  484. ('non-versioned', {'versioned': False, 'expected': ['bar']}),
  485. ('versioned', {'versioned': True, 'expected': ['bar>=1.2.3']})
  486. ]
  487. subdirectory_scenarios = [
  488. ('non-subdirectory', {'has_subdirectory': False}),
  489. ('has-subdirectory', {'has_subdirectory': True})
  490. ]
  491. scenarios = [
  492. ('normal', {'url': "foo\nbar", 'expected': ['foo', 'bar']}),
  493. ('normal_with_comments', {
  494. 'url': "# this is a comment\nfoo\n# and another one\nbar",
  495. 'expected': ['foo', 'bar']}),
  496. ('removes_index_lines', {'url': '-f foobar', 'expected': []}),
  497. ]
  498. scenarios = scenarios + testscenarios.multiply_scenarios([
  499. ('ssh_egg_url', {'url': 'git+ssh://foo.com/zipball#egg=bar'}),
  500. ('git_https_egg_url', {'url': 'git+https://foo.com/zipball#egg=bar'}),
  501. ('http_egg_url', {'url': 'https://foo.com/zipball#egg=bar'}),
  502. ], versioned_scenarios, subdirectory_scenarios)
  503. scenarios = scenarios + testscenarios.multiply_scenarios(
  504. [
  505. ('git_egg_url',
  506. {'url': 'git://foo.com/zipball#egg=bar', 'name': 'bar'})
  507. ], [
  508. ('non-editable', {'editable': False}),
  509. ('editable', {'editable': True}),
  510. ],
  511. versioned_scenarios, subdirectory_scenarios)
  512. def test_parse_requirements(self):
  513. tmp_file = tempfile.NamedTemporaryFile()
  514. req_string = self.url
  515. if hasattr(self, 'editable') and self.editable:
  516. req_string = ("-e %s" % req_string)
  517. if hasattr(self, 'versioned') and self.versioned:
  518. req_string = ("%s-1.2.3" % req_string)
  519. if hasattr(self, 'has_subdirectory') and self.has_subdirectory:
  520. req_string = ("%s&subdirectory=baz" % req_string)
  521. with open(tmp_file.name, 'w') as fh:
  522. fh.write(req_string)
  523. self.assertEqual(self.expected,
  524. packaging.parse_requirements([tmp_file.name]))
  525. class ParseDependencyLinksTest(base.BaseTestCase):
  526. def setUp(self):
  527. super(ParseDependencyLinksTest, self).setUp()
  528. _, self.tmp_file = tempfile.mkstemp(prefix="openstack",
  529. suffix=".setup")
  530. def test_parse_dependency_normal(self):
  531. with open(self.tmp_file, "w") as fh:
  532. fh.write("http://test.com\n")
  533. self.assertEqual(
  534. ["http://test.com"],
  535. packaging.parse_dependency_links([self.tmp_file]))
  536. def test_parse_dependency_with_git_egg_url(self):
  537. with open(self.tmp_file, "w") as fh:
  538. fh.write("-e git://foo.com/zipball#egg=bar")
  539. self.assertEqual(
  540. ["git://foo.com/zipball#egg=bar"],
  541. packaging.parse_dependency_links([self.tmp_file]))
  542. class TestVersions(base.BaseTestCase):
  543. scenarios = [
  544. ('preversioned', dict(preversioned=True)),
  545. ('postversioned', dict(preversioned=False)),
  546. ]
  547. def setUp(self):
  548. super(TestVersions, self).setUp()
  549. self.repo = self.useFixture(TestRepo(self.package_dir))
  550. self.useFixture(GPGKeyFixture())
  551. self.useFixture(base.DiveDir(self.package_dir))
  552. def test_email_parsing_errors_are_handled(self):
  553. mocked_open = mock.mock_open()
  554. with mock.patch('pbr.packaging.open', mocked_open):
  555. with mock.patch('email.message_from_file') as message_from_file:
  556. message_from_file.side_effect = [
  557. email.errors.MessageError('Test'),
  558. {'Name': 'pbr_testpackage'}]
  559. version = packaging._get_version_from_pkg_metadata(
  560. 'pbr_testpackage')
  561. self.assertTrue(message_from_file.called)
  562. self.assertIsNone(version)
  563. def test_capitalized_headers(self):
  564. self.repo.commit()
  565. self.repo.tag('1.2.3')
  566. self.repo.commit('Sem-Ver: api-break')
  567. version = packaging._get_version_from_git()
  568. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  569. def test_capitalized_headers_partial(self):
  570. self.repo.commit()
  571. self.repo.tag('1.2.3')
  572. self.repo.commit('Sem-ver: api-break')
  573. version = packaging._get_version_from_git()
  574. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  575. def test_multi_inline_symbols_no_space(self):
  576. self.repo.commit()
  577. self.repo.tag('1.2.3')
  578. self.repo.commit('Sem-ver: feature,api-break')
  579. version = packaging._get_version_from_git()
  580. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  581. def test_multi_inline_symbols_spaced(self):
  582. self.repo.commit()
  583. self.repo.tag('1.2.3')
  584. self.repo.commit('Sem-ver: feature, api-break')
  585. version = packaging._get_version_from_git()
  586. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  587. def test_multi_inline_symbols_reversed(self):
  588. self.repo.commit()
  589. self.repo.tag('1.2.3')
  590. self.repo.commit('Sem-ver: api-break,feature')
  591. version = packaging._get_version_from_git()
  592. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  593. def test_leading_space(self):
  594. self.repo.commit()
  595. self.repo.tag('1.2.3')
  596. self.repo.commit(' sem-ver: api-break')
  597. version = packaging._get_version_from_git()
  598. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  599. def test_leading_space_multiline(self):
  600. self.repo.commit()
  601. self.repo.tag('1.2.3')
  602. self.repo.commit(
  603. (
  604. ' Some cool text\n'
  605. ' sem-ver: api-break'
  606. )
  607. )
  608. version = packaging._get_version_from_git()
  609. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  610. def test_leading_characters_symbol_not_found(self):
  611. self.repo.commit()
  612. self.repo.tag('1.2.3')
  613. self.repo.commit(' ssem-ver: api-break')
  614. version = packaging._get_version_from_git()
  615. self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
  616. def test_tagged_version_has_tag_version(self):
  617. self.repo.commit()
  618. self.repo.tag('1.2.3')
  619. version = packaging._get_version_from_git('1.2.3')
  620. self.assertEqual('1.2.3', version)
  621. def test_tagged_version_with_semver_compliant_prerelease(self):
  622. self.repo.commit()
  623. self.repo.tag('1.2.3-rc2')
  624. version = packaging._get_version_from_git()
  625. self.assertEqual('1.2.3.0rc2', version)
  626. def test_non_canonical_tagged_version_bump(self):
  627. self.repo.commit()
  628. self.repo.tag('1.4')
  629. self.repo.commit('Sem-Ver: api-break')
  630. version = packaging._get_version_from_git()
  631. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  632. def test_untagged_version_has_dev_version_postversion(self):
  633. self.repo.commit()
  634. self.repo.tag('1.2.3')
  635. self.repo.commit()
  636. version = packaging._get_version_from_git()
  637. self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
  638. def test_untagged_pre_release_has_pre_dev_version_postversion(self):
  639. self.repo.commit()
  640. self.repo.tag('1.2.3.0a1')
  641. self.repo.commit()
  642. version = packaging._get_version_from_git()
  643. self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
  644. def test_untagged_version_minor_bump(self):
  645. self.repo.commit()
  646. self.repo.tag('1.2.3')
  647. self.repo.commit('sem-ver: deprecation')
  648. version = packaging._get_version_from_git()
  649. self.assertThat(version, matchers.StartsWith('1.3.0.dev1'))
  650. def test_untagged_version_major_bump(self):
  651. self.repo.commit()
  652. self.repo.tag('1.2.3')
  653. self.repo.commit('sem-ver: api-break')
  654. version = packaging._get_version_from_git()
  655. self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
  656. def test_untagged_version_has_dev_version_preversion(self):
  657. self.repo.commit()
  658. self.repo.tag('1.2.3')
  659. self.repo.commit()
  660. version = packaging._get_version_from_git('1.2.5')
  661. self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
  662. def test_untagged_version_after_pre_has_dev_version_preversion(self):
  663. self.repo.commit()
  664. self.repo.tag('1.2.3.0a1')
  665. self.repo.commit()
  666. version = packaging._get_version_from_git('1.2.5')
  667. self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
  668. def test_untagged_version_after_rc_has_dev_version_preversion(self):
  669. self.repo.commit()
  670. self.repo.tag('1.2.3.0a1')
  671. self.repo.commit()
  672. version = packaging._get_version_from_git('1.2.3')
  673. self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
  674. def test_untagged_version_after_semver_compliant_prerelease_tag(self):
  675. self.repo.commit()
  676. self.repo.tag('1.2.3-rc2')
  677. self.repo.commit()
  678. version = packaging._get_version_from_git()
  679. self.assertEqual('1.2.3.0rc3.dev1', version)
  680. def test_preversion_too_low_simple(self):
  681. # That is, the target version is either already released or not high
  682. # enough for the semver requirements given api breaks etc.
  683. self.repo.commit()
  684. self.repo.tag('1.2.3')
  685. self.repo.commit()
  686. # Note that we can't target 1.2.3 anymore - with 1.2.3 released we
  687. # need to be working on 1.2.4.
  688. err = self.assertRaises(
  689. ValueError, packaging._get_version_from_git, '1.2.3')
  690. self.assertThat(err.args[0], matchers.StartsWith('git history'))
  691. def test_preversion_too_low_semver_headers(self):
  692. # That is, the target version is either already released or not high
  693. # enough for the semver requirements given api breaks etc.
  694. self.repo.commit()
  695. self.repo.tag('1.2.3')
  696. self.repo.commit('sem-ver: feature')
  697. # Note that we can't target 1.2.4, the feature header means we need
  698. # to be working on 1.3.0 or above.
  699. err = self.assertRaises(
  700. ValueError, packaging._get_version_from_git, '1.2.4')
  701. self.assertThat(err.args[0], matchers.StartsWith('git history'))
  702. def test_get_kwargs_corner_cases(self):
  703. # No tags:
  704. def get_kwargs(tag):
  705. git_dir = self.repo._basedir + '/.git'
  706. return packaging._get_increment_kwargs(git_dir, tag)
  707. def _check_combinations(tag):
  708. self.repo.commit()
  709. self.assertEqual(dict(), get_kwargs(tag))
  710. self.repo.commit('sem-ver: bugfix')
  711. self.assertEqual(dict(), get_kwargs(tag))
  712. self.repo.commit('sem-ver: feature')
  713. self.assertEqual(dict(minor=True), get_kwargs(tag))
  714. self.repo.uncommit()
  715. self.repo.commit('sem-ver: deprecation')
  716. self.assertEqual(dict(minor=True), get_kwargs(tag))
  717. self.repo.uncommit()
  718. self.repo.commit('sem-ver: api-break')
  719. self.assertEqual(dict(major=True), get_kwargs(tag))
  720. self.repo.commit('sem-ver: deprecation')
  721. self.assertEqual(dict(major=True, minor=True), get_kwargs(tag))
  722. _check_combinations('')
  723. self.repo.tag('1.2.3')
  724. _check_combinations('1.2.3')
  725. def test_invalid_tag_ignored(self):
  726. # Fix for bug 1356784 - we treated any tag as a version, not just those
  727. # that are valid versions.
  728. self.repo.commit()
  729. self.repo.tag('1')
  730. self.repo.commit()
  731. # when the tree is tagged and its wrong:
  732. self.repo.tag('badver')
  733. version = packaging._get_version_from_git()
  734. self.assertThat(version, matchers.StartsWith('1.0.1.dev1'))
  735. # When the tree isn't tagged, we also fall through.
  736. self.repo.commit()
  737. version = packaging._get_version_from_git()
  738. self.assertThat(version, matchers.StartsWith('1.0.1.dev2'))
  739. # We don't fall through x.y versions
  740. self.repo.commit()
  741. self.repo.tag('1.2')
  742. self.repo.commit()
  743. self.repo.tag('badver2')
  744. version = packaging._get_version_from_git()
  745. self.assertThat(version, matchers.StartsWith('1.2.1.dev1'))
  746. # Or x.y.z versions
  747. self.repo.commit()
  748. self.repo.tag('1.2.3')
  749. self.repo.commit()
  750. self.repo.tag('badver3')
  751. version = packaging._get_version_from_git()
  752. self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
  753. # Or alpha/beta/pre versions
  754. self.repo.commit()
  755. self.repo.tag('1.2.4.0a1')
  756. self.repo.commit()
  757. self.repo.tag('badver4')
  758. version = packaging._get_version_from_git()
  759. self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1'))
  760. # Non-release related tags are ignored.
  761. self.repo.commit()
  762. self.repo.tag('2')
  763. self.repo.commit()
  764. self.repo.tag('non-release-tag/2014.12.16-1')
  765. version = packaging._get_version_from_git()
  766. self.assertThat(version, matchers.StartsWith('2.0.1.dev1'))
  767. def test_valid_tag_honoured(self):
  768. # Fix for bug 1370608 - we converted any target into a 'dev version'
  769. # even if there was a distance of 0 - indicating that we were on the
  770. # tag itself.
  771. self.repo.commit()
  772. self.repo.tag('1.3.0.0a1')
  773. version = packaging._get_version_from_git()
  774. self.assertEqual('1.3.0.0a1', version)
  775. def test_skip_write_git_changelog(self):
  776. # Fix for bug 1467440
  777. self.repo.commit()
  778. self.repo.tag('1.2.3')
  779. os.environ['SKIP_WRITE_GIT_CHANGELOG'] = '1'
  780. version = packaging._get_version_from_git('1.2.3')
  781. self.assertEqual('1.2.3', version)
  782. def tearDown(self):
  783. super(TestVersions, self).tearDown()
  784. os.environ.pop('SKIP_WRITE_GIT_CHANGELOG', None)
  785. class TestRequirementParsing(base.BaseTestCase):
  786. def test_requirement_parsing(self):
  787. pkgs = {
  788. 'test_reqparse':
  789. {
  790. 'requirements.txt': textwrap.dedent("""\
  791. bar
  792. quux<1.0; python_version=='2.6'
  793. requests-aws>=0.1.4 # BSD License (3 clause)
  794. Routes>=1.12.3,!=2.0,!=2.1;python_version=='2.7'
  795. requests-kerberos>=0.6;python_version=='2.7' # MIT
  796. """),
  797. 'setup.cfg': textwrap.dedent("""\
  798. [metadata]
  799. name = test_reqparse
  800. [extras]
  801. test =
  802. foo
  803. baz>3.2 :python_version=='2.7' # MIT
  804. bar>3.3 :python_version=='2.7' # MIT # Apache
  805. """)},
  806. }
  807. pkg_dirs = self.useFixture(CreatePackages(pkgs)).package_dirs
  808. pkg_dir = pkg_dirs['test_reqparse']
  809. # pkg_resources.split_sections uses None as the title of an
  810. # anonymous section instead of the empty string. Weird.
  811. expected_requirements = {
  812. None: ['bar', 'requests-aws>=0.1.4'],
  813. ":(python_version=='2.6')": ['quux<1.0'],
  814. ":(python_version=='2.7')": ['Routes!=2.0,!=2.1,>=1.12.3',
  815. 'requests-kerberos>=0.6'],
  816. 'test': ['foo'],
  817. "test:(python_version=='2.7')": ['baz>3.2', 'bar>3.3']
  818. }
  819. venv = self.useFixture(Venv('reqParse'))
  820. bin_python = venv.python
  821. # Two things are tested by this
  822. # 1) pbr properly parses markers from requiremnts.txt and setup.cfg
  823. # 2) bdist_wheel causes pbr to not evaluate markers
  824. self._run_cmd(bin_python, ('setup.py', 'bdist_wheel'),
  825. allow_fail=False, cwd=pkg_dir)
  826. egg_info = os.path.join(pkg_dir, 'test_reqparse.egg-info')
  827. requires_txt = os.path.join(egg_info, 'requires.txt')
  828. with open(requires_txt, 'rt') as requires:
  829. generated_requirements = dict(
  830. pkg_resources.split_sections(requires))
  831. # NOTE(dhellmann): We have to spell out the comparison because
  832. # the rendering for version specifiers in a range is not
  833. # consistent across versions of setuptools.
  834. for section, expected in expected_requirements.items():
  835. exp_parsed = [
  836. pkg_resources.Requirement.parse(s)
  837. for s in expected
  838. ]
  839. gen_parsed = [
  840. pkg_resources.Requirement.parse(s)
  841. for s in generated_requirements[section]
  842. ]
  843. self.assertEqual(exp_parsed, gen_parsed)
  844. class TestPEP517Support(base.BaseTestCase):
  845. def test_pep_517_support(self):
  846. # Note that the current PBR PEP517 entrypoints rely on a valid
  847. # PBR setup.py existing.
  848. pkgs = {
  849. 'test_pep517':
  850. {
  851. 'requirements.txt': textwrap.dedent("""\
  852. sphinx
  853. iso8601
  854. """),
  855. # Override default setup.py to remove setup_requires.
  856. 'setup.py': textwrap.dedent("""\
  857. #!/usr/bin/env python
  858. import setuptools
  859. setuptools.setup(pbr=True)
  860. """),
  861. 'setup.cfg': textwrap.dedent("""\
  862. [metadata]
  863. name = test_pep517
  864. summary = A tiny test project
  865. author = PBR Team
  866. author_email = foo@example.com
  867. home_page = https://example.com/
  868. classifier =
  869. Intended Audience :: Information Technology
  870. Intended Audience :: System Administrators
  871. License :: OSI Approved :: Apache Software License
  872. Operating System :: POSIX :: Linux
  873. Programming Language :: Python
  874. Programming Language :: Python :: 2
  875. Programming Language :: Python :: 2.7
  876. Programming Language :: Python :: 3
  877. Programming Language :: Python :: 3.6
  878. Programming Language :: Python :: 3.7
  879. Programming Language :: Python :: 3.8
  880. """),
  881. # note that we use 36.6.0 rather than 64.0.0 since the
  882. # latter doesn't support Python < 3.8 and we run our tests
  883. # against Python 2.7 still. That's okay since we're not
  884. # testing PEP-660 functionality here (which requires the
  885. # newer setuptools)
  886. 'pyproject.toml': textwrap.dedent("""\
  887. [build-system]
  888. requires = ["pbr", "setuptools>=36.6.0", "wheel"]
  889. build-backend = "pbr.build"
  890. """)},
  891. }
  892. pkg_dirs = self.useFixture(CreatePackages(pkgs)).package_dirs
  893. pkg_dir = pkg_dirs['test_pep517']
  894. venv = self.useFixture(Venv('PEP517'))
  895. # Test building sdists and wheels works. Note we do not use pip here
  896. # because pip will forcefully install the latest version of PBR on
  897. # pypi to satisfy the build-system requires. This means we can't self
  898. # test changes using pip. Build with --no-isolation appears to avoid
  899. # this problem.
  900. self._run_cmd(venv.python, ('-m', 'build', '--no-isolation', '.'),
  901. allow_fail=False, cwd=pkg_dir)
  902. class TestRepositoryURLDependencies(base.BaseTestCase):
  903. def setUp(self):
  904. super(TestRepositoryURLDependencies, self).setUp()
  905. self.requirements = os.path.join(tempfile.mkdtemp(),
  906. 'requirements.txt')
  907. with open(self.requirements, 'w') as f:
  908. f.write('\n'.join([
  909. '-e git+git://git.pro-ject.org/oslo.messaging#egg=oslo.messaging-1.0.0-rc', # noqa
  910. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize', # noqa
  911. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize-beta', # noqa
  912. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta', # noqa
  913. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-4.0.1', # noqa
  914. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha.beta.1', # noqa
  915. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa
  916. '-e git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-2.0.0-rc.1+build.123', # noqa
  917. '-e git+git://git.project.org/Proj#egg=Proj1',
  918. 'git+https://git.project.org/Proj#egg=Proj2-0.0.1',
  919. '-e git+ssh://git.project.org/Proj#egg=Proj3',
  920. 'svn+svn://svn.project.org/svn/Proj#egg=Proj4-0.0.2',
  921. '-e svn+http://svn.project.org/svn/Proj/trunk@2019#egg=Proj5',
  922. 'hg+http://hg.project.org/Proj@da39a3ee5e6b#egg=Proj-0.0.3',
  923. '-e hg+http://hg.project.org/Proj@2019#egg=Proj',
  924. 'hg+http://hg.project.org/Proj@v1.0#egg=Proj-0.0.4',
  925. '-e hg+http://hg.project.org/Proj@special_feature#egg=Proj',
  926. 'git://foo.com/zipball#egg=foo-bar-1.2.4',
  927. 'pypi-proj1', 'pypi-proj2']))
  928. def test_egg_fragment(self):
  929. expected = [
  930. 'django-thumborize',
  931. 'django-thumborize-beta',
  932. 'django-thumborize2-beta',
  933. 'django-thumborize2-beta>=4.0.1',
  934. 'django-thumborize2-beta>=1.0.0-alpha.beta.1',
  935. 'django-thumborize2-beta>=1.0.0-alpha-a.b-c-long+build.1-aef.1-its-okay', # noqa
  936. 'django-thumborize2-beta>=2.0.0-rc.1+build.123',
  937. 'django-thumborize-beta>=0.0.4',
  938. 'django-thumborize-beta>=1.2.3',
  939. 'django-thumborize-beta>=10.20.30',
  940. 'django-thumborize-beta>=1.1.2-prerelease+meta',
  941. 'django-thumborize-beta>=1.1.2+meta',
  942. 'django-thumborize-beta>=1.1.2+meta-valid',
  943. 'django-thumborize-beta>=1.0.0-alpha',
  944. 'django-thumborize-beta>=1.0.0-beta',
  945. 'django-thumborize-beta>=1.0.0-alpha.beta',
  946. 'django-thumborize-beta>=1.0.0-alpha.beta.1',
  947. 'django-thumborize-beta>=1.0.0-alpha.1',
  948. 'django-thumborize-beta>=1.0.0-alpha0.valid',
  949. 'django-thumborize-beta>=1.0.0-alpha.0valid',
  950. 'django-thumborize-beta>=1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa
  951. 'django-thumborize-beta>=1.0.0-rc.1+build.1',
  952. 'django-thumborize-beta>=2.0.0-rc.1+build.123',
  953. 'django-thumborize-beta>=1.2.3-beta',
  954. 'django-thumborize-beta>=10.2.3-DEV-SNAPSHOT',
  955. 'django-thumborize-beta>=1.2.3-SNAPSHOT-123',
  956. 'django-thumborize-beta>=1.0.0',
  957. 'django-thumborize-beta>=2.0.0',
  958. 'django-thumborize-beta>=1.1.7',
  959. 'django-thumborize-beta>=2.0.0+build.1848',
  960. 'django-thumborize-beta>=2.0.1-alpha.1227',
  961. 'django-thumborize-beta>=1.0.0-alpha+beta',
  962. 'django-thumborize-beta>=1.2.3----RC-SNAPSHOT.12.9.1--.12+788',
  963. 'django-thumborize-beta>=1.2.3----R-S.12.9.1--.12+meta',
  964. 'django-thumborize-beta>=1.2.3----RC-SNAPSHOT.12.9.1--.12',
  965. 'django-thumborize-beta>=1.0.0+0.build.1-rc.10000aaa-kk-0.1',
  966. 'django-thumborize-beta>=999999999999999999.99999999999999.9999999999999', # noqa
  967. 'Proj1',
  968. 'Proj2>=0.0.1',
  969. 'Proj3',
  970. 'Proj4>=0.0.2',
  971. 'Proj5',
  972. 'Proj>=0.0.3',
  973. 'Proj',
  974. 'Proj>=0.0.4',
  975. 'Proj',
  976. 'foo-bar>=1.2.4',
  977. ]
  978. tests = [
  979. 'egg=django-thumborize',
  980. 'egg=django-thumborize-beta',
  981. 'egg=django-thumborize2-beta',
  982. 'egg=django-thumborize2-beta-4.0.1',
  983. 'egg=django-thumborize2-beta-1.0.0-alpha.beta.1',
  984. 'egg=django-thumborize2-beta-1.0.0-alpha-a.b-c-long+build.1-aef.1-its-okay', # noqa
  985. 'egg=django-thumborize2-beta-2.0.0-rc.1+build.123',
  986. 'egg=django-thumborize-beta-0.0.4',
  987. 'egg=django-thumborize-beta-1.2.3',
  988. 'egg=django-thumborize-beta-10.20.30',
  989. 'egg=django-thumborize-beta-1.1.2-prerelease+meta',
  990. 'egg=django-thumborize-beta-1.1.2+meta',
  991. 'egg=django-thumborize-beta-1.1.2+meta-valid',
  992. 'egg=django-thumborize-beta-1.0.0-alpha',
  993. 'egg=django-thumborize-beta-1.0.0-beta',
  994. 'egg=django-thumborize-beta-1.0.0-alpha.beta',
  995. 'egg=django-thumborize-beta-1.0.0-alpha.beta.1',
  996. 'egg=django-thumborize-beta-1.0.0-alpha.1',
  997. 'egg=django-thumborize-beta-1.0.0-alpha0.valid',
  998. 'egg=django-thumborize-beta-1.0.0-alpha.0valid',
  999. 'egg=django-thumborize-beta-1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa
  1000. 'egg=django-thumborize-beta-1.0.0-rc.1+build.1',
  1001. 'egg=django-thumborize-beta-2.0.0-rc.1+build.123',
  1002. 'egg=django-thumborize-beta-1.2.3-beta',
  1003. 'egg=django-thumborize-beta-10.2.3-DEV-SNAPSHOT',
  1004. 'egg=django-thumborize-beta-1.2.3-SNAPSHOT-123',
  1005. 'egg=django-thumborize-beta-1.0.0',
  1006. 'egg=django-thumborize-beta-2.0.0',
  1007. 'egg=django-thumborize-beta-1.1.7',
  1008. 'egg=django-thumborize-beta-2.0.0+build.1848',
  1009. 'egg=django-thumborize-beta-2.0.1-alpha.1227',
  1010. 'egg=django-thumborize-beta-1.0.0-alpha+beta',
  1011. 'egg=django-thumborize-beta-1.2.3----RC-SNAPSHOT.12.9.1--.12+788', # noqa
  1012. 'egg=django-thumborize-beta-1.2.3----R-S.12.9.1--.12+meta',
  1013. 'egg=django-thumborize-beta-1.2.3----RC-SNAPSHOT.12.9.1--.12',
  1014. 'egg=django-thumborize-beta-1.0.0+0.build.1-rc.10000aaa-kk-0.1', # noqa
  1015. 'egg=django-thumborize-beta-999999999999999999.99999999999999.9999999999999', # noqa
  1016. 'egg=Proj1',
  1017. 'egg=Proj2-0.0.1',
  1018. 'egg=Proj3',
  1019. 'egg=Proj4-0.0.2',
  1020. 'egg=Proj5',
  1021. 'egg=Proj-0.0.3',
  1022. 'egg=Proj',
  1023. 'egg=Proj-0.0.4',
  1024. 'egg=Proj',
  1025. 'egg=foo-bar-1.2.4',
  1026. ]
  1027. for index, test in enumerate(tests):
  1028. self.assertEqual(expected[index],
  1029. re.sub(r'egg=([^&]+).*$',
  1030. packaging.egg_fragment,
  1031. test))
  1032. def test_parse_repo_url_requirements(self):
  1033. result = packaging.parse_requirements([self.requirements])
  1034. self.assertEqual(['oslo.messaging>=1.0.0-rc',
  1035. 'django-thumborize',
  1036. 'django-thumborize-beta',
  1037. 'django-thumborize2-beta',
  1038. 'django-thumborize2-beta>=4.0.1',
  1039. 'django-thumborize2-beta>=1.0.0-alpha.beta.1',
  1040. 'django-thumborize2-beta>=1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa
  1041. 'django-thumborize2-beta>=2.0.0-rc.1+build.123',
  1042. 'Proj1', 'Proj2>=0.0.1', 'Proj3',
  1043. 'Proj4>=0.0.2', 'Proj5', 'Proj>=0.0.3',
  1044. 'Proj', 'Proj>=0.0.4', 'Proj',
  1045. 'foo-bar>=1.2.4', 'pypi-proj1',
  1046. 'pypi-proj2'], result)
  1047. def test_parse_repo_url_dependency_links(self):
  1048. result = packaging.parse_dependency_links([self.requirements])
  1049. self.assertEqual(
  1050. [
  1051. 'git+git://git.pro-ject.org/oslo.messaging#egg=oslo.messaging-1.0.0-rc', # noqa
  1052. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize', # noqa
  1053. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize-beta', # noqa
  1054. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta', # noqa
  1055. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-4.0.1', # noqa
  1056. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha.beta.1', # noqa
  1057. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', # noqa
  1058. 'git+git://git.pro-ject.org/django-thumborize#egg=django-thumborize2-beta-2.0.0-rc.1+build.123', # noqa
  1059. 'git+git://git.project.org/Proj#egg=Proj1',
  1060. 'git+https://git.project.org/Proj#egg=Proj2-0.0.1',
  1061. 'git+ssh://git.project.org/Proj#egg=Proj3',
  1062. 'svn+svn://svn.project.org/svn/Proj#egg=Proj4-0.0.2',
  1063. 'svn+http://svn.project.org/svn/Proj/trunk@2019#egg=Proj5',
  1064. 'hg+http://hg.project.org/Proj@da39a3ee5e6b#egg=Proj-0.0.3',
  1065. 'hg+http://hg.project.org/Proj@2019#egg=Proj',
  1066. 'hg+http://hg.project.org/Proj@v1.0#egg=Proj-0.0.4',
  1067. 'hg+http://hg.project.org/Proj@special_feature#egg=Proj',
  1068. 'git://foo.com/zipball#egg=foo-bar-1.2.4'], result)
  1069. def get_soabi():
  1070. soabi = None
  1071. try:
  1072. soabi = sysconfig.get_config_var('SOABI')
  1073. arch = sysconfig.get_config_var('MULTIARCH')
  1074. except IOError:
  1075. pass
  1076. if soabi and arch and 'pypy' in sysconfig.get_scheme_names():
  1077. soabi = '%s-%s' % (soabi, arch)
  1078. if soabi is None and 'pypy' in sysconfig.get_scheme_names():
  1079. # NOTE(sigmavirus24): PyPy only added support for the SOABI config var
  1080. # to sysconfig in 2015. That was well after 2.2.1 was published in the
  1081. # Ubuntu 14.04 archive.
  1082. for suffix in get_suffixes():
  1083. if suffix.startswith('.pypy') and suffix.endswith('.so'):
  1084. soabi = suffix.split('.')[1]
  1085. break
  1086. return soabi