Skip to content

Commit 2bff41a

Browse files
committed
version: fix local label comparison
1 parent ed9ac8c commit 2bff41a

File tree

2 files changed

+121
-45
lines changed

2 files changed

+121
-45
lines changed

src/poetry/core/constraints/version/version_range.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,25 @@ def is_any(self) -> bool:
5858
def is_simple(self) -> bool:
5959
return self._min is None or self._max is None
6060

61+
@property
62+
def is_inclusive(self) -> bool:
63+
return self._include_min or self.include_max
64+
65+
@property
66+
def is_exclusive(self) -> bool:
67+
return not self.is_inclusive
68+
6169
def allows(self, other: Version) -> bool:
6270
if self._min is not None:
6371
_this, _other = self.allowed_min, other
6472

6573
assert _this is not None
6674

67-
if not _this.is_postrelease() and _other.is_postrelease():
75+
if (
76+
self.is_exclusive
77+
and not _this.is_postrelease()
78+
and _other.is_postrelease()
79+
):
6880
# The exclusive ordered comparison >V MUST NOT allow a post-release
6981
# of the given version unless V itself is a post release.
7082
# https://peps.python.org/pep-0440/#exclusive-ordered-comparison

tests/constraints/version/test_version_range.py

Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -106,50 +106,114 @@ def test_allows_post_releases_with_min(base: Version, other: Version) -> None:
106106
assert range.allows(other)
107107

108108

