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

Fixes unstable next breaking version when major is 0 #475

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
20 changes: 8 additions & 12 deletions src/poetry/core/semver/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
@dataclasses.dataclass(frozen=True)
class Version(PEP440Version, VersionRangeConstraint):
"""
A parsed semantic version number.
A version constraint representing a single version.
"""

@property
Expand All @@ -32,21 +32,17 @@ def stable(self) -> Version:
if self.is_stable():
return self

return self.next_patch()
post = self.post if self.pre is None else None
return Version(release=self.release, post=post, epoch=self.epoch)

def next_breaking(self) -> Version:
if self.major == 0:
if self.minor is not None and self.minor != 0:
return self.next_minor()
if self.major > 0 or self.minor is None:
return self.stable.next_major()

if self.precision == 1:
return self.next_major()
elif self.precision == 2:
return self.next_minor()
if self.minor > 0 or self.patch is None:
return self.stable.next_minor()

return self.next_patch()

return self.stable.next_major()
return self.stable.next_patch()

@property
def min(self) -> Version:
Expand Down
33 changes: 33 additions & 0 deletions tests/semver/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,30 @@ def test_parse_constraint_tilde(input: str, constraint: VersionRange) -> None:
Version.from_parts(0, 0, 3), Version.from_parts(0, 0, 4), True
),
),
(
"^0.0.3-alpha.21",
VersionRange(
Version.from_parts(0, 0, 3, pre=ReleaseTag("alpha", 21)),
Version.from_parts(0, 0, 4),
True,
),
),
(
"^0.1.3-alpha.21",
VersionRange(
Version.from_parts(0, 1, 3, pre=ReleaseTag("alpha", 21)),
Version.from_parts(0, 2, 0),
True,
),
),
(
"^0.0.0-alpha.21",
VersionRange(
Version.from_parts(0, 0, 0, pre=ReleaseTag("alpha", 21)),
Version.from_parts(0, 0, 1),
True,
),
),
],
)
def test_parse_constraint_caret(input: str, constraint: VersionRange) -> None:
Expand Down Expand Up @@ -392,6 +416,15 @@ def test_parse_constraints_with_trailing_comma(
("^1", ">=1,<2"),
("^1.0", ">=1.0,<2.0"),
("^1.0.0", ">=1.0.0,<2.0.0"),
("^1.0.0-alpha.1", ">=1.0.0-alpha.1,<2.0.0"),
("^0", ">=0,<1"),
("^0.1", ">=0.1,<0.2"),
("^0.0.2", ">=0.0.2,<0.0.3"),
("^0.1.2", ">=0.1.2,<0.2.0"),
("^0-alpha.1", ">=0-alpha.1,<1"),
("^0.1-alpha.1", ">=0.1-alpha.1,<0.2"),
("^0.0.2-alpha.1", ">=0.0.2-alpha.1,<0.0.3"),
("^0.1.2-alpha.1", ">=0.1.2-alpha.1,<0.2.0"),
("~1", ">=1,<2"),
("~1.0", ">=1.0,<1.1"),
("~1.0.0", ">=1.0.0,<1.1.0"),
Expand Down
139 changes: 139 additions & 0 deletions tests/semver/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,145 @@ def test_parse_invalid(value: str | None) -> None:
Version.parse(value) # type: ignore[arg-type]


@pytest.mark.parametrize(
"version, expected",
[
("1", "1"),
("1.2", "1.2"),
("1.2.3", "1.2.3"),
("2!1.2.3", "2!1.2.3"),
("1.2.3+local", "1.2.3+local"),
("1.2.3.4", "1.2.3.4"),
("1.dev0", "1"),
("1.2dev0", "1.2"),
("1.2.3dev0", "1.2.3"),
("1.2.3.4dev0", "1.2.3.4"),
("1.post1", "1.post1"),
("1.2.post1", "1.2.post1"),
("1.2.3.post1", "1.2.3.post1"),
("1.post1.dev0", "1.post1"),
("1.2.post1.dev0", "1.2.post1"),
("1.2.3.post1.dev0", "1.2.3.post1"),
("1.a1", "1"),
("1.2a1", "1.2"),
("1.2.3a1", "1.2.3"),
("1.2.3.4a1", "1.2.3.4"),
("1.a1.post2", "1"),
("1.2a1.post2", "1.2"),
("1.2.3a1.post2", "1.2.3"),
("1.2.3.4a1.post2", "1.2.3.4"),
("1.a1.post2.dev0", "1"),
("1.2a1.post2.dev0", "1.2"),
("1.2.3a1.post2.dev0", "1.2.3"),
("1.2.3.4a1.post2.dev0", "1.2.3.4"),
],
)
def test_stable(version: str, expected: str) -> None:
subject = Version.parse(version)

assert subject.stable.text == expected


@pytest.mark.parametrize(
"version, expected",
[
("1", "2"),
("1.2", "2.0"),
("1.2.3", "2.0.0"),
("2!1.2.3", "2!2.0.0"),
("1.2.3+local", "2.0.0"),
("1.2.3.4", "2.0.0.0"),
("1.dev0", "2"),
("1.2dev0", "2.0"),
("1.2.3dev0", "2.0.0"),
("1.2.3.4dev0", "2.0.0.0"),
("1.post1", "2"),
("1.2.post1", "2.0"),
("1.2.3.post1", "2.0.0"),
("1.post1.dev0", "2"),
("1.2.post1.dev0", "2.0"),
("1.2.3.post1.dev0", "2.0.0"),
("2.a1", "3"),
("2.2a1", "3.0"),
("2.2.3a1", "3.0.0"),
("2.2.3.4a1", "3.0.0.0"),
("2.a1.post2", "3"),
("2.2a1.post2", "3.0"),
("2.2.3a1.post2", "3.0.0"),
("2.2.3.4a1.post2", "3.0.0.0"),
("2.a1.post2.dev0", "3"),
("2.2a1.post2.dev0", "3.0"),
("2.2.3a1.post2.dev0", "3.0.0"),
("2.2.3.4a1.post2.dev0", "3.0.0.0"),
],
)
def test_next_breaking_for_major_over_0_results_into_next_major_and_preserves_precision(
version: str, expected: str
) -> None:
subject = Version.parse(version)

assert subject.next_breaking().text == expected


@pytest.mark.parametrize(
"version, expected",
[
("0", "1"),
("0.0", "0.1"),
("0.2", "0.3"),
("0.2.3", "0.3.0"),
("2!0.2.3", "2!0.3.0"),
("0.2.3+local", "0.3.0"),
("0.2.3.4", "0.3.0.0"),
("0.0.3.4", "0.0.4.0"),
("0.dev0", "1"),
("0.0dev0", "0.1"),
("0.2dev0", "0.3"),
("0.2.3dev0", "0.3.0"),
("0.0.3dev0", "0.0.4"),
("0.post1", "1"),
("0.0.post1", "0.1"),
("0.2.post1", "0.3"),
("0.2.3.post1", "0.3.0"),
("0.0.3.post1", "0.0.4"),
("0.post1.dev0", "1"),
("0.0.post1.dev0", "0.1"),
("0.2.post1.dev0", "0.3"),
("0.2.3.post1.dev0", "0.3.0"),
("0.0.3.post1.dev0", "0.0.4"),
("0.a1", "1"),
("0.0a1", "0.1"),
("0.2a1", "0.3"),
("0.2.3a1", "0.3.0"),
("0.2.3.4a1", "0.3.0.0"),
("0.0.3.4a1", "0.0.4.0"),
("0.a1.post2", "1"),
("0.0a1.post2", "0.1"),
("0.2a1.post2", "0.3"),
("0.2.3a1.post2", "0.3.0"),
("0.2.3.4a1.post2", "0.3.0.0"),
("0.0.3.4a1.post2", "0.0.4.0"),
("0.a1.post2.dev0", "1"),
("0.0a1.post2.dev0", "0.1"),
("0.2a1.post2.dev0", "0.3"),
("0.2.3a1.post2.dev0", "0.3.0"),
("0.2.3.4a1.post2.dev0", "0.3.0.0"),
("0.0.3.4a1.post2.dev0", "0.0.4.0"),
("0-alpha.1", "1"),
("0.0-alpha.1", "0.1"),
("0.2-alpha.1", "0.3"),
("0.0.1-alpha.2", "0.0.2"),
("0.1.2-alpha.1", "0.2.0"),
],
)
def test_next_breaking_for_major_0_is_treated_with_more_care_and_preserves_precision(
version: str, expected: str
) -> None:
subject = Version.parse(version)

assert subject.next_breaking().text == expected


@pytest.mark.parametrize(
"versions",
[
Expand Down
82 changes: 82 additions & 0 deletions tests/version/pep440/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,88 @@ def test_next_prerelease(version: str, expected: str) -> None:
assert v.next_prerelease().text == expected


@pytest.mark.parametrize(
"version, expected",
[
("1", True),
("1.2", True),
("1.2.3", True),
("2!1.2.3", True),
("1.2.3+local", True),
("1.2.3.4", True),
("1.dev0", False),
("1.2dev0", False),
("1.2.3dev0", False),
("1.2.3.4dev0", False),
("1.post1", True),
("1.2.post1", True),
("1.2.3.post1", True),
("1.post1.dev0", False),
("1.2.post1.dev0", False),
("1.2.3.post1.dev0", False),
("1.a1", False),
("1.2a1", False),
("1.2.3a1", False),
("1.2.3.4a1", False),
("1.a1.post2", False),
("1.2a1.post2", False),
("1.2.3a1.post2", False),
("1.2.3.4a1.post2", False),
("1.a1.post2.dev0", False),
("1.2a1.post2.dev0", False),
("1.2.3a1.post2.dev0", False),
("1.2.3.4a1.post2.dev0", False),
],
)
def test_is_stable(version: str, expected: bool) -> None:
subject = PEP440Version.parse(version)

assert subject.is_stable() == expected
assert subject.is_unstable() == (not expected)


@pytest.mark.parametrize(
"version, expected",
[
("0", True),
("0.2", True),
("0.2.3", True),
("2!0.2.3", True),
("0.2.3+local", True),
("0.2.3.4", True),
("0.dev0", False),
("0.2dev0", False),
("0.2.3dev0", False),
("0.2.3.4dev0", False),
("0.post1", True),
("0.2.post1", True),
("0.2.3.post1", True),
("0.post1.dev0", False),
("0.2.post1.dev0", False),
("0.2.3.post1.dev0", False),
("0.a1", False),
("0.2a1", False),
("0.2.3a1", False),
("0.2.3.4a1", False),
("0.a1.post2", False),
("0.2a1.post2", False),
("0.2.3a1.post2", False),
("0.2.3.4a1.post2", False),
("0.a1.post2.dev0", False),
("0.2a1.post2.dev0", False),
("0.2.3a1.post2.dev0", False),
("0.2.3.4a1.post2.dev0", False),
],
)
def test_is_stable_all_major_0_versions_are_treated_as_normal_versions(
version: str, expected: bool
) -> None:
subject = PEP440Version.parse(version)

assert subject.is_stable() == expected
assert subject.is_unstable() == (not expected)


@pytest.mark.parametrize(
"version, expected",
[
Expand Down