diff --git a/src/poetry/core/constraints/generic/constraint.py b/src/poetry/core/constraints/generic/constraint.py index 7ec1d151e..cbe8d42f0 100644 --- a/src/poetry/core/constraints/generic/constraint.py +++ b/src/poetry/core/constraints/generic/constraint.py @@ -43,44 +43,72 @@ def operator(self) -> str: return self._operator def allows(self, other: BaseConstraint) -> bool: - if not isinstance(other, Constraint): - raise ValueError("Unimplemented comparison of constraints") + if not isinstance(other, Constraint) or other.operator != "==": + raise ValueError( + f"Invalid argument for allows" + f' ("other" must be a constraint with operator "=="): {other}' + ) is_equal_op = self._operator == "==" is_non_equal_op = self._operator == "!=" - is_other_equal_op = other.operator == "==" - is_other_non_equal_op = other.operator == "!=" - if is_equal_op and is_other_equal_op: + if is_equal_op: return self._value == other.value - if ( - is_equal_op - and is_other_non_equal_op - or is_non_equal_op - and is_other_equal_op - or is_non_equal_op - and is_other_non_equal_op - ): + if is_non_equal_op: return self._value != other.value return False def allows_all(self, other: BaseConstraint) -> bool: - if not isinstance(other, Constraint): - return other.is_empty() + from poetry.core.constraints.generic import MultiConstraint + from poetry.core.constraints.generic import UnionConstraint + + if isinstance(other, Constraint): + if other.operator == "==": + return self.allows(other) + + return self == other - return other == self + if isinstance(other, MultiConstraint): + return any(self.allows_all(c) for c in other.constraints) + + if isinstance(other, UnionConstraint): + return all(self.allows_all(c) for c in other.constraints) + + return other.is_empty() def allows_any(self, other: BaseConstraint) -> bool: + from poetry.core.constraints.generic import MultiConstraint + from poetry.core.constraints.generic import UnionConstraint + + is_equal_op = self._operator == "==" + is_non_equal_op = self._operator == "!=" + + if is_equal_op: + return other.allows(self) + if isinstance(other, Constraint): - is_non_equal_op = self._operator == "!=" + is_other_equal_op = other.operator == "==" is_other_non_equal_op = other.operator == "!=" - if is_non_equal_op and is_other_non_equal_op: + if is_other_equal_op: + return self.allows(other) + + if is_equal_op and is_other_non_equal_op: return self._value != other.value - return other.allows(self) + return is_non_equal_op and is_other_non_equal_op + + elif isinstance(other, MultiConstraint): + return is_non_equal_op + + elif isinstance(other, UnionConstraint): + return is_non_equal_op and any( + self.allows_any(c) for c in other.constraints + ) + + return other.is_any() def invert(self) -> Constraint: return Constraint(self._value, "!=" if self._operator == "==" else "==") diff --git a/src/poetry/core/constraints/generic/multi_constraint.py b/src/poetry/core/constraints/generic/multi_constraint.py index 0c9a96b63..401cff417 100644 --- a/src/poetry/core/constraints/generic/multi_constraint.py +++ b/src/poetry/core/constraints/generic/multi_constraint.py @@ -29,44 +29,27 @@ def allows(self, other: BaseConstraint) -> bool: return all(constraint.allows(other) for constraint in self._constraints) def allows_all(self, other: BaseConstraint) -> bool: - if other.is_any(): - return False - - if other.is_empty(): - return True - - if not isinstance(other, MultiConstraint): - return self.allows(other) - - our_constraints = iter(self._constraints) - their_constraints = iter(other.constraints) - our_constraint = next(our_constraints, None) - their_constraint = next(their_constraints, None) - - while our_constraint and their_constraint: - if our_constraint.allows_all(their_constraint): - their_constraint = next(their_constraints, None) - else: - our_constraint = next(our_constraints, None) + if isinstance(other, MultiConstraint): + return all(c in other.constraints for c in self._constraints) - return their_constraint is None + return all(c.allows_all(other) for c in self._constraints) def allows_any(self, other: BaseConstraint) -> bool: - if other.is_any(): - return True - - if other.is_empty(): - return True + from poetry.core.constraints.generic import UnionConstraint if isinstance(other, Constraint): - return self.allows(other) + if other.operator == "==": + return self.allows(other) - if isinstance(other, MultiConstraint): + return other.operator == "!=" + + if isinstance(other, UnionConstraint): return any( - c1.allows(c2) for c1 in self.constraints for c2 in other.constraints + all(c1.allows_any(c2) for c1 in self.constraints) + for c2 in other.constraints ) - return False + return isinstance(other, MultiConstraint) or other.is_any() def invert(self) -> UnionConstraint: from poetry.core.constraints.generic import UnionConstraint diff --git a/src/poetry/core/constraints/generic/union_constraint.py b/src/poetry/core/constraints/generic/union_constraint.py index cd19a6a68..ad64c15cf 100644 --- a/src/poetry/core/constraints/generic/union_constraint.py +++ b/src/poetry/core/constraints/generic/union_constraint.py @@ -24,47 +24,23 @@ def allows( return any(constraint.allows(other) for constraint in self._constraints) def allows_any(self, other: BaseConstraint) -> bool: - if other.is_empty(): - return False - - if other.is_any(): - return True - - if isinstance(other, (UnionConstraint, MultiConstraint)): - constraints = other.constraints - else: - constraints = (other,) + if isinstance(other, UnionConstraint): + return any( + c1.allows_any(c2) + for c1 in self._constraints + for c2 in other.constraints + ) - return any( - our_constraint.allows_any(their_constraint) - for our_constraint in self._constraints - for their_constraint in constraints - ) + return any(c.allows_any(other) for c in self._constraints) def allows_all(self, other: BaseConstraint) -> bool: - if other.is_any(): - return False - - if other.is_empty(): - return True - - if isinstance(other, (UnionConstraint, MultiConstraint)): - constraints = other.constraints - else: - constraints = (other,) - - our_constraints = iter(self._constraints) - their_constraints = iter(constraints) - our_constraint = next(our_constraints, None) - their_constraint = next(their_constraints, None) - - while our_constraint and their_constraint: - if our_constraint.allows_all(their_constraint): - their_constraint = next(their_constraints, None) - else: - our_constraint = next(our_constraints, None) + if isinstance(other, UnionConstraint): + return all( + any(c1.allows_all(c2) for c1 in self._constraints) + for c2 in other.constraints + ) - return their_constraint is None + return any(c.allows_all(other) for c in self._constraints) def invert(self) -> MultiConstraint: inverted_constraints = [c.invert() for c in self._constraints] diff --git a/tests/constraints/generic/test_constraint.py b/tests/constraints/generic/test_constraint.py index b9eb4fbad..6a5a2173f 100644 --- a/tests/constraints/generic/test_constraint.py +++ b/tests/constraints/generic/test_constraint.py @@ -15,41 +15,118 @@ from poetry.core.constraints.generic import BaseConstraint -def test_allows() -> None: - c = Constraint("win32") - - assert c.allows(Constraint("win32")) - assert not c.allows(Constraint("linux")) - - c = Constraint("win32", "!=") - - assert not c.allows(Constraint("win32")) - assert c.allows(Constraint("linux")) - - -def test_allows_any() -> None: - c = Constraint("win32") - - assert c.allows_any(Constraint("win32")) - assert not c.allows_any(Constraint("linux")) - assert c.allows_any(UnionConstraint(Constraint("win32"), Constraint("linux"))) - assert c.allows_any(Constraint("linux", "!=")) - - c = Constraint("win32", "!=") - - assert not c.allows_any(Constraint("win32")) - assert c.allows_any(Constraint("linux")) - assert c.allows_any(UnionConstraint(Constraint("win32"), Constraint("linux"))) - assert c.allows_any(Constraint("linux", "!=")) +@pytest.mark.parametrize( + ("constraint1", "constraint2", "expected"), + [ + (Constraint("win32"), Constraint("win32"), True), + (Constraint("win32"), Constraint("linux"), False), + (Constraint("win32", "!="), Constraint("win32"), False), + (Constraint("win32", "!="), Constraint("linux"), True), + ], +) +def test_allows( + constraint1: Constraint, constraint2: Constraint, expected: bool +) -> None: + assert constraint1.allows(constraint2) is expected -def test_allows_all() -> None: - c = Constraint("win32") - - assert c.allows_all(Constraint("win32")) - assert not c.allows_all(Constraint("linux")) - assert not c.allows_all(Constraint("linux", "!=")) - assert not c.allows_all(UnionConstraint(Constraint("win32"), Constraint("linux"))) +@pytest.mark.parametrize( + ("constraint1", "constraint2", "expected_any", "expected_all"), + [ + (Constraint("win32"), EmptyConstraint(), False, True), + (Constraint("win32"), AnyConstraint(), True, False), + (Constraint("win32"), Constraint("win32"), True, True), + (Constraint("win32"), Constraint("linux"), False, False), + (Constraint("win32"), Constraint("win32", "!="), False, False), + (Constraint("win32"), Constraint("linux", "!="), True, False), + ( + Constraint("win32"), + UnionConstraint(Constraint("win32"), Constraint("linux")), + True, + False, + ), + ( + Constraint("win32"), + UnionConstraint(Constraint("darwin"), Constraint("linux")), + False, + False, + ), + ( + Constraint("win32"), + UnionConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + Constraint("win32"), + UnionConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + Constraint("win32"), + MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + False, + False, + ), + ( + Constraint("win32"), + MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + (Constraint("win32", "!="), EmptyConstraint(), False, True), + (Constraint("win32", "!="), AnyConstraint(), True, False), + (Constraint("win32", "!="), Constraint("win32"), False, False), + (Constraint("win32", "!="), Constraint("linux"), True, True), + (Constraint("win32", "!="), Constraint("win32", "!="), True, True), + (Constraint("win32", "!="), Constraint("linux", "!="), True, False), + ( + Constraint("win32", "!="), + UnionConstraint(Constraint("win32"), Constraint("linux")), + True, + False, + ), + ( + Constraint("win32", "!="), + UnionConstraint(Constraint("darwin"), Constraint("linux")), + True, + True, + ), + ( + Constraint("win32", "!="), + UnionConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + Constraint("win32", "!="), + UnionConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + Constraint("win32", "!="), + MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + True, + True, + ), + ( + Constraint("win32", "!="), + MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ], +) +def test_allows_any_and_allows_all( + constraint1: Constraint, + constraint2: BaseConstraint, + expected_any: bool, + expected_all: bool, +) -> None: + assert constraint1.allows_any(constraint2) is expected_any + assert constraint1.allows_all(constraint2) is expected_all @pytest.mark.parametrize( diff --git a/tests/constraints/generic/test_multi_constraint.py b/tests/constraints/generic/test_multi_constraint.py index cbb6b679c..9391ff09a 100644 --- a/tests/constraints/generic/test_multi_constraint.py +++ b/tests/constraints/generic/test_multi_constraint.py @@ -1,7 +1,13 @@ from __future__ import annotations +import pytest + +from poetry.core.constraints.generic import AnyConstraint +from poetry.core.constraints.generic import BaseConstraint from poetry.core.constraints.generic import Constraint +from poetry.core.constraints.generic import EmptyConstraint from poetry.core.constraints.generic import MultiConstraint +from poetry.core.constraints.generic import UnionConstraint def test_allows() -> None: @@ -12,25 +18,83 @@ def test_allows() -> None: assert c.allows(Constraint("darwin")) -def test_allows_any() -> None: - c = MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")) - - assert c.allows_any(Constraint("darwin")) - assert c.allows_any(Constraint("darwin", "!=")) - assert not c.allows_any(Constraint("win32")) - assert c.allows_any(c) - assert c.allows_any( - MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!=")) - ) - - -def test_allows_all() -> None: +@pytest.mark.parametrize( + ("constraint", "expected_any", "expected_all"), + [ + (EmptyConstraint(), False, True), + (AnyConstraint(), True, False), + (Constraint("win32"), False, False), + (Constraint("linux"), False, False), + (Constraint("darwin"), True, True), + (Constraint("win32", "!="), True, False), + (Constraint("linux", "!="), True, False), + (Constraint("darwin", "!="), True, False), + ( + UnionConstraint(Constraint("win32"), Constraint("linux")), + False, + False, + ), + ( + UnionConstraint( + Constraint("win32"), Constraint("linux"), Constraint("darwin") + ), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin"), Constraint("linux")), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin"), Constraint("osx")), + True, + True, + ), + ( + UnionConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin", "!="), Constraint("osx", "!=")), + True, + False, + ), + ( + MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + True, + True, + ), + ( + MultiConstraint( + Constraint("win32", "!="), + Constraint("linux", "!="), + Constraint("darwin", "!="), + ), + True, + True, + ), + ( + MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + MultiConstraint(Constraint("darwin", "!="), Constraint("osx", "!=")), + True, + False, + ), + ], +) +def test_allows_any_and_allows_all( + constraint: BaseConstraint, expected_any: bool, expected_all: bool +) -> None: c = MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")) - - assert c.allows_all(Constraint("darwin")) - assert c.allows_all(Constraint("darwin", "!=")) - assert not c.allows_all(Constraint("win32")) - assert c.allows_all(c) - assert not c.allows_all( - MultiConstraint(Constraint("win32", "!="), Constraint("darwin", "!=")) - ) + assert c.allows_any(constraint) == expected_any + assert c.allows_all(constraint) == expected_all diff --git a/tests/constraints/generic/test_union_constraint.py b/tests/constraints/generic/test_union_constraint.py index 59d3a9d32..55624407c 100644 --- a/tests/constraints/generic/test_union_constraint.py +++ b/tests/constraints/generic/test_union_constraint.py @@ -1,6 +1,12 @@ from __future__ import annotations +import pytest + +from poetry.core.constraints.generic import AnyConstraint +from poetry.core.constraints.generic import BaseConstraint from poetry.core.constraints.generic import Constraint +from poetry.core.constraints.generic import EmptyConstraint +from poetry.core.constraints.generic import MultiConstraint from poetry.core.constraints.generic import UnionConstraint @@ -12,21 +18,83 @@ def test_allows() -> None: assert not c.allows(Constraint("darwin")) -def test_allows_any() -> None: - c = UnionConstraint(Constraint("win32"), Constraint("linux")) - - assert c.allows_any(c) - assert c.allows_any(UnionConstraint(Constraint("win32"), Constraint("darwin"))) - assert not c.allows_any(UnionConstraint(Constraint("linux2"), Constraint("darwin"))) - assert c.allows_any(Constraint("win32")) - assert not c.allows_any(Constraint("darwin")) - - -def test_allows_all() -> None: +@pytest.mark.parametrize( + ("constraint", "expected_any", "expected_all"), + [ + (EmptyConstraint(), False, True), + (AnyConstraint(), True, False), + (Constraint("win32"), True, True), + (Constraint("linux"), True, True), + (Constraint("darwin"), False, False), + (Constraint("win32", "!="), True, False), + (Constraint("linux", "!="), True, False), + (Constraint("darwin", "!="), True, False), + ( + UnionConstraint(Constraint("win32"), Constraint("linux")), + True, + True, + ), + ( + UnionConstraint( + Constraint("win32"), Constraint("linux"), Constraint("darwin") + ), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin"), Constraint("linux")), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin"), Constraint("osx")), + False, + False, + ), + ( + UnionConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + UnionConstraint(Constraint("darwin", "!="), Constraint("osx", "!=")), + True, + False, + ), + ( + MultiConstraint(Constraint("win32", "!="), Constraint("linux", "!=")), + False, + False, + ), + ( + MultiConstraint( + Constraint("win32", "!="), + Constraint("linux", "!="), + Constraint("darwin", "!="), + ), + False, + False, + ), + ( + MultiConstraint(Constraint("darwin", "!="), Constraint("linux", "!=")), + True, + False, + ), + ( + MultiConstraint(Constraint("darwin", "!="), Constraint("osx", "!=")), + True, + False, + ), + ], +) +def test_allows_any_and_allows_all( + constraint: BaseConstraint, expected_any: bool, expected_all: bool +) -> None: c = UnionConstraint(Constraint("win32"), Constraint("linux")) - - assert c.allows_all(c) - assert not c.allows_all(UnionConstraint(Constraint("win32"), Constraint("darwin"))) - assert not c.allows_all(UnionConstraint(Constraint("linux2"), Constraint("darwin"))) - assert c.allows_all(Constraint("win32")) - assert not c.allows_all(Constraint("darwin")) + assert c.allows_any(constraint) == expected_any + assert c.allows_all(constraint) == expected_all