diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c9342c68..82cc1d66 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +Next release +------------ + +- Fix ``VersionRange.invert()`` for enumerated ranges: a range made only of ``!=`` constraints now contains every version other than the excluded ones. https://github.com/aboutcode-org/univers/issues/214 + + Version v32.0.1 ---------------- diff --git a/src/univers/version_constraint.py b/src/univers/version_constraint.py index 2613a487..87c676df 100644 --- a/src/univers/version_constraint.py +++ b/src/univers/version_constraint.py @@ -517,9 +517,13 @@ def contains_version(version, constraints): # Split the constraint list in two sub lists: # a first list where the comparator is "=" or "!=" # a second list where the comparator is neither "=" nor "!=" + equality_constraints = constraints constraints = [c for c in constraints if c.comparator not in ("=", "!=")] if not constraints: - return False + # Only "=" and "!=" constraints are left. When all of them are "!=", the + # range is made of every version except the excluded ones, and the + # "tested version" was already checked against all of these above. + return all(c.comparator == "!=" for c in equality_constraints) # If we end up with constraints list contains only one item. if len(constraints) == 1: diff --git a/tests/test_version_range.py b/tests/test_version_range.py index b2cb3c4a..5715118a 100644 --- a/tests/test_version_range.py +++ b/tests/test_version_range.py @@ -458,3 +458,44 @@ def test_version_range_lexicographic(): assert LexicographicVersion(-123) in VersionRange.from_string("vers:lexicographic/<~") assert LexicographicVersion(None) in VersionRange.from_string("vers:lexicographic/*") assert LexicographicVersion("ABC") in VersionRange.from_string("vers:lexicographic/>abc|<=None") + + +def test_range_with_only_unequal_constraints_contains_all_other_versions(): + vers_range = VersionRange.from_string("vers:pypi/!=1.0.0|!=2.0.0") + version_class = vers_range.version_class + assert version_class("1.0.0") not in vers_range + assert version_class("2.0.0") not in vers_range + assert version_class("0.5.0") in vers_range + assert version_class("1.5.0") in vers_range + assert version_class("3.0.0") in vers_range + + +def test_invert_enumerated_range_is_the_complement(): + # https://github.com/aboutcode-org/univers/issues/214 + vers_range = VersionRange.from_string("vers:pypi/1.0.0|2.0.0") + inverse = vers_range.invert() + assert str(inverse) == "vers:pypi/!=1.0.0|!=2.0.0" + version_class = vers_range.version_class + for version in ("0.5.0", "1.0.0", "1.5.0", "2.0.0", "3.0.0"): + version = version_class(version) + assert (version in vers_range) != (version in inverse) + + +def test_invert_twice_of_enumerated_range_gives_the_same_range(): + vers_range = VersionRange.from_string("vers:gem/1.0|2.0|3.0") + assert vers_range.invert().invert() == vers_range + inverse = vers_range.invert() + version_class = vers_range.version_class + for version in ("0.1", "1.0", "1.5", "2.0", "2.5", "3.0", "4.0"): + version = version_class(version) + assert (version in vers_range) != (version in inverse) + + +def test_range_mixing_equal_and_unequal_constraints_only_contains_equal_versions(): + vers_range = VersionRange.from_string("vers:pypi/1.0.0|!=2.0.0") + version_class = vers_range.version_class + assert version_class("1.0.0") in vers_range + assert version_class("2.0.0") not in vers_range + assert version_class("0.5.0") not in vers_range + assert version_class("1.5.0") not in vers_range + assert version_class("3.0.0") not in vers_range