From d710d2216bd6d880780640edebaf0f95790bca3e Mon Sep 17 00:00:00 2001 From: ziad hany Date: Tue, 4 Apr 2023 17:31:44 +0200 Subject: [PATCH] Add support for CargoVersion and CargoVersionRange Signed-off-by: ziadhany --- src/univers/version_range.py | 56 ++++++++++- src/univers/versions.py | 6 ++ tests/test_cargo.py | 151 ++++++++++++++++++++++++++++++ tests/test_cargo_version_range.py | 98 +++++++++++++++++++ 4 files changed, 307 insertions(+), 4 deletions(-) create mode 100644 tests/test_cargo.py create mode 100644 tests/test_cargo_version_range.py diff --git a/src/univers/version_range.py b/src/univers/version_range.py index 37138aae..23e484cb 100644 --- a/src/univers/version_range.py +++ b/src/univers/version_range.py @@ -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): @@ -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 @@ -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. } @@ -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. } @@ -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): diff --git a/src/univers/versions.py b/src/univers/versions.py index 768a2c86..561d7f58 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -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 @@ -686,6 +687,10 @@ def bump(self, index): return self.value and self.value.bump(index) +class CargoVersion(SemverVersion): + pass + + AVAILABLE_VERSIONS = [ SemverVersion, GolangVersion, @@ -702,4 +707,5 @@ def bump(self, index): OpensslVersion, LegacyOpensslVersion, AlpineLinuxVersion, + CargoVersion, ] diff --git a/tests/test_cargo.py b/tests/test_cargo.py new file mode 100644 index 00000000..de186864 --- /dev/null +++ b/tests/test_cargo.py @@ -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")) diff --git a/tests/test_cargo_version_range.py b/tests/test_cargo_version_range.py new file mode 100644 index 00000000..4cab90b0 --- /dev/null +++ b/tests/test_cargo_version_range.py @@ -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)