# This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. import itertools import operator import pytest from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet from packaging.version import Version, parse from .test_version import VERSIONS LEGACY_SPECIFIERS = [ "==2.1.0.3", "!=2.2.0.5", "<=5", ">=7.9a1", "<1.0.dev1", ">2.0.post1", ] SPECIFIERS = [ "~=2.0", "==2.1.*", "==2.1.0.3", "!=2.2.*", "!=2.2.0.5", "<=5", ">=7.9a1", "<1.0.dev1", ">2.0.post1", ] class TestSpecifier: @pytest.mark.parametrize("specifier", SPECIFIERS) def test_specifiers_valid(self, specifier): Specifier(specifier) @pytest.mark.parametrize( "specifier", [ # Operator-less specifier "2.0", # Invalid operator "=>2.0", # Version-less specifier "==", # Local segment on operators which don't support them "~=1.0+5", ">=1.0+deadbeef", "<=1.0+abc123", ">1.0+watwat", "<1.0+1.0", # Prefix matching on operators which don't support them "~=1.0.*", ">=1.0.*", "<=1.0.*", ">1.0.*", "<1.0.*", # Combination of local and prefix matching on operators which do # support one or the other "==1.0.*+5", "!=1.0.*+deadbeef", # Prefix matching cannot be used with a pre-release, post-release, # dev or local version "==2.0a1.*", "!=2.0a1.*", "==2.0.post1.*", "!=2.0.post1.*", "==2.0.dev1.*", "!=2.0.dev1.*", "==1.0+5.*", "!=1.0+deadbeef.*", # Prefix matching must appear at the end "==1.0.*.5", # Compatible operator requires 2 digits in the release operator "~=1", # Cannot use a prefix matching after a .devN version "==1.0.dev1.*", "!=1.0.dev1.*", ], ) def test_specifiers_invalid(self, specifier): with pytest.raises(InvalidSpecifier): Specifier(specifier) @pytest.mark.parametrize( "version", [ # Various development release incarnations "1.0dev", "1.0.dev", "1.0dev1", "1.0dev", "1.0-dev", "1.0-dev1", "1.0DEV", "1.0.DEV", "1.0DEV1", "1.0DEV", "1.0.DEV1", "1.0-DEV", "1.0-DEV1", # Various alpha incarnations "1.0a", "1.0.a", "1.0.a1", "1.0-a", "1.0-a1", "1.0alpha", "1.0.alpha", "1.0.alpha1", "1.0-alpha", "1.0-alpha1", "1.0A", "1.0.A", "1.0.A1", "1.0-A", "1.0-A1", "1.0ALPHA", "1.0.ALPHA", "1.0.ALPHA1", "1.0-ALPHA", "1.0-ALPHA1", # Various beta incarnations "1.0b", "1.0.b", "1.0.b1", "1.0-b", "1.0-b1", "1.0beta", "1.0.beta", "1.0.beta1", "1.0-beta", "1.0-beta1", "1.0B", "1.0.B", "1.0.B1", "1.0-B", "1.0-B1", "1.0BETA", "1.0.BETA", "1.0.BETA1", "1.0-BETA", "1.0-BETA1", # Various release candidate incarnations "1.0c", "1.0.c", "1.0.c1", "1.0-c", "1.0-c1", "1.0rc", "1.0.rc", "1.0.rc1", "1.0-rc", "1.0-rc1", "1.0C", "1.0.C", "1.0.C1", "1.0-C", "1.0-C1", "1.0RC", "1.0.RC", "1.0.RC1", "1.0-RC", "1.0-RC1", # Various post release incarnations "1.0post", "1.0.post", "1.0post1", "1.0post", "1.0-post", "1.0-post1", "1.0POST", "1.0.POST", "1.0POST1", "1.0POST", "1.0.POST1", "1.0-POST", "1.0-POST1", "1.0-5", # Local version case insensitivity "1.0+AbC" # Integer Normalization "1.01", "1.0a05", "1.0b07", "1.0c056", "1.0rc09", "1.0.post000", "1.1.dev09000", "00!1.2", "0100!0.0", # Various other normalizations "v1.0", " \r \f \v v1.0\t\n", ], ) def test_specifiers_normalized(self, version): if "+" not in version: ops = ["~=", "==", "!=", "<=", ">=", "<", ">"] else: ops = ["==", "!="] for op in ops: Specifier(op + version) @pytest.mark.parametrize( ("specifier", "expected"), [ # Single item specifiers should just be reflexive ("!=2.0", "!=2.0"), ("<2.0", "<2.0"), ("<=2.0", "<=2.0"), ("==2.0", "==2.0"), (">2.0", ">2.0"), (">=2.0", ">=2.0"), ("~=2.0", "~=2.0"), # Spaces should be removed ("< 2", "<2"), ], ) def test_specifiers_str_and_repr(self, specifier, expected): spec = Specifier(specifier) assert str(spec) == expected assert repr(spec) == f"" @pytest.mark.parametrize("specifier", SPECIFIERS) def test_specifiers_hash(self, specifier): assert hash(Specifier(specifier)) == hash(Specifier(specifier)) @pytest.mark.parametrize( ("left", "right", "op"), itertools.chain( * # Verify that the equal (==) operator works correctly [[(x, x, operator.eq) for x in SPECIFIERS]] + # Verify that the not equal (!=) operator works correctly [ [(x, y, operator.ne) for j, y in enumerate(SPECIFIERS) if i != j] for i, x in enumerate(SPECIFIERS) ] ), ) def test_comparison_true(self, left, right, op): assert op(Specifier(left), Specifier(right)) assert op(left, Specifier(right)) assert op(Specifier(left), right) @pytest.mark.parametrize(("left", "right"), [("==2.8.0", "==2.8")]) def test_comparison_canonicalizes(self, left, right): assert Specifier(left) == Specifier(right) assert left == Specifier(right) assert Specifier(left) == right @pytest.mark.parametrize( ("left", "right", "op"), itertools.chain( * # Verify that the equal (==) operator works correctly [[(x, x, operator.ne) for x in SPECIFIERS]] + # Verify that the not equal (!=) operator works correctly [ [(x, y, operator.eq) for j, y in enumerate(SPECIFIERS) if i != j] for i, x in enumerate(SPECIFIERS) ] ), ) def test_comparison_false(self, left, right, op): assert not op(Specifier(left), Specifier(right)) assert not op(left, Specifier(right)) assert not op(Specifier(left), right) def test_comparison_non_specifier(self): assert Specifier("==1.0") != 12 assert not Specifier("==1.0") == 12 assert Specifier("==1.0") != "12" assert not Specifier("==1.0") == "12" @pytest.mark.parametrize( ("version", "spec", "expected"), [ (v, s, True) for v, s in [ # Test the equality operation ("2.0", "==2"), ("2.0", "==2.0"), ("2.0", "==2.0.0"), ("2.0+deadbeef", "==2"), ("2.0+deadbeef", "==2.0"), ("2.0+deadbeef", "==2.0.0"), ("2.0+deadbeef", "==2+deadbeef"), ("2.0+deadbeef", "==2.0+deadbeef"), ("2.0+deadbeef", "==2.0.0+deadbeef"), ("2.0+deadbeef.0", "==2.0.0+deadbeef.00"), # Test the equality operation with a prefix ("2.dev1", "==2.*"), ("2a1", "==2.*"), ("2a1.post1", "==2.*"), ("2b1", "==2.*"), ("2b1.dev1", "==2.*"), ("2c1", "==2.*"), ("2c1.post1.dev1", "==2.*"), ("2c1.post1.dev1", "==2.0.*"), ("2rc1", "==2.*"), ("2rc1", "==2.0.*"), ("2", "==2.*"), ("2", "==2.0.*"), ("2", "==0!2.*"), ("0!2", "==2.*"), ("2.0", "==2.*"), ("2.0.0", "==2.*"), ("2.1+local.version", "==2.1.*"), # Test the in-equality operation ("2.1", "!=2"), ("2.1", "!=2.0"), ("2.0.1", "!=2"), ("2.0.1", "!=2.0"), ("2.0.1", "!=2.0.0"), ("2.0", "!=2.0+deadbeef"), # Test the in-equality operation with a prefix ("2.0", "!=3.*"), ("2.1", "!=2.0.*"), # Test the greater than equal operation ("2.0", ">=2"), ("2.0", ">=2.0"), ("2.0", ">=2.0.0"), ("2.0.post1", ">=2"), ("2.0.post1.dev1", ">=2"), ("3", ">=2"), # Test the less than equal operation ("2.0", "<=2"), ("2.0", "<=2.0"), ("2.0", "<=2.0.0"), ("2.0.dev1", "<=2"), ("2.0a1", "<=2"), ("2.0a1.dev1", "<=2"), ("2.0b1", "<=2"), ("2.0b1.post1", "<=2"), ("2.0c1", "<=2"), ("2.0c1.post1.dev1", "<=2"), ("2.0rc1", "<=2"), ("1", "<=2"), # Test the greater than operation ("3", ">2"), ("2.1", ">2.0"), ("2.0.1", ">2"), ("2.1.post1", ">2"), ("2.1+local.version", ">2"), # Test the less than operation ("1", "<2"), ("2.0", "<2.1"), ("2.0.dev0", "<2.1"), # Test the compatibility operation ("1", "~=1.0"), ("1.0.1", "~=1.0"), ("1.1", "~=1.0"), ("1.9999999", "~=1.0"), ("1.1", "~=1.0a1"), ("2022.01.01", "~=2022.01.01"), # Test that epochs are handled sanely ("2!1.0", "~=2!1.0"), ("2!1.0", "==2!1.*"), ("2!1.0", "==2!1.0"), ("2!1.0", "!=1.0"), ("2!1.0.0", "==2!1.0.*"), ("2!1.0.0", "==2!1.*"), ("1.0", "!=2!1.0"), ("1.0", "<=2!0.1"), ("2!1.0", ">=2.0"), ("1.0", "<2!0.1"), ("2!1.0", ">2.0"), # Test some normalization rules ("2.0.5", ">2.0dev"), ] ] + [ (v, s, False) for v, s in [ # Test the equality operation ("2.1", "==2"), ("2.1", "==2.0"), ("2.1", "==2.0.0"), ("2.0", "==2.0+deadbeef"), # Test the equality operation with a prefix ("2.0", "==3.*"), ("2.1", "==2.0.*"), # Test the in-equality operation ("2.0", "!=2"), ("2.0", "!=2.0"), ("2.0", "!=2.0.0"), ("2.0+deadbeef", "!=2"), ("2.0+deadbeef", "!=2.0"), ("2.0+deadbeef", "!=2.0.0"), ("2.0+deadbeef", "!=2+deadbeef"), ("2.0+deadbeef", "!=2.0+deadbeef"), ("2.0+deadbeef", "!=2.0.0+deadbeef"), ("2.0+deadbeef.0", "!=2.0.0+deadbeef.00"), # Test the in-equality operation with a prefix ("2.dev1", "!=2.*"), ("2a1", "!=2.*"), ("2a1.post1", "!=2.*"), ("2b1", "!=2.*"), ("2b1.dev1", "!=2.*"), ("2c1", "!=2.*"), ("2c1.post1.dev1", "!=2.*"), ("2c1.post1.dev1", "!=2.0.*"), ("2rc1", "!=2.*"), ("2rc1", "!=2.0.*"), ("2", "!=2.*"), ("2", "!=2.0.*"), ("2.0", "!=2.*"), ("2.0.0", "!=2.*"), # Test the greater than equal operation ("2.0.dev1", ">=2"), ("2.0a1", ">=2"), ("2.0a1.dev1", ">=2"), ("2.0b1", ">=2"), ("2.0b1.post1", ">=2"), ("2.0c1", ">=2"), ("2.0c1.post1.dev1", ">=2"), ("2.0rc1", ">=2"), ("1", ">=2"), # Test the less than equal operation ("2.0.post1", "<=2"), ("2.0.post1.dev1", "<=2"), ("3", "<=2"), # Test the greater than operation ("1", ">2"), ("2.0.dev1", ">2"), ("2.0a1", ">2"), ("2.0a1.post1", ">2"), ("2.0b1", ">2"), ("2.0b1.dev1", ">2"), ("2.0c1", ">2"), ("2.0c1.post1.dev1", ">2"), ("2.0rc1", ">2"), ("2.0", ">2"), ("2.0.post1", ">2"), ("2.0.post1.dev1", ">2"), ("2.0+local.version", ">2"), # Test the less than operation ("2.0.dev1", "<2"), ("2.0a1", "<2"), ("2.0a1.post1", "<2"), ("2.0b1", "<2"), ("2.0b2.dev1", "<2"), ("2.0c1", "<2"), ("2.0c1.post1.dev1", "<2"), ("2.0rc1", "<2"), ("2.0", "<2"), ("2.post1", "<2"), ("2.post1.dev1", "<2"), ("3", "<2"), # Test the compatibility operation ("2.0", "~=1.0"), ("1.1.0", "~=1.0.0"), ("1.1.post1", "~=1.0.0"), # Test that epochs are handled sanely ("1.0", "~=2!1.0"), ("2!1.0", "~=1.0"), ("2!1.0", "==1.0"), ("1.0", "==2!1.0"), ("2!1.0", "==1.*"), ("1.0", "==2!1.*"), ("2!1.0", "!=2!1.0"), ] ], ) def test_specifiers(self, version, spec, expected): spec = Specifier(spec, prereleases=True) if expected: # Test that the plain string form works assert version in spec assert spec.contains(version) # Test that the version instance form works assert Version(version) in spec assert spec.contains(Version(version)) else: # Test that the plain string form works assert version not in spec assert not spec.contains(version) # Test that the version instance form works assert Version(version) not in spec assert not spec.contains(Version(version)) @pytest.mark.parametrize( ("version", "spec", "expected"), [ ("1.0.0", "===1.0", False), ("1.0.dev0", "===1.0", False), # Test identity comparison by itself ("1.0", "===1.0", True), ("1.0.dev0", "===1.0.dev0", True), ], ) def test_specifiers_identity(self, version, spec, expected): spec = Specifier(spec) if expected: # Identity comparisons only support the plain string form assert version in spec else: # Identity comparisons only support the plain string form assert version not in spec @pytest.mark.parametrize( ("specifier", "expected"), [ ("==1.0", False), (">=1.0", False), ("<=1.0", False), ("~=1.0", False), ("<1.0", False), (">1.0", False), ("<1.0.dev1", False), (">1.0.dev1", False), ("==1.0.*", False), ("==1.0.dev1", True), (">=1.0.dev1", True), ("<=1.0.dev1", True), ("~=1.0.dev1", True), ], ) def test_specifier_prereleases_detection(self, specifier, expected): assert Specifier(specifier).prereleases == expected @pytest.mark.parametrize( ("specifier", "version", "expected"), [ (">=1.0", "2.0.dev1", False), (">=2.0.dev1", "2.0a1", True), ("==2.0.*", "2.0a1.dev1", False), ("<=2.0", "1.0.dev1", False), ("<=2.0.dev1", "1.0a1", True), ], ) def test_specifiers_prereleases(self, specifier, version, expected): spec = Specifier(specifier) if expected: assert version in spec spec.prereleases = False assert version not in spec else: assert version not in spec spec.prereleases = True assert version in spec @pytest.mark.parametrize( ("specifier", "prereleases", "input", "expected"), [ (">=1.0", None, ["2.0a1"], ["2.0a1"]), (">=1.0.dev1", None, ["1.0", "2.0a1"], ["1.0", "2.0a1"]), (">=1.0.dev1", False, ["1.0", "2.0a1"], ["1.0"]), ], ) def test_specifier_filter(self, specifier, prereleases, input, expected): spec = Specifier(specifier) kwargs = {"prereleases": prereleases} if prereleases is not None else {} assert list(spec.filter(input, **kwargs)) == expected @pytest.mark.parametrize( ("spec", "op"), [ ("~=2.0", "~="), ("==2.1.*", "=="), ("==2.1.0.3", "=="), ("!=2.2.*", "!="), ("!=2.2.0.5", "!="), ("<=5", "<="), (">=7.9a1", ">="), ("<1.0.dev1", "<"), (">2.0.post1", ">"), # === is an escape hatch in PEP 440 ("===lolwat", "==="), ], ) def test_specifier_operator_property(self, spec, op): assert Specifier(spec).operator == op @pytest.mark.parametrize( ("spec", "version"), [ ("~=2.0", "2.0"), ("==2.1.*", "2.1.*"), ("==2.1.0.3", "2.1.0.3"), ("!=2.2.*", "2.2.*"), ("!=2.2.0.5", "2.2.0.5"), ("<=5", "5"), (">=7.9a1", "7.9a1"), ("<1.0.dev1", "1.0.dev1"), (">2.0.post1", "2.0.post1"), # === is an escape hatch in PEP 440 ("===lolwat", "lolwat"), ], ) def test_specifier_version_property(self, spec, version): assert Specifier(spec).version == version @pytest.mark.parametrize( ("spec", "expected_length"), [("", 0), ("==2.0", 1), (">=2.0", 1), (">=2.0,<3", 2), (">=2.0,<3,==2.4", 3)], ) def test_length(self, spec, expected_length): spec = SpecifierSet(spec) assert len(spec) == expected_length @pytest.mark.parametrize( ("spec", "expected_items"), [ ("", []), ("==2.0", ["==2.0"]), (">=2.0", [">=2.0"]), (">=2.0,<3", [">=2.0", "<3"]), (">=2.0,<3,==2.4", [">=2.0", "<3", "==2.4"]), ], ) def test_iteration(self, spec, expected_items): spec = SpecifierSet(spec) items = {str(item) for item in spec} assert items == set(expected_items) def test_specifier_equal_for_compatible_operator(self): assert Specifier("~=1.18.0") != Specifier("~=1.18") def test_specifier_hash_for_compatible_operator(self): assert hash(Specifier("~=1.18.0")) != hash(Specifier("~=1.18")) class TestSpecifierSet: @pytest.mark.parametrize("version", VERSIONS) def test_empty_specifier(self, version): spec = SpecifierSet(prereleases=True) assert version in spec assert spec.contains(version) assert parse(version) in spec assert spec.contains(parse(version)) def test_specifier_prereleases_explicit(self): spec = SpecifierSet() assert not spec.prereleases assert "1.0.dev1" not in spec assert not spec.contains("1.0.dev1") spec.prereleases = True assert spec.prereleases assert "1.0.dev1" in spec assert spec.contains("1.0.dev1") spec = SpecifierSet(prereleases=True) assert spec.prereleases assert "1.0.dev1" in spec assert spec.contains("1.0.dev1") spec.prereleases = False assert not spec.prereleases assert "1.0.dev1" not in spec assert not spec.contains("1.0.dev1") spec = SpecifierSet(prereleases=True) assert spec.prereleases assert "1.0.dev1" in spec assert spec.contains("1.0.dev1") spec.prereleases = None assert not spec.prereleases assert "1.0.dev1" not in spec assert not spec.contains("1.0.dev1") def test_specifier_contains_prereleases(self): spec = SpecifierSet() assert spec.prereleases is None assert not spec.contains("1.0.dev1") assert spec.contains("1.0.dev1", prereleases=True) spec = SpecifierSet(prereleases=True) assert spec.prereleases assert spec.contains("1.0.dev1") assert not spec.contains("1.0.dev1", prereleases=False) def test_specifier_contains_installed_prereleases(self): spec = SpecifierSet("~=1.0") assert not spec.contains("1.0.0.dev1", installed=True) assert spec.contains("1.0.0.dev1", prereleases=True, installed=True) spec = SpecifierSet("~=1.0", prereleases=True) assert spec.contains("1.0.0.dev1", installed=True) assert not spec.contains("1.0.0.dev1", prereleases=False, installed=False) @pytest.mark.parametrize( ("specifier", "specifier_prereleases", "prereleases", "input", "expected"), [ # General test of the filter method ("", None, None, ["1.0", "2.0a1"], ["1.0"]), (">=1.0.dev1", None, None, ["1.0", "2.0a1"], ["1.0", "2.0a1"]), ("", None, None, ["1.0a1"], ["1.0a1"]), ("", None, None, ["1.0", Version("2.0")], ["1.0", Version("2.0")]), # Test overriding with the prereleases parameter on filter ("", None, False, ["1.0a1"], []), (">=1.0.dev1", None, False, ["1.0", "2.0a1"], ["1.0"]), ("", None, True, ["1.0", "2.0a1"], ["1.0", "2.0a1"]), # Test overriding with the overall specifier ("", True, None, ["1.0", "2.0a1"], ["1.0", "2.0a1"]), ("", False, None, ["1.0", "2.0a1"], ["1.0"]), (">=1.0.dev1", True, None, ["1.0", "2.0a1"], ["1.0", "2.0a1"]), (">=1.0.dev1", False, None, ["1.0", "2.0a1"], ["1.0"]), ("", True, None, ["1.0a1"], ["1.0a1"]), ("", False, None, ["1.0a1"], []), ], ) def test_specifier_filter( self, specifier_prereleases, specifier, prereleases, input, expected ): if specifier_prereleases is None: spec = SpecifierSet(specifier) else: spec = SpecifierSet(specifier, prereleases=specifier_prereleases) kwargs = {"prereleases": prereleases} if prereleases is not None else {} assert list(spec.filter(input, **kwargs)) == expected @pytest.mark.parametrize( ("specifier", "expected"), [ # Single item specifiers should just be reflexive ("!=2.0", "!=2.0"), ("<2.0", "<2.0"), ("<=2.0", "<=2.0"), ("==2.0", "==2.0"), (">2.0", ">2.0"), (">=2.0", ">=2.0"), ("~=2.0", "~=2.0"), # Spaces should be removed ("< 2", "<2"), # Multiple item specifiers should work ("!=2.0,>1.0", "!=2.0,>1.0"), ("!=2.0 ,>1.0", "!=2.0,>1.0"), ], ) def test_specifiers_str_and_repr(self, specifier, expected): spec = SpecifierSet(specifier) assert str(spec) == expected assert repr(spec) == f"" @pytest.mark.parametrize("specifier", SPECIFIERS + LEGACY_SPECIFIERS) def test_specifiers_hash(self, specifier): assert hash(SpecifierSet(specifier)) == hash(SpecifierSet(specifier)) @pytest.mark.parametrize( ("left", "right", "expected"), [(">2.0", "<5.0", ">2.0,<5.0")] ) def test_specifiers_combine(self, left, right, expected): result = SpecifierSet(left) & SpecifierSet(right) assert result == SpecifierSet(expected) result = SpecifierSet(left) & right assert result == SpecifierSet(expected) result = SpecifierSet(left, prereleases=True) & SpecifierSet(right) assert result == SpecifierSet(expected) assert result.prereleases result = SpecifierSet(left, prereleases=False) & SpecifierSet(right) assert result == SpecifierSet(expected) assert not result.prereleases result = SpecifierSet(left) & SpecifierSet(right, prereleases=True) assert result == SpecifierSet(expected) assert result.prereleases result = SpecifierSet(left) & SpecifierSet(right, prereleases=False) assert result == SpecifierSet(expected) assert not result.prereleases result = SpecifierSet(left, prereleases=True) & SpecifierSet( right, prereleases=True ) assert result == SpecifierSet(expected) assert result.prereleases result = SpecifierSet(left, prereleases=False) & SpecifierSet( right, prereleases=False ) assert result == SpecifierSet(expected) assert not result.prereleases with pytest.raises(ValueError): result = SpecifierSet(left, prereleases=True) & SpecifierSet( right, prereleases=False ) with pytest.raises(ValueError): result = SpecifierSet(left, prereleases=False) & SpecifierSet( right, prereleases=True ) def test_specifiers_combine_not_implemented(self): with pytest.raises(TypeError): SpecifierSet() & 12 @pytest.mark.parametrize( ("left", "right", "op"), itertools.chain( * # Verify that the equal (==) operator works correctly [[(x, x, operator.eq) for x in SPECIFIERS]] + # Verify that the not equal (!=) operator works correctly [ [(x, y, operator.ne) for j, y in enumerate(SPECIFIERS) if i != j] for i, x in enumerate(SPECIFIERS) ] ), ) def test_comparison_true(self, left, right, op): assert op(SpecifierSet(left), SpecifierSet(right)) assert op(SpecifierSet(left), Specifier(right)) assert op(Specifier(left), SpecifierSet(right)) assert op(left, SpecifierSet(right)) assert op(SpecifierSet(left), right) @pytest.mark.parametrize( ("left", "right", "op"), itertools.chain( * # Verify that the equal (==) operator works correctly [[(x, x, operator.ne) for x in SPECIFIERS]] + # Verify that the not equal (!=) operator works correctly [ [(x, y, operator.eq) for j, y in enumerate(SPECIFIERS) if i != j] for i, x in enumerate(SPECIFIERS) ] ), ) def test_comparison_false(self, left, right, op): assert not op(SpecifierSet(left), SpecifierSet(right)) assert not op(SpecifierSet(left), Specifier(right)) assert not op(Specifier(left), SpecifierSet(right)) assert not op(left, SpecifierSet(right)) assert not op(SpecifierSet(left), right) @pytest.mark.parametrize(("left", "right"), [("==2.8.0", "==2.8")]) def test_comparison_canonicalizes(self, left, right): assert SpecifierSet(left) == SpecifierSet(right) assert left == SpecifierSet(right) assert SpecifierSet(left) == right def test_comparison_non_specifier(self): assert SpecifierSet("==1.0") != 12 assert not SpecifierSet("==1.0") == 12 @pytest.mark.parametrize( ("version", "specifier", "expected"), [ ("1.0.0+local", "==1.0.0", True), ("1.0.0+local", "!=1.0.0", False), ("1.0.0+local", "<=1.0.0", True), ("1.0.0+local", ">=1.0.0", True), ("1.0.0+local", "<1.0.0", False), ("1.0.0+local", ">1.0.0", False), ], ) def test_comparison_ignores_local(self, version, specifier, expected): assert (Version(version) in SpecifierSet(specifier)) == expected def test_contains_with_compatible_operator(self): combination = SpecifierSet("~=1.18.0") & SpecifierSet("~=1.18") assert "1.19.5" not in combination assert "1.18.0" in combination