From 9992889e3eacdd981a2241e038951c0ed5df19ce Mon Sep 17 00:00:00 2001 From: mazine Date: Fri, 16 Sep 2022 13:53:55 -0400 Subject: [PATCH] Fixes unstable version next breaking version --- src/poetry/core/semver/version.py | 20 ++-- tests/semver/test_helpers.py | 33 +++++++ tests/semver/test_version.py | 139 +++++++++++++++++++++++++++ tests/version/pep440/test_version.py | 82 ++++++++++++++++ 4 files changed, 262 insertions(+), 12 deletions(-) diff --git a/src/poetry/core/semver/version.py b/src/poetry/core/semver/version.py index 2fe6fe873..66a1bbe5a 100644 --- a/src/poetry/core/semver/version.py +++ b/src/poetry/core/semver/version.py @@ -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 @@ -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: diff --git a/tests/semver/test_helpers.py b/tests/semver/test_helpers.py index cc136a796..41d0cab30 100644 --- a/tests/semver/test_helpers.py +++ b/tests/semver/test_helpers.py @@ -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: @@ -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"), diff --git a/tests/semver/test_version.py b/tests/semver/test_version.py index 31a8bffdb..a1c333552 100644 --- a/tests/semver/test_version.py +++ b/tests/semver/test_version.py @@ -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", [ diff --git a/tests/version/pep440/test_version.py b/tests/version/pep440/test_version.py index d9ccb99d7..49ffe9469 100644 --- a/tests/version/pep440/test_version.py +++ b/tests/version/pep440/test_version.py @@ -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", [