Bug 1453274 - Support esr version strings. r=rail
authorJustin Wood <Callek@gmail.com>
Mon, 23 Apr 2018 14:14:04 -0400
changeset 468615 f12de32d3468fd00d541c2a8557cb0a33ebf398d
parent 468614 0b24ec75e7531c08d7cb33c3cf0c07788c08500f
child 468616 1bd0b3c256ab364125ee5c113a18f5449cd00ec7
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrail
bugs1453274
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)