compatibility: Implement a version comparator.

This commit is contained in:
Greg Price 2018-12-04 14:22:05 -08:00 committed by Tim Abbott
parent 60de598cb5
commit 557aca2aa7
2 changed files with 82 additions and 1 deletions

View File

@ -1,5 +1,43 @@
from zerver.lib.test_classes import ZulipTestCase
from zerver.views.compatibility import version_lt
class VersionTest(ZulipTestCase):
data = [case.split() for case in '''
1.2.3 < 1.2.4
1.2.3 = 1.2.3
1.4.1 > 1.2.3
1.002a = 1.2a
1.2.3 ? 1.2-dev
1.2-dev ? 1.2a
1.2a ? 1.2rc3
1.2rc3 ? 1.2
1.2 ? 1.2-g0f1e2d3c4
10.1 > 1.2
0.17.18 < 16.2.96
9.10.11 < 16.2.96
15.1.95 < 16.2.96
16.2.96 = 16.2.96
20.0.103 > 16.2.96
'''.strip().split('\n')]
def test_version_lt(self) -> None:
for ver1, cmp, ver2 in self.data:
msg = 'expected {} {} {}'.format(ver1, cmp, ver2)
if cmp == '<':
self.assertTrue(version_lt(ver1, ver2), msg=msg)
self.assertFalse(version_lt(ver2, ver1), msg=msg)
elif cmp == '=':
self.assertFalse(version_lt(ver1, ver2), msg=msg)
self.assertFalse(version_lt(ver2, ver1), msg=msg)
elif cmp == '>':
self.assertFalse(version_lt(ver1, ver2), msg=msg)
self.assertTrue(version_lt(ver2, ver1), msg=msg)
elif cmp == '?':
self.assertIsNone(version_lt(ver1, ver2), msg=msg)
self.assertIsNone(version_lt(ver2, ver1), msg=msg)
else:
assert False # nocoverage
class CompatibilityTest(ZulipTestCase):
def test_compatibility(self) -> None:

View File

@ -1,10 +1,53 @@
from django.http import HttpResponse, HttpRequest
from typing import Any, List, Dict, Optional
import re
from typing import Any, List, Dict, Optional, Tuple, Union
from zerver.lib.response import json_error, json_success
from zerver.lib.user_agent import parse_user_agent
def pop_int(ver: str) -> Tuple[Optional[int], str]:
match = re.search(r'^(\d+)(.*)', ver)
if match is None:
return None, ver
return int(match.group(1)), match.group(2)
def version_lt(ver1: str, ver2: str) -> Optional[bool]:
'''
Compare two Zulip-style version strings.
Versions are dot-separated sequences of decimal integers,
followed by arbitrary trailing decoration. Comparison is
lexicographic on the integer sequences, and refuses to
guess how any trailing decoration compares to any other,
to further numerals, or to nothing.
Returns:
True if ver1 < ver2
False if ver1 >= ver2
None if can't tell.
'''
while True:
# Pull off the leading numeral from each version.
maj1, rest1 = pop_int(ver1)
maj2, rest2 = pop_int(ver2)
if maj1 is None or maj2 is None:
# One or both has no leading numeral; some unknown format.
return None
if maj1 < maj2:
return True
if maj1 > maj2:
return False
# Leading numerals are equal.
if rest1 == rest2:
return False
if not(rest1.startswith('.') and rest2.startswith('.')):
return None
ver1 = rest1[1:]
ver2 = rest2[1:]
def check_global_compatibility(request: HttpRequest) -> HttpResponse:
user_agent = parse_user_agent(request.META["HTTP_USER_AGENT"])
if user_agent['name'] == "ZulipInvalid":