Repository: rbarrois/python-semanticversion

Issue:
Version bump from prereleases not working as expected
I was expecting that a prerelease version, when bumped, would go up to the 'full' or release version without bumping the main version number if not needed.

Probably makes more sense if I give examples.

What I expected:

```
1.0.0-dev --[major]--> 1.0.0
1.0.1-dev --[major]--> 2.0.0
1.1.0-dev --[major]--> 2.0.0

1.0.0-dev --[minor]--> 1.0.0
1.0.1-dev --[minor]--> 1.1.0
1.1.0-dev --[minor]--> 1.1.0

1.0.0-dev --[patch]--> 1.0.0
1.0.1-dev --[patch]--> 1.0.1
1.1.0-dev --[patch]--> 1.1.0
```

What currently happens:
```
1.0.0-dev --[major]--> 2.0.0
1.0.1-dev --[major]--> 2.0.0
1.1.0-dev --[major]--> 2.0.0

1.0.0-dev --[minor]--> 1.1.0
1.0.1-dev --[minor]--> 1.1.0
1.1.0-dev --[minor]--> 1.2.0

1.0.0-dev --[patch]--> 1.0.1
1.0.1-dev --[patch]--> 1.0.2
1.1.0-dev --[patch]--> 1.1.1
```

If this a bug in the implementation, or have I missed something in the spec? Thanks!



Discussion:

Patch:
diff --git a/CREDITS b/CREDITS
index c700c77..1506f2a 100644
--- a/CREDITS
+++ b/CREDITS
@@ -18,8 +18,9 @@ Contributors
 The project has received contributions from (in alphabetical order):
 
 * Raphaël Barrois <raphael.barrois+semver@polytechnique.org> (https://github.com/rbarrois)
-* Michael Hrivnak <mhrivnak@hrivnak.org> (https://github.com/mhrivnak)
 * Rick Eyre <rick.eyre@outlook.com> (https://github.com/rickeyre)
+* Michael Hrivnak <mhrivnak@hrivnak.org> (https://github.com/mhrivnak)
+* William Minchin <w_minchin@hotmail.com> (https://github.com/minchinweb)
 
 
 Contributor license agreement
diff --git a/ChangeLog b/ChangeLog
index cd7c3db..7b233fe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,35 +1,12 @@
 ChangeLog
 =========
 
-2.5.0 (master)
---------------
+2.4.3 (unreleased)
+------------------
 
 *Bugfix:*
 
-    `#18 <https://github.com/rbarrois/python-semanticversion/issues/18>`_: According to SemVer 2.0.0, build numbers aren't ordered.
-
-    * Remove specs of the ``Spec('<1.1.3+')`` form
-    * Comparing ``Version('0.1.0')`` to ``Version('0.1.0+bcd')`` has new
-      rules::
-
-          >>> Version('0.1.0+1') == Version('0.1.0+bcd')
-          False
-          >>> Version('0.1.0+1') != Version('0.1.0+bcd')
-          True
-          >>> Version('0.1.0+1') < Version('0.1.0+bcd')
-          False
-          >>> Version('0.1.0+1') > Version('0.1.0+bcd')
-          False
-          >>> Version('0.1.0+1') <= Version('0.1.0+bcd')
-          False
-          >>> Version('0.1.0+1') >= Version('0.1.0+bcd')
-          False
-          >>> compare(Version('0.1.0+1'), Version('0.1.0+bcd'))
-          NotImplemented
-
-    * :func:`semantic_version.compare` returns ``NotImplemented`` when its
-      parameters differ only by build metadata
-    * ``Spec('<=1.3.0')`` now matches ``Version('1.3.0+abde24fe883')``
+    * Fix handling of bumping pre-release versions, thanks to @minchinweb.
 
 2.4.2 (2015-07-02)
 ------------------
diff --git a/README.rst b/README.rst
index 484692e..5923a30 100644
--- a/README.rst
+++ b/README.rst
@@ -247,18 +247,19 @@ definition or (for the empty pre-release number) if a single dash is appended
     False
 
 
-Including build metadata in specifications
-""""""""""""""""""""""""""""""""""""""""""
+Including build identifiers in specifications
+"""""""""""""""""""""""""""""""""""""""""""""
 
-Build metadata has no ordering; thus, the only meaningful comparison including
-build metadata is equality.
+The same rule applies for the build identifier: comparisons will include it only
+if it was included in the :class:`Spec` definition, or - for the unnumbered build
+version - if a single + is appended to the definition(``1.0.0+``, ``1.0.0-alpha+``):
 
 
 .. code-block:: pycon
 
-    >>> Version('1.0.0+build2') in Spec('<=1.0.0')   # Build metadata ignored
+    >>> Version('1.0.0+build2') in Spec('<=1.0.0')   # Build identifier ignored
     True
-    >>> Version('1.0.0+build2') in Spec('==1.0.0+build2')  # Include build in checks
+    >>> Version('1.0.0+build2') in Spec('<=1.0.0+')  # Include build in checks
     False
 
 
diff --git a/docs/reference.rst b/docs/reference.rst
index 261e738..3550c25 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -22,13 +22,9 @@ Module-level functions
     :param str v1: The first version to compare
     :param str v2: The second version to compare
     :raises: :exc:`ValueError`, if any version string is invalid
-    :rtype: ``int``, -1 / 0 / 1 as for a :func:`cmp` comparison;
-            ``NotImplemented`` if versions only differ by build metadata
+    :rtype: ``int``, -1 / 0 / 1 as for a :func:`cmp` comparison
 
 
-.. warning:: Since build metadata has no ordering,
-             ``compare(Version('0.1.1'), Version('0.1.1+3'))`` returns ``NotImplemented``
-
 
 .. function:: match(spec, version)
 
@@ -111,9 +107,9 @@ Representing a version (the Version class)
 
     .. attribute:: build
 
-        ``tuple`` of ``strings``, the build metadata.
+        ``tuple`` of ``strings``, the build component.
 
-        It contains the various dot-separated identifiers in the build metadata.
+        It contains the various dot-separated identifiers in the build component.
 
         May be ``None`` for a :attr:`partial` version number in a ``<major>``, ``<major>.<minor>``,
         ``<major>.<minor>.<patch>`` or ``<major>.<minor>.<patch>-<prerelease>`` format.
@@ -155,7 +151,7 @@ Representing a version (the Version class)
             For instance, ``Version('1.0', partial=True)`` means "any version beginning in ``1.0``".
 
             ``Version('1.0.1-alpha', partial=True)`` means "The ``1.0.1-alpha`` version or any
-            any release differing only in build metadata": ``1.0.1-alpha+build3`` matches, ``1.0.1-alpha.2`` doesn't.
+            ulterior build of that same version": ``1.0.1-alpha+build3`` matches, ``1.0.1-alpha.2`` doesn't.
 
         Examples::
 
@@ -250,6 +246,7 @@ The main issue with representing version specifications is that the usual syntax
 does not map well onto `SemVer`_ precedence rules:
 
 * A specification of ``<1.3.4`` is not expected to allow ``1.3.4-rc2``, but strict `SemVer`_ comparisons allow it ;
+* Converting the previous specification to ``<=1.3.3`` in order to avoid ``1.3.4``
   prereleases has the issue of excluding ``1.3.3+build3`` ;
 * It may be necessary to exclude either all variations on a patch-level release
   (``!=1.3.3``) or specifically one build-level release (``1.3.3-build.434``).
@@ -259,7 +256,7 @@ In order to have version specification behave naturally, the rules are the follo
 
 * If no pre-release number was included in the specification, pre-release numbers
   are ignored when deciding whether a version satisfies a specification.
-* If no build metadata was included in the specification, build metadata is ignored
+* If no build number was included in the specification, build numbers are ignored
   when deciding whether a version satisfies a specification.
 
 This means that::
@@ -270,7 +267,7 @@ This means that::
     True
     >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1')
     True
-    >>> Version('1.1.1-rc1+build4') in Spec('==1.1.1-rc1+build2')
+    >>> Version('1.1.1-rc1+build4') in Spec('<=1.1.1-rc1+build2')
     False
 
 
@@ -288,31 +285,20 @@ rules apply:
     >>> Version('1.1.1-rc1') in Spec('<1.1.1-')
     True
 
-* Setting a build metadata separator without build metadata (``<=1.1.1+``)
-  forces matches "up to the build metadata"; use this to include/exclude a
-  release lacking build metadata while excluding/including all other builds
-  of that release
+* Setting a build separator without a build identifier (``>1.1.1+``) forces
+  satisfaction tests to include both prerelease and build identifiers::
 
-    >>> Version('1.1.1') in Spec('==1.1.1+')
-    True
-    >>> Version('1.1.1+2') in Spec('==1.1.1+')
+    >>> Version('1.1.1+build2') in Spec('>1.1.1')
     False
-
-
-.. warning:: As stated in the `SemVer`_ specification, the ordering of build metadata is *undefined*.
-             Thus, a :class:`Spec` string can only mention build metadata to include or exclude a specific version:
-
-             * ``==1.1.1+b1234`` includes this specific build
-             * ``!=1.1.1+b1234`` excludes it (but would match ``1.1.1+b1235``
-             * ``<1.1.1+b1`` is invalid
-
+    >>> Version('1.1.1+build2') in Spec('>1.1.1+')
+    True
 
 .. class:: Spec(spec_string[, spec_string[, ...]])
 
     Stores a list of :class:`SpecItem` and matches any :class:`Version` against all
     contained :class:`specs <SpecItem>`.
 
-    It is built from a comma-separated list of version specifications::
+    It is build from a comma-separated list of version specifications::
 
         >>> Spec('>=1.0.0,<1.2.0,!=1.1.4')
         <Spec: (
@@ -441,16 +427,16 @@ rules apply:
 
         >>> SpecItem('>=0.1.1').match(Version('0.1.1-rc1'))  # pre-release satisfy conditions
         True
-        >>> Version('0.1.1+build2') in SpecItem('>=0.1.1')   # build metadata is ignored when checking for precedence
+        >>> Version('0.1.1+build2') in SpecItem('>=0.1.1')   # build version satisfy specifications
         True
         >>>
         >>> # Use the '-' marker to include the pre-release component in checks
         >>> SpecItem('>=0.1.1-').match(Version('0.1.1-rc1')
         False
-        >>> # Use the '+' marker to include the build metadata in checks
-        >>> SpecItem('==0.1.1+').match(Version('0.1.1+b1234')
-        False
         >>>
+        >>> # Use the '+' marker to include the build identifier in checks
+        >>> SpecItem('<=0.1.1-alpha+').match(Version('0.1.1-alpha+build1'))
+        False
 
 
     .. rubric:: Attributes
diff --git a/semantic_version/base.py b/semantic_version/base.py
index 982fcc8..4d9b87e 100644
--- a/semantic_version/base.py
+++ b/semantic_version/base.py
@@ -89,15 +89,24 @@ class Version(object):
         return int(value)
 
     def next_major(self):
-        return Version('.'.join(str(x) for x in [self.major + 1, 0, 0]))
+        if self.prerelease and self.minor is 0 and self.patch is 0:
+            return Version('.'.join(str(x) for x in [self.major, self.minor, self.patch]))
+        else:
+            return Version('.'.join(str(x) for x in [self.major + 1, 0, 0]))
 
     def next_minor(self):
-        return Version(
-            '.'.join(str(x) for x in [self.major, self.minor + 1, 0]))
+        if self.prerelease and self.patch is 0:
+            return Version('.'.join(str(x) for x in [self.major, self.minor, self.patch]))
+        else:
+            return Version(
+                '.'.join(str(x) for x in [self.major, self.minor + 1, 0]))
 
     def next_patch(self):
-        return Version(
-            '.'.join(str(x) for x in [self.major, self.minor, self.patch + 1]))
+        if self.prerelease:
+            return Version('.'.join(str(x) for x in [self.major, self.minor, self.patch]))
+        else:
+            return Version(
+                '.'.join(str(x) for x in [self.major, self.minor, self.patch + 1]))
 
     @classmethod
     def coerce(cls, version_string, partial=False):
@@ -290,14 +299,20 @@ class Version(object):
                 return 0
 
         def build_cmp(a, b):
-            """Compare build metadata.
+            """Compare build components.
 
-            Special rule: there is no ordering on build metadata.
+            Special rule: a version without build component has lower
+            precedence than one with a build component.
             """
-            if a == b:
-                return 0
+            if a and b:
+                return identifier_list_cmp(a, b)
+            elif a:
+                # Versions with build field have higher precedence
+                return 1
+            elif b:
+                return -1
             else:
-                return NotImplemented
+                return 0
 
         def make_optional(orig_cmp_fun):
             """Convert a cmp-like function to consider 'None == *'."""
@@ -326,7 +341,10 @@ class Version(object):
                 build_cmp,
             ]
 
-    def __compare(self, other):
+    def __cmp__(self, other):
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+
         field_pairs = zip(self, other)
         comparison_functions = self._comparison_functions(partial=self.partial or other.partial)
         comparisons = zip(comparison_functions, self, other)
@@ -338,48 +356,44 @@ class Version(object):
 
         return 0
 
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+
+        return self.__cmp__(other) == 0
+
     def __hash__(self):
         return hash((self.major, self.minor, self.patch, self.prerelease, self.build))
 
-    def __cmp__(self, other):
+    def __ne__(self, other):
         if not isinstance(other, self.__class__):
             return NotImplemented
-        return self.__compare(other)
 
-    def __compare_helper(self, other, condition, notimpl_target):
-        """Helper for comparison.
+        return self.__cmp__(other) != 0
 
-        Allows the caller to provide:
-        - The condition
-        - The return value if the comparison is meaningless (ie versions with
-            build metadata).
-        """
+    def __lt__(self, other):
         if not isinstance(other, self.__class__):
             return NotImplemented
 
-        cmp_res = self.__cmp__(other)
-        if cmp_res is NotImplemented:
-            return notimpl_target
-
-        return condition(cmp_res)
-
-    def __eq__(self, other):
-        return self.__compare_helper(other, lambda x: x == 0, notimpl_target=False)
-
-    def __ne__(self, other):
-        return self.__compare_helper(other, lambda x: x != 0, notimpl_target=True)
-
-    def __lt__(self, other):
-        return self.__compare_helper(other, lambda x: x < 0, notimpl_target=False)
+        return self.__cmp__(other) < 0
 
     def __le__(self, other):
-        return self.__compare_helper(other, lambda x: x <= 0, notimpl_target=False)
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+
+        return self.__cmp__(other) <= 0
 
     def __gt__(self, other):
-        return self.__compare_helper(other, lambda x: x > 0, notimpl_target=False)
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+
+        return self.__cmp__(other) > 0
 
     def __ge__(self, other):
-        return self.__compare_helper(other, lambda x: x >= 0, notimpl_target=False)
+        if not isinstance(other, self.__class__):
+            return NotImplemented
+
+        return self.__cmp__(other) >= 0
 
 
 class SpecItem(object):
@@ -415,10 +429,6 @@ class SpecItem(object):
 
         kind, version = match.groups()
         spec = Version(version, partial=True)
-        if spec.build is not None and kind not in (cls.KIND_EQUAL, cls.KIND_NEQ):
-            raise ValueError(
-                    "Invalid requirement specification %r: build numbers have no ordering."
-                    % requirement_string)
         return (kind, spec)
 
     def match(self, version):
diff --git a/semantic_version/compat.py b/semantic_version/compat.py
index 4dd60fe..bea6f67 100644
--- a/semantic_version/compat.py
+++ b/semantic_version/compat.py
@@ -2,14 +2,17 @@
 # Copyright (c) 2012-2014 The python-semanticversion project
 # This code is distributed under the two-clause BSD License.
 
+import sys
 
-def base_cmp(x, y):
-    if x == y:
-        return 0
-    elif x > y:
-        return 1
-    elif x < y:
-        return -1
-    else:
-        # Fix Py2's behavior: cmp(x, y) returns -1 for unorderable types
-        return NotImplemented
+is_python2 = (sys.version_info[0] == 2)
+
+if is_python2:  # pragma: no cover
+    base_cmp = cmp
+else:  # pragma: no cover
+    def base_cmp(x, y):
+        if x < y:
+            return -1
+        elif x > y:
+            return 1
+        else:
+            return 0


Test patch:
diff --git a/tests/test_base.py b/tests/test_base.py
index ae23d86..00c1216 100755
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -64,7 +64,7 @@ class TopLevelTestCase(unittest.TestCase):
         ('0.1.1', '0.1.1', 0),
         ('0.1.1', '0.1.0', 1),
         ('0.1.0-alpha', '0.1.0', -1),
-        ('0.1.0-alpha+2', '0.1.0-alpha', NotImplemented),
+        ('0.1.0-alpha+2', '0.1.0-alpha', 1),
     )
 
     def test_compare(self):
@@ -179,6 +179,7 @@ class VersionTestCase(unittest.TestCase):
         '1.1.2': (1, 1, 2, None, None),
         '1.1.3-rc4.5': (1, 1, 3, ('rc4', '5'), None),
         '1.0.0-': (1, 0, 0, (), None),
+        '1.0.0+': (1, 0, 0, (), ()),
         '1.0.0-rc.1+build.1': (1, 0, 0, ('rc', '1'), ('build', '1')),
         '1.0.0+0.3.7': (1, 0, 0, (), ('0', '3', '7')),
         '1.3.7+build': (1, 3, 7, (), ('build',)),
@@ -241,11 +242,111 @@ class VersionTestCase(unittest.TestCase):
         self.assertTrue(v != '0.1.0')
         self.assertFalse(v == '0.1.0')
 
-    def test_bump_versions(self):
+    def test_bump_clean_versions(self):
         # We Test each property explicitly as the == comparator for versions
         # does not distinguish between prerelease or builds for equality.
 
+        v = base.Version('1.0.0+build')
+        v = v.next_major()
+        self.assertEqual(v.major, 2)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.0+build')
+        v = v.next_minor()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 1)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.0+build')
+        v = v.next_patch()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 1)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.1.0+build')
+        v = v.next_major()
+        self.assertEqual(v.major, 2)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.1.0+build')
+        v = v.next_minor()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 2)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.1.0+build')
+        v = v.next_patch()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 1)
+        self.assertEqual(v.patch, 1)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.1+build')
+        v = v.next_major()
+        self.assertEqual(v.major, 2)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.1+build')
+        v = v.next_minor()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 1)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.1+build')
+        v = v.next_patch()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 2)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+    def test_bump_prerelease_versions(self):
+        # We Test each property explicitly as the == comparator for versions
+        # does not distinguish between prerelease or builds for equality.
+
+        v = base.Version('1.0.0-pre+build')
+        v = v.next_major()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.0-pre+build')
+        v = v.next_minor()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
         v = base.Version('1.0.0-pre+build')
+        v = v.next_patch()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.1.0-pre+build')
         v = v.next_major()
         self.assertEqual(v.major, 2)
         self.assertEqual(v.minor, 0)
@@ -253,7 +354,7 @@ class VersionTestCase(unittest.TestCase):
         self.assertEqual(v.prerelease, ())
         self.assertEqual(v.build, ())
 
-        v = base.Version('1.0.1-pre+build')
+        v = base.Version('1.1.0-pre+build')
         v = v.next_minor()
         self.assertEqual(v.major, 1)
         self.assertEqual(v.minor, 1)
@@ -265,35 +366,48 @@ class VersionTestCase(unittest.TestCase):
         v = v.next_patch()
         self.assertEqual(v.major, 1)
         self.assertEqual(v.minor, 1)
-        self.assertEqual(v.patch, 1)
+        self.assertEqual(v.patch, 0)
         self.assertEqual(v.prerelease, ())
         self.assertEqual(v.build, ())
 
+        v = base.Version('1.0.1-pre+build')
+        v = v.next_major()
+        self.assertEqual(v.major, 2)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
 
-class SpecItemTestCase(unittest.TestCase):
-    invalids = [
-        '<=0.1.1+build3',
-        '<=0.1.1+',
-        '>0.2.3-rc2+',
-    ]
+        v = base.Version('1.0.1-pre+build')
+        v = v.next_minor()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 1)
+        self.assertEqual(v.patch, 0)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
+
+        v = base.Version('1.0.1-pre+build')
+        v = v.next_patch()
+        self.assertEqual(v.major, 1)
+        self.assertEqual(v.minor, 0)
+        self.assertEqual(v.patch, 1)
+        self.assertEqual(v.prerelease, ())
+        self.assertEqual(v.build, ())
 
-    def test_invalids(self):
-        for invalid in self.invalids:
-            with self.assertRaises(ValueError, msg="SpecItem(%r) should be invalid" % invalid):
-                _v = base.SpecItem(invalid)
 
+class SpecItemTestCase(unittest.TestCase):
     components = {
         '==0.1.0': (base.SpecItem.KIND_EQUAL, 0, 1, 0, None, None),
         '==0.1.2-rc3': (base.SpecItem.KIND_EQUAL, 0, 1, 2, ('rc3',), None),
         '==0.1.2+build3.14': (base.SpecItem.KIND_EQUAL, 0, 1, 2, (), ('build3', '14')),
-        '<=0.1.1': (base.SpecItem.KIND_LTE, 0, 1, 1, None, None),
+        '<=0.1.1+': (base.SpecItem.KIND_LTE, 0, 1, 1, (), ()),
         '<0.1.1': (base.SpecItem.KIND_LT, 0, 1, 1, None, None),
         '<=0.1.1': (base.SpecItem.KIND_LTE, 0, 1, 1, None, None),
-        '!=0.1.1+': (base.SpecItem.KIND_NEQ, 0, 1, 1, (), ()),
         '<=0.1.1-': (base.SpecItem.KIND_LTE, 0, 1, 1, (), None),
         '>=0.2.3-rc2': (base.SpecItem.KIND_GTE, 0, 2, 3, ('rc2',), None),
+        '>0.2.3-rc2+': (base.SpecItem.KIND_GT, 0, 2, 3, ('rc2',), ()),
         '>=2.0.0': (base.SpecItem.KIND_GTE, 2, 0, 0, None, None),
-        '!=0.1.1+rc3': (base.SpecItem.KIND_NEQ, 0, 1, 1, (), ('rc3',)),
+        '!=0.1.1+': (base.SpecItem.KIND_NEQ, 0, 1, 1, (), ()),
         '!=0.3.0': (base.SpecItem.KIND_NEQ, 0, 3, 0, None, None),
     }
 
