/
niceSOFT
/
python3-setuptools_scm
Обзор
Документация
Войти
/
niceSOFT
/
python3-setuptools_scm
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
vcs-versioning/testing_vcs/test_version.py
674 строки
20 KB
pre-commit-ci[bot]
style: pre-commit fixes
03 авг 2026, 21:57
03 авг 2026, 21:57
8a32949
Код
Авторство
О чём код?
from __future__ import annotations import re from dataclasses import replace from datetime import date, datetime, timedelta, timezone from typing import Any import pytest from vcs_versioning import Configuration, NonNormalizedVersion from vcs_versioning._scm_version import ScmVersion, meta from vcs_versioning._version_schemes import ( calver_by_date, format_version, guess_next_date_ver, guess_next_version, no_guess_dev_version, only_version, release_branch_semver_version, 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", id="short_tag"), pytest.param(meta("1.0.0rc1", config=c), "1.0.0rc1", id="exact_rc"), pytest.param(meta("2.0.dev0", config=c), "2.0.dev0", id="exact_dev0"), 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", ), pytest.param( meta("2.0.dev0", distance=5, branch="default", config=c), "2.0.0.dev5", id="dev0_anchor_default_branch", ), pytest.param( meta("2.0.dev0", distance=5, branch="feature/fun", config=c), "2.0.0.dev5", id="dev0_anchor_feature_branch", ), pytest.param( meta("3.0.dev0", distance=1, branch="default", config=c), "3.0.0.dev1", id="dev0_anchor_single_commit", ), ], ) def test_next_semver(version: ScmVersion, expected_next: str) -> None: computed = simplified_semver_version(version) assert computed == expected_next @pytest.mark.parametrize( ("version", "expected_next"), [ pytest.param(meta("1.0.0", config=c), "1.0.0", id="exact"), pytest.param(meta("2.0.dev0", config=c), "2.0.dev0", id="exact_dev0"), pytest.param(meta("1.0.0rc1", config=c), "1.0.0rc1", id="exact_rc"), 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", ), pytest.param( meta("2.0.dev0", distance=5, branch="master", config=c), "2.0.0.dev5", id="dev0_anchor_development_branch", ), pytest.param( meta("2.0.dev0", distance=5, branch="release-2.0", config=c), "2.0.dev5", id="dev0_anchor_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): result = meta(tag, config=c) else: result = meta(tag, config=c) 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=[ 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="vcs_versioning._version_schemes: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_scm_version_matches() -> None: """Test ScmVersion.matches() method.""" # Create a test version version = meta( "1.2.3", distance=5, dirty=True, node="abc123def456", branch="main", config=c, ) # Test exact matches assert version.matches(tag="1.2.3") assert version.matches(distance=5) assert version.matches(dirty=True) assert version.matches(branch="main") assert version.matches(exact=False) # distance > 0 # Test multiple matches assert version.matches(tag="1.2.3", distance=5, dirty=True) # Test node prefix matching assert version.matches(node_prefix="abc") assert version.matches(node_prefix="abc123") assert version.matches(node_prefix="abc123def456") # Test mismatches - we only care that they're falsy assert not version.matches(tag="1.2.4") assert not version.matches(distance=3) assert not version.matches(dirty=False) assert not version.matches(node_prefix="xyz") # Test multiple mismatches assert not version.matches(tag="1.2.4", distance=3, dirty=False) def test_scm_version_matches_exact() -> None: """Test ScmVersion.matches() with exact versions.""" # Exact version (tag with no distance and not dirty) exact_version = meta("2.0.0", distance=0, dirty=False, config=c) assert exact_version.matches(exact=True) assert exact_version.matches(tag="2.0.0", exact=True) # Non-exact version non_exact = meta("2.0.0", distance=1, dirty=False, config=c) assert not non_exact.matches(exact=True) def test_scm_version_matches_none_node() -> None: """Test ScmVersion.matches() when node is None.""" version = meta("1.0.0", node=None, config=c) # Should fail node_prefix match when node is None assert not version.matches(node_prefix="abc") 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) @pytest.mark.parametrize( ("old_name", "new_name"), [ ("python-simplified-semver", "semver-pep440"), ("release-branch-semver", "semver-pep440-release-branch"), ], ) def test_deprecated_scheme_names_warn(old_name: str, new_name: str) -> None: with pytest.warns( DeprecationWarning, match=rf"'{re.escape(old_name)}' has been renamed to '{re.escape(new_name)}'", ): format_version( meta( "1.0.0", distance=2, branch="default", config=replace( c, version_scheme=old_name, local_scheme="no-local-version" ), ) ) @pytest.mark.parametrize( ("old_name", "new_name", "tag", "branch", "expected"), [ pytest.param( "python-simplified-semver", "semver-pep440", "1.0.0", "default", "1.0.1.dev2", id="simplified-default-branch", ), pytest.param( "python-simplified-semver", "semver-pep440", "1.0.0", "feature/fun", "1.1.0.dev2", id="simplified-feature-branch", ), pytest.param( "release-branch-semver", "semver-pep440-release-branch", "1.0.0", "master", "1.1.0.dev2", id="release-branch-dev", ), pytest.param( "release-branch-semver", "semver-pep440-release-branch", "1.0.0", "release-1.0", "1.0.1.dev2", id="release-branch-maintenance", ), ], ) def test_deprecated_scheme_names_still_work( old_name: str, new_name: str, tag: str, branch: str, expected: str ) -> None: base = replace(c, local_scheme="no-local-version") version_old = meta( tag, distance=2, branch=branch, config=replace(base, version_scheme=old_name) ) version_new = meta( tag, distance=2, branch=branch, config=replace(base, version_scheme=new_name) ) with pytest.warns(DeprecationWarning): result_old = format_version(version_old) result_new = format_version(version_new) assert result_old == expected assert result_old == result_new def test_all_entrypoints_return_none() -> None: version = meta( "1.0", config=replace( c, version_scheme=lambda v: None, ), ) with pytest.raises( ValueError, match=( 'None of the "setuptools_scm.version_scheme" entrypoints matching' r" .*? returned a value." ), ): format_version(version)