9e56fd8c4de6e0422f517b76ffd5f039bf43286f
[sdc/sdc-distribution-client.git] /
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 os
42 import re
43 import sys
44 import tempfile
45 import textwrap
46
47 import fixtures
48 import mock
49 import pkg_resources
50 import six
51 import testscenarios
52 from testtools import matchers
53
54 from pbr import git
55 from pbr import packaging
56 from pbr.tests import base
57
58
59 class TestRepo(fixtures.Fixture):
60     """A git repo for testing with.
61
62     Use of TempHomeDir with this fixture is strongly recommended as due to the
63     lack of config --local in older gits, it will write to the users global
64     configuration without TempHomeDir.
65     """
66
67     def __init__(self, basedir):
68         super(TestRepo, self).__init__()
69         self._basedir = basedir
70
71     def setUp(self):
72         super(TestRepo, self).setUp()
73         base._run_cmd(['git', 'init', '.'], self._basedir)
74         base._config_git()
75         base._run_cmd(['git', 'add', '.'], self._basedir)
76
77     def commit(self, message_content='test commit'):
78         files = len(os.listdir(self._basedir))
79         path = self._basedir + '/%d' % files
80         open(path, 'wt').close()
81         base._run_cmd(['git', 'add', path], self._basedir)
82         base._run_cmd(['git', 'commit', '-m', message_content], self._basedir)
83
84     def uncommit(self):
85         base._run_cmd(['git', 'reset', '--hard', 'HEAD^'], self._basedir)
86
87     def tag(self, version):
88         base._run_cmd(
89             ['git', 'tag', '-sm', 'test tag', version], self._basedir)
90
91
92 class GPGKeyFixture(fixtures.Fixture):
93     """Creates a GPG key for testing.
94
95     It's recommended that this be used in concert with a unique home
96     directory.
97     """
98
99     def setUp(self):
100         super(GPGKeyFixture, self).setUp()
101         tempdir = self.useFixture(fixtures.TempDir())
102         gnupg_version_re = re.compile('^gpg\s.*\s([\d+])\.([\d+])\.([\d+])')
103         gnupg_version = base._run_cmd(['gpg', '--version'], tempdir.path)
104         for line in gnupg_version[0].split('\n'):
105             gnupg_version = gnupg_version_re.match(line)
106             if gnupg_version:
107                 gnupg_version = (int(gnupg_version.group(1)),
108                                  int(gnupg_version.group(2)),
109                                  int(gnupg_version.group(3)))
110                 break
111         else:
112             if gnupg_version is None:
113                 gnupg_version = (0, 0, 0)
114         config_file = tempdir.path + '/key-config'
115         f = open(config_file, 'wt')
116         try:
117             if gnupg_version[0] == 2 and gnupg_version[1] >= 1:
118                 f.write("""
119                 %no-protection
120                 %transient-key
121                 """)
122             f.write("""
123             %no-ask-passphrase
124             Key-Type: RSA
125             Name-Real: Example Key
126             Name-Comment: N/A
127             Name-Email: example@example.com
128             Expire-Date: 2d
129             Preferences: (setpref)
130             %commit
131             """)
132         finally:
133             f.close()
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
147
148 class TestPackagingInGitRepoWithCommit(base.BaseTestCase):
149
150     scenarios = [
151         ('preversioned', dict(preversioned=True)),
152         ('postversioned', dict(preversioned=False)),
153     ]
154
155     def setUp(self):
156         super(TestPackagingInGitRepoWithCommit, self).setUp()
157         repo = self.useFixture(TestRepo(self.package_dir))
158         repo.commit()
159
160     def test_authors(self):
161         self.run_setup('sdist', allow_fail=False)
162         # One commit, something should be in the authors list
163         with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
164             body = f.read()
165         self.assertNotEqual(body, '')
166
167     def test_changelog(self):
168         self.run_setup('sdist', allow_fail=False)
169         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
170             body = f.read()
171         # One commit, something should be in the ChangeLog list
172         self.assertNotEqual(body, '')
173
174     def test_manifest_exclude_honoured(self):
175         self.run_setup('sdist', allow_fail=False)
176         with open(os.path.join(
177                 self.package_dir,
178                 'pbr_testpackage.egg-info/SOURCES.txt'), 'r') as f:
179             body = f.read()
180         self.assertThat(
181             body, matchers.Not(matchers.Contains('pbr_testpackage/extra.py')))
182         self.assertThat(body, matchers.Contains('pbr_testpackage/__init__.py'))
183
184     def test_install_writes_changelog(self):
185         stdout, _, _ = self.run_setup(
186             'install', '--root', self.temp_dir + 'installed',
187             allow_fail=False)
188         self.expectThat(stdout, matchers.Contains('Generating ChangeLog'))
189
190
191 class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase):
192
193     def setUp(self):
194         super(TestPackagingInGitRepoWithoutCommit, self).setUp()
195         self.useFixture(TestRepo(self.package_dir))
196         self.run_setup('sdist', allow_fail=False)
197
198     def test_authors(self):
199         # No commits, no authors in list
200         with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
201             body = f.read()
202         self.assertEqual(body, '\n')
203
204     def test_changelog(self):
205         # No commits, nothing should be in the ChangeLog list
206         with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
207             body = f.read()
208         self.assertEqual(body, 'CHANGES\n=======\n\n')
209
210
211 class TestPackagingInPlainDirectory(base.BaseTestCase):
212
213     def setUp(self):
214         super(TestPackagingInPlainDirectory, self).setUp()
215
216     def test_authors(self):
217         self.run_setup('sdist', allow_fail=False)
218         # Not a git repo, no AUTHORS file created
219         filename = os.path.join(self.package_dir, 'AUTHORS')
220         self.assertFalse(os.path.exists(filename))
221
222     def test_changelog(self):
223         self.run_setup('sdist', allow_fail=False)
224         # Not a git repo, no ChangeLog created
225         filename = os.path.join(self.package_dir, 'ChangeLog')
226         self.assertFalse(os.path.exists(filename))
227
228     def test_install_no_ChangeLog(self):
229         stdout, _, _ = self.run_setup(
230             'install', '--root', self.temp_dir + 'installed',
231             allow_fail=False)
232         self.expectThat(
233             stdout, matchers.Not(matchers.Contains('Generating ChangeLog')))
234
235
236 class TestPresenceOfGit(base.BaseTestCase):
237
238     def testGitIsInstalled(self):
239         with mock.patch.object(git,
240                                '_run_shell_command') as _command:
241             _command.return_value = 'git version 1.8.4.1'
242             self.assertEqual(True, git._git_is_installed())
243
244     def testGitIsNotInstalled(self):
245         with mock.patch.object(git,
246                                '_run_shell_command') as _command:
247             _command.side_effect = OSError
248             self.assertEqual(False, git._git_is_installed())
249
250
251 class TestNestedRequirements(base.BaseTestCase):
252
253     def test_nested_requirement(self):
254         tempdir = tempfile.mkdtemp()
255         requirements = os.path.join(tempdir, 'requirements.txt')
256         nested = os.path.join(tempdir, 'nested.txt')
257         with open(requirements, 'w') as f:
258             f.write('-r ' + nested)
259         with open(nested, 'w') as f:
260             f.write('pbr')
261         result = packaging.parse_requirements([requirements])
262         self.assertEqual(result, ['pbr'])
263
264
265 class TestVersions(base.BaseTestCase):
266
267     scenarios = [
268         ('preversioned', dict(preversioned=True)),
269         ('postversioned', dict(preversioned=False)),
270     ]
271
272     def setUp(self):
273         super(TestVersions, self).setUp()
274         self.repo = self.useFixture(TestRepo(self.package_dir))
275         self.useFixture(GPGKeyFixture())
276         self.useFixture(base.DiveDir(self.package_dir))
277
278     def test_capitalized_headers(self):
279         self.repo.commit()
280         self.repo.tag('1.2.3')
281         self.repo.commit('Sem-Ver: api-break')
282         version = packaging._get_version_from_git()
283         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
284
285     def test_capitalized_headers_partial(self):
286         self.repo.commit()
287         self.repo.tag('1.2.3')
288         self.repo.commit('Sem-ver: api-break')
289         version = packaging._get_version_from_git()
290         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
291
292     def test_tagged_version_has_tag_version(self):
293         self.repo.commit()
294         self.repo.tag('1.2.3')
295         version = packaging._get_version_from_git('1.2.3')
296         self.assertEqual('1.2.3', version)
297
298     def test_untagged_version_has_dev_version_postversion(self):
299         self.repo.commit()
300         self.repo.tag('1.2.3')
301         self.repo.commit()
302         version = packaging._get_version_from_git()
303         self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
304
305     def test_untagged_pre_release_has_pre_dev_version_postversion(self):
306         self.repo.commit()
307         self.repo.tag('1.2.3.0a1')
308         self.repo.commit()
309         version = packaging._get_version_from_git()
310         self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
311
312     def test_untagged_version_minor_bump(self):
313         self.repo.commit()
314         self.repo.tag('1.2.3')
315         self.repo.commit('sem-ver: deprecation')
316         version = packaging._get_version_from_git()
317         self.assertThat(version, matchers.StartsWith('1.3.0.dev1'))
318
319     def test_untagged_version_major_bump(self):
320         self.repo.commit()
321         self.repo.tag('1.2.3')
322         self.repo.commit('sem-ver: api-break')
323         version = packaging._get_version_from_git()
324         self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
325
326     def test_untagged_version_has_dev_version_preversion(self):
327         self.repo.commit()
328         self.repo.tag('1.2.3')
329         self.repo.commit()
330         version = packaging._get_version_from_git('1.2.5')
331         self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
332
333     def test_untagged_version_after_pre_has_dev_version_preversion(self):
334         self.repo.commit()
335         self.repo.tag('1.2.3.0a1')
336         self.repo.commit()
337         version = packaging._get_version_from_git('1.2.5')
338         self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
339
340     def test_untagged_version_after_rc_has_dev_version_preversion(self):
341         self.repo.commit()
342         self.repo.tag('1.2.3.0a1')
343         self.repo.commit()
344         version = packaging._get_version_from_git('1.2.3')
345         self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
346
347     def test_preversion_too_low_simple(self):
348         # That is, the target version is either already released or not high
349         # enough for the semver requirements given api breaks etc.
350         self.repo.commit()
351         self.repo.tag('1.2.3')
352         self.repo.commit()
353         # Note that we can't target 1.2.3 anymore - with 1.2.3 released we
354         # need to be working on 1.2.4.
355         err = self.assertRaises(
356             ValueError, packaging._get_version_from_git, '1.2.3')
357         self.assertThat(err.args[0], matchers.StartsWith('git history'))
358
359     def test_preversion_too_low_semver_headers(self):
360         # That is, the target version is either already released or not high
361         # enough for the semver requirements given api breaks etc.
362         self.repo.commit()
363         self.repo.tag('1.2.3')
364         self.repo.commit('sem-ver: feature')
365         # Note that we can't target 1.2.4, the feature header means we need
366         # to be working on 1.3.0 or above.
367         err = self.assertRaises(
368             ValueError, packaging._get_version_from_git, '1.2.4')
369         self.assertThat(err.args[0], matchers.StartsWith('git history'))
370
371     def test_get_kwargs_corner_cases(self):
372         # No tags:
373         git_dir = self.repo._basedir + '/.git'
374         get_kwargs = lambda tag: packaging._get_increment_kwargs(git_dir, tag)
375
376         def _check_combinations(tag):
377             self.repo.commit()
378             self.assertEqual(dict(), get_kwargs(tag))
379             self.repo.commit('sem-ver: bugfix')
380             self.assertEqual(dict(), get_kwargs(tag))
381             self.repo.commit('sem-ver: feature')
382             self.assertEqual(dict(minor=True), get_kwargs(tag))
383             self.repo.uncommit()
384             self.repo.commit('sem-ver: deprecation')
385             self.assertEqual(dict(minor=True), get_kwargs(tag))
386             self.repo.uncommit()
387             self.repo.commit('sem-ver: api-break')
388             self.assertEqual(dict(major=True), get_kwargs(tag))
389             self.repo.commit('sem-ver: deprecation')
390             self.assertEqual(dict(major=True, minor=True), get_kwargs(tag))
391         _check_combinations('')
392         self.repo.tag('1.2.3')
393         _check_combinations('1.2.3')
394
395     def test_invalid_tag_ignored(self):
396         # Fix for bug 1356784 - we treated any tag as a version, not just those
397         # that are valid versions.
398         self.repo.commit()
399         self.repo.tag('1')
400         self.repo.commit()
401         # when the tree is tagged and its wrong:
402         self.repo.tag('badver')
403         version = packaging._get_version_from_git()
404         self.assertThat(version, matchers.StartsWith('1.0.1.dev1'))
405         # When the tree isn't tagged, we also fall through.
406         self.repo.commit()
407         version = packaging._get_version_from_git()
408         self.assertThat(version, matchers.StartsWith('1.0.1.dev2'))
409         # We don't fall through x.y versions
410         self.repo.commit()
411         self.repo.tag('1.2')
412         self.repo.commit()
413         self.repo.tag('badver2')
414         version = packaging._get_version_from_git()
415         self.assertThat(version, matchers.StartsWith('1.2.1.dev1'))
416         # Or x.y.z versions
417         self.repo.commit()
418         self.repo.tag('1.2.3')
419         self.repo.commit()
420         self.repo.tag('badver3')
421         version = packaging._get_version_from_git()
422         self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
423         # Or alpha/beta/pre versions
424         self.repo.commit()
425         self.repo.tag('1.2.4.0a1')
426         self.repo.commit()
427         self.repo.tag('badver4')
428         version = packaging._get_version_from_git()
429         self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1'))
430         # Non-release related tags are ignored.
431         self.repo.commit()
432         self.repo.tag('2')
433         self.repo.commit()
434         self.repo.tag('non-release-tag/2014.12.16-1')
435         version = packaging._get_version_from_git()
436         self.assertThat(version, matchers.StartsWith('2.0.1.dev1'))
437
438     def test_valid_tag_honoured(self):
439         # Fix for bug 1370608 - we converted any target into a 'dev version'
440         # even if there was a distance of 0 - indicating that we were on the
441         # tag itself.
442         self.repo.commit()
443         self.repo.tag('1.3.0.0a1')
444         version = packaging._get_version_from_git()
445         self.assertEqual('1.3.0.0a1', version)
446
447     def test_skip_write_git_changelog(self):
448         # Fix for bug 1467440
449         self.repo.commit()
450         self.repo.tag('1.2.3')
451         os.environ['SKIP_WRITE_GIT_CHANGELOG'] = '1'
452         version = packaging._get_version_from_git('1.2.3')
453         self.assertEqual('1.2.3', version)
454
455     def tearDown(self):
456         super(TestVersions, self).tearDown()
457         os.environ.pop('SKIP_WRITE_GIT_CHANGELOG', None)
458
459
460 class TestRequirementParsing(base.BaseTestCase):
461
462     def test_requirement_parsing(self):
463         tempdir = self.useFixture(fixtures.TempDir()).path
464         requirements = os.path.join(tempdir, 'requirements.txt')
465         with open(requirements, 'wt') as f:
466             f.write(textwrap.dedent(six.u("""\
467                 bar
468                 quux<1.0; python_version=='2.6'
469                 requests-aws>=0.1.4    # BSD License (3 clause)
470                 Routes>=1.12.3,!=2.0,!=2.1;python_version=='2.7'
471                 requests-kerberos>=0.6;python_version=='2.7' # MIT
472             """)))
473         setup_cfg = os.path.join(tempdir, 'setup.cfg')
474         with open(setup_cfg, 'wt') as f:
475             f.write(textwrap.dedent(six.u("""\
476                 [metadata]
477                 name = test_reqparse
478
479                 [extras]
480                 test =
481                     foo
482                     baz>3.2 :python_version=='2.7' # MIT
483                     bar>3.3 :python_version=='2.7' # MIT # Apache
484             """)))
485         # pkg_resources.split_sections uses None as the title of an
486         # anonymous section instead of the empty string. Weird.
487         expected_requirements = {
488             None: ['bar', 'requests-aws>=0.1.4'],
489             ":(python_version=='2.6')": ['quux<1.0'],
490             ":(python_version=='2.7')": ['Routes>=1.12.3,!=2.0,!=2.1',
491                                          'requests-kerberos>=0.6'],
492             'test': ['foo'],
493             "test:(python_version=='2.7')": ['baz>3.2', 'bar>3.3']
494         }
495
496         setup_py = os.path.join(tempdir, 'setup.py')
497         with open(setup_py, 'wt') as f:
498             f.write(textwrap.dedent(six.u("""\
499                 #!/usr/bin/env python
500                 import setuptools
501                 setuptools.setup(
502                     setup_requires=['pbr'],
503                     pbr=True,
504                 )
505             """)))
506
507         self._run_cmd(sys.executable, (setup_py, 'egg_info'),
508                       allow_fail=False, cwd=tempdir)
509         egg_info = os.path.join(tempdir, 'test_reqparse.egg-info')
510
511         requires_txt = os.path.join(egg_info, 'requires.txt')
512         with open(requires_txt, 'rt') as requires:
513             generated_requirements = dict(
514                 pkg_resources.split_sections(requires))
515
516         self.assertEqual(expected_requirements, generated_requirements)
517
518
519 def load_tests(loader, in_tests, pattern):
520     return testscenarios.load_tests_apply_scenarios(loader, in_tests, pattern)