/
niceSOFT
/
python3-setuptools_scm
Обзор
Документация
Войти
/
niceSOFT
/
python3-setuptools_scm
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
v9.2.2
testing/test_version.py
544 строки
16 KB
Ronny Pfannschmidt
add codespell pre-commit hook
05 авг 2025, 11:19
05 авг 2025, 11:19
73491bd
Код
Авторство
О чём код?
from __future__ import annotations import re from dataclasses import replace from datetime import date from datetime import datetime from datetime import timedelta from datetime import timezone from typing import Any import pytest from setuptools_scm import Configuration from setuptools_scm import NonNormalizedVersion from setuptools_scm.version import ScmVersion from setuptools_scm.version import calver_by_date from setuptools_scm.version import format_version from setuptools_scm.version import guess_next_date_ver from setuptools_scm.version import guess_next_version from setuptools_scm.version import meta from setuptools_scm.version import no_guess_dev_version from setuptools_scm.version import only_version from setuptools_scm.version import release_branch_semver_version from setuptools_scm.version import simplified_semver_version c = Configuration() c_non_normalize = Configuration(version_cls=NonNormalizedVersion) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param(meta("1.0", config=c), "1.0.0", id="short_tag"), pytest.param( meta("1.0.0", distance=2, branch="default", config=c), "1.0.1.dev2", id="normal_branch", ), pytest.param( meta("1.0", distance=2, branch="default", config=c), "1.0.1.dev2", id="normal_branch_short_tag", ), pytest.param( meta("1.0.0", distance=2, branch="feature", config=c), "1.1.0.dev2", id="feature_branch", ), pytest.param( meta("1.0", distance=2, branch="feature", config=c), "1.1.0.dev2", id="feature_branch_short_tag", ), pytest.param( meta("1.0.0", distance=2, branch="features/test", config=c), "1.1.0.dev2", id="feature_in_branch", ), pytest.param( meta(NonNormalizedVersion("v1.0"), distance=2, branch="default", config=c), "1.0.1.dev2", id="non-normalized-allowed", ), ], ) def test_next_semver(version: ScmVersion, expected_next: str) -> None: computed = simplified_semver_version(version) assert computed == expected_next def test_next_semver_bad_tag() -> None: # Create a mock version class that represents an invalid version for testing error handling from typing import cast from setuptools_scm._version_cls import _VersionT class BrokenVersionForTest: """A mock version that behaves like a string but passes type checking.""" def __init__(self, version_str: str): self._version_str = version_str def __str__(self) -> str: return self._version_str def __repr__(self) -> str: return f"BrokenVersionForTest({self._version_str!r})" # Cast to the expected type to avoid type checking issues broken_tag = cast(_VersionT, BrokenVersionForTest("1.0.0-foo")) version = meta(broken_tag, preformatted=True, config=c) with pytest.raises( ValueError, match=r"1\.0\.0-foo.* can't be parsed as numeric version" ): simplified_semver_version(version) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param( meta("1.0.0", distance=2, branch="master", config=c), "1.1.0.dev2", id="development_branch", ), pytest.param( meta("1.0.0rc1", distance=2, branch="master", config=c), "1.1.0.dev2", id="development_branch_release_candidate", ), pytest.param( meta("1.0.0", distance=2, branch="maintenance/1.0.x", config=c), "1.0.1.dev2", id="release_branch_legacy_version", ), pytest.param( meta("1.0.0", distance=2, branch="v1.0.x", config=c), "1.0.1.dev2", id="release_branch_with_v_prefix", ), pytest.param( meta("1.0.0", distance=2, branch="release-1.0", config=c), "1.0.1.dev2", id="release_branch_with_prefix", ), pytest.param( meta("1.0.0", distance=2, branch="bugfix/3434", config=c), "1.1.0.dev2", id="false_positive_release_branch", ), ], ) def test_next_release_branch_semver(version: ScmVersion, expected_next: str) -> None: computed = release_branch_semver_version(version) assert computed == expected_next def m(tag: str, **kw: Any) -> ScmVersion: return meta(tag, **kw, config=c) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param( m("1.0.0", distance=2), "1.0.0.post1.dev2", id="dev_distance", ), pytest.param( m("1.0.dev0", distance=2), "1.0.dev2", id="dev_distance_after_dev_tag" ), pytest.param( m("1.0", distance=2), "1.0.post1.dev2", id="dev_distance_short_tag", ), pytest.param( m("1.0.0"), "1.0.0", id="no_dev_distance", ), ], ) def test_no_guess_version(version: ScmVersion, expected_next: str) -> None: computed = no_guess_dev_version(version) assert computed == expected_next @pytest.mark.parametrize( ("version", "match"), [ ("1.0.dev1", "choosing custom numbers for the `.devX` distance"), ("1.0.post1", "already is a post release"), ], ) def test_no_guess_version_bad(version: str, match: str) -> None: with pytest.raises(ValueError, match=match): no_guess_dev_version(m(version, distance=1)) def test_bump_dev_version_zero() -> None: assert guess_next_version(m("1.0.dev0")) == "1.0" def test_bump_dev_version_nonzero_raises() -> None: match = ( "choosing custom numbers for the `.devX` distance " "is not supported.\n " "The 1.0.dev1 can't be bumped\n" "Please drop the tag or create a new supported one ending in .dev0" ) with pytest.raises(ValueError, match=match): guess_next_version(m("1.0.dev1")) @pytest.mark.parametrize( "version", [ "1.dev0", "1.0.dev456", "1.0a1", "1.0a2.dev456", "1.0a12.dev456", "1.0a12", "1.0b1.dev456", "1.0b2", "1.0b2.post345.dev456", "1.0b2.post345", "1.0rc1.dev456", "1.0rc1", "1.0", "1.0.post456.dev34", "1.0.post456", "1.0.15", "1.1.dev1", ], ) def test_only_version(version: str) -> None: assert version == only_version(meta(version, config=c)) assert version == only_version(meta(version, distance=2, config=c)) @pytest.mark.parametrize( ("tag", "expected"), [ ("v1.0.0", "1.0.0"), ("v1.0.0-rc.1", "1.0.0rc1"), ("v1.0.0-rc.1+-25259o4382757gjurh54", "1.0.0rc1"), ], ) def test_tag_regex1(tag: str, expected: str) -> None: if "+" in tag: # pytest bug wrt cardinality with pytest.warns(UserWarning): # noqa: PT030 result = meta(tag, config=c) else: result = meta(tag, config=c) assert not isinstance(result.tag, str) assert result.tag.public == expected def test_regex_match_but_no_version() -> None: with pytest.raises( ValueError, match=( r'The tag_regex "\(\?P<version>\)\.\*" matched tag "v1",' " however the matched group has no value" ), ): meta("v1", config=replace(c, tag_regex=re.compile(r"(?P<version>).*"))) @pytest.mark.issue("https://github.com/pypa/setuptools-scm/issues/471") def test_version_bump_bad() -> None: class YikesVersion: val: str def __init__(self, val: str) -> None: self.val = val def __str__(self) -> str: return self.val config = Configuration(version_cls=YikesVersion) # type: ignore[arg-type] with pytest.raises( ValueError, match=r".*does not end with a number to bump, " "please correct or use a custom version scheme", ): guess_next_version(tag_version=meta("2.0.0-alpha.5-PMC", config=config)) def test_format_version_schemes() -> None: version = meta( "1.0", config=replace( c, local_scheme="no-local-version", version_scheme=[ # type: ignore[arg-type] lambda v: None, "guess-next-dev", ], ), ) assert format_version(version) == "1.0" def test_custom_version_schemes() -> None: version = meta( "1.0", config=replace( c, local_scheme="no-local-version", version_scheme="setuptools_scm.version:no_guess_dev_version", ), ) custom_computed = format_version(version) assert custom_computed == no_guess_dev_version(version) # Fixed time for consistent test behavior across timezone boundaries # This prevents issue #687 where tests failed around midnight in non-UTC timezones _TEST_TIME = datetime(2023, 12, 15, 12, 0, 0, tzinfo=timezone.utc) def date_offset(base_date: date | None = None, days_offset: int = 0) -> date: if base_date is None: base_date = _TEST_TIME.date() return base_date - timedelta(days=days_offset) def date_to_str( base_date: date | None = None, days_offset: int = 0, fmt: str = "%y.%m.%d", ) -> str: return format(date_offset(base_date, days_offset), fmt) @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param( meta(date_to_str(days_offset=3), config=c_non_normalize), date_to_str(days_offset=3), id="exact", ), pytest.param( meta(date_to_str() + ".1", config=c_non_normalize), date_to_str() + ".1", id="exact patch", ), pytest.param( meta("20.01.02", config=c), "20.1.2", id="leading 0s", ), pytest.param( meta( date_to_str(days_offset=3), config=c_non_normalize, dirty=True, time=_TEST_TIME, ), date_to_str() + ".0.dev0", id="dirty other day", ), pytest.param( meta( date_to_str(), config=c_non_normalize, distance=2, branch="default", time=_TEST_TIME, ), date_to_str() + ".1.dev2", id="normal branch", ), pytest.param( meta(date_to_str(fmt="%Y.%m.%d"), config=c_non_normalize), date_to_str(fmt="%Y.%m.%d"), id="4 digits year", ), pytest.param( meta( date_to_str(), config=c_non_normalize, distance=2, branch="release-2021.05.06", ), "2021.05.06", id="release branch", ), pytest.param( meta( date_to_str() + ".2", config=c_non_normalize, distance=2, branch="release-21.5.1", ), "21.5.1", id="release branch short", ), pytest.param( meta( date_to_str(days_offset=3) + ".2", config=c_non_normalize, node_date=date_offset(days_offset=2), ), date_to_str(days_offset=3) + ".2", id="node date clean", ), pytest.param( meta( date_to_str(days_offset=2) + ".2", config=c_non_normalize, distance=2, node_date=date_offset(days_offset=2), ), date_to_str(days_offset=2) + ".3.dev2", id="node date distance", ), pytest.param( meta( "1.2.0", config=c_non_normalize, distance=2, node_date=date_offset(days_offset=2), ), date_to_str(days_offset=2) + ".0.dev2", marks=pytest.mark.filterwarnings( "ignore:.*not correspond to a valid versioning date.*:UserWarning" ), id="using on old version tag", ), ], ) def test_calver_by_date(version: ScmVersion, expected_next: str) -> None: computed = calver_by_date(version) assert computed == expected_next @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="SemVer exact stays"), pytest.param( meta("1.0.0", config=c_non_normalize, dirty=True, time=_TEST_TIME), "23.12.15.0.dev0", id="SemVer dirty is replaced by date", marks=pytest.mark.filterwarnings("ignore:.*legacy version.*:UserWarning"), ), ], ) def test_calver_by_date_semver(version: ScmVersion, expected_next: str) -> None: computed = calver_by_date(version) assert computed == expected_next def test_calver_by_date_future_warning() -> None: with pytest.warns(UserWarning, match="your previous tag*"): calver_by_date( meta( date_to_str(days_offset=-2), config=c_non_normalize, distance=2, time=_TEST_TIME, ) ) @pytest.mark.parametrize( ("tag", "node_date", "expected"), [ pytest.param("20.03.03", date(2020, 3, 4), "20.03.04.0", id="next day"), pytest.param("20.03.03", date(2020, 3, 3), "20.03.03.1", id="same day"), pytest.param( "20.03.03.2", date(2020, 3, 3), "20.03.03.3", id="same day with patch" ), pytest.param( "v20.03.03", date(2020, 3, 4), "v20.03.04.0", id="next day with v prefix" ), ], ) def test_calver_guess_next_data(tag: str, node_date: date, expected: str) -> None: version = meta(tag, config=c_non_normalize, node_date=node_date) next = guess_next_date_ver( version, node_date=node_date, version_cls=c_non_normalize.version_cls, ) assert next == expected def test_custom_version_cls() -> None: """Test that we can pass our own version class instead of pkg_resources""" class MyVersion: def __init__(self, tag_str: str) -> None: self.tag = tag_str def __str__(self) -> str: return f"Custom {self.tag}" def __repr__(self) -> str: return f"MyVersion<Custom{self.tag}>" @property def public(self) -> str: """The public portion of the version (without local part).""" return self.tag.split("+")[0] @property def local(self) -> str | None: """The local version segment.""" if "+" in self.tag: return self.tag.split("+", 1)[1] return None config = Configuration(version_cls=MyVersion) # type: ignore[arg-type] scm_version = meta("1.0.0-foo", config=config) assert isinstance(scm_version.tag, MyVersion) assert str(scm_version.tag) == "Custom 1.0.0-foo" @pytest.mark.parametrize("config_key", ["version_scheme", "local_scheme"]) def test_no_matching_entrypoints(config_key: str) -> None: version = meta( "1.0", config=replace(c, **{config_key: "nonexistent"}), # type: ignore[arg-type] ) with pytest.raises( ValueError, match=( r'Couldn\'t find any implementations for entrypoint "setuptools_scm\..*?"' ' with value "nonexistent"' ), ): format_version(version) def test_all_entrypoints_return_none() -> None: version = meta( "1.0", config=replace( c, version_scheme=lambda v: None, # type: ignore[arg-type,return-value] ), ) with pytest.raises( ValueError, match=( 'None of the "setuptools_scm.version_scheme" entrypoints matching' r" .*? returned a value." ), ): format_version(version)