From e2228c90d3fcee09543e6155f3cca31003a188a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 13 Feb 2014 01:34:01 +0100 Subject: Upgrade to semver-2.0.0 (Closes #3) --- README.rst | 2 +- semantic_version/base.py | 24 +++++++ tests/test_base.py | 4 +- tests/test_spec.py | 163 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 tests/test_spec.py 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 + + -- cgit v1.2.1