Skip to content

Commit

Permalink
Add support for CargoVersion and CargoVersionRange
Browse files Browse the repository at this point in the history
Signed-off-by: ziadhany <[email protected]>
  • Loading branch information
ziadhany committed Apr 4, 2023
1 parent 7a4e715 commit d710d22
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 4 deletions.
56 changes: 52 additions & 4 deletions src/univers/version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from univers.utils import remove_spaces
from univers.version_constraint import VersionConstraint
from univers.version_constraint import contains_version
from univers.versions import CargoVersion


class InvalidVersionRange(Exception):
Expand Down Expand Up @@ -299,7 +300,8 @@ class NpmVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
"=": "=", # This is not a native node-semver comparator, but is used in the gitlab version range for npm packages.
"=": "=",
# This is not a native node-semver comparator, but is used in the gitlab version range for npm packages.
}

@classmethod
Expand Down Expand Up @@ -797,7 +799,8 @@ class ComposerVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
"=": "=", # This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
"=": "=",
# This is not a native composer-semver comparator, but is used in the gitlab version range for composer packages.
}


Expand Down Expand Up @@ -899,7 +902,8 @@ class GolangVersionRange(VersionRange):
">=": ">=",
"<": "<",
">": ">",
"=": "=", # This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
"=": "=",
# This is not a native golang-semver comparator, but is used in the gitlab version range for go packages.
}


Expand All @@ -922,7 +926,51 @@ class HexVersionRange(VersionRange):

class CargoVersionRange(VersionRange):
scheme = "cargo"
version_class = versions.SemverVersion
version_class = versions.CargoVersion

@classmethod
def from_native(cls, string):
"""
Return a VersionRange built from a scheme-specific, native version range
``string``. Subclasses can implement.
"""
if string == "*":
return cls(
constraints=[VersionConstraint(comparator="*", version_class=cls.version_class)]
)

constraint_strings = string.split(",")
constraints = []

# caret
if string.startswith("^"):
string = string.replace("^", "=")

# tilde
if string.startswith("~"):
version = string.lstrip("~")
lower_bound = CargoVersion(version)
if lower_bound.minor == 0 and lower_bound.patch == 0:
upper_bound = CargoVersion(str(lower_bound.value.next_major()))
else:
upper_bound = CargoVersion(str(lower_bound.value.next_minor()))

return cls(
constraints=(
VersionConstraint(comparator=">=", version=lower_bound),
VersionConstraint(comparator="<", version=upper_bound),
)
)

for constraint in constraint_strings:
if not constraint:
raise InvalidVersionRange
else:
vs = VersionConstraint.split(string)
version = cls.version_class(vs[1])
constraint = VersionConstraint(comparator=vs[0], version=version)
constraints.append(constraint)
return cls(constraints=tuple(constraints))


