summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaphaël Barrois <raphael.barrois@polytechnique.org>2014-02-13 01:34:01 +0100
committerRaphaël Barrois <raphael.barrois@polytechnique.org>2014-02-13 01:34:08 +0100
commite2228c90d3fcee09543e6155f3cca31003a188a9 (patch)
tree389904ee00463ca3b736936afa7d9dc0b2527c3f
parentd742b34053f9ed1eb1aafbd6e07db2f65520a527 (diff)
downloadsemantic-version-e2228c90d3fcee09543e6155f3cca31003a188a9.tar.gz
Upgrade to semver-2.0.0 (Closes #3)
-rw-r--r--README.rst2
-rw-r--r--semantic_version/base.py24
-rwxr-xr-xtests/test_base.py4
-rw-r--r--tests/test_spec.py163
4 files changed, 190 insertions, 3 deletions
diff --git a/README.rst b/README.rst
index 79438e7..8e82b90 100644
--- a/README.rst
+++ b/README.rst
@@ -7,7 +7,7 @@ python-semanticversion
======================
This small python library provides a few tools to handle `SemVer`_ in Python.
-It follows strictly the 2.0.0-rc1 version of the SemVer scheme.
+It follows strictly the 2.0.0 version of the SemVer scheme.
.. image:: https://secure.travis-ci.org/rbarrois/python-semanticversion.png?branch=master
:target: http://travis-ci.org/rbarrois/python-semanticversion/
diff --git a/semantic_version/base.py b/semantic_version/base.py
index f5153b2..e46857e 100644
--- a/semantic_version/base.py
+++ b/semantic_version/base.py
@@ -16,6 +16,12 @@ def _to_int(value):
except ValueError:
return value, False
+def _has_leading_zero(value):
+ return (value
+ and value[0] == '0'
+ and value.isdigit()
+ and value != '0')
+
def identifier_cmp(a, b):
"""Compare two identifier (for pre-release/build components)."""
@@ -176,6 +182,13 @@ class Version(object):
major, minor, patch, prerelease, build = match.groups()
+ if _has_leading_zero(major):
+ raise ValueError("Invalid leading zero in major: %r" % version_string)
+ if _has_leading_zero(minor):
+ raise ValueError("Invalid leading zero in minor: %r" % version_string)
+ if _has_leading_zero(patch):
+ raise ValueError("Invalid leading zero in patch: %r" % version_string)
+
major = int(major)
minor = cls._coerce(minor, partial)
patch = cls._coerce(patch, partial)
@@ -190,6 +203,7 @@ class Version(object):
prerelease = ()
else:
prerelease = tuple(prerelease.split('.'))
+ cls._validate_identifiers(prerelease, allow_leading_zeroes=False)
if build is None:
if partial:
@@ -200,9 +214,19 @@ class Version(object):
build = ()
else:
build = tuple(build.split('.'))
+ cls._validate_identifiers(build, allow_leading_zeroes=True)
return (major, minor, patch, prerelease, build)
+ @classmethod
+ def _validate_identifiers(cls, identifiers, allow_leading_zeroes=False):
+ for item in identifiers:
+ if not item:
+ raise ValueError("Invalid empty identifier %r in %r"
+ % (item, '.'.join(identifiers)))
+ if item[0] == '0' and item.isdigit() and item != '0' and not allow_leading_zeroes:
+ raise ValueError("Invalid leading zero in identifier %r" % item)
+
def __iter__(self):
return iter((self.major, self.minor, self.patch, self.prerelease, self.build))
diff --git a/tests/test_base.py b/tests/test_base.py
index 3006ba0..0016776 100755
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -358,8 +358,8 @@ class SpecItemTestCase(unittest.TestCase):
class CoerceTestCase(unittest.TestCase):
examples = {
# Dict of target: [list of equivalents]
- '0.0.0': ('0', '0.0', '0.0.0', '0.0.0+', '0-', '00000000.00'),
- '0.1.0': ('0.1', '0.1+', '0.1-', '0.1.0', '0.000001.000000000000'),
+ '0.0.0': ('0', '0.0', '0.0.0', '0.0.0+', '0-'),
+ '0.1.0': ('0.1', '0.1+', '0.1-', '0.1.0'),
'0.1.0+2': ('0.1.0+2', '0.1.0.2'),
'0.1.0+2.3.4': ('0.1.0+2.3.4', '0.1.0+2+3+4', '0.1.0.2+3+4'),
'0.1.0+2-3.4': ('0.1.0+2-3.4', '0.1.0+2-3+4', '0.1.0.2-3+4', '0.1.0.2_3+4'),
diff --git a/tests/test_spec.py b/tests/test_spec.py
new file mode 100644
index 0000000..decc2c4
--- /dev/null
+++ b/tests/test_spec.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (c) 2012-2013 Raphaël Barrois
+# This code is distributed under the two-clause BSD License.
+
+"""Test conformance to the specs."""
+
+from .compat import unittest, is_python2
+
+import semantic_version
+
+# shortcut
+Version = semantic_version.Version
+
+
+class FormatTests(unittest.TestCase):
+ """Tests proper version validation."""
+
+ def test_major_minor_patch(self):
+ ### SPEC:
+ # A normal version number MUST take the form X.Y.Z
+
+ with self.assertRaises(ValueError):
+ Version('1')
+ with self.assertRaises(ValueError):
+ Version('1.1')
+ # Doesn't raise
+ Version('1.2.3')
+ with self.assertRaises(ValueError):
+ Version('1.2.3.4')
+
+ ### SPEC:
+ # Where X, Y, and Z are non-negative integers,
+
+ with self.assertRaises(ValueError):
+ Version('1.2.A')
+ with self.assertRaises(ValueError):
+ Version('1.-2.3')
+ # Valid
+ v = Version('1.2.3')
+ self.assertEqual(1, v.major)
+ self.assertEqual(2, v.minor)
+ self.assertEqual(3, v.patch)
+
+
+ ### Spec:
+ # And MUST NOT contain leading zeroes
+ with self.assertRaises(ValueError):
+ Version('1.2.01')
+ with self.assertRaises(ValueError):
+ Version('1.02.1')
+ with self.assertRaises(ValueError):
+ Version('01.2.1')
+ # Valid
+ v = Version('0.0.0')
+ self.assertEqual(0, v.major)
+ self.assertEqual(0, v.minor)
+ self.assertEqual(0, v.patch)
+
+ def test_prerelease(self):
+ ### SPEC:
+ # A pre-release version MAY be denoted by appending a hyphen and a
+ # series of dot separated identifiers immediately following the patch
+ # version.
+
+ with self.assertRaises(ValueError):
+ Version('1.2.3 -23')
+ # Valid
+ v = Version('1.2.3-23')
+ self.assertEqual(('23',), v.prerelease)
+
+ ### SPEC:
+ # Identifiers MUST comprise only ASCII alphanumerics and hyphen.
+ # Identifiers MUST NOT be empty
+ with self.assertRaises(ValueError):
+ Version('1.2.3-a,')
+ with self.assertRaises(ValueError):
+ Version('1.2.3-..')
+
+ ### SPEC:
+ # Numeric identifiers MUST NOT include leading zeroes.
+
+ with self.assertRaises(ValueError):
+ Version('1.2.3-a0.01')
+ with self.assertRaises(ValueError):
+ Version('1.2.3-00')
+ # Valid
+ v = Version('1.2.3-0a.0.000zz')
+ self.assertEqual(('0a', '0', '000zz'), v.prerelease)
+
+ def test_build(self):
+ ### SPEC:
+ # Build metadata MAY be denoted by appending a plus sign and a series of
+ # dot separated identifiers immediately following the patch or
+ # pre-release version
+
+ v = Version('1.2.3')
+ self.assertEqual((), v.build)
+ with self.assertRaises(ValueError):
+ Version('1.2.3 +4')
+
+ ### SPEC:
+ # Identifiers MUST comprise only ASCII alphanumerics and hyphen.
+ # Identifiers MUST NOT be empty
+ with self.assertRaises(ValueError):
+ Version('1.2.3+a,')
+ with self.assertRaises(ValueError):
+ Version('1.2.3+..')
+
+ # Leading zeroes allowed
+ v = Version('1.2.3+0.0a.01')
+ self.assertEqual(('0', '0a', '01'), v.build)
+
+ def test_precedence(self):
+ ### SPEC:
+ # Precedence is determined by the first difference when comparing 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
+ self.assertLess(Version('1.0.0'), Version('2.0.0'))
+ self.assertLess(Version('2.0.0'), Version('2.1.0'))
+ self.assertLess(Version('2.1.0'), Version('2.1.1'))
+
+ ### SPEC:
+ # When major, minor, and patch are equal, a pre-release version has
+ # lower precedence than a normal version.
+ # Example: 1.0.0-alpha < 1.0.0
+ self.assertLess(Version('1.0.0-alpha'), Version('1.0.0'))
+
+ ### SPEC:
+ # Precedence for two pre-release versions with the same major, minor,
+ # and patch version MUST be determined by comparing each dot separated
+ # identifier from left to right until a difference is found as follows:
+ # identifiers consisting of only digits are compared numerically
+ self.assertLess(Version('1.0.0-1'), Version('1.0.0-2'))
+
+ # and identifiers with letters or hyphens are compared lexically in
+ # ASCII sort order.
+ self.assertLess(Version('1.0.0-aa'), Version('1.0.0-ab'))
+
+ # Numeric identifiers always have lower precedence than
+ # non-numeric identifiers.
+ self.assertLess(Version('1.0.0-9'), Version('1.0.0-a'))
+
+ # A larger set of pre-release fields has a higher precedence than a
+ # smaller set, if all of the preceding identifiers are equal.
+ self.assertLess(Version('1.0.0-a.b.c'), Version('1.0.0-a.b.c.0'))
+
+ # Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
+ self.assertLess(Version('1.0.0-alpha'), Version('1.0.0-alpha.1'))
+ self.assertLess(Version('1.0.0-alpha.1'), Version('1.0.0-alpha.beta'))
+ self.assertLess(Version('1.0.0-alpha.beta'), Version('1.0.0-beta'))
+ self.assertLess(Version('1.0.0-beta'), Version('1.0.0-beta.2'))
+ self.assertLess(Version('1.0.0-beta.2'), Version('1.0.0-beta.11'))
+ self.assertLess(Version('1.0.0-beta.11'), Version('1.0.0-rc.1'))
+ self.assertLess(Version('1.0.0-rc.1'), Version('1.0.0'))
+
+
+
+class PrecedenceTestCase(unittest.TestCase):
+ pass
+
+