-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change removes all uses of distutils modules and replaces them with either direct replacements or extractions from the library. LooseVersion is extracted into ccmlib.utils.version and dir_util.copy_tree is replaced by shutil.copytree. Fixes #537
- Loading branch information
Showing
7 changed files
with
234 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,127 @@ | ||
import re | ||
from packaging.version import parse, Version | ||
|
||
|
||
# NOTE: following regex is taken from the 'semver' package as is: | ||
# https://python-semver.readthedocs.io/en/2.10.0/readme.html | ||
SEMVER_REGEX = re.compile( | ||
r""" | ||
^ | ||
(?P<major>0|[1-9]\d*) | ||
\. | ||
(?P<minor>0|[1-9]\d*) | ||
\. | ||
(?P<patch>0|[1-9]\d*) | ||
(?:-(?P<prerelease> | ||
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) | ||
(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* | ||
))? | ||
(?:\+(?P<build> | ||
[0-9a-zA-Z-]+ | ||
(?:\.[0-9a-zA-Z-]+)* | ||
))? | ||
$ | ||
""", | ||
re.VERBOSE, | ||
) | ||
|
||
class ComparableScyllaVersion: | ||
"""Accepts and compares known 'non-semver' and 'semver'-like Scylla versions.""" | ||
|
||
def __init__(self, version_string: str): | ||
parsed_version = self.parse(version_string) | ||
self.v_major = int(parsed_version[0]) | ||
self.v_minor = int(parsed_version[1]) | ||
self.v_patch = int(parsed_version[2]) | ||
self.v_pre_release = parsed_version[3] or '' | ||
self.v_build = parsed_version[4] or '' | ||
|
||
@staticmethod | ||
def parse(version_string: str): | ||
"""Parse scylla-binary and scylla-docker-tag versions into a proper semver structure.""" | ||
# NOTE: remove 'with build-id' part if exists and other possible non-semver parts | ||
_scylla_version = (version_string or '').split(" ")[0] | ||
|
||
# NOTE: replace '~' which gets returned by the scylla binary | ||
_scylla_version = _scylla_version.replace('~', '-') | ||
|
||
# NOTE: remove docker-specific parts if version is taken from a docker tag | ||
_scylla_version = _scylla_version.replace('-aarch64', '') | ||
_scylla_version = _scylla_version.replace('-x86_64', '') | ||
|
||
# NOTE: transform gce-image version like '2024.2.0.dev.0.20231219.c7cdb16538f2.1' | ||
if gce_image_v_match := re.search(r"(\d+\.\d+\.\d+\.)([a-z0-9]+\.)(.*)", _scylla_version): | ||
_scylla_version = f"{gce_image_v_match[1][:-1]}-{gce_image_v_match[2][:-1]}-{gce_image_v_match[3]}" | ||
|
||
# NOTE: make short scylla version like '5.2' be correct semver string | ||
_scylla_version_parts = re.split(r'\.|-', _scylla_version) | ||
if len(_scylla_version_parts) == 2: | ||
_scylla_version = f"{_scylla_version}.0" | ||
elif len(_scylla_version_parts) > 2 and re.search( | ||
r"\D+", _scylla_version_parts[2].split("-")[0]): | ||
_scylla_version = f"{_scylla_version_parts[0]}.{_scylla_version_parts[1]}.0-{_scylla_version_parts[2]}" | ||
for part in _scylla_version_parts[3:]: | ||
_scylla_version += f".{part}" | ||
|
||
# NOTE: replace '-0' with 'dev-0', '-1' with 'dev-1' and so on | ||
# to match docker and scylla binary version structures correctly. | ||
if no_dev_match := re.search(r"(\d+\.\d+\.\d+)(\-\d+)(\.20[0-9]{6}.*)", _scylla_version): | ||
_scylla_version = f"{no_dev_match[1]}-dev{no_dev_match[2]}{no_dev_match[3]}" | ||
|
||
# NOTE: replace '.' with '+' symbol between build date and build commit | ||
# to satisfy semver structure | ||
if dotted_build_id_match := re.search(r"(.*\.20[0-9]{6})(\.)([\.\d\w]+)", _scylla_version): | ||
_scylla_version = f"{dotted_build_id_match[1]}+{dotted_build_id_match[3]}" | ||
|
||
if match := SEMVER_REGEX.match(_scylla_version): | ||
return match.groups() | ||
raise ValueError( | ||
f"Cannot parse provided '{version_string}' scylla_version for the comparison. " | ||
f"Transformed scylla_version: {_scylla_version}") | ||
|
||
def __str__(self): | ||
result = f"{self.v_major}.{self.v_minor}.{self.v_patch}" | ||
if self.v_pre_release: | ||
result += f"-{self.v_pre_release}" | ||
if self.v_build: | ||
result += f"+{self.v_build}" | ||
return result | ||
|
||
def _transform_to_comparable(self, other): | ||
if isinstance(other, str): | ||
return self.__class__(other) | ||
elif isinstance(other, self.__class__): | ||
return other | ||
raise ValueError("Got unexpected type for the comparison: %s" % type(other)) | ||
|
||
def as_comparable(self): | ||
# NOTE: absence of the 'pre-release' part means we have 'GA' version which is newer than | ||
# any of the 'pre-release' ones. | ||
# So, make empty 'pre-release' prevail over any defined one. | ||
return (self.v_major, self.v_minor, self.v_patch, self.v_pre_release or 'xyz') | ||
|
||
def __lt__(self, other): | ||
return self.as_comparable() < self._transform_to_comparable(other).as_comparable() | ||
|
||
def __le__(self, other): | ||
return self.as_comparable() <= self._transform_to_comparable(other).as_comparable() | ||
|
||
def __eq__(self, other): | ||
return self.as_comparable() == self._transform_to_comparable(other).as_comparable() | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
def __ge__(self, other): | ||
return not self.__lt__(other) | ||
|
||
def __gt__(self, other): | ||
return not self.__le__(other) | ||
|
||
|
||
class ComparableCassandraVersion(ComparableScyllaVersion): | ||
pass | ||
|
||
def parse_version(v: str) -> Version: | ||
v = v.replace('~', '-') | ||
return parse(v) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import pytest | ||
|
||
from ccmlib.utils.version import ComparableScyllaVersion | ||
|
||
@pytest.mark.parametrize("version_string, expected", ( | ||
("5.1", (5, 1, 0, '', '')), | ||
("5.1.0", (5, 1, 0, '', '')), | ||
("5.1.1", (5, 1, 1, '', '')), | ||
("5.1.0-rc1", (5, 1, 0, 'rc1', '')), | ||
("5.1.0~rc1", (5, 1, 0, 'rc1', '')), | ||
("5.1.rc1", (5, 1, 0, 'rc1', '')), | ||
("2022.1.3-0.20220922.539a55e35", (2022, 1, 3, "dev-0.20220922", "539a55e35")), | ||
("2022.1.3-0.20220922.539a55e35 with build-id d1fb2faafd95058a04aad30b675ff7d2b930278d", | ||
(2022, 1, 3, "dev-0.20220922", "539a55e35")), | ||
("2022.1.3-dev-0.20220922.539a55e35", (2022, 1, 3, "dev-0.20220922", "539a55e35")), | ||
("5.2.0~rc1-0.20230207.8ff4717fd010", (5, 2, 0, "rc1-0.20230207", "8ff4717fd010")), | ||
("5.2.0-dev-0.20230109.08b3a9c786d9", (5, 2, 0, "dev-0.20230109", "08b3a9c786d9")), | ||
("5.2.0-dev-0.20230109.08b3a9c786d9-x86_64", (5, 2, 0, "dev-0.20230109", "08b3a9c786d9")), | ||
("5.2.0-dev-0.20230109.08b3a9c786d9-aarch64", (5, 2, 0, "dev-0.20230109", "08b3a9c786d9")), | ||
("2024.2.0.dev.0.20231219.c7cdb16538f2.1", (2024, 2, 0, "dev-0.20231219", "c7cdb16538f2.1")), | ||
("2024.1.0.rc2.0.20231218.a063c2c16185.1", (2024, 1, 0, "rc2-0.20231218", "a063c2c16185.1")), | ||
("2.6-dev-0.20211108.5f1e01cbb34-SNAPSHOT-5f1e01cbb34", (2, 6, 0, "dev.0.20211108", '5f1e01cbb34.SNAPSHOT.5f1e01cbb34')), | ||
)) | ||
def test_comparable_scylla_version_init_positive(version_string, expected): | ||
comparable_scylla_version = ComparableScyllaVersion(version_string) | ||
assert comparable_scylla_version.v_major == expected[0] | ||
assert comparable_scylla_version.v_minor == expected[1] | ||
assert comparable_scylla_version.v_patch == expected[2] | ||
assert comparable_scylla_version.v_pre_release == expected[3] | ||
assert comparable_scylla_version.v_build == expected[4] | ||
|
||
|
||
@pytest.mark.parametrize("version_string", (None, "", "5", "2023", "2023.dev")) | ||
def test_comparable_scylla_versions_init_negative(version_string): | ||
try: | ||
ComparableScyllaVersion(version_string) | ||
except ValueError: | ||
pass | ||
else: | ||
assert False, ( | ||
f"'ComparableScyllaVersion' must raise a ValueError for the '{version_string}' " | ||
"provided input") | ||
|
||
|
||
def _compare_versions(version_string_left, version_string_right, | ||
is_left_greater, is_equal, comparable_class): | ||
comparable_version_left = comparable_class(version_string_left) | ||
comparable_version_right = comparable_class(version_string_right) | ||
|
||
compare_expected_result_err_msg = ( | ||
"One of 'is_left_greater' and 'is_equal' must be 'True' and another one must be 'False'") | ||
assert is_left_greater or is_equal, compare_expected_result_err_msg | ||
assert not (is_left_greater and is_equal) | ||
if is_left_greater: | ||
assert comparable_version_left > comparable_version_right | ||
assert comparable_version_left >= comparable_version_right | ||
assert comparable_version_left > version_string_right | ||
assert comparable_version_left >= version_string_right | ||
assert comparable_version_right < comparable_version_left | ||
assert comparable_version_right <= comparable_version_left | ||
assert comparable_version_right < version_string_left | ||
assert comparable_version_right <= version_string_left | ||
else: | ||
assert comparable_version_left == comparable_version_right | ||
assert comparable_version_left == version_string_right | ||
assert comparable_version_left >= comparable_version_right | ||
assert comparable_version_left >= version_string_right | ||
assert comparable_version_right <= comparable_version_left | ||
assert comparable_version_right <= version_string_left | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"version_string_left, version_string_right, is_left_greater, is_equal, comparable_class", ( | ||
("5.2.2", "5.2.2", False, True, ComparableScyllaVersion), | ||
("5.2.0", "5.1.2", True, False, ComparableScyllaVersion), | ||
("5.2.1", "5.2.0", True, False, ComparableScyllaVersion), | ||
("5.2.10", "5.2.9", True, False, ComparableScyllaVersion), | ||
("5.2.0", "5.2.0~rc1-0.20230207.8ff4717fd010", True, False, ComparableScyllaVersion), | ||
("5.2.0", "5.2.0-dev-0.20230109.08b3a9c786d9", True, False, ComparableScyllaVersion), | ||
("2023.1.0", "2023.1.rc1", True, False, ComparableScyllaVersion), | ||
("5.2.0", "5.1.rc1", True, False, ComparableScyllaVersion), | ||
("5.2.0-dev-0.20230109.8ff4717fd010", "5.2.0-dev-0.20230109.08b3a9c786d9", | ||
False, True, ComparableScyllaVersion), | ||
)) | ||
def test_comparable_scylla_versions_compare(version_string_left, version_string_right, | ||
is_left_greater, is_equal, comparable_class): | ||
_compare_versions( | ||
version_string_left, version_string_right, is_left_greater, is_equal, comparable_class) | ||
|
||
|
||
@pytest.mark.parametrize("version_string_input, version_string_output", ( | ||
("5.2.2", "5.2.2"), | ||
("2023.1.13", "2023.1.13"), | ||
("5.2.0~rc0-0.20230207", "5.2.0-rc0-0.20230207"), | ||
("5.2.0-rc1-0.20230207", "5.2.0-rc1-0.20230207"), | ||
("5.2.0~dev-0.20230207.8ff4717fd010", "5.2.0-dev-0.20230207+8ff4717fd010"), | ||
)) | ||
def test_comparable_scylla_versions_to_str(version_string_input, version_string_output): | ||
assert str(ComparableScyllaVersion(version_string_input)) == version_string_output |