summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.testr.conf2
-rw-r--r--doc/source/index.rst19
-rw-r--r--doc/source/semver.rst101
-rw-r--r--pbr/core.py5
-rw-r--r--pbr/options.py5
-rw-r--r--pbr/packaging.py39
-rw-r--r--pbr/tests/base.py12
-rw-r--r--pbr/tests/test_hooks.py8
-rw-r--r--pbr/tests/test_integration.py165
-rw-r--r--pbr/tests/test_packaging.py76
-rw-r--r--pbr/tests/test_version.py170
-rw-r--r--pbr/tests/testpackage/MANIFEST.in1
-rw-r--r--pbr/tests/testpackage/pbr_testpackage/extra.py0
-rw-r--r--pbr/tests/util.py2
-rw-r--r--pbr/util.py7
-rw-r--r--pbr/version.py125
-rw-r--r--test-requirements.txt1
-rw-r--r--tools/integration.sh123
18 files changed, 501 insertions, 360 deletions
diff --git a/.testr.conf b/.testr.conf
index 1641f86..b3cfc54 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -1,4 +1,4 @@
[DEFAULT]
-test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
+test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f36fc6f..bc5441d 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -10,6 +10,11 @@ the results in as the arguments to a call to `setup.py` - so the heavy
lifting of handling python packaging needs is still being done by
`setuptools`.
+Note that we don't support the `easy_install` aspects of setuptools: while
+we depend on setup_requires, for any install_requires we recommend that they
+be installed prior to running `setup.py install` - either by hand, or by using
+an install tool such as `pip`.
+
What It Does
============
@@ -107,21 +112,11 @@ You can also have a requirement file for each specific major version of
Python. If you want to have a different package list for Python 3, just drop
a requirements-py3.txt, and it will be used instead.
-It's also possible to select a requirement file specific for an OS. The format
-is requirements-{osname}.txt, where ``{osname}`` is the equivalent of
-``platform.system()``. The two approaches, Python version and OS version, can
-be combined.
-
The requirement files are tried in that order (N being the Python major
-version number used to install the package and OS being the current
-platform's name in lowercase, retrieved with ``platform.system()``):
+version number used to install the package):
-* requirements-OS-pyN.txt
-* tools/pip-requires-OS-pyN
-* requirements-OS.txt
-* tools/pip-requires-OS
* requirements-pyN.txt
-* tools/pip-requires-pyN
+* tools/pip-requires-py3
* requirements.txt
* tools/pip-requires
diff --git a/doc/source/semver.rst b/doc/source/semver.rst
index 8b10112..c5cf5bf 100644
--- a/doc/source/semver.rst
+++ b/doc/source/semver.rst
@@ -1,25 +1,34 @@
-Linux Compatible Semantic Versioning 3.0.0
-==========================================
+Linux/Python Compatible Semantic Versioning 3.0.0
+=================================================
This is a fork of Semantic Versioning 2.0. The specific changes have to do
with the format of pre-release and build labels, specifically to make them
-not confusing when co-existing with Linux Distribution packaging.
-Inspiration for the format of the pre-release and build labels came from
-Python's PEP440.
+not confusing when co-existing with Linux distribution packaging and Python
+packaging. Inspiration for the format of the pre-release and build labels
+came from Python's PEP440.
+
+Changes vs SemVer 2.0
+---------------------
+
+#. dev versions are defined. These are extremely useful when
+ dealing with CI and CD systems when 'every commit is a release' is not
+ feasible.
+
+#. All versions have been made PEP-440 compatible, because of our deep
+ roots in Python. Pre-release versions are now separated by . not -, and
+ use a/b/c rather than alpha/beta etc.
Summary
-------
-Given a version number MAJOR.MINOR.PATCH, increment the:
+Given a version number MAJOR.MINOR.PATCH,
+increment the:
#. MAJOR version when you make incompatible API changes,
#. MINOR version when you add functionality in a backwards-compatible
manner, and
#. PATCH version when you make backwards-compatible bug fixes.
-Additional labels for pre-release and build metadata are available as
-extensions to the MAJOR.MINOR.PATCH format.
-
Introduction
------------
@@ -56,12 +65,6 @@ I call this system "Semantic Versioning." Under this scheme, version
numbers and the way they change convey meaning about the underlying code
and what has been modified from one version to the next.
-Linux Compatible Semantic Versioning is different from Semantic
-Versioning in that it does not employ the use of the hyphen in ways that
-are ambiguous when used with or adjacent to software packaged with dpkg or
-rpm. Instead, it draws from PEP440's approach of indicating pre-releases
-with leading characters in the version segment.
-
Semantic Versioning Specification (SemVer)
------------------------------------------
@@ -111,24 +114,27 @@ document are to be interpreted as described in `RFC
#. A pre-release version MAY be denoted by appending a dot
separated identifier immediately following the patch version.
- The identifier MUST comprise only a, b, rc followed by non-negative
+ The identifier MUST comprise only a, b, c followed by non-negative
integer value. The identifier MUST NOT be empty.
Pre-release versions have a lower precedence than the associated normal
version. A pre-release version indicates that
the version is unstable and might not satisfy the intended
compatibility requirements as denoted by its associated normal
- version. Examples: 1.0.0.a1, 1.0.0.b99, 1.0.0.rc1000.
+ version. Examples: 1.0.0.a1, 1.0.0.b99, 1.0.0.c1000.
#. A development version MAY be denoted by appending a dot separated
identifier immediately following the patch version.
The identifier MUST comprise the string dev followed by non-negative
integer value. The identifier MUST NOT be empty. Development versions
- have a lower precedence than the associated normal version. A development
- version is a completely unsupported and conveys no API promises when
- related to other versions. They are more useful as communication
- vehicles between developers of a community, whereas pre-releases, while
- potentially prone to break still, are intended for externally facing
- communication of not-yet-released ideas. Example: 1.0.0.dev1.
+ have a lower precedence than the associated normal version or pre-release
+ version. A development version is a completely unsupported and conveys no
+ API promises when related to other versions. They are more useful as
+ communication vehicles between developers of a community, whereas
+ pre-releases, while potentially prone to break still, are intended for
+ externally facing communication of not-yet-released ideas. Dev versions
+ are not public artifacts and should never be placed in public
+ repositories: they are intended as developer-local resources. Examples:
+ 1.0.0.dev1, 1.0.0.a1.dev1
#. git version metadata MAY be denoted by appending a dot separated
identifier immediately following a development or pre-release version.
@@ -149,23 +155,26 @@ document are to be interpreted as described in `RFC
#. Precedence refers to how versions are compared to each other when
ordered. Precedence MUST be calculated by separating the version
- into major, minor, patch and pre-release identifiers in that order
- (Build metadata does not figure into precedence). Precedence is
- determined by the first difference when comparing each of these
+ into major, minor, patch, pre-release, and development identifiers in
+ that order (Build metadata does not figure into precedence). Precedence
+ is determined by the first difference when comparing each of these
identifiers from left to right as follows: Major, minor, and patch
versions are always compared numerically. Example: 1.0.0 < 2.0.0 <
2.1.0 < 2.1.1. When major, minor, and patch are equal, a pre-release
version has lower precedence than a normal version. Example:
- 1.0.0.a1 < 1.0.0. When major, minor, and patch are equal, a development
- version as a lower precedence than a normal version and of a pre-release
- version. Example: 1.0.0.dev1 < 1.0.0 and 1.0.0dev9 < 1.0.0a1.
- Precedence for two pre-release or development versions with
- the same major, minor, and patch version MUST be determined by
- comparing the identifier to the right of the patch version as follows:
+ 1.0.0.a1 < 1.0.0. When major, minor, patch and pre-release are equal, a
+ development version has a lower precedence than a normal version and of a
+ pre-release version. Example: 1.0.0.dev1 < 1.0.0 and 1.0.0.dev9 <
+ 1.0.0.a1 and 1.0.0.a1 < 1.0.0.a2.dev4. Precedence for two pre-release
+ versions with the same major, minor, and patch version MUST be determined
+ by comparing the identifier to the right of the patch version as follows:
if the alpha portion matches, the numeric portion is compared in
- numerical sort order. If the alpha portion does not match, the sort
- order is dev < a < b < rc. Example: 1.0.0.dev8 < 1.0.0.dev9
- 1.0.0.a1 < 1.0.0.b2 < 1.0.0.rc1 < 1.0.0.
+ numerical sort order. If the alpha portion does not match, the sort order
+ is dev < a < b < c. Example: 1.0.0.dev8 < 1.0.0.dev9 < 1.0.0.a1.dev3 <
+ 1.0.0.a1 < 1.0.0.b2 < 1.0.0.c1 < 1.0.0. Precedence for dev versions if
+ all other components are equal is done by comparing their numeric
+ component. If all other components are not equal, predence is determined
+ by comparing the other components.
Why Use Semantic Versioning?
----------------------------
@@ -302,23 +311,15 @@ limits on the size of the string.
About
-----
-The Linux Compatible Semantic Versioning specification was modified by
-`Monty Taylor <http://inaugust.com>`__, member of `The Satori
-Group <http://satori-group.com>`__, co-founder of OpenStack and Free
-Software Hacker.
+The Linux/Python Compatible Semantic Versioning specification is maintained
+by the `OpenStack <http://openstack.org>`_ project.
-It was based on The Semantic Versioning specification, which was
+It is based on The Semantic Versioning specification, which was
authored by `Tom Preston-Werner <http://tom.preston-werner.com>`__,
-inventor of Gravatars and cofounder of GitHub, with inputs from `PEP
-440 <http://www.python.org/dev/peps/pep-0440/>`__ which was authored by
-`Nick Coughlan <http://www.boredomandlaziness.org>`__ who is a core
-Python developer and generally a great guy. I don't really know which
-things Nick invented or co-founded, and I'm not really sure why we'd
-need to list those here, but Tom did, so I figured coding style is
-usually about sticking to the style that was there before you showed up.
-
-If you'd like to leave feedback, please `open an issue on
-GitHub <https://github.com/emonty/semver/issues>`__.
+with inputs from `PEP 440 <http://www.python.org/dev/peps/pep-0440/>`_
+
+If you'd like to leave feedback, please `open an issue
+<https://bugs.launchpad.net/pbr/+filebug>`_.
License
-------
diff --git a/pbr/core.py b/pbr/core.py
index 0f4b94a..ef6a546 100644
--- a/pbr/core.py
+++ b/pbr/core.py
@@ -37,6 +37,11 @@
# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
from distutils import core
from distutils import errors
diff --git a/pbr/options.py b/pbr/options.py
index 5a7023c..105b200 100644
--- a/pbr/options.py
+++ b/pbr/options.py
@@ -35,6 +35,11 @@
# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
import os
diff --git a/pbr/packaging.py b/pbr/packaging.py
index 486f029..9ed1dbb 100644
--- a/pbr/packaging.py
+++ b/pbr/packaging.py
@@ -23,10 +23,7 @@ from __future__ import unicode_literals
from distutils.command import install as du_install
from distutils import log
import email
-import functools
-import itertools
import os
-import platform
import re
import sys
@@ -55,26 +52,10 @@ def get_requirements_files():
# Returns a list composed of:
# - REQUIREMENTS_FILES with -py2 or -py3 in the name
# (e.g. requirements-py3.txt)
- # - REQUIREMENTS_FILES with -{platform.system} in the name
- # (e.g. requirements-windows.txt)
- # - REQUIREMENTS_FILES with both Python version and platform's
- # system in the name
- # (e.g. requirements-freebsd-py2.txt)
# - REQUIREMENTS_FILES
- pyversion = sys.version_info[0]
- system = platform.system().lower()
- parts = list(map(os.path.splitext, REQUIREMENTS_FILES))
-
- version_cb = functools.partial("{1}-py{0}{2}".format, pyversion)
- platform_cb = functools.partial("{1}-{0}{2}".format, system)
- both_cb = functools.partial("{2}-{0}-py{1}{3}".format, system, pyversion)
-
- return list(itertools.chain(
- itertools.starmap(both_cb, parts),
- itertools.starmap(platform_cb, parts),
- itertools.starmap(version_cb, parts),
- REQUIREMENTS_FILES,
- ))
+ return (list(map(('-py' + str(sys.version_info[0])).join,
+ map(os.path.splitext, REQUIREMENTS_FILES)))
+ + list(REQUIREMENTS_FILES))
def append_text_list(config, key, text_list):
@@ -436,9 +417,13 @@ class LocalEggInfo(egg_info.egg_info):
If we are in an sdist command, then we always want to update
SOURCES.txt. If we are not in an sdist command, then it doesn't
matter one flip, and is actually destructive.
+ However, if we're in a git context, it's always the right thing to do
+ to recreate SOURCES.txt
"""
manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
- if not os.path.exists(manifest_filename) or 'sdist' in sys.argv:
+ if (not os.path.exists(manifest_filename) or
+ os.path.exists('.git') or
+ 'sdist' in sys.argv):
log.info("[pbr] Processing SOURCES.txt")
mm = LocalManifestMaker(self.distribution)
mm.manifest = manifest_filename
@@ -570,10 +555,12 @@ def _get_version_from_git_target(git_dir, target_version):
dict(new=new_version, target=target_version))
if distance == 0:
return last_semver
+ new_dev = new_version.to_dev(distance)
if target_version is not None:
- return target_version.to_dev(distance)
- else:
- return new_version.to_dev(distance)
+ target_dev = target_version.to_dev(distance)
+ if target_dev > new_dev:
+ return target_dev
+ return new_dev
def _get_version_from_git(pre_version=None):
diff --git a/pbr/tests/base.py b/pbr/tests/base.py
index e1068f1..09bd642 100644
--- a/pbr/tests/base.py
+++ b/pbr/tests/base.py
@@ -161,3 +161,15 @@ def _run_cmd(args, cwd):
for content in streams:
print(content)
return (streams) + (p.returncode,)
+
+
+def _config_git():
+ _run_cmd(
+ ['git', 'config', '--global', 'user.email', 'example@example.com'],
+ None)
+ _run_cmd(
+ ['git', 'config', '--global', 'user.name', 'OpenStack Developer'],
+ None)
+ _run_cmd(
+ ['git', 'config', '--global', 'user.signingkey',
+ 'example@example.com'], None)
diff --git a/pbr/tests/test_hooks.py b/pbr/tests/test_hooks.py
index b9e874a..e355408 100644
--- a/pbr/tests/test_hooks.py
+++ b/pbr/tests/test_hooks.py
@@ -41,7 +41,8 @@
import os
import textwrap
-from testtools.matchers import Contains
+from testtools.content import text_content
+from testtools.matchers import Contains, EndsWith
from pbr.tests import base
from pbr.tests import util
@@ -83,13 +84,14 @@ class TestHooks(base.BaseTestCase):
assert 'build_ext post-hook' not in stdout
assert return_code == 0
- stdout, _, return_code = self.run_setup('build_ext')
+ stdout, stderr, return_code = self.run_setup('build_ext')
+ self.addDetailUniqueName('stderr', text_content(stderr))
assert textwrap.dedent("""
running build_ext
running pre_hook pbr_testpackage._setup_hooks.test_pre_hook for command build_ext
build_ext pre-hook
""") in stdout # flake8: noqa
- assert stdout.endswith('build_ext post-hook')
+ self.expectThat(stdout, EndsWith('build_ext post-hook'))
assert return_code == 0
def test_custom_commands_known(self):
diff --git a/pbr/tests/test_integration.py b/pbr/tests/test_integration.py
new file mode 100644
index 0000000..dcc71df
--- /dev/null
+++ b/pbr/tests/test_integration.py
@@ -0,0 +1,165 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os.path
+import shlex
+import subprocess
+
+import fixtures
+import testscenarios
+import testtools
+from testtools import content
+import virtualenv
+
+from pbr.tests import base
+
+PIPFLAGS = shlex.split(os.environ.get('PIPFLAGS', ''))
+PIPVERSION = os.environ.get('PIPVERSION', 'pip')
+PBRVERSION = os.environ.get('PBRVERSION', 'pbr')
+REPODIR = os.environ.get('REPODIR', '')
+WHEELHOUSE = os.environ.get('WHEELHOUSE', '')
+PIP_CMD = ['-m', 'pip'] + PIPFLAGS + ['install', '-f', WHEELHOUSE]
+PROJECTS = shlex.split(os.environ.get('PROJECTS', ''))
+
+
+def all_projects():
+ if not REPODIR:
+ return
+ # Future: make this path parameterisable.
+ excludes = set(['pypi-mirror', 'jeepyb', 'tempest', 'requirements'])
+ for name in PROJECTS:
+ name = name.strip()
+ short_name = name.split('/')[-1]
+ try:
+ with open(os.path.join(
+ REPODIR, short_name, 'setup.py'), 'rt') as f:
+ if 'pbr' not in f.read():
+ continue
+ except IOError:
+ continue
+ if short_name in excludes:
+ continue
+ yield (short_name, dict(name=name, short_name=short_name))
+
+
+class CapturedSubprocess(fixtures.Fixture):
+ """Run a process and capture its output.
+
+ :attr stdout: The output (a string).
+ :attr stderr: The standard error (a string).
+ :attr returncode: The return code of the process.
+
+ Note that stdout and stderr are decoded from the bytestrings subprocess
+ returns using error=replace.
+ """
+
+ def __init__(self, label, *args, **kwargs):
+ """Create a CapturedSubprocess.
+
+ :param label: A label for the subprocess in the test log. E.g. 'foo'.
+ :param *args: The *args to pass to Popen.
+ :param **kwargs: The **kwargs to pass to Popen.
+ """
+ super(CapturedSubprocess, self).__init__()
+ self.label = label
+ self.args = args
+ self.kwargs = kwargs
+ self.kwargs['stderr'] = subprocess.PIPE
+ self.kwargs['stdin'] = subprocess.PIPE
+ self.kwargs['stdout'] = subprocess.PIPE
+
+ def setUp(self):
+ super(CapturedSubprocess, self).setUp()
+ proc = subprocess.Popen(*self.args, **self.kwargs)
+ out, err = proc.communicate()
+ self.out = out.decode('utf-8', 'replace')
+ self.err = err.decode('utf-8', 'replace')
+ self.addDetail(self.label + '-stdout', content.text_content(self.out))
+ self.addDetail(self.label + '-stderr', content.text_content(self.err))
+ self.returncode = proc.returncode
+ if proc.returncode:
+ raise AssertionError('Failed process %s' % proc.returncode)
+ self.addCleanup(delattr, self, 'out')
+ self.addCleanup(delattr, self, 'err')
+ self.addCleanup(delattr, self, 'returncode')
+
+
+class TestIntegration(base.BaseTestCase):
+
+ scenarios = list(all_projects())
+
+ def setUp(self):
+ # Integration tests need a higher default - big repos can be slow to
+ # clone, particularly under guest load.
+ os.environ['OS_TEST_TIMEOUT'] = os.environ.get('OS_TEST_TIMEOUT', 240)
+ super(TestIntegration, self).setUp()
+ base._config_git()
+
+ def venv(self, reason):
+ path = self.useFixture(fixtures.TempDir()).path
+ virtualenv.create_environment(path, clear=True)
+ python = os.path.join(path, 'bin', 'python')
+ self.useFixture(CapturedSubprocess(
+ 'mkvenv-' + reason, [python] + PIP_CMD + [
+ '-U', PIPVERSION, 'wheel', PBRVERSION]))
+ return path, python
+
+ @testtools.skipUnless(
+ os.environ.get('PBR_INTEGRATION', None) == '1',
+ 'integration tests not enabled')
+ def test_integration(self):
+ # Test that we can:
+ # - run sdist from the repo in a venv
+ # - install the resulting tarball in a new venv
+ # - pip install the repo
+ # - pip install -e the repo
+ # We don't break these into separate tests because we'd need separate
+ # source dirs to isolate from side effects of running pip, and the
+ # overheads of setup would start to beat the benefits of parallelism.
+ self.useFixture(CapturedSubprocess(
+ 'sync-req',
+ ['python', 'update.py', os.path.join(REPODIR, self.short_name)],
+ cwd=os.path.join(REPODIR, 'requirements')))
+ self.useFixture(CapturedSubprocess(
+ 'commit-requirements',
+ 'git diff --quiet || git commit -amrequirements',
+ cwd=os.path.join(REPODIR, self.short_name), shell=True))
+ path = os.path.join(
+ self.useFixture(fixtures.TempDir()).path, 'project')
+ self.useFixture(CapturedSubprocess(
+ 'clone',
+ ['git', 'clone', os.path.join(REPODIR, self.short_name), path]))
+ _, python = self.venv('sdist')
+ self.useFixture(CapturedSubprocess(
+ 'sdist', [python, 'setup.py', 'sdist'], cwd=path))
+ _, python = self.venv('tarball')
+ filename = os.path.join(
+ path, 'dist', os.listdir(os.path.join(path, 'dist'))[0])
+ self.useFixture(CapturedSubprocess(
+ 'tarball', [python] + PIP_CMD + [filename]))
+ root, python = self.venv('install-git')
+ self.useFixture(CapturedSubprocess(
+ 'install-git', [python] + PIP_CMD + ['git+file://' + path]))
+ if self.short_name == 'nova':
+ found = False
+ for _, _, filenames in os.walk(root):
+ if 'migrate.cfg' in filenames:
+ found = True
+ self.assertTrue(found)
+ _, python = self.venv('install-e')
+ self.useFixture(CapturedSubprocess(
+ 'install-e', [python] + PIP_CMD + ['-e', path]))
+
+
+def load_tests(loader, in_tests, pattern):
+ return testscenarios.load_tests_apply_scenarios(loader, in_tests, pattern)
diff --git a/pbr/tests/test_packaging.py b/pbr/tests/test_packaging.py
index d048a3c..741934e 100644
--- a/pbr/tests/test_packaging.py
+++ b/pbr/tests/test_packaging.py
@@ -40,7 +40,6 @@
import os
import re
-import sys
import tempfile
import fixtures
@@ -68,15 +67,7 @@ class TestRepo(fixtures.Fixture):
def setUp(self):
super(TestRepo, self).setUp()
base._run_cmd(['git', 'init', '.'], self._basedir)
- base._run_cmd(
- ['git', 'config', '--global', 'user.email', 'example@example.com'],
- self._basedir)
- base._run_cmd(
- ['git', 'config', '--global', 'user.name', 'OpenStack Developer'],
- self._basedir)
- base._run_cmd(
- ['git', 'config', '--global', 'user.signingkey',
- 'example@example.com'], self._basedir)
+ base._config_git()
base._run_cmd(['git', 'add', '.'], self._basedir)
def commit(self, message_content='test commit'):
@@ -167,6 +158,15 @@ class TestPackagingInGitRepoWithCommit(base.BaseTestCase):
# One commit, something should be in the ChangeLog list
self.assertNotEqual(body, '')
+ def test_manifest_exclude_honoured(self):
+ with open(os.path.join(
+ self.package_dir,
+ 'pbr_testpackage.egg-info/SOURCES.txt'), 'r') as f:
+ body = f.read()
+ self.assertThat(
+ body, matchers.Not(matchers.Contains('pbr_testpackage/extra.py')))
+ self.assertThat(body, matchers.Contains('pbr_testpackage/__init__.py'))
+
class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase):
@@ -234,32 +234,6 @@ class TestNestedRequirements(base.BaseTestCase):
self.assertEqual(result, ['pbr'])
-class TestGetRequirements(base.BaseTestCase):
-
- def test_get_requirements_file(self):
- platforms = ['Windows', 'Freebsd', 'Darwin', 'Linux']
- version = sys.version_info[0]
-
- with mock.patch('platform.system') as platform_system:
- platform_system.side_effect = platforms
-
- for platform in map(str.lower, platforms):
- expected = [
- 'requirements-{plat}-py{ver}.txt'.format(
- plat=platform, ver=version),
- 'tools/pip-requires-{plat}-py{ver}'.format(
- plat=platform, ver=version),
- 'requirements-{0}.txt'.format(platform),
- 'tools/pip-requires-{0}'.format(platform),
- 'requirements-py{0}.txt'.format(version),
- 'tools/pip-requires-py{0}'.format(version),
- 'requirements.txt',
- 'tools/pip-requires',
- ]
- files = packaging.get_requirements_files()
- self.assertEqual(expected, files)
-
-
class TestVersions(base.BaseTestCase):
scenarios = [
@@ -300,6 +274,13 @@ class TestVersions(base.BaseTestCase):
version = packaging._get_version_from_git()
self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
+ def test_untagged_pre_release_has_pre_dev_version_postversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
+
def test_untagged_version_minor_bump(self):
self.repo.commit()
self.repo.tag('1.2.3')
@@ -321,6 +302,20 @@ class TestVersions(base.BaseTestCase):
version = packaging._get_version_from_git('1.2.5')
self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
+ def test_untagged_version_after_pre_has_dev_version_preversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git('1.2.5')
+ self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
+
+ def test_untagged_version_after_rc_has_dev_version_preversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git('1.2.3')
+ self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
+
def test_preversion_too_low_simple(self):
# That is, the target version is either already released or not high
# enough for the semver requirements given api breaks etc.
@@ -403,7 +398,14 @@ class TestVersions(base.BaseTestCase):
self.repo.commit()
self.repo.tag('badver4')
version = packaging._get_version_from_git()
- self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
+ self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1'))
+ # Non-release related tags are ignored.
+ self.repo.commit()
+ self.repo.tag('2')
+ self.repo.commit()
+ self.repo.tag('non-release-tag/2014.12.16-1')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.1.dev1'))
def test_valid_tag_honoured(self):
# Fix for bug 1370608 - we converted any target into a 'dev version'
diff --git a/pbr/tests/test_version.py b/pbr/tests/test_version.py
index 73f384b..14c8d17 100644
--- a/pbr/tests/test_version.py
+++ b/pbr/tests/test_version.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import operator
+import itertools
from testtools import matchers
@@ -26,72 +26,41 @@ from_pip_string = version.SemanticVersion.from_pip_string
class TestSemanticVersion(base.BaseTestCase):
- def test_equality(self):
- base = version.SemanticVersion(1, 2, 3)
- base2 = version.SemanticVersion(1, 2, 3)
- major = version.SemanticVersion(2, 2, 3)
- minor = version.SemanticVersion(1, 3, 3)
- patch = version.SemanticVersion(1, 2, 4)
- pre_base = version.SemanticVersion(1, 2, 3, 'a', 4)
- pre_base2 = version.SemanticVersion(1, 2, 3, 'a', 4)
- pre_type = version.SemanticVersion(1, 2, 3, 'b', 4)
- pre_serial = version.SemanticVersion(1, 2, 3, 'a', 5)
- dev_base = version.SemanticVersion(1, 2, 3, dev_count=6)
- dev_base2 = version.SemanticVersion(1, 2, 3, dev_count=6)
- dev_count = version.SemanticVersion(1, 2, 3, dev_count=7)
- self.assertEqual(base, base2)
- self.assertNotEqual(base, major)
- self.assertNotEqual(base, minor)
- self.assertNotEqual(base, patch)
- self.assertNotEqual(base, pre_type)
- self.assertNotEqual(base, pre_serial)
- self.assertNotEqual(base, dev_count)
- self.assertEqual(pre_base, pre_base2)
- self.assertNotEqual(pre_base, pre_type)
- self.assertNotEqual(pre_base, pre_serial)
- self.assertNotEqual(pre_base, dev_count)
- self.assertEqual(dev_base, dev_base2)
- self.assertNotEqual(dev_base, dev_count)
- simple = version.SemanticVersion(1)
- explicit_minor = version.SemanticVersion(1, 0)
- explicit_patch = version.SemanticVersion(1, 0, 0)
- self.assertEqual(simple, explicit_minor)
- self.assertEqual(simple, explicit_patch)
- self.assertEqual(explicit_minor, explicit_patch)
-
def test_ordering(self):
- base = version.SemanticVersion(1, 2, 3)
- major = version.SemanticVersion(2, 2, 3)
- minor = version.SemanticVersion(1, 3, 3)
- patch = version.SemanticVersion(1, 2, 4)
- pre_alpha = version.SemanticVersion(1, 2, 3, 'a', 4)
- pre_beta = version.SemanticVersion(1, 2, 3, 'b', 3)
- pre_rc = version.SemanticVersion(1, 2, 3, 'rc', 2)
- pre_serial = version.SemanticVersion(1, 2, 3, 'a', 5)
- dev_base = version.SemanticVersion(1, 2, 3, dev_count=6)
- dev_count = version.SemanticVersion(1, 2, 3, dev_count=7)
- self.assertThat(base, matchers.LessThan(major))
- self.assertThat(major, matchers.GreaterThan(base))
- self.assertThat(base, matchers.LessThan(minor))
- self.assertThat(minor, matchers.GreaterThan(base))
- self.assertThat(base, matchers.LessThan(patch))
- self.assertThat(patch, matchers.GreaterThan(base))
- self.assertThat(pre_alpha, matchers.LessThan(base))
- self.assertThat(base, matchers.GreaterThan(pre_alpha))
- self.assertThat(pre_alpha, matchers.LessThan(pre_beta))
- self.assertThat(pre_beta, matchers.GreaterThan(pre_alpha))
- self.assertThat(pre_beta, matchers.LessThan(pre_rc))
- self.assertThat(pre_rc, matchers.GreaterThan(pre_beta))
- self.assertThat(pre_alpha, matchers.LessThan(pre_serial))
- self.assertThat(pre_serial, matchers.GreaterThan(pre_alpha))
- self.assertThat(pre_serial, matchers.LessThan(pre_beta))
- self.assertThat(pre_beta, matchers.GreaterThan(pre_serial))
- self.assertThat(dev_base, matchers.LessThan(base))
- self.assertThat(base, matchers.GreaterThan(dev_base))
- self.assertRaises(TypeError, operator.lt, pre_alpha, dev_base)
- self.assertRaises(TypeError, operator.lt, dev_base, pre_alpha)
- self.assertThat(dev_base, matchers.LessThan(dev_count))
- self.assertThat(dev_count, matchers.GreaterThan(dev_base))
+ ordered_versions = [
+ "1.2.3.dev6",
+ "1.2.3.dev7",
+ "1.2.3.a4.dev12",
+ "1.2.3.a4.dev13",
+ "1.2.3.a4",
+ "1.2.3.a5.dev1",
+ "1.2.3.a5",
+ "1.2.3.b3.dev1",
+ "1.2.3.b3",
+ "1.2.3.rc2.dev1",
+ "1.2.3.rc2",
+ "1.2.3.rc3.dev1",
+ "1.2.3",
+ "1.2.4",
+ "1.3.3",
+ "2.2.3",
+ ]
+ for v in ordered_versions:
+ sv = version.SemanticVersion.from_pip_string(v)
+ self.expectThat(sv, matchers.Equals(sv))
+ for left, right in itertools.combinations(ordered_versions, 2):
+ l_pos = ordered_versions.index(left)
+ r_pos = ordered_versions.index(right)
+ if l_pos < r_pos:
+ m1 = matchers.LessThan
+ m2 = matchers.GreaterThan
+ else:
+ m1 = matchers.GreaterThan
+ m2 = matchers.LessThan
+ left_sv = version.SemanticVersion.from_pip_string(left)
+ right_sv = version.SemanticVersion.from_pip_string(right)
+ self.expectThat(left_sv, m1(right_sv))
+ self.expectThat(right_sv, m2(left_sv))
def test_from_pip_string_legacy_alpha(self):
expected = version.SemanticVersion(
@@ -99,6 +68,22 @@ class TestSemanticVersion(base.BaseTestCase):
parsed = from_pip_string('1.2.0rc1')
self.assertEqual(expected, parsed)
+ def test_from_pip_string_legacy_postN(self):
+ # When pbr trunk was incompatible with PEP-440, a stable release was
+ # made that used postN versions to represent developer builds. As
+ # we expect only to be parsing versions of our own, we map those
+ # into dev builds of the next version.
+ expected = version.SemanticVersion(1, 2, 4, dev_count=5)
+ parsed = from_pip_string('1.2.3.post5')
+ self.expectThat(expected, matchers.Equals(parsed))
+ expected = version.SemanticVersion(1, 2, 3, 'a', 5, dev_count=6)
+ parsed = from_pip_string('1.2.3.0a4.post6')
+ self.expectThat(expected, matchers.Equals(parsed))
+ # We can't define a mapping for .postN.devM, so it should raise.
+ self.expectThat(
+ lambda: from_pip_string('1.2.3.post5.dev6'),
+ matchers.raises(ValueError))
+
def test_from_pip_string_legacy_nonzero_lead_in(self):
# reported in bug 1361251
expected = version.SemanticVersion(
@@ -130,6 +115,13 @@ class TestSemanticVersion(base.BaseTestCase):
parsed = from_pip_string('2014.2.b2')
self.assertEqual(expected, parsed)
+ def test_from_pip_string_pure_git_hash(self):
+ self.assertRaises(ValueError, from_pip_string, '6eed5ae')
+
+ def test_from_pip_string_non_digit_start(self):
+ self.assertRaises(ValueError, from_pip_string,
+ 'non-release-tag/2014.12.16-1')
+
def test_final_version(self):
semver = version.SemanticVersion(1, 2, 3)
self.assertEqual((1, 2, 3, 'final', 0), semver.version_tuple())
@@ -173,8 +165,13 @@ class TestSemanticVersion(base.BaseTestCase):
self.assertEqual(semver, from_pip_string("1.2.0.dev5"))
def test_alpha_dev_version(self):
- self.assertRaises(
- ValueError, version.SemanticVersion, 1, 2, 4, 'a', 1, '12')
+ semver = version.SemanticVersion(1, 2, 4, 'a', 1, 12)
+ self.assertEqual((1, 2, 4, 'alphadev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~a1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0a1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.a1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0a1.dev12"))
def test_alpha_version(self):
semver = version.SemanticVersion(1, 2, 4, 'a', 1)
@@ -213,8 +210,13 @@ class TestSemanticVersion(base.BaseTestCase):
self.assertEqual(semver, from_pip_string("1.2.4.0a0"))
def test_beta_dev_version(self):
- self.assertRaises(
- ValueError, version.SemanticVersion, 1, 2, 4, 'b', 5, '12')
+ semver = version.SemanticVersion(1, 2, 4, 'b', 1, 12)
+ self.assertEqual((1, 2, 4, 'betadev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~b1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0b1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.b1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0b1.dev12"))
def test_beta_version(self):
semver = version.SemanticVersion(1, 2, 4, 'b', 1)
@@ -241,13 +243,9 @@ class TestSemanticVersion(base.BaseTestCase):
def test_decrement_release(self):
# The next patch version of a release version requires a change to the
# patch level.
- semver = version.SemanticVersion(1, 2, 5)
- self.assertEqual(
- version.SemanticVersion(1, 2, 6), semver.increment())
+ semver = version.SemanticVersion(2, 2, 5)
self.assertEqual(
- version.SemanticVersion(1, 3, 0), semver.increment(minor=True))
- self.assertEqual(
- version.SemanticVersion(2, 0, 0), semver.increment(major=True))
+ version.SemanticVersion(2, 2, 4), semver.decrement())
def test_increment_nonrelease(self):
# The next patch version of a non-release version is another
@@ -274,8 +272,13 @@ class TestSemanticVersion(base.BaseTestCase):
version.SemanticVersion(2, 0, 0), semver.increment(major=True))
def test_rc_dev_version(self):
- self.assertRaises(
- ValueError, version.SemanticVersion, 1, 2, 4, 'rc', 1, '12')
+ semver = version.SemanticVersion(1, 2, 4, 'rc', 1, 12)
+ self.assertEqual((1, 2, 4, 'candidatedev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~rc1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0rc1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.rc1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0rc1.dev12"))
def test_rc_version(self):
semver = version.SemanticVersion(1, 2, 4, 'rc', 1)
@@ -291,14 +294,5 @@ class TestSemanticVersion(base.BaseTestCase):
version.SemanticVersion(1, 2, 3, dev_count=1),
version.SemanticVersion(1, 2, 3).to_dev(1))
self.assertEqual(
- version.SemanticVersion(1, 2, 3, dev_count=1),
+ version.SemanticVersion(1, 2, 3, 'rc', 1, dev_count=1),
version.SemanticVersion(1, 2, 3, 'rc', 1).to_dev(1))
-
- def test_to_release(self):
- self.assertEqual(
- version.SemanticVersion(1, 2, 3),
- version.SemanticVersion(
- 1, 2, 3, dev_count=1).to_release())
- self.assertEqual(
- version.SemanticVersion(1, 2, 3),
- version.SemanticVersion(1, 2, 3, 'rc', 1).to_release())
diff --git a/pbr/tests/testpackage/MANIFEST.in b/pbr/tests/testpackage/MANIFEST.in
index cdc95ea..2e35f3e 100644
--- a/pbr/tests/testpackage/MANIFEST.in
+++ b/pbr/tests/testpackage/MANIFEST.in
@@ -1 +1,2 @@
include data_files/*
+exclude pbr_testpackage/extra.py
diff --git a/pbr/tests/testpackage/pbr_testpackage/extra.py b/pbr/tests/testpackage/pbr_testpackage/extra.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pbr/tests/testpackage/pbr_testpackage/extra.py
diff --git a/pbr/tests/util.py b/pbr/tests/util.py
index de5a740..e847746 100644
--- a/pbr/tests/util.py
+++ b/pbr/tests/util.py
@@ -51,7 +51,7 @@ except ImportError:
@contextlib.contextmanager
def open_config(filename):
- cfg = configparser.ConfigParser()
+ cfg = configparser.SafeConfigParser()
cfg.read(filename)
yield cfg
with open(filename, 'w') as fp:
diff --git a/pbr/util.py b/pbr/util.py
index ad76366..63566eb 100644
--- a/pbr/util.py
+++ b/pbr/util.py
@@ -37,6 +37,11 @@
# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
"""The code in this module is mostly copy/pasted out of the distutils2 source
code, as recommended by Tarek Ziade. As such, it may be subject to some change
@@ -196,7 +201,7 @@ def cfg_to_args(path='setup.cfg'):
"""
# The method source code really starts here.
- parser = configparser.RawConfigParser()
+ parser = configparser.SafeConfigParser()
if not os.path.exists(path):
raise DistutilsFileError("file '%s' does not exist" %
os.path.abspath(path))
diff --git a/pbr/version.py b/pbr/version.py
index dac0966..d3fe401 100644
--- a/pbr/version.py
+++ b/pbr/version.py
@@ -20,6 +20,7 @@ Utilities for consuming the version from pkg_resources.
import itertools
import operator
+import sys
import pkg_resources
@@ -51,11 +52,6 @@ class SemanticVersion(object):
:param prerelease: For prerelease versions, what number prerelease.
Defaults to 0.
:param dev_count: How many commits since the last release.
-
- :raises: ValueError if both a prerelease version and dev_count is
- supplied. This is because semver (see the pbr semver documentation)
- does not permit both a prerelease version and a dev marker at the same
- time.
"""
self._major = major
self._minor = minor
@@ -64,11 +60,7 @@ class SemanticVersion(object):
self._prerelease = prerelease
if self._prerelease_type and not self._prerelease:
self._prerelease = 0
- self._dev_count = dev_count
- if prerelease_type is not None and dev_count is not None:
- raise ValueError(
- "invalid version: cannot have prerelease and dev strings %s %s"
- % (prerelease_type, dev_count))
+ self._dev_count = dev_count or 0 # Normalise 0 to None.
def __eq__(self, other):
if not isinstance(other, SemanticVersion):
@@ -78,6 +70,29 @@ class SemanticVersion(object):
def __hash__(self):
return sum(map(hash, self.__dict__.values()))
+ def _sort_key(self):
+ """Return a key for sorting SemanticVersion's on."""
+ # key things:
+ # - final is after rc's, so we make that a/b/rc/z
+ # - dev==None is after all other devs, so we use sys.maxsize there.
+ # - unqualified dev releases come before any pre-releases.
+ # So we do:
+ # (major, minor, patch) - gets the major grouping.
+ # (0|1) unqualified dev flag
+ # (a/b/rc/z) - release segment grouping
+ # pre-release level
+ # dev count, maxsize for releases.
+ rc_lookup = {'a': 'a', 'b': 'b', 'rc': 'rc', None: 'z'}
+ if self._dev_count and not self._prerelease_type:
+ uq_dev = 0
+ else:
+ uq_dev = 1
+ return (
+ self._major, self._minor, self._patch,
+ uq_dev,
+ rc_lookup[self._prerelease_type], self._prerelease,
+ self._dev_count or sys.maxsize)
+
def __lt__(self, other):
"""Compare self and other, another Semantic Version."""
# NB(lifeless) this could perhaps be rewritten as
@@ -86,39 +101,7 @@ class SemanticVersion(object):
# if this ever becomes performance sensitive.
if not isinstance(other, SemanticVersion):
raise TypeError("ordering to non-SemanticVersion is undefined")
- this_tuple = (self._major, self._minor, self._patch)
- other_tuple = (other._major, other._minor, other._patch)
- if this_tuple < other_tuple:
- return True
- elif this_tuple > other_tuple:
- return False
- if self._prerelease_type:
- if other._prerelease_type:
- # Use the a < b < rc cheat
- this_tuple = (self._prerelease_type, self._prerelease)
- other_tuple = (other._prerelease_type, other._prerelease)
- return this_tuple < other_tuple
- elif other._dev_count:
- raise TypeError(
- "ordering pre-release with dev builds is undefined")
- else:
- return True
- elif self._dev_count:
- if other._dev_count:
- if self._dev_count < other._dev_count:
- return True
- else:
- return False
- elif other._prerelease_type:
- raise TypeError(
- "ordering pre-release with dev builds is undefined")
- else:
- return True
- else:
- # This is not pre-release.
- # If the other is pre-release or dev, we are greater, which is ! <
- # If the other is not pre-release, we are equal, which is ! <
- return False
+ return self._sort_key() < other._sort_key()
def __le__(self, other):
return self == other or self < other
@@ -159,6 +142,9 @@ class SemanticVersion(object):
ever released - we're treating that as a critical bug that we ever
made them and have stopped doing that.
"""
+ # Versions need to start numerically, ignore if not
+ if not version_string[:1].isdigit():
+ raise ValueError("Invalid version %r" % version_string)
input_components = version_string.split('.')
# decimals first (keep pre-release and dev/hashes to the right)
components = [c for c in input_components if c.isdigit()]
@@ -181,6 +167,7 @@ class SemanticVersion(object):
major = int(components[0])
minor = int(components[1])
dev_count = None
+ post_count = None
prerelease_type = None
prerelease = None
@@ -215,17 +202,28 @@ class SemanticVersion(object):
# Current RC/beta layout
prerelease_type, prerelease = _parse_type(remainder[0])
remainder = remainder[1:]
- if remainder:
+ while remainder:
component = remainder[0]
if component.startswith('dev'):
dev_count = int(component[3:])
+ elif component.startswith('post'):
+ dev_count = None
+ post_count = int(component[4:])
else:
raise ValueError(
'Unknown remainder %r in %r'
% (remainder, version_string))
- return SemanticVersion(
+ remainder = remainder[1:]
+ result = SemanticVersion(
major, minor, patch, prerelease_type=prerelease_type,
prerelease=prerelease, dev_count=dev_count)
+ if post_count:
+ if dev_count:
+ raise ValueError(
+ 'Cannot combine postN and devN - no mapping in %r'
+ % (version_string,))
+ result = result.increment().to_dev(post_count)
+ return result
def brief_string(self):
"""Return the short version minus any alpha/beta tags."""
@@ -239,7 +237,7 @@ class SemanticVersion(object):
"""
return self._long_version("~")
- def decrement(self, minor=False, major=False):
+ def decrement(self):
"""Return a decremented SemanticVersion.
Decrementing versions doesn't make a lot of sense - this method only
@@ -329,7 +327,10 @@ class SemanticVersion(object):
"%s%s%s%s" % (pre_separator, rc_marker, self._prerelease_type,
self._prerelease))
if self._dev_count:
- segments.append(pre_separator)
+ if not self._prerelease_type:
+ segments.append(pre_separator)
+ else:
+ segments.append('.')
segments.append('dev')
segments.append(self._dev_count)
return "".join(str(s) for s in segments)
@@ -357,15 +358,8 @@ class SemanticVersion(object):
:param dev_count: The number of commits since the last release.
"""
return SemanticVersion(
- self._major, self._minor, self._patch, dev_count=dev_count)
-
- def to_release(self):
- """Discard any pre-release or dev metadata.
-
- :return: A new SemanticVersion with major/minor/patch the same as this
- one.
- """
- return SemanticVersion(self._major, self._minor, self._patch)
+ self._major, self._minor, self._patch, self._prerelease_type,
+ self._prerelease, dev_count=dev_count)
def version_tuple(self):
"""Present the version as a version_info tuple.
@@ -374,7 +368,10 @@ class SemanticVersion(object):
documentation for sys.version_info.
Since semver and PEP-440 represent overlapping but not subsets of
- versions, we have to have some heuristic / mapping rules:
+ versions, we have to have some heuristic / mapping rules, and have
+ extended the releaselevel field to have alphadev, betadev and
+ candidatedev values. When they are present the dev count is used
+ to provide the serial.
- a/b/rc take precedence.
- if there is no pre-release version the dev version is used.
- serial is taken from the dev/a/b/c component.
@@ -382,12 +379,16 @@ class SemanticVersion(object):
"""
segments = [self._major, self._minor, self._patch]
if self._prerelease_type:
- type_map = {'a': 'alpha',
- 'b': 'beta',
- 'rc': 'candidate',
+ type_map = {('a', False): 'alpha',
+ ('b', False): 'beta',
+ ('rc', False): 'candidate',
+ ('a', True): 'alphadev',
+ ('b', True): 'betadev',
+ ('rc', True): 'candidatedev',
}
- segments.append(type_map[self._prerelease_type])
- segments.append(self._prerelease)
+ segments.append(
+ type_map[(self._prerelease_type, bool(self._dev_count))])
+ segments.append(self._dev_count or self._prerelease)
elif self._dev_count:
segments.append('dev')
segments.append(self._dev_count - 1)
diff --git a/test-requirements.txt b/test-requirements.txt
index 75ad928..5f8cfb8 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -9,3 +9,4 @@ testrepository>=0.0.18
testresources>=0.2.4
testscenarios>=0.4
testtools>=0.9.34
+virtualenv
diff --git a/tools/integration.sh b/tools/integration.sh
index a385203..e58267e 100644
--- a/tools/integration.sh
+++ b/tools/integration.sh
@@ -1,15 +1,24 @@
#!/bin/bash -xe
+# Parameters:
+# PBR_PIP_VERSION :- if not set, run pip's latest release, if set must be a
+# valid reference file entry describing what pip to use.
+# WHEELHOUSE :- if not set, use a temporary wheelhouse, set to a specific path
+# to use an existing one.
+# PIPFLAGS :- may be set to any pip global option for e.g. debugging.
+# Bootstrappping the mkenv needs to install *a* pip
+export PIPVERSION=pip
+PIPFLAGS=${PIPFLAGS:-}
function mkvenv {
venv=$1
rm -rf $venv
virtualenv $venv
- $venv/bin/pip install -U pip wheel
+ $venv/bin/pip install $PIPFLAGS -U $PIPVERSION wheel
# If a change to PBR is being tested, preinstall the wheel for it
if [ -n "$PBR_CHANGE" ] ; then
- $venv/bin/pip install $pbrsdistdir/dist/pbr-*.whl
+ $venv/bin/pip install $PIPFLAGS $pbrsdistdir/dist/pbr-*.whl
fi
}
@@ -20,6 +29,7 @@ BASE=${BASE:-/opt/stack}
REPODIR=${REPODIR:-$BASE/new}
# TODO: Figure out how to get this on to the box properly
+sudo apt-get update
sudo apt-get install -y --force-yes libvirt-dev libxml2-dev libxslt-dev libmysqlclient-dev libpq-dev libnspr4-dev pkg-config libsqlite3-dev libzmq-dev libffi-dev libldap2-dev libsasl2-dev ccache
# FOR numpy / pyyaml
@@ -33,16 +43,25 @@ tmpdir=$(mktemp -d)
# Set up a wheelhouse
export WHEELHOUSE=${WHEELHOUSE:-$tmpdir/.wheelhouse}
-export PIP_WHEEL_DIR=${PIP_WHEEL_DIR:-$WHEELHOUSE}
-export PIP_FIND_LINKS=${PIP_FIND_LINKS:-file://$WHEELHOUSE}
mkvenv $tmpdir/wheelhouse
+# Specific PIP version - must succeed to be useful.
+# - build/download a local wheel so we don't hit the network on each venv.
+if [ -n "${PBR_PIP_VERSION:-}" ]; then
+ td=$(mktemp -d)
+ $tmpdir/wheelhouse/bin/pip wheel -w $td $PBR_PIP_VERSION
+ # This version will now be installed in every new venv.
+ export PIPVERSION="$td/$(ls $td)"
+ $tmpdir/wheelhouse/bin/pip install -U $PIPVERSION
+ # We have pip in global-requirements as open-ended requirements,
+ # but since we don't use -U in any other invocations, our version
+ # of pip should be sticky.
+fi
+# Build wheels for everything so we don't hit the network on each venv.
# Not all packages properly build wheels (httpretty for example).
# Do our best but ignore errors when making wheels.
set +e
-grep -v '^#' $REPODIR/requirements/global-requirements.txt | while read req
-do
- $tmpdir/wheelhouse/bin/pip wheel "$req"
-done
+$tmpdir/wheelhouse/bin/pip $PIPFLAGS wheel -w $WHEELHOUSE -f $WHEELHOUSE -r \
+ $REPODIR/requirements/global-requirements.txt
set -e
#BRANCH
@@ -113,7 +132,7 @@ mkvenv $epvenv
eppbrdir=$tmpdir/eppbrdir
git clone $REPODIR/pbr $eppbrdir
-$epvenv/bin/pip install -e $eppbrdir
+$epvenv/bin/pip $PIPFLAGS install -f $WHEELHOUSE -e $eppbrdir
# First check develop
PBR_VERSION=0.0 $epvenv/bin/python setup.py develop
@@ -129,73 +148,19 @@ $epvenv/bin/test_cmd | grep 'Test cmd'
projectdir=$tmpdir/projects
mkdir -p $projectdir
-
-for PROJECT in $PROJECTS ; do
- SHORT_PROJECT=$(basename $PROJECT)
- if ! grep 'pbr' $REPODIR/$SHORT_PROJECT/setup.py >/dev/null 2>&1
- then
- # project doesn't use pbr
- continue
- fi
- if [ $SHORT_PROJECT = 'pypi-mirror' ]; then
- # pypi-mirror doesn't consume the mirror
- continue
- fi
- if [ $SHORT_PROJECT = 'jeepyb' ]; then
- # pypi-mirror doesn't consume the mirror
- continue
- fi
- if [ $SHORT_PROJECT = 'tempest' ]; then
- # Tempest doesn't really install
- continue
- fi
- if [ $SHORT_PROJECT = 'requirements' ]; then
- # requirements doesn't really install
- continue
- fi
-
- # set up the project synced with the global requirements
- sudo chown -R $USER $REPODIR/$SHORT_PROJECT
- (cd $REPODIR/requirements && python update.py $REPODIR/$SHORT_PROJECT)
- pushd $REPODIR/$SHORT_PROJECT
- if ! git diff --quiet ; then
- git commit -a -m'Update requirements'
- fi
- popd
-
- # Clone from synced repo
- shortprojectdir=$projectdir/$SHORT_PROJECT
- git clone $REPODIR/$SHORT_PROJECT $shortprojectdir
-
- # Test that we can make a tarball from scratch
- sdistvenv=$tmpdir/sdist
- mkvenv $sdistvenv
- cd $shortprojectdir
- $sdistvenv/bin/python setup.py sdist
-
- cd $tmpdir
-
- # Test that the tarball installs
- tarballvenv=$tmpdir/tarball
- mkvenv $tarballvenv
- $tarballvenv/bin/pip install $shortprojectdir/dist/*tar.gz
-
- # Test pip installing
- pipvenv=$tmpdir/pip
- mkvenv $pipvenv
- $pipvenv/bin/pip install git+file://$shortprojectdir
-
- # Test python setup.py install
- installvenv=$tmpdir/install
- mkvenv $installvenv
-
- installprojectdir=$projectdir/install$SHORT_PROJECT
- git clone $shortprojectdir $installprojectdir
- cd $installprojectdir
- $installvenv/bin/python setup.py install
-
- # Ensure the install_package_data is doing the thing it should do
- if [ $SHORT_PROJECT = 'nova' ]; then
- find $installvenv | grep migrate.cfg
- fi
-done
+sudo chown -R $USER $REPODIR
+
+export PBR_INTEGRATION=1
+export PIPFLAGS
+export PIPVERSION
+PBRVERSION=pbr
+if [ -n "$PBR_CHANGE" ] ; then
+ PBRVERSION=$(ls $pbrsdistdir/dist/pbr-*.whl)
+fi
+export PBRVERSION
+export PROJECTS
+export REPODIR
+export WHEELHOUSE
+export OS_TEST_TIMEOUT=240
+cd $REPODIR/pbr
+tox -epy27 -- test_integration