109-
def test_allows_post_releases_with_post_and_local_min() -> None:
110-
one = Version.parse("3.0.0+local.1")
111-
two = Version.parse("3.0.0-1")
112-
three = Version.parse("3.0.0-1+local.1")
113-
four = Version.parse("3.0.0+local.2")
114-
115-
assert not VersionRange(min=one, include_min=True).allows(two)
116-
assert VersionRange(min=one, include_min=True).allows(three)
117-
assert VersionRange(min=one, include_min=True).allows(four)
118-
119-
assert not VersionRange(min=two, include_min=True).allows(one)
120-
assert VersionRange(min=two, include_min=True).allows(three)
121-
assert not VersionRange(min=two, include_min=True).allows(four)
122-
123-
assert not VersionRange(min=three, include_min=True).allows(one)
124-
assert not VersionRange(min=three, include_min=True).allows(two)
125-
assert not VersionRange(min=three, include_min=True).allows(four)
126-
127-
assert not VersionRange(min=four, include_min=True).allows(one)
128-
assert not VersionRange(min=four, include_min=True).allows(two)
129-
assert not VersionRange(min=four, include_min=True).allows(three)
130-
131-
132-
def test_allows_post_releases_with_post_and_local_max() -> None:
133-
one = Version.parse("3.0.0+local.1")
134-
two = Version.parse("3.0.0-1")
135-
three = Version.parse("3.0.0-1+local.1")
136-
four = Version.parse("3.0.0+local.2")
137-
138-
assert not VersionRange(max=one, include_max=True).allows(two)
139-
assert not VersionRange(max=one, include_max=True).allows(three)
140-
assert not VersionRange(max=one, include_max=True).allows(four)
141-
142-
assert VersionRange(max=two, include_max=True).allows(one)
143-
assert VersionRange(max=two, include_max=True).allows(three)
144-
assert VersionRange(max=two, include_max=True).allows(four)
145-
146-
assert VersionRange(max=three, include_max=True).allows(one)
147-
assert VersionRange(max=three, include_max=True).allows(two)
148-
assert VersionRange(max=three, include_max=True).allows(four)
149-
150-
assert VersionRange(max=four, include_max=True).allows(one)
151-
assert not VersionRange(max=four, include_max=True).allows(two)
152-
assert not VersionRange(max=four, include_max=True).allows(three)
109+
@pytest.mark.parametrize(
110+
("version", "check_version", "allowed"),
111+
[
112+
("3.0.0+local.1", "3.0.0-1", True),
113+
("3.0.0+local.1", "3.0.0-1+local.1", True),
114+
("3.0.0+local.1", "3.0.0+local.2", True),
115+
("3.0.0+local.1", "4.0.0+local.2", True),
116+
("3.0.0-1", "3.0.0+local.1", False),
117+
("3.0.0-1", "3.0.0-1+local.1", True),
118+
("3.0.0-1", "3.0.0+local.2", False),
119+
("3.0.0-1", "4.0.0+local.2", True),
120+
("3.0.0-1+local.1", "3.0.0+local.1", False),
121+
("3.0.0-1+local.1", "3.0.0-1", False),
122+
("3.0.0-1+local.1", "3.0.0-2", True),
123+
("3.0.0-1+local.1", "3.0.0+local.2", False),
124+
("3.0.0-1+local.1", "4.0.0+local.2", True),
125+
("3.0.0+local.2", "3.0.0+local.1", False),
126+
("3.0.0+local.2", "3.0.0-1", True),
127+
("3.0.0+local.2", "3.0.0-1+local.1", True),
128+
("3.0.0+local.2", "4.0.0+local.2", True),
129+
("4.0.0+local.2", "3.0.0+local.1", False),
130+
("4.0.0+local.2", "3.0.0-1", False),
131+
("4.0.0+local.2", "3.0.0-1+local.1", False),
132+
("4.0.0+local.2", "3.0.0+local.2", False),
133+
],
134+
)
135+
def test_allows_post_releases_with_post_and_local_min(
136+
version: str, check_version: str, allowed: bool
137+
) -> None:
138+
result = VersionRange(min=Version.parse(version), include_min=True).allows(
139+
Version.parse(check_version)
140+
)
141+
assert (allowed and result) or not (allowed or result)
142+
143+
144+
@pytest.mark.parametrize(
145+
("version", "check_version", "allowed"),
146+
[
147+
("3.0.0+local.1", "3.0.0-1", False),
148+
("3.0.0+local.1", "3.0.0-1+local.1", False),
149+
("3.0.0+local.1", "3.0.0+local.2", False),
150+
("3.0.0+local.1", "4.0.0+local.2", False),
151+
("3.0.0-1", "3.0.0+local.1", True),
152+
("3.0.0-1", "3.0.0-1+local.1", True),
153+
("3.0.0-1", "3.0.0+local.2", True),
154+
("3.0.0-1", "4.0.0+local.2", False),
155+
("3.0.0-1+local.1", "3.0.0+local.1", True),
156+
("3.0.0-1+local.1", "3.0.0-1", True),
157+
("3.0.0-1+local.1", "3.0.0+local.2", True),
158+
("3.0.0-1+local.1", "4.0.0+local.2", False),
159+
("3.0.0+local.2", "3.0.0+local.1", True),
160+
("3.0.0+local.2", "3.0.0-1", False),
161+
("3.0.0+local.2", "3.0.0-1+local.1", False),
162+
("3.0.0+local.2", "4.0.0+local.2", False),
163+
("4.0.0+local.2", "3.0.0+local.1", True),
164+
("4.0.0+local.2", "3.0.0-1", True),
165+
("4.0.0+local.2", "3.0.0-1+local.1", True),
166+
("4.0.0+local.2", "3.0.0+local.2", True),
167+
],
168+
)
169+
def test_allows_post_releases_with_post_and_local_max(
170+
version: str, check_version: str, allowed: bool
171+
) -> None:
172+
assert (
173+
VersionRange(max=Version.parse(version), include_max=True).allows(
174+
Version.parse(check_version)
175+
)
176+
== allowed
177+
)
178+
179+
180+
@pytest.mark.parametrize(
181+
("constraint", "check_version", "allowed"),
182+
[
183+
("==3.0.0", "3.0.0+local.1", True),
184+
("==3.0.0+local2", "3.0.0+local.1", False),
185+
(">=3.0.0+local.1", "3.0.0+local.1", True),
186+
(">=3.0.0+local.1", "3.0.0+local.2", True),
187+
(">=3.0.0+local.2", "3.0.0+local.1", False),
188+
(">=3.0.0+cuda", "3.0.0+cuda", True),
189+
(">=3.0.0+cpu", "3.0.0+cuda", True), # cuda > cpu (lexicographically)
190+
(">1.7", "1.7.1", True),
191+
(">1.7", "1.7.0.post1", False),
192+
(">1.7.post2", "1.7.1", True),
193+
(">1.7.post2", "1.7.0.post3", True),
194+
(">1.7.post2", "1.7.0", False),
195+
("<1.7.0", "1.7.0.rc1", False),
196+
(">1.7.0", "1.7.0+local.1", False),
197+
("<1.7.0", "1.7.0+local.1", False),
198+
("<=1.7.0", "1.7.0+local.1", True),
199+
(">=3.0.0+local.1", "3.0.0", False),
200+
(">=3.0.0+local.1", "3.0.0+local.1", True),
201+
(">=3.0.0+local.1", "3.0.0+local.2", True),
202+
(">=3.0.0+local.1", "3.0.0-1", True),
203+
(">3.0.0+local.1", "3.0.0", False),
204+
(">3.0.0+local.1", "3.0.0+local.1", False),
205+
(">3.0.0+local.1", "3.0.0+local.2", True),
206+
(">3.0.0+local.1", "3.0.0-1", False),
207+
("<=3.0.0+local.1", "3.0.0", True),
208+
("<=3.0.0+local.1", "3.0.0+local.1", True),
209+
("<=3.0.0+local.1", "3.0.0+local.0", True),
210+
("<=3.0.0+local.1", "3.0.0-1", False),
211+
],
212+
)
213+
def test_allows_with_local_version_identifier(
214+
constraint: str, check_version: str, allowed: bool
215+
) -> None:
216+
assert parse_constraint(constraint).allows(Version.parse(check_version)) == allowed
153217

154218

155219
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)