@@ -345,17 +459,13 @@ class SpecItemTestCase(unittest.TestCase):
             ['0.2.3-rc3', '0.2.3', '0.2.3+1', '0.2.3-rc2', '0.2.3-rc2+1'],
             ['0.2.3-rc1', '0.2.2'],
         ),
-        '==0.2.3+': (
-            ['0.2.3'],
-            ['0.2.3+rc1', '0.2.4', '0.2.3-rc2'],
-        ),
-        '!=0.2.3-rc2+12': (
-            ['0.2.3-rc3', '0.2.3', '0.2.3-rc2+1', '0.2.4', '0.2.3-rc3+12'],
-            ['0.2.3-rc2+12'],
+        '>0.2.3-rc2+': (
+            ['0.2.3-rc3', '0.2.3', '0.2.3-rc2+1'],
+            ['0.2.3-rc1', '0.2.2', '0.2.3-rc2'],
         ),
-        '==2.0.0+b1': (
-            ['2.0.0+b1'],
-            ['2.1.1', '1.9.9', '1.9.9999', '2.0.0', '2.0.0-rc4'],
+        '>2.0.0+': (
+            ['2.1.1', '2.0.0+b1', '3.1.4'],
+            ['1.9.9', '1.9.9999', '2.0.0', '2.0.0-rc4'],
         ),
         '!=0.1.1': (
             ['0.1.2', '0.1.0', '1.4.2'],
@@ -454,17 +564,13 @@ class SpecTestCase(unittest.TestCase):
                 self.assertTrue(repr(base.SpecItem(spec_text)) in repr(spec_list))
 
     matches = {
-        # At least 0.1.1 including pre-releases, less than 0.1.2 excluding pre-releases
         '>=0.1.1,<0.1.2': (
             ['0.1.1', '0.1.1+4', '0.1.1-alpha'],
             ['0.1.2-alpha', '0.1.2', '1.3.4'],
         ),
-        # At least 0.1.0 without pre-releases, less than 0.1.4 excluding pre-releases,
-        # neither 0.1.3-rc1 nor any build of that version,
-        # not 0.1.0+b3 precisely
-        '>=0.1.0-,!=0.1.3-rc1,!=0.1.0+b3,<0.1.4': (
+        '>=0.1.0+,!=0.1.3-rc1,<0.1.4': (
             ['0.1.1', '0.1.0+b4', '0.1.2', '0.1.3-rc2'],
-            ['0.0.1', '0.1.0+b3', '0.1.4', '0.1.4-alpha', '0.1.3-rc1+4',
+            ['0.0.1', '0.1.4', '0.1.4-alpha', '0.1.3-rc1+4',
              '0.1.0-alpha', '0.2.2', '0.1.4-rc1'],
         ),
     }
diff --git a/tests/test_match.py b/tests/test_match.py
index 6926e0a..155a612 100755
--- a/tests/test_match.py
+++ b/tests/test_match.py
@@ -15,7 +15,6 @@ class MatchTestCase(unittest.TestCase):
         '<=0.1.4a',
         '>0.1.1.1',
         '~0.1.2-rc23,1',
-        '<0.1.2-rc1.3-14.15+build.2012-01-01.11h34',
     ]
 
     valid_specs = [
@@ -26,7 +25,7 @@ class MatchTestCase(unittest.TestCase):
         '>0.1.2-rc1',
         '>=0.1.2-rc1.3.4',
         '==0.1.2+build42-12.2012-01-01.12h23',
-        '!=0.1.2-rc1.3-14.15+build.2012-01-01.11h34',
+        '<0.1.2-rc1.3-14.15+build.2012-01-01.11h34',
     ]
 
     matches = {
@@ -54,19 +53,11 @@ class MatchTestCase(unittest.TestCase):
             '0.1.2',
             '0.1.2+build4',
         ],
-        '!=0.1.2+': [
-            '0.1.2+1',
-            '0.1.2-rc1',
-        ],
-        '!=0.1.2-': [
+        '<0.1.2+': [
             '0.1.1',
             '0.1.2-rc1',
-        ],
-        '!=0.1.2+345': [
-            '0.1.1',
-            '0.1.2-rc1+345',
-            '0.1.2+346',
-            '0.2.3+345',
+            '0.1.2-rc1.3.4',
+            '0.1.2-rc1+build4.5',
         ],
         '>=0.1.1': [
             '0.1.1',
@@ -81,6 +72,12 @@ class MatchTestCase(unittest.TestCase):
             '0.2.0',
             '1.0.0',
         ],
+        '>0.1.1+': [
+            '0.1.1+b2',
+            '0.1.2-rc1',
+            '1.1.1',
+            '2.0.4',
+        ],
         '<0.1.1-': [
             '0.1.1-alpha',
             '0.1.1-rc4',
@@ -90,8 +87,7 @@ class MatchTestCase(unittest.TestCase):
 
     def test_invalid(self):
         for invalid in self.invalid_specs:
-            with self.assertRaises(ValueError, msg="Spec(%r) should be invalid" % invalid):
-                semantic_version.Spec(invalid)
+            self.assertRaises(ValueError, semantic_version.Spec, invalid)
 
     def test_simple(self):
         for valid in self.valid_specs:
@@ -126,9 +122,11 @@ class MatchTestCase(unittest.TestCase):
         self.assertFalse(version in strict_spec, "%r should not be in %r" % (version, strict_spec))
 
     def test_build_check(self):
-        spec = semantic_version.Spec('<=0.1.1-rc1')
+        strict_spec = semantic_version.Spec('<=0.1.1-rc1+')
+        lax_spec = semantic_version.Spec('<=0.1.1-rc1')
         version = semantic_version.Version('0.1.1-rc1+4.2')
-        self.assertTrue(version in spec, "%r should be in %r" % (version, spec))
+        self.assertTrue(version in lax_spec, "%r should be in %r" % (version, lax_spec))
+        self.assertFalse(version in strict_spec, "%r should not be in %r" % (version, strict_spec))
 
 
 if __name__ == '__main__':  # pragma: no cover
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index c7651d2..5112ca5 100755
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -3,7 +3,6 @@
 # Copyright (c) 2012-2014 The python-semanticversion project
 # This code is distributed under the two-clause BSD License.
 
-import itertools
 import unittest
 
 import semantic_version
@@ -45,8 +44,12 @@ class ComparisonTestCase(unittest.TestCase):
         '1.0.0-beta.2',
         '1.0.0-beta.11',
         '1.0.0-rc.1',
+        '1.0.0-rc.1+build.1',
         '1.0.0',
+        '1.0.0+0.3.7',
         '1.3.7+build',
+        '1.3.7+build.2.b8f12d7',
+        '1.3.7+build.11.e0f985a',
     ]
 
     def test_comparisons(self):
@@ -64,36 +67,6 @@ class ComparisonTestCase(unittest.TestCase):
                 cmp_res = -1 if i < j else (1 if i > j else 0)
                 self.assertEqual(cmp_res, semantic_version.compare(first, second))
 
-    unordered = [
-        [
-            '1.0.0-rc.1',
-            '1.0.0-rc.1+build.1',
-        ],
-        [
-            '1.0.0',
-            '1.0.0+0.3.7',
-        ],
-        [
-            '1.3.7',
-            '1.3.7+build',
-            '1.3.7+build.2.b8f12d7',
-            '1.3.7+build.11.e0f985a',
-        ],
-    ]
-
-    def test_unordered(self):
-        for group in self.unordered:
-            for a, b in itertools.combinations(group, 2):
-                v1 = semantic_version.Version(a)
-                v2 = semantic_version.Version(b)
-                self.assertTrue(v1 == v1, "%r != %r" % (v1, v1))
-                self.assertFalse(v1 != v1, "%r != %r" % (v1, v1))
-                self.assertFalse(v1 == v2, "%r == %r" % (v1, v2))
-                self.assertTrue(v1 != v2, "%r !!= %r" % (v1, v2))
-                self.assertFalse(v1 < v2, "%r !< %r" % (v1, v2))
-                self.assertFalse(v1 <= v2, "%r !<= %r" % (v1, v2))
-                self.assertFalse(v2 > v1, "%r !> %r" % (v2, v1))
-                self.assertFalse(v2 >= v1, "%r !>= %r" % (v2, v1))
 
 if __name__ == '__main__':  # pragma: no cover
     unittest.main()
diff --git a/tests/test_spec.py b/tests/test_spec.py
index a13cb0b..7a645f9 100644
--- a/tests/test_spec.py
+++ b/tests/test_spec.py
@@ -154,3 +154,10 @@ class FormatTests(unittest.TestCase):
         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
+
+