From baa1aaf498e61b4f88348a587aae175a41fdf7e3 Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Mon, 13 Mar 2023 00:14:41 +0530 Subject: [PATCH 1/3] Fix comparison in version subclasses Signed-off-by: Tushar Goel --- src/univers/versions.py | 127 ++++++++++--------------------- tests/test_pypi_version.py | 17 ++++- tests/test_version_comparison.py | 104 +++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 88 deletions(-) create mode 100644 tests/test_version_comparison.py diff --git a/src/univers/versions.py b/src/univers/versions.py index b950af06..c9e279c8 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -4,9 +4,6 @@ # # Visit https://aboutcode.org and https://github.com/nexB/univers for support and download. -import functools -from functools import total_ordering - import attr import semantic_version from packaging import version as packaging_version @@ -59,7 +56,7 @@ def is_valid_alpine_version(s): return str(i) == left -@attr.s(frozen=True, order=False, hash=True) +@attr.s(frozen=True, order=True, eq=True, hash=True) class Version: """ Base version mixin to subclass for each version syntax implementation. @@ -73,14 +70,16 @@ class Version: """ # the original string used to build this Version - string = attr.ib(type=str) + string = attr.ib(type=str, eq=False, order=False, hash=False) # the normalized string for this Version, stored without spaces and # lowercased. Any leading v is removed too. - normalized_string = attr.ib(type=str, default=None, repr=False) + normalized_string = attr.ib( + type=str, default=None, repr=False, eq=False, order=False, hash=False + ) # a comparable scheme-specific version object constructed from the version string - value = attr.ib(default=None, repr=False) + value = attr.ib(default=None, repr=False, eq=True, order=True, hash=True) def __attrs_post_init__(self): normalized_string = self.normalize(self.string) @@ -110,7 +109,7 @@ def normalize(cls, string): Return a normalized version string from ``string ``. Subclass can override. """ # FIXME: Is removing spaces and strip v the right thing to do? - return remove_spaces(string).rstrip("v ").strip() + return remove_spaces(string).lstrip("vV") @classmethod def build_value(self, string): @@ -133,33 +132,7 @@ def satisfies(self, constraint): def __str__(self): return str(self.value) - def __eq__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return self.value.__eq__(other.value) - - def __lt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return self.value.__lt__(other.value) - - def __gt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return self.value.__gt__(other.value) - def __le__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return self.value.__le__(other.value) - - def __ge__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return self.value.__ge__(other.value) - - -@attr.s(frozen=True, order=False, hash=True) class GenericVersion(Version): @classmethod def is_valid(cls, string): @@ -174,13 +147,13 @@ def is_valid(cls, string): return super(GenericVersion, cls).is_valid(string) -@attr.s(frozen=True, order=False, eq=False, hash=True) class PypiVersion(Version): """ Python PEP 440 version as implemented in packaging with fallback to "legacy" """ # TODO: ensure we deal with triple equal + # TODO: use packvers and handle legacy versions @classmethod def build_value(cls, string): @@ -199,10 +172,14 @@ def is_valid(cls, string): except packaging_version.InvalidVersion: return False - return False + +class EnhancedSemanticVersion(semantic_version.Version): + @property + def precedence_key(self): + key = super(EnhancedSemanticVersion, self).precedence_key + return key + (self.build or ()) -@attr.s(frozen=True, order=False, eq=False, hash=True) class SemverVersion(Version): """ Strict semver v2.0 with 3 segments. @@ -210,7 +187,7 @@ class SemverVersion(Version): @classmethod def build_value(cls, string): - return semantic_version.Version.coerce(string) + return EnhancedSemanticVersion.coerce(string) @classmethod def is_valid(cls, string): @@ -265,7 +242,6 @@ def is_even(s): return (int(s) % 2) == 0 -@attr.s(frozen=True, order=False, eq=False, hash=True) class NginxVersion(SemverVersion): """ Semver with 3 segments and extra attribute for stable vs. unstable branches @@ -279,7 +255,6 @@ def is_stable(self): return is_even(self.minor) -@attr.s(frozen=True, order=False, eq=False, hash=True) class RubygemsVersion(Version): """ Rubygems encourages semver version but does not enforce it. @@ -296,7 +271,6 @@ def is_valid(cls, string): return gem.GemVersion.is_correct(string) -@attr.s(frozen=True, order=False, eq=False, hash=True) class ArchLinuxVersion(Version): def __eq__(self, other): if not isinstance(other, self.__class__): @@ -324,7 +298,6 @@ def __ge__(self, other): return arch.vercmp(self.value, other.value) >= 0 -@attr.s(frozen=True, order=False, eq=False, hash=True) class DebianVersion(Version): @classmethod def build_value(cls, string): @@ -335,7 +308,6 @@ def is_valid(cls, string): return debian.Version.is_valid(string) -@attr.s(frozen=True, order=False, eq=False, hash=True) class MavenVersion(Version): # See https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html # https://github.com/apache/maven/tree/master/maven-artifact/src/main/java/org/apache/maven/artifact/versioning @@ -346,8 +318,6 @@ def build_value(cls, string): # We will use total ordering to sort the versions, since these versions also consider prereleases. -@attr.s(frozen=True, order=False, eq=False, hash=True) -@functools.total_ordering class NugetVersion(Version): # See https://docs.microsoft.com/en-us/nuget/concepts/package-versioning @@ -363,24 +333,10 @@ def is_valid(cls, string): except ValueError: return False - def __str__(self): - return str(self.string) - def __lt__(self, other): - return nuget.Version.from_string(self.string) < nuget.Version.from_string(other.string) - - def __eq__(self, other): - return nuget.Version.from_string(self.string) == nuget.Version.from_string(other.string) - - -@attr.s(frozen=True, order=False, eq=False, hash=True) class RpmVersion(Version): """ Represent an RPM version. - - For example:: - - # 1:1.1.4|>=2.8.16|<=2.8.16-z """ @classmethod @@ -388,8 +344,6 @@ def build_value(cls, string): return rpm.RpmVersion.from_string(string) -@total_ordering -@attr.s(frozen=True, order=False, eq=False, hash=True) class GentooVersion(Version): @classmethod def is_valid(cls, string): @@ -405,44 +359,30 @@ def __lt__(self, other): return NotImplemented return gentoo.vercmp(self.value, other.value) == -1 - -@attr.s(frozen=True, order=False, eq=False, hash=True) -class AlpineLinuxVersion(Version): - @classmethod - def is_valid(cls, string): - return is_valid_alpine_version(string) and gentoo.is_valid(string) - - def __eq__(self, other): + def __gt__(self, other): if not isinstance(other, self.__class__): return NotImplemented - return gentoo.vercmp(self.value, other.value) == 0 + return gentoo.vercmp(self.value, other.value) == 1 - def __lt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return gentoo.vercmp(self.value, other.value) < 0 - def __gt__(self, other): - if not isinstance(other, self.__class__): - return NotImplemented - return gentoo.vercmp(self.value, other.value) > 0 +class AlpineLinuxVersion(GentooVersion): + @classmethod + def is_valid(cls, string): + return is_valid_alpine_version(string) and gentoo.is_valid(string) -@attr.s(frozen=True, order=False, eq=False, hash=True) class ComposerVersion(SemverVersion): @classmethod def build_value(cls, string): - return semantic_version.Version.coerce(string.lstrip("vV")) + return super().build_value(string.lstrip("vV")) -@attr.s(frozen=True, order=False, eq=False, hash=True) class GolangVersion(SemverVersion): @classmethod def build_value(cls, string): - return semantic_version.Version.coerce(string.lstrip("vV")) + return super().build_value(string.lstrip("vV")) -@attr.s(frozen=True, order=False, eq=False, hash=True) class LegacyOpensslVersion(Version): """ Represent an Legacy Openssl Version. @@ -558,7 +498,6 @@ def is_prerelease(self): return self.patch.startswith(("-beta", "-alpha")) -@attr.s(frozen=True, order=False, eq=False, hash=True) class OpensslVersion(Version): """ Internally tracks two types of openssl versions @@ -654,7 +593,6 @@ def __ge__(self, other): @attr.s(frozen=True, order=False, eq=False, hash=True) -@total_ordering class ConanVersion(Version): @classmethod def build_value(cls, string): @@ -775,3 +713,22 @@ def __ge__(self, other): if not isinstance(other, ConanVersion): other = ConanVersion(str(other)) return self.value >= other.value + + +AVAILABLE_VERSIONS = [ + SemverVersion, + GolangVersion, + PypiVersion, + GenericVersion, + ComposerVersion, + NginxVersion, + ArchLinuxVersion, + DebianVersion, + RpmVersion, + MavenVersion, + NugetVersion, + GentooVersion, + OpensslVersion, + LegacyOpensslVersion, + AlpineLinuxVersion, +] diff --git a/tests/test_pypi_version.py b/tests/test_pypi_version.py index b33173d7..d4ade6da 100644 --- a/tests/test_pypi_version.py +++ b/tests/test_pypi_version.py @@ -8,7 +8,8 @@ from packaging import version as packaging_version -from univers import versions +from univers.versions import InvalidVersion +from univers.versions import PypiVersion # version comparison is already tested at: # https://github.com/pypa/packaging/blob/main/tests/test_version.py @@ -16,6 +17,16 @@ class TestPYPIVersion(TestCase): def test_constructor(self): - pypi_version = versions.PypiVersion("2.4.5") + pypi_version = PypiVersion("2.4.5") assert pypi_version.value == packaging_version.Version("2.4.5") - self.assertRaises(versions.InvalidVersion, versions.PypiVersion, "2.//////") + self.assertRaises(InvalidVersion, PypiVersion, "2.//////") + + def test_compare(self): + pypi_version = PypiVersion("2.4.5") + assert pypi_version == PypiVersion("2.4.5") + assert pypi_version != PypiVersion("2.4.6") + assert pypi_version > PypiVersion("2.4.4") + assert pypi_version >= PypiVersion("2.4.4") + assert pypi_version < PypiVersion("2.4.6") + assert pypi_version <= PypiVersion("2.4.6") + assert PypiVersion("2.4") == PypiVersion("2.4.0") diff --git a/tests/test_version_comparison.py b/tests/test_version_comparison.py new file mode 100644 index 00000000..00a7dfed --- /dev/null +++ b/tests/test_version_comparison.py @@ -0,0 +1,104 @@ +# +# Copyright (c) nexB Inc. and others. +# SPDX-License-Identifier: Apache-2.0 +# +# Visit https://aboutcode.org and https://github.com/nexB/univers for support and download. + +import pytest + +from univers import versions + + +def test_version_comparison_for_semver_edge_case(): + assert versions.SemverVersion("1.0.0") == versions.SemverVersion("1.0") + assert versions.SemverVersion("1.2.3+42") != versions.SemverVersion("1.2.3+23") + assert versions.SemverVersion("1.2.3+42") > versions.SemverVersion("1.2.3+23") + + +def test_version_comparison_for_golang_edge_case(): + assert versions.GolangVersion("1.0.0") == versions.GolangVersion("v1") + assert versions.GolangVersion("1.0.0") == versions.GolangVersion("v1.0") + assert versions.GolangVersion("1.0.0") == versions.GolangVersion("v1.0.0") + assert versions.GolangVersion("v1.0") > versions.GolangVersion("v0.3.0") + assert versions.GolangVersion("v0.1.0") < versions.GolangVersion("v0.3.0") + assert versions.GolangVersion("v0.1") != versions.GolangVersion("v0.1.1") + assert versions.GolangVersion("v0.1.1") >= versions.GolangVersion("v0.1") + assert versions.GolangVersion("v0.1.1") >= versions.GolangVersion("v0.1.1") + assert versions.GolangVersion("v0.1.1") <= versions.GolangVersion("v0.1.1") + assert versions.GolangVersion("v0.1.1") <= versions.GolangVersion("v0.1.2") + + +def test_version_comparison_for_pypi_edge_case(): + assert versions.PypiVersion("2.4") == versions.PypiVersion("2.4.0") + assert versions.PypiVersion("2.5") > versions.PypiVersion("2.4.0") + assert versions.PypiVersion("2.5") != versions.PypiVersion("2.4.0") + assert versions.PypiVersion("2.4") < versions.PypiVersion("2.5.0") + assert versions.PypiVersion("2.6") >= versions.PypiVersion("2.5.0") + assert versions.PypiVersion("2.3") <= versions.PypiVersion("2.5.0") + assert versions.PypiVersion("2.6") >= versions.PypiVersion("2.6.0") + assert versions.PypiVersion("2.5") <= versions.PypiVersion("2.5.0") + + +def test_version_comparison_for_golang_edge_case(): + assert versions.ComposerVersion("1.0.0") == versions.ComposerVersion("v1") + assert versions.ComposerVersion("1.0.0") == versions.ComposerVersion("v1.0") + assert versions.ComposerVersion("1.0.0") == versions.ComposerVersion("v1.0.0") + assert versions.ComposerVersion("v1.0") > versions.ComposerVersion("v0.3.0") + assert versions.ComposerVersion("v0.1.0") < versions.ComposerVersion("v0.3.0") + assert versions.ComposerVersion("v0.1") != versions.ComposerVersion("v0.1.1") + assert versions.ComposerVersion("v0.1.1") >= versions.ComposerVersion("v0.1") + assert versions.ComposerVersion("v0.1.1") >= versions.ComposerVersion("v0.1.1") + assert versions.ComposerVersion("v0.1.1") <= versions.ComposerVersion("v0.1.1") + assert versions.ComposerVersion("v0.1.1") <= versions.ComposerVersion("v0.1.2") + + +def test_version_comparison_for_nginx_edge_case(): + assert versions.NginxVersion("1.0.0") == versions.NginxVersion("1") + assert versions.NginxVersion("1.0.0") == versions.NginxVersion("1.0") + assert versions.NginxVersion("1.0.0") == versions.NginxVersion("1.0.0") + assert versions.NginxVersion("1.0") > versions.NginxVersion("0.3.0") + assert versions.NginxVersion("0.1.0") < versions.NginxVersion("0.3.0") + assert versions.NginxVersion("0.1") != versions.NginxVersion("0.1.1") + assert versions.NginxVersion("0.1.1") >= versions.NginxVersion("0.1") + assert versions.NginxVersion("0.1.1") >= versions.NginxVersion("0.1.1") + assert versions.NginxVersion("0.1.1") <= versions.NginxVersion("0.1.1") + assert versions.NginxVersion("0.1.1") <= versions.NginxVersion("0.1.2") + + +def test_version_comparison_for_gem_edge_case(): + assert versions.RubygemsVersion("1.3.0") == versions.RubygemsVersion("1.3") + assert versions.RubygemsVersion("1.3.0") <= versions.RubygemsVersion("1.3") + assert versions.RubygemsVersion("1.1.3") <= versions.RubygemsVersion("1.3") + assert versions.RubygemsVersion("1.4.pre") >= versions.RubygemsVersion("1.3") + assert versions.RubygemsVersion("1.4.pre") != versions.RubygemsVersion("1.4") + + +@pytest.mark.parametrize( + "version_class", + [ + versions.SemverVersion, + versions.GolangVersion, + versions.PypiVersion, + versions.GenericVersion, + versions.ComposerVersion, + versions.NginxVersion, + versions.ArchLinuxVersion, + versions.DebianVersion, + versions.RpmVersion, + versions.MavenVersion, + versions.NugetVersion, + versions.GentooVersion, + versions.OpensslVersion, + versions.LegacyOpensslVersion, + versions.AlpineLinuxVersion, + ], +) +def test_version_comparators_are_working(version_class): + assert version_class("1.0.0") == version_class("1.0.0") + assert version_class("1.0.0") != version_class("1.0.1") + assert version_class("1.0.0") < version_class("1.0.1") + assert version_class("1.0.1") > version_class("1.0.0") + assert version_class("1.0.0") <= version_class("1.0.1") + assert version_class("1.0.1") >= version_class("1.0.0") + assert version_class("1.0.0") <= version_class("1.0.0") + assert version_class("1.0.0") >= version_class("1.0.0") From b4dd2da824796022966c6e50c7436d8d8959f6df Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Mon, 13 Mar 2023 19:45:58 +0530 Subject: [PATCH 2/3] Adjust tests according to rebase Signed-off-by: Tushar Goel --- src/univers/versions.py | 37 ------------------- tests/data/conan_advisory.json | 2 +- .../openssl/openssl_versort_expected.json | 24 ++++++------ tests/test_conan_version_comparison.py | 12 +++--- tests/test_conan_version_range.py | 2 +- 5 files changed, 20 insertions(+), 57 deletions(-) diff --git a/src/univers/versions.py b/src/univers/versions.py index c9e279c8..0dfb6639 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -592,7 +592,6 @@ def __ge__(self, other): return isinstance(self.value, SemverVersion) -@attr.s(frozen=True, order=False, eq=False, hash=True) class ConanVersion(Version): @classmethod def build_value(cls, string): @@ -678,42 +677,6 @@ def next_patch(self): def bump(self, index): return self.value and self.value.bump(index) - def __eq__(self, other): - if other is None: - return False - if not isinstance(other, ConanVersion): - other = ConanVersion.build_value(other) - return self.value == other - return self.value == other.value - - def __lt__(self, other): - if other is None: - return False - if not isinstance(other, ConanVersion): - other = ConanVersion(str(other)) - return self.value < other.value - - def __le__(self, other): - if other is None: - return False - if not isinstance(other, ConanVersion): - other = ConanVersion(str(other)) - return self.value <= other.value - - def __gt__(self, other): - if other is None: - return False - if not isinstance(other, ConanVersion): - other = ConanVersion(str(other)) - return self.value > other.value - - def __ge__(self, other): - if other is None: - return False - if not isinstance(other, ConanVersion): - other = ConanVersion(str(other)) - return self.value >= other.value - AVAILABLE_VERSIONS = [ SemverVersion, diff --git a/tests/data/conan_advisory.json b/tests/data/conan_advisory.json index 007954d7..fc861a8d 100644 --- a/tests/data/conan_advisory.json +++ b/tests/data/conan_advisory.json @@ -2446,7 +2446,7 @@ "test_index": 350, "scheme": "conan", "native": ">=1.0.2 <=1.0.2v", - "expected_vers": "vers:conan/<=1.0.2|>=1.0.2", + "expected_vers": "vers:conan/>=1.0.2|<=1.0.2v", "gitlab_scheme": "conan" }, { diff --git a/tests/data/openssl/openssl_versort_expected.json b/tests/data/openssl/openssl_versort_expected.json index cb62a14e..3de46dfa 100644 --- a/tests/data/openssl/openssl_versort_expected.json +++ b/tests/data/openssl/openssl_versort_expected.json @@ -169,11 +169,6 @@ "normalized_string": "0.9.8", "value": "0.9.8" }, - { - "string": "0.9.8v", - "normalized_string": "0.9.8", - "value": "0.9.8" - }, { "string": "0.9.8a", "normalized_string": "0.9.8a", @@ -279,6 +274,11 @@ "normalized_string": "0.9.8u", "value": "0.9.8u" }, + { + "string": "0.9.8v", + "normalized_string": "0.9.8v", + "value": "0.9.8v" + }, { "string": "0.9.8w", "normalized_string": "0.9.8w", @@ -554,11 +554,6 @@ "normalized_string": "1.0.2", "value": "1.0.2" }, - { - "string": "1.0.2v", - "normalized_string": "1.0.2", - "value": "1.0.2" - }, { "string": "1.0.2a", "normalized_string": "1.0.2a", @@ -664,6 +659,11 @@ "normalized_string": "1.0.2u", "value": "1.0.2u" }, + { + "string": "1.0.2v", + "normalized_string": "1.0.2v", + "value": "1.0.2v" + }, { "string": "1.0.2w", "normalized_string": "1.0.2w", @@ -696,8 +696,8 @@ }, { "string": "1.0.2zc-dev", - "normalized_string": "1.0.2zc-de", - "value": "1.0.2zc-de" + "normalized_string": "1.0.2zc-dev", + "value": "1.0.2zc-dev" }, { "string": "1.0.2zd", diff --git a/tests/test_conan_version_comparison.py b/tests/test_conan_version_comparison.py index ccbe1eab..7e4da43d 100644 --- a/tests/test_conan_version_comparison.py +++ b/tests/test_conan_version_comparison.py @@ -63,12 +63,12 @@ def test_comparison(v1, v2): def test_comparison_with_integer(): v1 = ConanVersion("13.0") # Issue: https://github.com/conan-io/conan/issues/12907 - assert v1 > 5 - assert v1 >= 5 - assert v1 < 20 - assert v1 <= 20 - assert v1 == 13 - assert v1 != 14 + assert v1 > ConanVersion("5") + assert v1 >= ConanVersion("5") + assert v1 < ConanVersion("20") + assert v1 <= ConanVersion("20") + assert v1 == ConanVersion("13") + assert v1 != ConanVersion("14") e = [ diff --git a/tests/test_conan_version_range.py b/tests/test_conan_version_range.py index 5c559228..1bf7e69f 100644 --- a/tests/test_conan_version_range.py +++ b/tests/test_conan_version_range.py @@ -58,7 +58,7 @@ def test_range(version_range, conditions, versions_in, versions_out): for condition_set, expected_condition_set in zip(r.condition_sets, conditions): for condition, expected_condition in zip(condition_set.conditions, expected_condition_set): assert condition.operator == expected_condition[0] - assert condition.version == expected_condition[1] + assert condition.version == ConanVersion(expected_condition[1]) for v in versions_in: assert ConanVersion(v) in r From 279c8cd9e22e577b4a185a3d13c505c2611e5cca Mon Sep 17 00:00:00 2001 From: Tushar Goel Date: Tue, 14 Mar 2023 17:13:11 +0530 Subject: [PATCH 3/3] Add tests for versions in univers Signed-off-by: Tushar Goel --- src/univers/versions.py | 14 +- tests/test_version_comparison.py | 64 --------- tests/test_versions.py | 220 +++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 67 deletions(-) create mode 100644 tests/test_versions.py diff --git a/src/univers/versions.py b/src/univers/versions.py index 0dfb6639..768a2c86 100644 --- a/src/univers/versions.py +++ b/src/univers/versions.py @@ -218,13 +218,13 @@ def build(self): return self.value and self.value.build def next_major(self): - return self.value and self.value.next_major() + return self.value and SemverVersion(str(self.value.next_major())) def next_minor(self): - return self.value and self.value.next_minor() + return self.value and SemverVersion(str(self.value.next_minor())) def next_patch(self): - return self.value and self.value.next_patch() + return self.value and SemverVersion(str(self.value.next_patch())) def is_even(s): @@ -316,6 +316,14 @@ class MavenVersion(Version): def build_value(cls, string): return maven.Version(string) + @classmethod + def is_valid(cls, string): + try: + cls.build_value(string) + return True + except ValueError: + return False + # We will use total ordering to sort the versions, since these versions also consider prereleases. class NugetVersion(Version): diff --git a/tests/test_version_comparison.py b/tests/test_version_comparison.py index 00a7dfed..b77843db 100644 --- a/tests/test_version_comparison.py +++ b/tests/test_version_comparison.py @@ -9,70 +9,6 @@ from univers import versions -def test_version_comparison_for_semver_edge_case(): - assert versions.SemverVersion("1.0.0") == versions.SemverVersion("1.0") - assert versions.SemverVersion("1.2.3+42") != versions.SemverVersion("1.2.3+23") - assert versions.SemverVersion("1.2.3+42") > versions.SemverVersion("1.2.3+23") - - -def test_version_comparison_for_golang_edge_case(): - assert versions.GolangVersion("1.0.0") == versions.GolangVersion("v1") - assert versions.GolangVersion("1.0.0") == versions.GolangVersion("v1.0") - assert versions.GolangVersion("1.0.0") == versions.GolangVersion("v1.0.0") - assert versions.GolangVersion("v1.0") > versions.GolangVersion("v0.3.0") - assert versions.GolangVersion("v0.1.0") < versions.GolangVersion("v0.3.0") - assert versions.GolangVersion("v0.1") != versions.GolangVersion("v0.1.1") - assert versions.GolangVersion("v0.1.1") >= versions.GolangVersion("v0.1") - assert versions.GolangVersion("v0.1.1") >= versions.GolangVersion("v0.1.1") - assert versions.GolangVersion("v0.1.1") <= versions.GolangVersion("v0.1.1") - assert versions.GolangVersion("v0.1.1") <= versions.GolangVersion("v0.1.2") - - -def test_version_comparison_for_pypi_edge_case(): - assert versions.PypiVersion("2.4") == versions.PypiVersion("2.4.0") - assert versions.PypiVersion("2.5") > versions.PypiVersion("2.4.0") - assert versions.PypiVersion("2.5") != versions.PypiVersion("2.4.0") - assert versions.PypiVersion("2.4") < versions.PypiVersion("2.5.0") - assert versions.PypiVersion("2.6") >= versions.PypiVersion("2.5.0") - assert versions.PypiVersion("2.3") <= versions.PypiVersion("2.5.0") - assert versions.PypiVersion("2.6") >= versions.PypiVersion("2.6.0") - assert versions.PypiVersion("2.5") <= versions.PypiVersion("2.5.0") - - -def test_version_comparison_for_golang_edge_case(): - assert versions.ComposerVersion("1.0.0") == versions.ComposerVersion("v1") - assert versions.ComposerVersion("1.0.0") == versions.ComposerVersion("v1.0") - assert versions.ComposerVersion("1.0.0") == versions.ComposerVersion("v1.0.0") - assert versions.ComposerVersion("v1.0") > versions.ComposerVersion("v0.3.0") - assert versions.ComposerVersion("v0.1.0") < versions.ComposerVersion("v0.3.0") - assert versions.ComposerVersion("v0.1") != versions.ComposerVersion("v0.1.1") - assert versions.ComposerVersion("v0.1.1") >= versions.ComposerVersion("v0.1") - assert versions.ComposerVersion("v0.1.1") >= versions.ComposerVersion("v0.1.1") - assert versions.ComposerVersion("v0.1.1") <= versions.ComposerVersion("v0.1.1") - assert versions.ComposerVersion("v0.1.1") <= versions.ComposerVersion("v0.1.2") - - -def test_version_comparison_for_nginx_edge_case(): - assert versions.NginxVersion("1.0.0") == versions.NginxVersion("1") - assert versions.NginxVersion("1.0.0") == versions.NginxVersion("1.0") - assert versions.NginxVersion("1.0.0") == versions.NginxVersion("1.0.0") - assert versions.NginxVersion("1.0") > versions.NginxVersion("0.3.0") - assert versions.NginxVersion("0.1.0") < versions.NginxVersion("0.3.0") - assert versions.NginxVersion("0.1") != versions.NginxVersion("0.1.1") - assert versions.NginxVersion("0.1.1") >= versions.NginxVersion("0.1") - assert versions.NginxVersion("0.1.1") >= versions.NginxVersion("0.1.1") - assert versions.NginxVersion("0.1.1") <= versions.NginxVersion("0.1.1") - assert versions.NginxVersion("0.1.1") <= versions.NginxVersion("0.1.2") - - -def test_version_comparison_for_gem_edge_case(): - assert versions.RubygemsVersion("1.3.0") == versions.RubygemsVersion("1.3") - assert versions.RubygemsVersion("1.3.0") <= versions.RubygemsVersion("1.3") - assert versions.RubygemsVersion("1.1.3") <= versions.RubygemsVersion("1.3") - assert versions.RubygemsVersion("1.4.pre") >= versions.RubygemsVersion("1.3") - assert versions.RubygemsVersion("1.4.pre") != versions.RubygemsVersion("1.4") - - @pytest.mark.parametrize( "version_class", [ diff --git a/tests/test_versions.py b/tests/test_versions.py new file mode 100644 index 00000000..ab5e414e --- /dev/null +++ b/tests/test_versions.py @@ -0,0 +1,220 @@ +# +# Copyright (c) nexB Inc. and others. +# SPDX-License-Identifier: Apache-2.0 +# +# Visit https://aboutcode.org and https://github.com/nexB/univers for support and download. + +from univers.version_constraint import VersionConstraint +from univers.versions import AlpineLinuxVersion +from univers.versions import ArchLinuxVersion +from univers.versions import ComposerVersion +from univers.versions import DebianVersion +from univers.versions import EnhancedSemanticVersion +from univers.versions import GentooVersion +from univers.versions import GolangVersion +from univers.versions import MavenVersion +from univers.versions import NginxVersion +from univers.versions import NugetVersion +from univers.versions import PypiVersion +from univers.versions import RpmVersion +from univers.versions import RubygemsVersion +from univers.versions import SemverVersion +from univers.versions import Version + + +def test_version(): + assert Version("1.2.3") == Version("1.2.3") + assert Version("1.2.3") != Version("1.2.4") + assert Version.is_valid("1.2.3") + assert not Version.is_valid(None) + assert Version.normalize("v1.2.3") == "1.2.3" + assert Version("1.2.3").satisfies(VersionConstraint(comparator=">=", version=Version("1.2.3"))) + + +def test_pypi_version(): + assert PypiVersion("1.2.3") == PypiVersion("1.2.3") + assert PypiVersion("1.2.3") != PypiVersion("1.2.4") + assert PypiVersion("1") == PypiVersion("1.0") + assert PypiVersion.is_valid("1.2.3") + assert not PypiVersion.is_valid("1.2.3a-1-a") + assert PypiVersion.normalize("v1.2.3") == "1.2.3" + assert PypiVersion("1").satisfies( + VersionConstraint(comparator=">=", version=PypiVersion("1.0.0")) + ) + assert PypiVersion("2.4") == PypiVersion("2.4.0") + assert PypiVersion("2.5") > PypiVersion("2.4.0") + assert PypiVersion("2.5") != PypiVersion("2.4.0") + assert PypiVersion("2.4") < PypiVersion("2.5.0") + assert PypiVersion("2.6") >= PypiVersion("2.5.0") + assert PypiVersion("2.3") <= PypiVersion("2.5.0") + assert PypiVersion("2.6") >= PypiVersion("2.6.0") + assert PypiVersion("2.5") <= PypiVersion("2.5.0") + + +def test_enhanced_semantic_version(): + assert EnhancedSemanticVersion("1.2.3-pre.1+build.1") != EnhancedSemanticVersion( + "1.2.3-pre.1+build.0" + ) + assert EnhancedSemanticVersion("1.2.3-pre.1+build.1") > EnhancedSemanticVersion( + "1.2.3-pre.1+build.0" + ) + assert ( + EnhancedSemanticVersion("1.2.3-pre.1+build.1").precedence_key + != EnhancedSemanticVersion("1.2.3-pre.1+build.0").precedence_key + ) + + +def test_semver_version(): + assert SemverVersion.is_valid("1.2.3") + assert SemverVersion.is_valid("1.2.3-pre.1+build.1") + version = SemverVersion("1.2.3-pre.1+build.1") + assert version.major == 1 + assert version.minor == 2 + assert version.patch == 3 + assert version.prerelease == ("pre", "1") + assert version.build == ("build", "1") + assert version.next_major() == SemverVersion("2.0.0") + assert version.next_minor() == SemverVersion("1.3.0") + assert version.next_patch() == SemverVersion("1.2.3") + assert SemverVersion("1.2.3").next_patch() == SemverVersion("1.2.4") + assert version.satisfies( + VersionConstraint(comparator=">=", version=SemverVersion("1.2.3-pre.1+build.1")) + ) + assert SemverVersion("1.0.0") == SemverVersion("1.0") + assert SemverVersion("1.2.3+42") != SemverVersion("1.2.3+23") + assert SemverVersion("1.2.3+42") > SemverVersion("1.2.3+23") + + +def test_nginx_version(): + assert NginxVersion("1.2").is_stable + assert NginxVersion("1.2.3").is_stable + assert not NginxVersion("1.3.0").is_stable + assert NginxVersion("1.0.0") == NginxVersion("1") + assert NginxVersion("1.0.0") == NginxVersion("1.0") + assert NginxVersion("1.0.0") == NginxVersion("1.0.0") + assert NginxVersion("1.0") > NginxVersion("0.3.0") + assert NginxVersion("0.1.0") < NginxVersion("0.3.0") + assert NginxVersion("0.1") != NginxVersion("0.1.1") + assert NginxVersion("0.1.1") >= NginxVersion("0.1") + assert NginxVersion("0.1.1") >= NginxVersion("0.1.1") + assert NginxVersion("0.1.1") <= NginxVersion("0.1.1") + assert NginxVersion("0.1.1") <= NginxVersion("0.1.2") + + +def test_rubygems_version(): + assert RubygemsVersion("1.2.0") == RubygemsVersion("1.2") + assert RubygemsVersion.is_valid("1.2ab") + assert RubygemsVersion.is_valid("1.2.3.a.b.c.d") + assert not RubygemsVersion.is_valid("aa") + assert RubygemsVersion("1.3.0") == RubygemsVersion("1.3") + assert RubygemsVersion("1.3.0") <= RubygemsVersion("1.3") + assert RubygemsVersion("1.1.3") <= RubygemsVersion("1.3") + assert RubygemsVersion("1.4.pre") >= RubygemsVersion("1.3") + assert RubygemsVersion("1.4.pre") != RubygemsVersion("1.4") + + +def test_arch_linux_version(): + assert ArchLinuxVersion("1.2.3-1") == ArchLinuxVersion("1.2.3-1") + assert ArchLinuxVersion("1.2.3-1") != ArchLinuxVersion("1.2.3-2") + assert ArchLinuxVersion("1.2.3-1") > ArchLinuxVersion("1.2.3-0") + assert ArchLinuxVersion("1.2.3-1") < ArchLinuxVersion("1.2.3-2") + assert ArchLinuxVersion("1.2.3-1") >= ArchLinuxVersion("1.2.3-1") + assert ArchLinuxVersion("1.2.3-1") <= ArchLinuxVersion("1.2.3-1") + + +def test_debian_version(): + assert DebianVersion("1.2.3-1") == DebianVersion("1.2.3-1") + assert not DebianVersion.is_valid("n") + assert DebianVersion.is_valid("1.2.3-1") + assert DebianVersion("1.2.3-1") == DebianVersion("1.2.3-1") + assert DebianVersion("1.2.3-1") != DebianVersion("1.2.3-2") + assert DebianVersion("1.2.3-1") > DebianVersion("1.2.3-0") + assert DebianVersion("1.2.3-1") < DebianVersion("1.2.3-2") + assert DebianVersion("1.2.3-1") >= DebianVersion("1.2.3-1") + assert DebianVersion("1.2.3-1") <= DebianVersion("1.2.3-1") + + +def test_maven_version(): + assert MavenVersion("1.2.3") == MavenVersion("1.2.3") + assert MavenVersion("1.2.3") != MavenVersion("1.2.4") + assert MavenVersion("1") == MavenVersion("1.0") + assert MavenVersion.is_valid("1.2.3") + assert MavenVersion("1.2.3") == MavenVersion("1.2.3") + assert MavenVersion("1.2.3") != MavenVersion("1.2.4") + assert MavenVersion("1.2.3") > MavenVersion("1.2.2") + assert MavenVersion("1.2.3") < MavenVersion("1.2.4") + assert MavenVersion("1.2.3") >= MavenVersion("1.2.3") + assert MavenVersion("1.2.3") <= MavenVersion("1.2.3") + + +def test_nuget_version(): + assert NugetVersion("1.2.3") == NugetVersion("1.2.3") + assert NugetVersion("1.2.3") != NugetVersion("1.2.4") + assert NugetVersion("1") == NugetVersion("1.0") + assert NugetVersion.is_valid("1.2.3") + assert not NugetVersion.is_valid("1.2.3a-1-a") + + +def test_rpm_version(): + assert RpmVersion("1.2.3-1") == RpmVersion("1.2.3-1") + assert RpmVersion("1.2.3-1") != RpmVersion("1.2.3-2") + assert RpmVersion("1.2.3-1") > RpmVersion("1.2.3-0") + assert RpmVersion("1.2.3-1") < RpmVersion("1.2.3-2") + assert RpmVersion("1.2.3-1") >= RpmVersion("1.2.3-1") + assert RpmVersion("1.2.3-1") <= RpmVersion("1.2.3-1") + + +def test_gentoo_version(): + assert GentooVersion("1.2.3") == GentooVersion("1.2.3") + assert GentooVersion("1.2.3") != GentooVersion("1.2.4") + assert GentooVersion.is_valid("1.2.3") + assert not GentooVersion.is_valid("1.2.3a-1-a") + + +def test_alpine_linux_version(): + assert AlpineLinuxVersion("1.2.3-r1") == AlpineLinuxVersion("1.2.3-r1") + assert AlpineLinuxVersion("1.2.3-r1") != AlpineLinuxVersion("1.2.3-r2") + assert AlpineLinuxVersion("1.2.3-r1") > AlpineLinuxVersion("1.2.3-r0") + assert AlpineLinuxVersion("1.2.3-r1") < AlpineLinuxVersion("1.2.3-r2") + assert AlpineLinuxVersion("1.2.3-r1") >= AlpineLinuxVersion("1.2.3-r1") + assert AlpineLinuxVersion("1.2.3-r1") <= AlpineLinuxVersion("1.2.3-r1") + assert AlpineLinuxVersion.is_valid("1.2.3-r1") + assert not AlpineLinuxVersion.is_valid("007") + + +def test_composer_version(): + assert ComposerVersion("1.2.3") == ComposerVersion("1.2.3") + assert ComposerVersion("1.2.3") != ComposerVersion("1.2.4") + assert ComposerVersion("1") == ComposerVersion("1.0") + assert ComposerVersion("v1.0") == ComposerVersion("1.0") + assert ComposerVersion.is_valid("1.2.3") + assert ComposerVersion.is_valid("1.2.3a-1-a") + assert ComposerVersion("1.0.0") == ComposerVersion("v1") + assert ComposerVersion("1.0.0") == ComposerVersion("v1.0") + assert ComposerVersion("1.0.0") == ComposerVersion("v1.0.0") + assert ComposerVersion("v1.0") > ComposerVersion("v0.3.0") + assert ComposerVersion("v0.1.0") < ComposerVersion("v0.3.0") + assert ComposerVersion("v0.1") != ComposerVersion("v0.1.1") + assert ComposerVersion("v0.1.1") >= ComposerVersion("v0.1") + assert ComposerVersion("v0.1.1") >= ComposerVersion("v0.1.1") + assert ComposerVersion("v0.1.1") <= ComposerVersion("v0.1.1") + assert ComposerVersion("v0.1.1") <= ComposerVersion("v0.1.2") + + +def test_golang_version(): + assert GolangVersion("1.2.3") == GolangVersion("1.2.3") + assert GolangVersion("1.2.3") != GolangVersion("1.2.4") + assert GolangVersion("1") == GolangVersion("1.0") + assert GolangVersion("v1.0") == GolangVersion("1.0") + assert GolangVersion.is_valid("1.2.3") + assert GolangVersion.is_valid("1.2.3a-1-a") + assert GolangVersion("1.0.0") == GolangVersion("v1") + assert GolangVersion("1.0.0") == GolangVersion("v1.0") + assert GolangVersion("1.0.0") == GolangVersion("v1.0.0") + assert GolangVersion("v1.0") > GolangVersion("v0.3.0") + assert GolangVersion("v0.1.0") < GolangVersion("v0.3.0") + assert GolangVersion("v0.1") != GolangVersion("v0.1.1") + assert GolangVersion("v0.1.1") >= GolangVersion("v0.1") + assert GolangVersion("v0.1.1") >= GolangVersion("v0.1.1") + assert GolangVersion("v0.1.1") <= GolangVersion("v0.1.1") + assert GolangVersion("v0.1.1") <= GolangVersion("v0.1.2")