Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

constraints: fix allows, allows_any and allows_all for generic constraints and increase test coverage #732

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading