Skip to content

Commit

Permalink
Fixes unstable next breaking version when major is 0 (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
mazinesy authored Sep 23, 2022
1 parent 091e345 commit 403a754
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 12 deletions.
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

0 comments on commit 403a754

Please sign in to comment.