Skip to content

Commit

Permalink
constraints: fix allows, allows_any and allows_all for generic constr…
Browse files Browse the repository at this point in the history
…aints and increase test coverage
  • Loading branch information
radoering committed May 31, 2024
1 parent 1459c57 commit e2f14b7
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 156 deletions.
66 changes: 47 additions & 19 deletions src/poetry/core/constraints/generic/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "==")
Expand Down
41 changes: 12 additions & 29 deletions src/poetry/core/constraints/generic/multi_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 13 additions & 37 deletions src/poetry/core/constraints/generic/union_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
143 changes: 110 additions & 33 deletions tests/constraints/generic/test_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading

0 comments on commit e2f14b7

Please sign in to comment.