Update and rename MantenerFIFO to MantenerFIFO.md
[vsorcdistro/.git] / ryu / .eggs / pbr-5.3.1-py2.7.egg / pbr / tests / test_packaging.py
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
41 import email
42 import email.errors
43 import imp
44 import os
45 import re
46 import sysconfig
47 import tempfile
48 import textwrap
49
50 import fixtures
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
60 from pbr import git
61 from pbr import packaging
62 from pbr.tests import base
63
64
65 PBR_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
66
67
68 class TestRepo(fixtures.Fixture):
69     """A git repo for testing with.
70
71     Use of TempHomeDir with this fixture is strongly recommended as due to the
72     lack of config --local in older gits, it will write to the users global
73     configuration without TempHomeDir.
74     """
75
76     def __init__(self, basedir):
77         super(TestRepo, self).__init__()
78         self._basedir = basedir
79
80     def setUp(self):
81         super(TestRepo, self).setUp()
82         base._run_cmd(['git', 'init', '.'], self._basedir)
83         base._config_git()
84         base._run_cmd(['git', 'add', '.'], self._basedir)
85
86     def commit(self, message_content='test commit'):
87         files = len(os.listdir(self._basedir))
88         path = self._basedir + '/%d' % files
89         open(path, 'wt').close()
90         base._run_cmd(['git', 'add', path], self._basedir)
91         base._run_cmd(['git', 'commit', '-m', message_content], self._basedir)
92
93     def uncommit(self):
94         base._run_cmd(['git', 'reset', '--hard', 'HEAD^'], self._basedir)
95
96     def tag(self, version):
97         base._run_cmd(
98             ['git', 'tag', '-sm', 'test tag', version], self._basedir)
99
100
101 class GPGKeyFixture(fixtures.Fixture):
102     """Creates a GPG key for testing.
103
104     It's recommended that this be used in concert with a unique home
105     directory.
106     """
107
108     def setUp(self):
109         super(GPGKeyFixture, self).setUp()
110         tempdir = self.useFixture(fixtures.TempDir())
111         gnupg_version_re = re.compile('^gpg\s.*\s([\d+])\.([\d+])\.([\d+])')
112         gnupg_version = base._run_cmd(['gpg', '--version'], tempdir.path)
113         for line in gnupg_version[0].split('\n'):
114             gnupg_version = gnupg_version_re.match(line)
115             if gnupg_version:
116                 gnupg_version = (int(gnupg_version.group(1)),
117                                  int(gnupg_version.group(2)),
118                                  int(gnupg_version.group(3)))
119                 break
120         else:
121             if gnupg_version is None:
122                 gnupg_version = (0, 0, 0)
123         config_file = tempdir.path + '/key-config'
124         f = open(config_file, 'wt')
125         try:
126             if gnupg_version[0] == 2 and gnupg_version[1] >= 1:
127                 f.write("""
128                 %no-protection
129                 %transient-key
130                 """)
131             f.write("""
132             %no-ask-passphrase
133             Key-Type: RSA
134             Name-Real: Example Key
135             Name-Comment: N/A
136             Name-Email: example@example.com
137             Expire-Date: 2d
138             Preferences: (setpref)
139             %commit
140             """)
141         finally:
142             f.close()
143         # Note that --quick-random (--debug-quick-random in GnuPG 2.x)
144         # does not have a corresponding preferences file setting and
145         # must be passed explicitly on the command line instead
146         if gnupg_version[0] == 1:
147             gnupg_random = '--quick-random'
148         elif gnupg_version[0] >= 2:
149             gnupg_random = '--debug-quick-random'
150         else:
151             gnupg_random = ''
152         base._run_cmd(
153             ['gpg', '--gen-key', '--batch', gnupg_random, config_file],
154             tempdir.path)
155
156
157 class Venv(fixtures.Fixture):
158     """Create a virtual environment for testing with.
159
160     :attr path: The path to the environment root.
161     :attr python: The path to the python binary in the environment.
162     """
163
164     def __init__(self, reason, modules=(), pip_cmd=None):
165         """Create a Venv fixture.
166
167         :param reason: A human readable string to bake into the venv
168             file path to aid diagnostics in the case of failures.
169         :param modules: A list of modules to install, defaults to latest
170             pip, wheel, and the working copy of PBR.
171         :attr pip_cmd: A list to override the default pip_cmd passed to
172             python for installing base packages.
173         """
174         self._reason = reason
175         if modules == ():
176             pbr = 'file://%s#egg=pbr' % PBR_ROOT
177             modules = ['pip', 'wheel', pbr]
178         self.modules = modules
179         if pip_cmd is None:
180             self.pip_cmd = ['-m', 'pip', 'install']
181         else:
182             self.pip_cmd = pip_cmd
183
184     def _setUp(self):
185         path = self.useFixture(fixtures.TempDir()).path
186         virtualenv.create_environment(path, clear=True)
187         python = os.path.join(path, 'bin', 'python')
188         command = [python] + self.pip_cmd + ['-U']
189         if self.modules and len(self.modules) > 0:
190             command.extend(self.modules)
191             self.useFixture(base.CapturedSubprocess(
192                 'mkvenv-' + self._reason, command))
193         self.addCleanup(delattr, self, 'path')
194         self.addCleanup(delattr, self, 'python')
195         self.path = path
196         self.python = python
197         return path, python
198
199
200 class CreatePackages(fixtures.Fixture):
201     """Creates packages from dict with defaults
202
203         :param package_dirs: A dict of package name to directory strings
204         {'pkg_a': '/tmp/path/to/tmp/pkg_a', 'pkg_b': '/tmp/path/to/tmp/pkg_b'}
205     """
206
207     defaults = {
208         'setup.py': textwrap.dedent(six.u("""\
209             #!/usr/bin/env python
210             import setuptools
211             setuptools.setup(
212                 setup_requires=['pbr'],
213                 pbr=True,
214             )
215         """)),
216         'setup.cfg': textwrap.dedent(six.u("""\
217             [metadata]
218             name = {pkg_name}
219         """))
220     }
221
222     def __init__(self, packages):
223         """Creates packages from dict with defaults
224
225             :param packages: a dict where the keys are the package name and a
226             value that is a second dict that may be empty, containing keys of
227             filenames and a string value of the contents.
228             {'package-a': {'requirements.txt': 'string', 'setup.cfg': 'string'}
229         """
230         self.packages = packages
231
232     def _writeFile(self, directory, file_name, contents):
233         path = os.path.abspath(os.path.join(directory, file_name))
234         path_dir = os.path.dirname(path)
235         if not os.path.exists(path_dir):
236             if path_dir.startswith(directory):
237                 os.makedirs(path_dir)
238             else:
239                 raise ValueError
240         with open(path, 'wt') as f:
241             f.write(contents)
242
243     def _setUp(self):
244         tmpdir = self.useFixture(fixtures.TempDir()).path
245         package_dirs = {}
246         for pkg_name in self.packages:
247             pkg_path = os.path.join(tmpdir, pkg_name)
248             package_dirs[pkg_name] = pkg_path
249             os.mkdir(pkg_path)
250             for cf in ['setup.py', 'setup.cfg']:
251                 if cf in self.packages[pkg_name]:
252                     contents = self.packages[pkg_name].pop(cf)
253                 else:
254                     contents = self.defaults[cf].format(pkg_name=pkg_name)
255                 self._writeFile(pkg_path, cf, contents)
256
257             for cf in self.packages[pkg_name]:
258                 self._writeFile(pkg_path, cf, self.packages[pkg_name][cf])
259             self.useFixture(TestRepo(pkg_path)).commit()
260         self.addCleanup(delattr, self, 'package_dirs')
261         self.package_dirs = package_dirs
262         return package_dirs
263
264
265 class TestPackagingInGitRepoWithCommit(base.BaseTestCase):
266
267     scenarios = [
268         ('preversioned', dict(preversioned=True)),
269         ('postversioned', dict(preversioned=False)),
270     ]
271
272     def setUp(self):
273         super(TestPackagingInGitRepoWithCommit, self).setUp()
274         self.repo = self.useFixture(TestRepo(self.package_dir))
275         self.repo.commit()
276
277     def test_authors(self):
278         self.run_setup('sdist', allow_fail=False)
279         # One commit, something should be in the authors list
280         with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
281             body = f.read()
282         self.assertNotEqual(body, '')
283
284     def test_changelog(self):
285         self.run_setup('sdist', allow_fail=False)
286         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
287             body = f.read()
288         # One commit, something should be in the ChangeLog list
289         self.assertNotEqual(body, '')
290
291     def test_changelog_handles_astrisk(self):
292         self.repo.commit(message_content="Allow *.openstack.org to work")
293         self.run_setup('sdist', allow_fail=False)
294         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
295             body = f.read()
296         self.assertIn('\*', body)
297
298     def test_changelog_handles_dead_links_in_commit(self):
299         self.repo.commit(message_content="See os_ for to_do about qemu_.")
300         self.run_setup('sdist', allow_fail=False)
301         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
302             body = f.read()
303         self.assertIn('os\_', body)
304         self.assertIn('to\_do', body)
305         self.assertIn('qemu\_', body)
306
307     def test_changelog_handles_backticks(self):
308         self.repo.commit(message_content="Allow `openstack.org` to `work")
309         self.run_setup('sdist', allow_fail=False)
310         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
311             body = f.read()
312         self.assertIn('\`', body)
313
314     def test_manifest_exclude_honoured(self):
315         self.run_setup('sdist', allow_fail=False)
316         with open(os.path.join(
317                 self.package_dir,
318                 'pbr_testpackage.egg-info/SOURCES.txt'), 'r') as f:
319             body = f.read()
320         self.assertThat(
321             body, matchers.Not(matchers.Contains('pbr_testpackage/extra.py')))
322         self.assertThat(body, matchers.Contains('pbr_testpackage/__init__.py'))
323
324     def test_install_writes_changelog(self):
325         stdout, _, _ = self.run_setup(
326             'install', '--root', self.temp_dir + 'installed',
327             allow_fail=False)
328         self.expectThat(stdout, matchers.Contains('Generating ChangeLog'))
329
330
331 class TestExtrafileInstallation(base.BaseTestCase):
332     def test_install_glob(self):
333         stdout, _, _ = self.run_setup(
334             'install', '--root', self.temp_dir + 'installed',
335             allow_fail=False)
336         self.expectThat(
337             stdout, matchers.Contains('copying data_files/a.txt'))
338         self.expectThat(
339             stdout, matchers.Contains('copying data_files/b.txt'))
340
341
342 class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase):
343
344     def setUp(self):
345         super(TestPackagingInGitRepoWithoutCommit, self).setUp()
346         self.useFixture(TestRepo(self.package_dir))
347         self.run_setup('sdist', allow_fail=False)
348
349     def test_authors(self):
350         # No commits, no authors in list
351         with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
352             body = f.read()
353         self.assertEqual('\n', body)
354
355     def test_changelog(self):
356         # No commits, nothing should be in the ChangeLog list
357         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
358             body = f.read()
359         self.assertEqual('CHANGES\n=======\n\n', body)
360
361
362 class TestPackagingWheels(base.BaseTestCase):
363
364     def setUp(self):
365         super(TestPackagingWheels, self).setUp()
366         self.useFixture(TestRepo(self.package_dir))
367         # Build the wheel
368         self.run_setup('bdist_wheel', allow_fail=False)
369         # Slowly construct the path to the generated whl
370         dist_dir = os.path.join(self.package_dir, 'dist')
371         relative_wheel_filename = os.listdir(dist_dir)[0]
372         absolute_wheel_filename = os.path.join(
373             dist_dir, relative_wheel_filename)
374         wheel_file = wheelfile.WheelFile(absolute_wheel_filename)
375         wheel_name = wheel_file.parsed_filename.group('namever')
376         # Create a directory path to unpack the wheel to
377         self.extracted_wheel_dir = os.path.join(dist_dir, wheel_name)
378         # Extract the wheel contents to the directory we just created
379         wheel_file.extractall(self.extracted_wheel_dir)
380         wheel_file.close()
381
382     def test_data_directory_has_wsgi_scripts(self):
383         # Build the path to the scripts directory
384         scripts_dir = os.path.join(
385             self.extracted_wheel_dir, 'pbr_testpackage-0.0.data/scripts')
386         self.assertTrue(os.path.exists(scripts_dir))
387         scripts = os.listdir(scripts_dir)
388
389         self.assertIn('pbr_test_wsgi', scripts)
390         self.assertIn('pbr_test_wsgi_with_class', scripts)
391         self.assertNotIn('pbr_test_cmd', scripts)
392         self.assertNotIn('pbr_test_cmd_with_class', scripts)
393
394     def test_generates_c_extensions(self):
395         built_package_dir = os.path.join(
396             self.extracted_wheel_dir, 'pbr_testpackage')
397         static_object_filename = 'testext.so'
398         soabi = get_soabi()
399         if soabi:
400             static_object_filename = 'testext.{0}.so'.format(soabi)
401         static_object_path = os.path.join(
402             built_package_dir, static_object_filename)
403
404         self.assertTrue(os.path.exists(built_package_dir))
405         self.assertTrue(os.path.exists(static_object_path))
406
407
408 class TestPackagingHelpers(testtools.TestCase):
409
410     def test_generate_script(self):
411         group = 'console_scripts'
412         entry_point = pkg_resources.EntryPoint(
413             name='test-ep',
414             module_name='pbr.packaging',
415             attrs=('LocalInstallScripts',))
416         header = '#!/usr/bin/env fake-header\n'
417         template = ('%(group)s %(module_name)s %(import_target)s '
418                     '%(invoke_target)s')
419
420         generated_script = packaging.generate_script(
421             group, entry_point, header, template)
422
423         expected_script = (
424             '#!/usr/bin/env fake-header\nconsole_scripts pbr.packaging '
425             'LocalInstallScripts LocalInstallScripts'
426         )
427         self.assertEqual(expected_script, generated_script)
428
429     def test_generate_script_validates_expectations(self):
430         group = 'console_scripts'
431         entry_point = pkg_resources.EntryPoint(
432             name='test-ep',
433             module_name='pbr.packaging')
434         header = '#!/usr/bin/env fake-header\n'
435         template = ('%(group)s %(module_name)s %(import_target)s '
436                     '%(invoke_target)s')
437         self.assertRaises(
438             ValueError, packaging.generate_script, group, entry_point, header,
439             template)
440
441         entry_point = pkg_resources.EntryPoint(
442             name='test-ep',
443             module_name='pbr.packaging',
444             attrs=('attr1', 'attr2', 'attr3'))
445         self.assertRaises(
446             ValueError, packaging.generate_script, group, entry_point, header,
447             template)
448
449
450 class TestPackagingInPlainDirectory(base.BaseTestCase):
451
452     def setUp(self):
453         super(TestPackagingInPlainDirectory, self).setUp()
454
455     def test_authors(self):
456         self.run_setup('sdist', allow_fail=False)
457         # Not a git repo, no AUTHORS file created
458         filename = os.path.join(self.package_dir, 'AUTHORS')
459         self.assertFalse(os.path.exists(filename))
460
461     def test_changelog(self):
462         self.run_setup('sdist', allow_fail=False)
463         # Not a git repo, no ChangeLog created
464         filename = os.path.join(self.package_dir, 'ChangeLog')
465         self.assertFalse(os.path.exists(filename))
466
467     def test_install_no_ChangeLog(self):
468         stdout, _, _ = self.run_setup(
469             'install', '--root', self.temp_dir + 'installed',
470             allow_fail=False)
471         self.expectThat(
472             stdout, matchers.Not(matchers.Contains('Generating ChangeLog')))
473
474
475 class TestPresenceOfGit(base.BaseTestCase):
476
477     def testGitIsInstalled(self):
478         with mock.patch.object(git,
479                                '_run_shell_command') as _command:
480             _command.return_value = 'git version 1.8.4.1'
481             self.assertEqual(True, git._git_is_installed())
482
483     def testGitIsNotInstalled(self):
484         with mock.patch.object(git,
485                                '_run_shell_command') as _command:
486             _command.side_effect = OSError
487             self.assertEqual(False, git._git_is_installed())
488
489
490 class ParseRequirementsTest(base.BaseTestCase):
491
492     def test_empty_requirements(self):
493         actual = packaging.parse_requirements([])
494         self.assertEqual([], actual)
495
496     def test_default_requirements(self):
497         """Ensure default files used if no files provided."""
498         tempdir = tempfile.mkdtemp()
499         requirements = os.path.join(tempdir, 'requirements.txt')
500         with open(requirements, 'w') as f:
501             f.write('pbr')
502         # the defaults are relative to where pbr is called from so we need to
503         # override them. This is OK, however, as we want to validate that
504         # defaults are used - not what those defaults are
505         with mock.patch.object(packaging, 'REQUIREMENTS_FILES', (
506                 requirements,)):
507             result = packaging.parse_requirements()
508         self.assertEqual(['pbr'], result)
509
510     def test_override_with_env(self):
511         """Ensure environment variable used if no files provided."""
512         _, tmp_file = tempfile.mkstemp(prefix='openstack', suffix='.setup')
513         with open(tmp_file, 'w') as fh:
514             fh.write("foo\nbar")
515         self.useFixture(
516             fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES', tmp_file))
517         self.assertEqual(['foo', 'bar'],
518                          packaging.parse_requirements())
519
520     def test_override_with_env_multiple_files(self):
521         _, tmp_file = tempfile.mkstemp(prefix='openstack', suffix='.setup')
522         with open(tmp_file, 'w') as fh:
523             fh.write("foo\nbar")
524         self.useFixture(
525             fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES',
526                                          "no-such-file," + tmp_file))
527         self.assertEqual(['foo', 'bar'],
528                          packaging.parse_requirements())
529
530     def test_index_present(self):
531         tempdir = tempfile.mkdtemp()
532         requirements = os.path.join(tempdir, 'requirements.txt')
533         with open(requirements, 'w') as f:
534             f.write('-i https://myindex.local\n')
535             f.write('  --index-url https://myindex.local\n')
536             f.write(' --extra-index-url https://myindex.local\n')
537             f.write('--find-links https://myindex.local\n')
538             f.write('arequirement>=1.0\n')
539         result = packaging.parse_requirements([requirements])
540         self.assertEqual(['arequirement>=1.0'], result)
541
542     def test_nested_requirements(self):
543         tempdir = tempfile.mkdtemp()
544         requirements = os.path.join(tempdir, 'requirements.txt')
545         nested = os.path.join(tempdir, 'nested.txt')
546         with open(requirements, 'w') as f:
547             f.write('-r ' + nested)
548         with open(nested, 'w') as f:
549             f.write('pbr')
550         result = packaging.parse_requirements([requirements])
551         self.assertEqual(['pbr'], result)
552
553
554 class ParseRequirementsTestScenarios(base.BaseTestCase):
555
556     versioned_scenarios = [
557         ('non-versioned', {'versioned': False, 'expected': ['bar']}),
558         ('versioned', {'versioned': True, 'expected': ['bar>=1.2.3']})
559     ]
560
561     subdirectory_scenarios = [
562         ('non-subdirectory', {'has_subdirectory': False}),
563         ('has-subdirectory', {'has_subdirectory': True})
564     ]
565
566     scenarios = [
567         ('normal', {'url': "foo\nbar", 'expected': ['foo', 'bar']}),
568         ('normal_with_comments', {
569             'url': "# this is a comment\nfoo\n# and another one\nbar",
570             'expected': ['foo', 'bar']}),
571         ('removes_index_lines', {'url': '-f foobar', 'expected': []}),
572     ]
573
574     scenarios = scenarios + testscenarios.multiply_scenarios([
575         ('ssh_egg_url', {'url': 'git+ssh://foo.com/zipball#egg=bar'}),
576         ('git_https_egg_url', {'url': 'git+https://foo.com/zipball#egg=bar'}),
577         ('http_egg_url', {'url': 'https://foo.com/zipball#egg=bar'}),
578     ], versioned_scenarios, subdirectory_scenarios)
579
580     scenarios = scenarios + testscenarios.multiply_scenarios(
581         [
582             ('git_egg_url',
583                 {'url': 'git://foo.com/zipball#egg=bar', 'name': 'bar'})
584         ], [
585             ('non-editable', {'editable': False}),
586             ('editable', {'editable': True}),
587         ],
588         versioned_scenarios, subdirectory_scenarios)
589
590     def test_parse_requirements(self):
591         tmp_file = tempfile.NamedTemporaryFile()
592         req_string = self.url
593         if hasattr(self, 'editable') and self.editable:
594             req_string = ("-e %s" % req_string)
595         if hasattr(self, 'versioned') and self.versioned:
596             req_string = ("%s-1.2.3" % req_string)
597         if hasattr(self, 'has_subdirectory') and self.has_subdirectory:
598             req_string = ("%s&subdirectory=baz" % req_string)
599         with open(tmp_file.name, 'w') as fh:
600             fh.write(req_string)
601         self.assertEqual(self.expected,
602                          packaging.parse_requirements([tmp_file.name]))
603
604
605 class ParseDependencyLinksTest(base.BaseTestCase):
606
607     def setUp(self):
608         super(ParseDependencyLinksTest, self).setUp()
609         _, self.tmp_file = tempfile.mkstemp(prefix="openstack",
610                                             suffix=".setup")
611
612     def test_parse_dependency_normal(self):
613         with open(self.tmp_file, "w") as fh:
614             fh.write("http://test.com\n")
615         self.assertEqual(
616             ["http://test.com"],
617             packaging.parse_dependency_links([self.tmp_file]))
618
619     def test_parse_dependency_with_git_egg_url(self):
620         with open(self.tmp_file, "w") as fh:
621             fh.write("-e git://foo.com/zipball#egg=bar")
622         self.assertEqual(
623             ["git://foo.com/zipball#egg=bar"],
624             packaging.parse_dependency_links([self.tmp_file]))
625
626
627 class TestVersions(base.BaseTestCase):
628
629     scenarios = [
630         ('preversioned', dict(preversioned=True)),
631         ('postversioned', dict(preversioned=False)),
632     ]
633
634     def setUp(self):
635         super(TestVersions, self).setUp()
636         self.repo = self.useFixture(TestRepo(self.package_dir))
637         self.useFixture(GPGKeyFixture())
638         self.useFixture(base.DiveDir(self.package_dir))
639
640     def test_email_parsing_errors_are_handled(self):
641         mocked_open = mock.mock_open()
642         with mock.patch('pbr.packaging.open', mocked_open):
643             with mock.patch('email.message_from_file') as message_from_file:
644                 message_from_file.side_effect = [
645                     email.errors.MessageError('Test'),
646                     {'Name': 'pbr_testpackage'}]
647                 version = packaging._get_version_from_pkg_metadata(
648                     'pbr_testpackage')
649
650         self.assertTrue(message_from_file.called)
651         self.assertIsNone(version)
652
653     def test_capitalized_headers(self):
654         self.repo.commit()
655         self.repo.tag('1.2.3')
656         self.repo.commit('Sem-Ver: api-break')
657         version = packaging._get_version_from_git()
658         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
659
660     def test_capitalized_headers_partial(self):
661         self.repo.commit()
662         self.repo.tag('1.2.3')
663         self.repo.commit('Sem-ver: api-break')
664         version = packaging._get_version_from_git()
665         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
666
667     def test_tagged_version_has_tag_version(self):
668         self.repo.commit()
669         self.repo.tag('1.2.3')
670         version = packaging._get_version_from_git('1.2.3')
671         self.assertEqual('1.2.3', version)
672
673     def test_tagged_version_with_semver_compliant_prerelease(self):
674         self.repo.commit()
675         self.repo.tag('1.2.3-rc2')
676         version = packaging._get_version_from_git()
677         self.assertEqual('1.2.3.0rc2', version)
678
679     def test_non_canonical_tagged_version_bump(self):
680         self.repo.commit()
681         self.repo.tag('1.4')
682         self.repo.commit('Sem-Ver: api-break')
683         version = packaging._get_version_from_git()
684         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
685
686     def test_untagged_version_has_dev_version_postversion(self):
687         self.repo.commit()
688         self.repo.tag('1.2.3')
689         self.repo.commit()
690         version = packaging._get_version_from_git()
691         self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
692
693     def test_untagged_pre_release_has_pre_dev_version_postversion(self):
694         self.repo.commit()
695         self.repo.tag('1.2.3.0a1')
696         self.repo.commit()
697         version = packaging._get_version_from_git()
698         self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
699
700     def test_untagged_version_minor_bump(self):
701         self.repo.commit()
702         self.repo.tag('1.2.3')
703         self.repo.commit('sem-ver: deprecation')
704         version = packaging._get_version_from_git()
705         self.assertThat(version, matchers.StartsWith('1.3.0.dev1'))
706
707     def test_untagged_version_major_bump(self):
708         self.repo.commit()
709         self.repo.tag('1.2.3')
710         self.repo.commit('sem-ver: api-break')
711         version = packaging._get_version_from_git()
712         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
713
714     def test_untagged_version_has_dev_version_preversion(self):
715         self.repo.commit()
716         self.repo.tag('1.2.3')
717         self.repo.commit()
718         version = packaging._get_version_from_git('1.2.5')
719         self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
720
721     def test_untagged_version_after_pre_has_dev_version_preversion(self):
722         self.repo.commit()
723         self.repo.tag('1.2.3.0a1')
724         self.repo.commit()
725         version = packaging._get_version_from_git('1.2.5')
726         self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
727
728     def test_untagged_version_after_rc_has_dev_version_preversion(self):
729         self.repo.commit()
730         self.repo.tag('1.2.3.0a1')
731         self.repo.commit()
732         version = packaging._get_version_from_git('1.2.3')
733         self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
734
735     def test_untagged_version_after_semver_compliant_prerelease_tag(self):
736         self.repo.commit()
737         self.repo.tag('1.2.3-rc2')
738         self.repo.commit()
739         version = packaging._get_version_from_git()
740         self.assertEqual('1.2.3.0rc3.dev1', version)
741
742     def test_preversion_too_low_simple(self):
743         # That is, the target version is either already released or not high
744         # enough for the semver requirements given api breaks etc.
745         self.repo.commit()
746         self.repo.tag('1.2.3')
747         self.repo.commit()
748         # Note that we can't target 1.2.3 anymore - with 1.2.3 released we
749         # need to be working on 1.2.4.
750         err = self.assertRaises(
751             ValueError, packaging._get_version_from_git, '1.2.3')
752         self.assertThat(err.args[0], matchers.StartsWith('git history'))
753
754     def test_preversion_too_low_semver_headers(self):
755         # That is, the target version is either already released or not high
756         # enough for the semver requirements given api breaks etc.
757         self.repo.commit()
758         self.repo.tag('1.2.3')
759         self.repo.commit('sem-ver: feature')
760         # Note that we can't target 1.2.4, the feature header means we need
761         # to be working on 1.3.0 or above.
762         err = self.assertRaises(
763             ValueError, packaging._get_version_from_git, '1.2.4')
764         self.assertThat(err.args[0], matchers.StartsWith('git history'))
765
766     def test_get_kwargs_corner_cases(self):
767         # No tags:
768         git_dir = self.repo._basedir + '/.git'
769         get_kwargs = lambda tag: packaging._get_increment_kwargs(git_dir, tag)
770
771         def _check_combinations(tag):
772             self.repo.commit()
773             self.assertEqual(dict(), get_kwargs(tag))
774             self.repo.commit('sem-ver: bugfix')
775             self.assertEqual(dict(), get_kwargs(tag))
776             self.repo.commit('sem-ver: feature')
777             self.assertEqual(dict(minor=True), get_kwargs(tag))
778             self.repo.uncommit()
779             self.repo.commit('sem-ver: deprecation')
780             self.assertEqual(dict(minor=True), get_kwargs(tag))
781             self.repo.uncommit()
782             self.repo.commit('sem-ver: api-break')
783             self.assertEqual(dict(major=True), get_kwargs(tag))
784             self.repo.commit('sem-ver: deprecation')
785             self.assertEqual(dict(major=True, minor=True), get_kwargs(tag))
786         _check_combinations('')
787         self.repo.tag('1.2.3')
788         _check_combinations('1.2.3')
789
790     def test_invalid_tag_ignored(self):
791         # Fix for bug 1356784 - we treated any tag as a version, not just those
792         # that are valid versions.
793         self.repo.commit()
794         self.repo.tag('1')
795         self.repo.commit()
796         # when the tree is tagged and its wrong:
797         self.repo.tag('badver')
798         version = packaging._get_version_from_git()
799         self.assertThat(version, matchers.StartsWith('1.0.1.dev1'))
800         # When the tree isn't tagged, we also fall through.
801         self.repo.commit()
802         version = packaging._get_version_from_git()
803         self.assertThat(version, matchers.StartsWith('1.0.1.dev2'))
804         # We don't fall through x.y versions
805         self.repo.commit()
806         self.repo.tag('1.2')
807         self.repo.commit()
808         self.repo.tag('badver2')
809         version = packaging._get_version_from_git()
810         self.assertThat(version, matchers.StartsWith('1.2.1.dev1'))
811         # Or x.y.z versions
812         self.repo.commit()
813         self.repo.tag('1.2.3')
814         self.repo.commit()
815         self.repo.tag('badver3')
816         version = packaging._get_version_from_git()
817         self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
818         # Or alpha/beta/pre versions
819         self.repo.commit()
820         self.repo.tag('1.2.4.0a1')
821         self.repo.commit()
822         self.repo.tag('badver4')
823         version = packaging._get_version_from_git()
824         self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1'))
825         # Non-release related tags are ignored.
826         self.repo.commit()
827         self.repo.tag('2')
828         self.repo.commit()
829         self.repo.tag('non-release-tag/2014.12.16-1')
830         version = packaging._get_version_from_git()
831         self.assertThat(version, matchers.StartsWith('2.0.1.dev1'))
832
833     def test_valid_tag_honoured(self):
834         # Fix for bug 1370608 - we converted any target into a 'dev version'
835         # even if there was a distance of 0 - indicating that we were on the
836         # tag itself.
837         self.repo.commit()
838         self.repo.tag('1.3.0.0a1')
839         version = packaging._get_version_from_git()
840         self.assertEqual('1.3.0.0a1', version)
841
842     def test_skip_write_git_changelog(self):
843         # Fix for bug 1467440
844         self.repo.commit()
845         self.repo.tag('1.2.3')
846         os.environ['SKIP_WRITE_GIT_CHANGELOG'] = '1'
847         version = packaging._get_version_from_git('1.2.3')
848         self.assertEqual('1.2.3', version)
849
850     def tearDown(self):
851         super(TestVersions, self).tearDown()
852         os.environ.pop('SKIP_WRITE_GIT_CHANGELOG', None)
853
854
855 class TestRequirementParsing(base.BaseTestCase):
856
857     def test_requirement_parsing(self):
858         pkgs = {
859             'test_reqparse':
860                 {
861                     'requirements.txt': textwrap.dedent("""\
862                         bar
863                         quux<1.0; python_version=='2.6'
864                         requests-aws>=0.1.4    # BSD License (3 clause)
865                         Routes>=1.12.3,!=2.0,!=2.1;python_version=='2.7'
866                         requests-kerberos>=0.6;python_version=='2.7' # MIT
867                     """),
868                     'setup.cfg': textwrap.dedent("""\
869                         [metadata]
870                         name = test_reqparse
871
872                         [extras]
873                         test =
874                             foo
875                             baz>3.2 :python_version=='2.7' # MIT
876                             bar>3.3 :python_version=='2.7' # MIT # Apache
877                     """)},
878         }
879         pkg_dirs = self.useFixture(CreatePackages(pkgs)).package_dirs
880         pkg_dir = pkg_dirs['test_reqparse']
881         # pkg_resources.split_sections uses None as the title of an
882         # anonymous section instead of the empty string. Weird.
883         expected_requirements = {
884             None: ['bar', 'requests-aws>=0.1.4'],
885             ":(python_version=='2.6')": ['quux<1.0'],
886             ":(python_version=='2.7')": ['Routes!=2.0,!=2.1,>=1.12.3',
887                                          'requests-kerberos>=0.6'],
888             'test': ['foo'],
889             "test:(python_version=='2.7')": ['baz>3.2', 'bar>3.3']
890         }
891         venv = self.useFixture(Venv('reqParse'))
892         bin_python = venv.python
893         # Two things are tested by this
894         # 1) pbr properly parses markers from requiremnts.txt and setup.cfg
895         # 2) bdist_wheel causes pbr to not evaluate markers
896         self._run_cmd(bin_python, ('setup.py', 'bdist_wheel'),
897                       allow_fail=False, cwd=pkg_dir)
898         egg_info = os.path.join(pkg_dir, 'test_reqparse.egg-info')
899
900         requires_txt = os.path.join(egg_info, 'requires.txt')
901         with open(requires_txt, 'rt') as requires:
902             generated_requirements = dict(
903                 pkg_resources.split_sections(requires))
904
905         # NOTE(dhellmann): We have to spell out the comparison because
906         # the rendering for version specifiers in a range is not
907         # consistent across versions of setuptools.
908
909         for section, expected in expected_requirements.items():
910             exp_parsed = [
911                 pkg_resources.Requirement.parse(s)
912                 for s in expected
913             ]
914             gen_parsed = [
915                 pkg_resources.Requirement.parse(s)
916                 for s in generated_requirements[section]
917             ]
918             self.assertEqual(exp_parsed, gen_parsed)
919
920
921 def get_soabi():
922     soabi = None
923     try:
924         soabi = sysconfig.get_config_var('SOABI')
925         arch = sysconfig.get_config_var('MULTIARCH')
926     except IOError:
927         pass
928     if soabi and arch and 'pypy' in sysconfig.get_scheme_names():
929         soabi = '%s-%s' % (soabi, arch)
930     if soabi is None and 'pypy' in sysconfig.get_scheme_names():
931         # NOTE(sigmavirus24): PyPy only added support for the SOABI config var
932         # to sysconfig in 2015. That was well after 2.2.1 was published in the
933         # Ubuntu 14.04 archive.
934         for suffix, _, _ in imp.get_suffixes():
935             if suffix.startswith('.pypy') and suffix.endswith('.so'):
936                 soabi = suffix.split('.')[1]
937                 break
938     return soabi