class MozillaVersionRange(VersionRange):
Expand Down
6 changes: 6 additions & 0 deletions src/univers/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Each subclass primary responsibility to is be comparable and orderable
"""


# TODO: Add mozilla versions https://github.com/mozilla-releng/mozilla-version
# TODO: Add conda versions https://github.com/conda/conda/blob/master/conda/models/version.py
# and https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#build-version-spec
Expand Down Expand Up @@ -686,6 +687,10 @@ def bump(self, index):
return self.value and self.value.bump(index)


class CargoVersion(SemverVersion):
pass


AVAILABLE_VERSIONS = [
SemverVersion,
GolangVersion,
Expand All @@ -702,4 +707,5 @@ def bump(self, index):
OpensslVersion,
LegacyOpensslVersion,
AlpineLinuxVersion,
CargoVersion,
]
151 changes: 151 additions & 0 deletions tests/test_cargo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import pytest

from univers.versions import CargoVersion


def test_compare():
"""
1.2.3 := >=1.2.3, <2.0.0
1.2 := >=1.2.0, <2.0.0
1 := >=1.0.0, <2.0.0
0.2.3 := >=0.2.3, <0.3.0
0.2 := >=0.2.0, <0.3.0
0.0.3 := >=0.0.3, <0.0.4
0.0 := >=0.0.0, <0.1.0
0 := >=0.0.0, <1.0.0
"""

assert CargoVersion("1.2.3") >= CargoVersion("1.2.3")
assert CargoVersion("1.2.3") < CargoVersion("2.0.0")

assert CargoVersion("1.2") >= CargoVersion("1.2.0")
assert CargoVersion("1.2") < CargoVersion("2.0.0")

assert CargoVersion("1") >= CargoVersion("1.0.0")
assert CargoVersion("1") < CargoVersion("2.0.0")

assert CargoVersion("0.2.3") >= CargoVersion("0.2.3")
assert CargoVersion("0.2.3") < CargoVersion("0.3.0")

assert CargoVersion("0.2") >= CargoVersion("0.2.0")
assert CargoVersion("0.2") < CargoVersion("0.3.0")

assert CargoVersion("0.0.3") >= CargoVersion("0.0.3")
assert CargoVersion("0.0.3") < CargoVersion("0.0.4")

assert CargoVersion("0.0") >= CargoVersion("0.0.0")
assert CargoVersion("0.0") < CargoVersion("0.1.0")

assert CargoVersion("0") >= CargoVersion("0.0.0")
assert CargoVersion("0") < CargoVersion("1.0.0")


version_list = [
("1.2.3", 1, 2, 3, (), ()),
("1.2.3-alpha1", 1, 2, 3, ("alpha1",), ()),
("1.2.3+build5", 1, 2, 3, (), ("build5",)),
("1.2.3+5build", 1, 2, 3, (), ("5build",)),
("1.2.3-alpha1+build5", 1, 2, 3, ("alpha1",), ("build5",)),
(
"1.2.3-1.alpha1.9+build5.7.3aedf",
1,
2,
3,
(
"1",
"alpha1",
"9",
),
(
"build5",
"7",
"3aedf",
),
),
(
"1.2.3-0a.alpha1.9+05build.7.3aedf",
1,
2,
3,
(
"0a",
"alpha1",
"9",
),
(
"05build",
"7",
"3aedf",
),
),
(
"0.4.0-beta.1+0851523",
0,
4,
0,
(
"beta",
"1",
),
("0851523",),
),
("1.1.0-beta-10", 1, 1, 0, ("beta-10",), ()),
]


@pytest.mark.parametrize(
"version, expected_major, expected_minor, expected_patch, expected_prerelease, expected_build",
version_list,
)
def test_cargo(
version, expected_major, expected_minor, expected_patch, expected_prerelease, expected_build
):
# https://github.com/dtolnay/semver/blob/master/tests/test_version.rs :
v1 = CargoVersion(version)
assert v1.major == expected_major
assert v1.minor == expected_minor
assert v1.patch == expected_patch
assert v1.prerelease == expected_prerelease
assert v1.build == expected_build


def test_cargo1():
assert CargoVersion("1.2.3") == CargoVersion("1.2.3")
assert CargoVersion("1.2.3-alpha1") == CargoVersion("1.2.3-alpha1")
assert CargoVersion("1.2.3+build.42") == CargoVersion("1.2.3+build.42")
assert CargoVersion("1.2.3-alpha1+42") == CargoVersion("1.2.3-alpha1+42")
assert CargoVersion("0.0.0") != CargoVersion("0.0.1")
assert CargoVersion("0.0.0") != CargoVersion("0.1.0")
assert CargoVersion("0.0.0") != CargoVersion("1.0.0")
assert CargoVersion("1.2.3-alpha") != CargoVersion("1.2.3-beta")
assert CargoVersion("1.2.3+23") != CargoVersion("1.2.3+42")

assert CargoVersion("0.0.0") < CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.0.0") < CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.0") < CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3-alpha1") < CargoVersion("1.2.3")
assert CargoVersion("1.2.3-alpha1") < CargoVersion("1.2.3-alpha2")
assert not (CargoVersion("1.2.3-alpha2") < CargoVersion("1.2.3-alpha2"))
assert CargoVersion("1.2.3+23") < CargoVersion("1.2.3+42")

assert CargoVersion("0.0.0") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.0.0") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.0") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3-alpha1") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3-alpha2") <= CargoVersion("1.2.3-alpha2")
assert CargoVersion("1.2.3+23") <= CargoVersion("1.2.3+42")

assert CargoVersion("1.2.3-alpha2") > CargoVersion("0.0.0")
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.0.0")
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.0")
assert CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.3-alpha1")
assert CargoVersion("1.2.3") > CargoVersion("1.2.3-alpha2")
assert not (CargoVersion("1.2.3-alpha2") > CargoVersion("1.2.3-alpha2"))
assert not (CargoVersion("1.2.3+23") > CargoVersion("1.2.3+42"))

assert CargoVersion("1.2.3-alpha2") >= CargoVersion("0.0.0")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.0.0")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.0")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.3-alpha1")
assert CargoVersion("1.2.3-alpha2") >= CargoVersion("1.2.3-alpha2")
assert not (CargoVersion("1.2.3+23") >= CargoVersion("1.2.3+42"))
98 changes: 98 additions & 0 deletions tests/test_cargo_version_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import pytest

from univers.version_range import CargoVersionRange
from univers.version_range import InvalidVersionRange
from univers.versions import CargoVersion

values = [
# https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
# caret
["^1.2.3", [[["=", "1.2.3"]]], ["1.2.3"], ["1.2.4"]],
# tilde
["~1.2.3", [[[">=", "1.2.3"], ["<", "1.3.0"]]], ["1.2.4"], ["2.0.1"]],
["~1.2", [[[">=", "1.2.0"], ["<", "1.3.0"]]], ["1.2.5"], ["1.3.1"]],
[
"~1",
[[[">=", "1.0.0"], ["<", "2.0.0"]]],
["1.3.0", "1.8.1"],
["2.1.0", "2.2"],
], # tilde increment the major
# wildcard
["*", [[[">=", "0.0.0"]]], ["1.0.0", "2.0.0"], []],
# ["1.*", [[[">=", "1.0.0"]]], ["1.0.0"], ["2", "1.0.1"]],
# ["1.2.*", [[[">=", "1.2.0"], ["<", "1.3.0"]]], ["1.2", "1.2.1"], ["2.1.0", "2.2"]],
# https://github.com/dtolnay/semver/blob/master/tests/test_version_req.rs :
["=1.0.0", [["=", "1.0.0"]], ["1.0.0"], ["1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"]],
["=0.9.0", [["=", "0.9.0"]], ["0.9.0"], ["0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]],
["=0.0.2", [["=", "0.0.2"]], ["0.0.2"], ["0.0.1", "0.0.3", "0.0.2-pre"]],
[
"=0.1.0-beta2.a",
[["=", "0.1.0-beta2.a"]],
["0.1.0-beta2.a"],
["0.9.1", "0.1.0", "0.1.1-beta2.a", "0.1.0-beta2"],
],
# ["=0.1.0+meta", [["=", "0.1.0+meta"]], ["0.1.0", "0.1.0+meta", "0.1.0+any"], []],
# ["<1.0.0", [["<", "1.0.0"]], ["0.1.0", "0.0.1"], ["1.0.0", "1.0.0-beta", "1.0.1", "0.9.9-alpha"]],
# [
# "<= 2.1.0-alpha2",
# [["<", "2.1.0-alpha2"], ["=", "2.1.0-alpha2"]],
# ["2.1.0-alpha2", "2.1.0-alpha1", "2.0.0", "1.0.0"],
# ["2.1.0", "2.2.0-alpha1", "2.0.0-alpha2", "1.0.0-alpha2"],
# ],
# [">1.0.0-alpha, <1.0.0", [[[">", "2.1.0-alpha2"], ["<", "1.0.0"]]], ["1.0.0-beta"], []],
# [">1.0.0-alpha, <1.0", [[[">", "1.0.0-alpha"], ["<", "1.0"]]], ["1.0.0-beta"], []],
# [">1.0.0-alpha, <1", [[[">", "1.0.0-alpha"], ["<", "1"]]], ["1.0.0-beta"], []],
# [
# ">=0.5.1-alpha3, <0.6",
# [[[">", "0.5.1-alpha3"], ["=", "0.5.1-alpha3"], ["<", "0.6"]]],
# ["0.5.1-alpha3", "0.5.1-alpha4", "0.5.1-beta", "0.5.1", "0.5.5"],
# ["0.5.1-alpha1", "0.5.2-alpha3", "0.5.5-pre", "0.5.0-pre"],
# ],
["~1", [], ["1.0.0", "1.0.1", "1.1.1"], ["0.9.1", "2.9.0", "0.0.9"]],
["~1.2", [], ["1.2.0", "1.2.1"], ["1.1.1", "1.3.0", "0.0.9"]],
["~1.2.2", [], ["1.2.2", "1.2.4"], ["1.2.1", "1.9.0", "1.0.9", "2.0.1", "0.1.3"]],
# [
# "~1.2.3-beta.2",
# [],
# ["1.2.3", "1.2.4", "1.2.3-beta.2", "1.2.3-beta.4"],
# ["1.3.3", "1.1.4", "1.2.3-beta.1", "1.2.4-beta.2"],
# ],
]

error_list = [
"> 0.1.0,",
"> 0.3.0, ,",
# "1.2.3 - 2.3.4",
# "> 0.0.9 <= 2.5.3",
# "=1.2.3 || =2.3.4",
# "1.1 || =1.2.3",
# "6.* || 8.* || >= 10.*",
# ">= >= 0.0.2",
# ">== 0.0.2",
# "a.0.0",
# "1.0.0-",
# ">=",
# "*.1",
# "1.*.1",
# ">=1.*.1",
# "*, 0.20.0-any",
# "0.20.0-any, *" "0.20.0-any, *, 1.0",
]


@pytest.mark.parametrize("version_range, conditions, versions_in, versions_out", values)
def test_range(version_range, conditions, versions_in, versions_out):
r = CargoVersionRange.from_native(version_range)
# TODO test Version Constraints

for v in versions_in:
assert CargoVersion(v) in r

for v in versions_out:
assert CargoVersion(v) not in r


def test_error():
for i in error_list:
with pytest.raises(InvalidVersionRange):
CargoVersionRange.from_native(i)

0 comments on commit d710d22

Please sign in to comment.