Bug 1453274 - Support esr version strings. r=rail draft
authorJustin Wood <Callek@gmail.com>
Mon, 23 Apr 2018 14:14:04 -0400
changeset 786631 33225a5ecb879705327af505920d4db040a91d7b
parent 786630 0eaeb17809fede07f6b9fc4ee5d856d0078f83be
push id107547
push userCallek@gmail.com
push dateMon, 23 Apr 2018 18:15:07 +0000
reviewersrail
bugs1453274
milestone61.0a1
Bug 1453274 - Support esr version strings. r=rail MozReview-Commit-ID: K7khNCzOwQK
python/mozrelease/mozrelease/versions.py
python/mozrelease/test/test_versions.py
--- a/python/mozrelease/mozrelease/versions.py
+++ b/python/mozrelease/mozrelease/versions.py
@@ -1,44 +1,94 @@
 from __future__ import absolute_import
 
-from distutils.version import StrictVersion
+from distutils.version import StrictVersion, LooseVersion
 import re
 
 
-class ModernMozillaVersion(StrictVersion):
+class MozillaVersionCompareMixin():
+    def __cmp__(self, other):
+        has_esr = set()
+        if isinstance(other, LooseModernMozillaVersion) and str(other).endswith('esr'):
+            # If other version ends with esr, coerce through MozillaVersion ending up with
+            # a StrictVersion if possible
+            has_esr.add('other')
+            other = MozillaVersion(str(other)[:-3])  # strip ESR from end of string
+        if isinstance(self, LooseModernMozillaVersion) and str(self).endswith('esr'):
+            # If our version ends with esr, coerce through MozillaVersion ending up with
+            # a StrictVersion if possible
+            has_esr.add('self')
+            self = MozillaVersion(str(self)[:-3])  # strip ESR from end of string
+        if isinstance(other, LooseModernMozillaVersion) or \
+                isinstance(self, LooseModernMozillaVersion):
+            # If we're still LooseVersion for self or other, run LooseVersion compare
+            # Being sure to pass through Loose Version type first
+            val = LooseVersion.__cmp__(
+                    LooseModernMozillaVersion(str(self)),
+                    LooseModernMozillaVersion(str(other)))
+        else:
+            # No versions are loose, therefore we can use StrictVersion
+            val = StrictVersion.__cmp__(self, other)
+        if has_esr.isdisjoint(set(['other', 'self'])) or \
+                has_esr.issuperset(set(['other', 'self'])):
+            #  If both had esr string or neither, then cmp() was accurate
+            return val
+        elif val is not 0:
+            # cmp is accurate here even if esr is present in only 1 compare, since
+            # versions are not equal
+            return val
+        elif 'other' in has_esr:
+            return -1  # esr is not greater than non esr
+        return 1  # non esr is greater than esr
+
+
+class ModernMozillaVersion(MozillaVersionCompareMixin, StrictVersion):
     """A version class that is slightly less restrictive than StrictVersion.
        Instead of just allowing "a" or "b" as prerelease tags, it allows any
        alpha. This allows us to support the once-shipped "3.6.3plugin1" and
        similar versions."""
     version_re = re.compile(r"""^(\d+) \. (\d+) (\. (\d+))?
                                 ([a-zA-Z]+(\d+))?$""", re.VERBOSE)
 
 
-class AncientMozillaVersion(StrictVersion):
+class AncientMozillaVersion(MozillaVersionCompareMixin, StrictVersion):
     """A version class that is slightly less restrictive than StrictVersion.
        Instead of just allowing "a" or "b" as prerelease tags, it allows any
        alpha. This allows us to support the once-shipped "3.6.3plugin1" and
        similar versions.
        It also supports versions w.x.y.z by transmuting to w.x.z, which
        is useful for versions like 1.5.0.x and 2.0.0.y"""
     version_re = re.compile(r"""^(\d+) \. (\d+) \. \d (\. (\d+))
                                 ([a-zA-Z]+(\d+))?$""", re.VERBOSE)
 
 
+class LooseModernMozillaVersion(MozillaVersionCompareMixin, LooseVersion):
+    """A version class that is more restrictive than LooseVersion.
+       This class reduces the valid strings to "esr", "a", "b" and "rc" in order
+       to support esr. StrictVersion requires a trailing number after all strings."""
+    component_re = re.compile(r'(\d+ | a | b | rc | esr | \.)', re.VERBOSE)
+
+    def __repr__(self):
+        return "LooseModernMozillaVersion ('%s')" % str(self)
+
+
 def MozillaVersion(version):
     try:
         return ModernMozillaVersion(version)
     except ValueError:
         pass
     try:
         if version.count('.') == 3:
             return AncientMozillaVersion(version)
     except ValueError:
         pass
+    try:
+        return LooseModernMozillaVersion(version)
+    except ValueError:
+        pass
     raise ValueError("Version number %s is invalid." % version)
 
 
 def getPrettyVersion(version):
     version = re.sub(r'a([0-9]+)$', r' Alpha \1', version)
     version = re.sub(r'b([0-9]+)$', r' Beta \1', version)
     version = re.sub(r'rc([0-9]+)$', r' RC \1', version)
     return version
--- a/python/mozrelease/test/test_versions.py
+++ b/python/mozrelease/test/test_versions.py
@@ -43,27 +43,27 @@ ALL_VERSIONS = [  # Keep this sorted
     '3.5.5',
     '3.5.6',
     '3.5.7',
     '3.5.8',
     '3.5.9',
     '3.5.10',
     # ... Start skipping around...
     '4.0b9',
-    # '10.0.2esr',
-    # '10.0.3esr',
+    '10.0.2esr',
+    '10.0.3esr',
     '32.0',
     '49.0a1',
     '49.0a2',
     '59.0',
     '60.0',
-    # '60.0esr',
-    # '60.0.1esr',
+    '60.0esr',
+    '60.0.1esr',
     '60.1',
-    # '60.1.0esr',
+    '60.1esr',
     '61.0',
 ]
 
 
 @pytest.fixture(scope='function',
                 params=range(len(ALL_VERSIONS) - 1),
                 ids=lambda x: "{}, {}".format(ALL_VERSIONS[x], ALL_VERSIONS[x+1]))
 def comparable_versions(request):
@@ -75,22 +75,26 @@ def comparable_versions(request):
 def test_versions_parseable(version):
     """Test that we can parse previously shipped versions.
 
     We only test 3.0 and up, since we never generate updates against
     versions that old."""
     assert MozillaVersion(version) is not None
 
 
-def test_versions_compare(comparable_versions):
+def test_versions_compare_less(comparable_versions):
     """Test that versions properly compare in order."""
     smaller_version, larger_version = comparable_versions
     assert MozillaVersion(smaller_version) < MozillaVersion(larger_version)
+
+
+def test_versions_compare_greater(comparable_versions):
+    """Test that versions properly compare in order."""
+    smaller_version, larger_version = comparable_versions
     assert MozillaVersion(larger_version) > MozillaVersion(smaller_version)
-    assert MozillaVersion(larger_version) != MozillaVersion(smaller_version)
 
 
 @pytest.mark.parametrize('version', ALL_VERSIONS)
 def test_versions_compare_equal(version):
     """Test that versions properly compare as equal through multiple passes."""
     assert MozillaVersion(version) == MozillaVersion(version)