diff --git a/commitizen/bump.py b/commitizen/bump.py index 76a8e15893..ea54b60454 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -2,60 +2,13 @@ import os import re -from collections import OrderedDict from glob import iglob -from logging import getLogger from string import Template -from typing import cast -from commitizen.defaults import BUMP_MESSAGE, ENCODING, MAJOR, MINOR, PATCH +from commitizen.defaults import BUMP_MESSAGE, ENCODING from commitizen.exceptions import CurrentVersionNotFoundError -from commitizen.git import GitCommit, smart_open -from commitizen.version_schemes import Increment, Version - -VERSION_TYPES = [None, PATCH, MINOR, MAJOR] - -logger = getLogger("commitizen") - - -def find_increment( - commits: list[GitCommit], regex: str, increments_map: dict | OrderedDict -) -> Increment | None: - if isinstance(increments_map, dict): - increments_map = OrderedDict(increments_map) - - # Most important cases are major and minor. - # Everything else will be considered patch. - select_pattern = re.compile(regex) - increment: str | None = None - - for commit in commits: - for message in commit.message.split("\n"): - result = select_pattern.search(message) - - if result: - found_keyword = result.group(1) - new_increment = None - for match_pattern in increments_map.keys(): - if re.match(match_pattern, found_keyword): - new_increment = increments_map[match_pattern] - break - - if new_increment is None: - logger.debug( - f"no increment needed for '{found_keyword}' in '{message}'" - ) - - if VERSION_TYPES.index(increment) < VERSION_TYPES.index(new_increment): - logger.debug( - f"increment detected is '{new_increment}' due to '{found_keyword}' in '{message}'" - ) - increment = new_increment - - if increment == MAJOR: - break - - return cast(Increment, increment) +from commitizen.git import smart_open +from commitizen.version_schemes import Version def update_version_in_files( diff --git a/commitizen/bump_rule.py b/commitizen/bump_rule.py new file mode 100644 index 0000000000..24ec77d6bc --- /dev/null +++ b/commitizen/bump_rule.py @@ -0,0 +1,253 @@ +from __future__ import annotations + +import re +from collections.abc import Iterable, Mapping +from enum import IntEnum, auto +from functools import cached_property +from typing import Callable, Protocol + +from commitizen.exceptions import NoPatternMapError + + +class SemVerIncrement(IntEnum): + """An enumeration representing semantic versioning increments. + + This class defines the three types of version increments according to semantic versioning: + - PATCH: For backwards-compatible bug fixes + - MINOR: For backwards-compatible functionality additions + - MAJOR: For incompatible API changes + """ + + PATCH = auto() + MINOR = auto() + MAJOR = auto() + + def __str__(self) -> str: + return self.name + + @classmethod + def safe_cast(cls, value: object) -> SemVerIncrement | None: + if not isinstance(value, str): + return None + try: + return cls[value] + except KeyError: + return None + + @classmethod + def safe_cast_dict(cls, d: Mapping[str, object]) -> dict[str, SemVerIncrement]: + return { + k: v + for k, v in ((k, SemVerIncrement.safe_cast(v)) for k, v in d.items()) + if v is not None + } + + @staticmethod + def get_highest_by_messages( + commit_messages: Iterable[str], + get_increment: Callable[[str], SemVerIncrement | None], + ) -> SemVerIncrement | None: + """Find the highest version increment from a list of messages. + + This function processes a list of messages and determines the highest version + increment needed based on the commit messages. It splits multi-line commit messages + and evaluates each line using the provided get_increment callable. + + Args: + commit_messages: A list of messages to analyze. + get_increment: A callable that takes a commit message string and returns an + SemVerIncrement value (MAJOR, MINOR, PATCH) or None if no increment is needed. + + Returns: + The highest version increment needed (MAJOR, MINOR, PATCH) or None if no + increment is needed. The order of precedence is MAJOR > MINOR > PATCH. + + Example: + >>> commit_messages = ["feat: new feature", "fix: bug fix"] + >>> rule = ConventionalCommitBumpRule() + >>> SemVerIncrement.get_highest_by_messages(commit_messages, lambda x: rule.get_increment(x, False)) + 'MINOR' + """ + return SemVerIncrement.get_highest( + get_increment(line) + for message in commit_messages + for line in message.split("\n") + ) + + @staticmethod + def get_highest( + increments: Iterable[SemVerIncrement | None], + ) -> SemVerIncrement | None: + return max(filter(None, increments), default=None) + + +class BumpRule(Protocol): + """A protocol defining the interface for version bump rules. + + This protocol specifies the contract that all version bump rule implementations must follow. + It defines how commit messages should be analyzed to determine the appropriate semantic + version increment. + + The protocol is used to ensure consistent behavior across different bump rule implementations, + such as conventional commits or custom rules. + """ + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> SemVerIncrement | None: + """Determine the version increment based on a commit message. + + This method analyzes a commit message to determine what kind of version increment + is needed according to the Conventional Commits specification. It handles special + cases for breaking changes and respects the major_version_zero flag. + + Args: + commit_message: The commit message to analyze. Should follow conventional commit format. + major_version_zero: If True, breaking changes will result in a MINOR version bump + instead of MAJOR. This is useful for projects in 0.x.x versions. + + Returns: + SemVerIncrement | None: The type of version increment needed: + - MAJOR: For breaking changes when major_version_zero is False + - MINOR: For breaking changes when major_version_zero is True, or for new features + - PATCH: For bug fixes, performance improvements, or refactors + - None: For commits that don't require a version bump (docs, style, etc.) + """ + + +class ConventionalCommitBumpRule(BumpRule): + _BREAKING_CHANGE_TYPES = set(["BREAKING CHANGE", "BREAKING-CHANGE"]) + _MINOR_CHANGE_TYPES = set(["feat"]) + _PATCH_CHANGE_TYPES = set(["fix", "perf", "refactor"]) + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> SemVerIncrement | None: + if not (m := self._head_pattern.match(commit_message)): + return None + + change_type = m.group("change_type") + if m.group("bang") or change_type in self._BREAKING_CHANGE_TYPES: + return ( + SemVerIncrement.MINOR if major_version_zero else SemVerIncrement.MAJOR + ) + + if change_type in self._MINOR_CHANGE_TYPES: + return SemVerIncrement.MINOR + + if change_type in self._PATCH_CHANGE_TYPES: + return SemVerIncrement.PATCH + + return None + + @cached_property + def _head_pattern(self) -> re.Pattern: + change_types = [ + *self._BREAKING_CHANGE_TYPES, + *self._PATCH_CHANGE_TYPES, + *self._MINOR_CHANGE_TYPES, + "docs", + "style", + "test", + "build", + "ci", + ] + re_change_type = r"(?P" + "|".join(change_types) + r")" + re_scope = r"(?P\(.+\))?" + re_bang = r"(?P!)?" + return re.compile(f"^{re_change_type}{re_scope}{re_bang}:") + + +class CustomBumpRule(BumpRule): + def __init__( + self, + bump_pattern: str, + bump_map: Mapping[str, SemVerIncrement], + bump_map_major_version_zero: Mapping[str, SemVerIncrement], + ): + """Initialize a custom bump rule for version incrementing. + + This constructor creates a rule that determines how version numbers should be + incremented based on commit messages. It validates and compiles the provided + pattern and maps for use in version bumping. + + The fallback logic is used for backward compatibility. + + Args: + bump_pattern: A regex pattern string used to match commit messages. + Example: r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + Or with fallback regex: r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" # First group is type + bump_map: A mapping of commit types to their corresponding version increments. + Example: { + "major": SemVerIncrement.MAJOR, + "bang": SemVerIncrement.MAJOR, + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", SemVerIncrement.MAJOR), + (r"^BREAKING[\-\ ]CHANGE", SemVerIncrement.MAJOR), + (r"^feat", SemVerIncrement.MINOR), + (r"^fix", SemVerIncrement.PATCH), + (r"^refactor", SemVerIncrement.PATCH), + (r"^perf", SemVerIncrement.PATCH), + } + bump_map_major_version_zero: A mapping of commit types to version increments + specifically for when the major version is 0. This allows for different + versioning behavior during initial development. + The format is the same as bump_map. + Example: { + "major": SemVerIncrement.MINOR, # MAJOR becomes MINOR in version zero + "bang": SemVerIncrement.MINOR, # Breaking changes become MINOR in version zero + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH + } + Or with fallback: { + (r"^.+!$", SemVerIncrement.MINOR), + (r"^BREAKING[\-\ ]CHANGE", SemVerIncrement.MINOR), + (r"^feat", SemVerIncrement.MINOR), + (r"^fix", SemVerIncrement.PATCH), + (r"^refactor", SemVerIncrement.PATCH), + (r"^perf", SemVerIncrement.PATCH), + } + + Raises: + NoPatternMapError: If any of the required parameters are empty or None + """ + if not bump_map or not bump_pattern or not bump_map_major_version_zero: + raise NoPatternMapError( + f"Invalid bump rule: {bump_pattern=} and {bump_map=} and {bump_map_major_version_zero=}" + ) + + self.bump_pattern = re.compile(bump_pattern) + self.bump_map = bump_map + self.bump_map_major_version_zero = bump_map_major_version_zero + + def get_increment( + self, commit_message: str, major_version_zero: bool + ) -> SemVerIncrement | None: + if not (m := self.bump_pattern.search(commit_message)): + return None + + effective_bump_map = ( + self.bump_map_major_version_zero if major_version_zero else self.bump_map + ) + + try: + if ret := SemVerIncrement.get_highest( + ( + increment + for name, increment in effective_bump_map.items() + if m.group(name) + ), + ): + return ret + except IndexError: + pass + + # Fallback to old school bump rule, for backward compatibility + found_keyword = m.group(1) + for match_pattern, increment in effective_bump_map.items(): + if re.match(match_pattern, found_keyword): + return increment + return None diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index 0a2bbe37fc..da0bb24cca 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -7,6 +7,9 @@ import questionary from commitizen import bump, factory, git, hooks, out +from commitizen.bump_rule import ( + SemVerIncrement, +) from commitizen.changelog_formats import get_changelog_format from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig @@ -20,7 +23,6 @@ InvalidManualVersion, NoCommitsFoundError, NoneIncrementExit, - NoPatternMapError, NotAGitProjectError, NotAllowed, NoVersionSpecifiedError, @@ -28,7 +30,6 @@ from commitizen.providers import get_provider from commitizen.tags import TagRules from commitizen.version_schemes import ( - Increment, InvalidVersion, Prerelease, get_version_scheme, @@ -119,25 +120,14 @@ def is_initial_tag( is_initial = questionary.confirm("Is this the first tag created?").ask() return is_initial - def find_increment(self, commits: list[git.GitCommit]) -> Increment | None: + def find_increment(self, commits: list[git.GitCommit]) -> SemVerIncrement | None: # Update the bump map to ensure major version doesn't increment. - is_major_version_zero: bool = self.bump_settings["major_version_zero"] - # self.cz.bump_map = defaults.bump_map_major_version_zero - bump_map = ( - self.cz.bump_map_major_version_zero - if is_major_version_zero - else self.cz.bump_map - ) - bump_pattern = self.cz.bump_pattern + is_major_version_zero = bool(self.bump_settings["major_version_zero"]) - if not bump_map or not bump_pattern: - raise NoPatternMapError( - f"'{self.config.settings['name']}' rule does not support bump" - ) - increment = bump.find_increment( - commits, regex=bump_pattern, increments_map=bump_map + return SemVerIncrement.get_highest_by_messages( + (commit.message for commit in commits), + lambda x: self.cz.bump_rule.get_increment(x, is_major_version_zero), ) - return increment def __call__(self) -> None: # noqa: C901 """Steps executed to bump.""" @@ -155,8 +145,8 @@ def __call__(self) -> None: # noqa: C901 dry_run: bool = self.arguments["dry_run"] is_yes: bool = self.arguments["yes"] - increment: Increment | None = self.arguments["increment"] - prerelease: Prerelease | None = self.arguments["prerelease"] + increment = SemVerIncrement.safe_cast(self.arguments["increment"]) + prerelease = Prerelease.safe_cast(self.arguments["prerelease"]) devrelease: int | None = self.arguments["devrelease"] is_files_only: bool | None = self.arguments["files_only"] is_local_version: bool = self.arguments["local_version"] @@ -272,7 +262,7 @@ def __call__(self) -> None: # noqa: C901 # we create an empty PATCH increment for empty tag if increment is None and allow_no_commit: - increment = "PATCH" + increment = SemVerIncrement.PATCH new_version = current_version.bump( increment, diff --git a/commitizen/cz/base.py b/commitizen/cz/base.py index 43455a74ca..cf9c20ea04 100644 --- a/commitizen/cz/base.py +++ b/commitizen/cz/base.py @@ -2,14 +2,17 @@ from abc import ABCMeta, abstractmethod from collections.abc import Iterable +from functools import cached_property from typing import Any, Callable, Protocol from jinja2 import BaseLoader, PackageLoader from prompt_toolkit.styles import Style, merge_styles from commitizen import git +from commitizen.bump_rule import BumpRule, CustomBumpRule, SemVerIncrement from commitizen.config.base_config import BaseConfig from commitizen.defaults import Questions +from commitizen.exceptions import NoPatternMapError class MessageBuilderHook(Protocol): @@ -25,9 +28,13 @@ def __call__( class BaseCommitizen(metaclass=ABCMeta): + _bump_rule: BumpRule | None = None + + # TODO: decide if these should be removed bump_pattern: str | None = None bump_map: dict[str, str] | None = None bump_map_major_version_zero: dict[str, str] | None = None + default_style_config: list[tuple[str, str]] = [ ("qmark", "fg:#ff9d00 bold"), ("question", "bold"), @@ -84,6 +91,44 @@ def style(self): ] ) + @cached_property + def bump_rule(self) -> BumpRule: + """Get the bump rule for version incrementing. + + This property returns a BumpRule instance that determines how version numbers + should be incremented based on commit messages. It first checks if a custom + bump rule was set via `_bump_rule`. If not, it falls back to creating a + CustomBumpRule using the class's bump pattern and maps. + + The CustomBumpRule requires three components to be defined: + - bump_pattern: A regex pattern to match commit messages + - bump_map: A mapping of commit types to version increments + - bump_map_major_version_zero: A mapping for version increments when major version is 0 + + Returns: + BumpRule: A rule instance that determines version increments + + Raises: + NoPatternMapError: If the required bump pattern or maps are not defined + """ + if self._bump_rule: + return self._bump_rule + + # Fallback to custom bump rule if no bump rule is provided + if ( + not self.bump_pattern + or not self.bump_map + or not self.bump_map_major_version_zero + ): + raise NoPatternMapError( + f"'{self.config.settings['name']}' rule does not support bump: {self.bump_pattern=}, {self.bump_map=}, {self.bump_map_major_version_zero=}" + ) + return CustomBumpRule( + self.bump_pattern, + SemVerIncrement.safe_cast_dict(self.bump_map), + SemVerIncrement.safe_cast_dict(self.bump_map_major_version_zero), + ) + def example(self) -> str: """Example of the commit message.""" raise NotImplementedError("Not Implemented yet") diff --git a/commitizen/cz/conventional_commits/conventional_commits.py b/commitizen/cz/conventional_commits/conventional_commits.py index af29a209fc..1738fab07f 100644 --- a/commitizen/cz/conventional_commits/conventional_commits.py +++ b/commitizen/cz/conventional_commits/conventional_commits.py @@ -2,6 +2,7 @@ import re from commitizen import defaults +from commitizen.bump_rule import ConventionalCommitBumpRule from commitizen.cz.base import BaseCommitizen from commitizen.cz.utils import multiple_line_breaker, required_validator from commitizen.defaults import Questions @@ -28,6 +29,8 @@ def parse_subject(text): class ConventionalCommitsCz(BaseCommitizen): + _bump_rule = ConventionalCommitBumpRule() + bump_pattern = defaults.BUMP_PATTERN bump_map = defaults.BUMP_MAP bump_map_major_version_zero = defaults.BUMP_MAP_MAJOR_VERSION_ZERO diff --git a/commitizen/defaults.py b/commitizen/defaults.py index 0b6c28e6a9..fd188878c6 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -5,6 +5,8 @@ from collections.abc import Iterable, MutableMapping, Sequence from typing import Any, TypedDict +from commitizen.bump_rule import SemVerIncrement + # Type Questions = Iterable[MutableMapping[str, Any]] @@ -107,31 +109,27 @@ class Settings(TypedDict, total=False): "extras": {}, } -MAJOR = "MAJOR" -MINOR = "MINOR" -PATCH = "PATCH" - CHANGELOG_FORMAT = "markdown" BUMP_PATTERN = r"^((BREAKING[\-\ ]CHANGE|\w+)(\(.+\))?!?):" BUMP_MAP = OrderedDict( ( - (r"^.+!$", MAJOR), - (r"^BREAKING[\-\ ]CHANGE", MAJOR), - (r"^feat", MINOR), - (r"^fix", PATCH), - (r"^refactor", PATCH), - (r"^perf", PATCH), + (r"^.+!$", str(SemVerIncrement.MAJOR)), + (r"^BREAKING[\-\ ]CHANGE", str(SemVerIncrement.MAJOR)), + (r"^feat", str(SemVerIncrement.MINOR)), + (r"^fix", str(SemVerIncrement.PATCH)), + (r"^refactor", str(SemVerIncrement.PATCH)), + (r"^perf", str(SemVerIncrement.PATCH)), ) ) BUMP_MAP_MAJOR_VERSION_ZERO = OrderedDict( ( - (r"^.+!$", MINOR), - (r"^BREAKING[\-\ ]CHANGE", MINOR), - (r"^feat", MINOR), - (r"^fix", PATCH), - (r"^refactor", PATCH), - (r"^perf", PATCH), + (r"^.+!$", str(SemVerIncrement.MINOR)), + (r"^BREAKING[\-\ ]CHANGE", str(SemVerIncrement.MINOR)), + (r"^feat", str(SemVerIncrement.MINOR)), + (r"^fix", str(SemVerIncrement.PATCH)), + (r"^refactor", str(SemVerIncrement.PATCH)), + (r"^perf", str(SemVerIncrement.PATCH)), ) ) CHANGE_TYPE_ORDER = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"] diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 84ded9316e..40ac9929a3 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -3,17 +3,19 @@ import re import sys import warnings +from enum import Enum from itertools import zip_longest from typing import ( TYPE_CHECKING, Any, ClassVar, - Literal, Protocol, cast, runtime_checkable, ) +from commitizen.bump_rule import SemVerIncrement + if sys.version_info >= (3, 10): from importlib import metadata else: @@ -22,7 +24,7 @@ from packaging.version import InvalidVersion # noqa: F401: expose the common exception from packaging.version import Version as _BaseVersion -from commitizen.defaults import MAJOR, MINOR, PATCH, Settings +from commitizen.defaults import Settings from commitizen.exceptions import VersionSchemeUnknown if TYPE_CHECKING: @@ -39,8 +41,21 @@ from typing import Self -Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] -Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] +class Prerelease(Enum): + ALPHA = "alpha" + BETA = "beta" + RC = "rc" + + @classmethod + def safe_cast(cls, value: object) -> Prerelease | None: + if not isinstance(value, str): + return None + try: + return cls[value.upper()] + except KeyError: + return None + + DEFAULT_VERSION_PARSER = r"v?(?P([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)" @@ -126,7 +141,7 @@ def __ne__(self, other: object) -> bool: def bump( self, - increment: Increment | None, + increment: SemVerIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -171,7 +186,7 @@ def prerelease(self) -> str | None: return None def generate_prerelease( - self, prerelease: str | None = None, offset: int = 0 + self, prerelease: Prerelease | None = None, offset: int = 0 ) -> str: """Generate prerelease @@ -186,20 +201,18 @@ def generate_prerelease( if not prerelease: return "" + prerelease_value = prerelease.value + new_prerelease_number = offset + # prevent down-bumping the pre-release phase, e.g. from 'b1' to 'a2' # https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases # https://semver.org/#spec-item-11 if self.is_prerelease and self.pre: - prerelease = max(prerelease, self.pre[0]) + prerelease_value = max(prerelease_value, self.pre[0]) + if prerelease_value.startswith(self.pre[0]): + new_prerelease_number = self.pre[1] + 1 - # version.pre is needed for mypy check - if self.is_prerelease and self.pre and prerelease.startswith(self.pre[0]): - prev_prerelease: int = self.pre[1] - new_prerelease_number = prev_prerelease + 1 - else: - new_prerelease_number = offset - pre_version = f"{prerelease}{new_prerelease_number}" - return pre_version + return f"{prerelease_value}{new_prerelease_number}" def generate_devrelease(self, devrelease: int | None) -> str: """Generate devrelease @@ -223,26 +236,30 @@ def generate_build_metadata(self, build_metadata: str | None) -> str: return f"+{build_metadata}" - def increment_base(self, increment: Increment | None = None) -> str: - prev_release = list(self.release) - increments = [MAJOR, MINOR, PATCH] - base = dict(zip_longest(increments, prev_release, fillvalue=0)) + def increment_base(self, increment: SemVerIncrement | None = None) -> str: + base = dict( + zip_longest( + (SemVerIncrement.MAJOR, SemVerIncrement.MINOR, SemVerIncrement.PATCH), + self.release, + fillvalue=0, + ) + ) - if increment == MAJOR: - base[MAJOR] += 1 - base[MINOR] = 0 - base[PATCH] = 0 - elif increment == MINOR: - base[MINOR] += 1 - base[PATCH] = 0 - elif increment == PATCH: - base[PATCH] += 1 + if increment == SemVerIncrement.MAJOR: + base[SemVerIncrement.MAJOR] += 1 + base[SemVerIncrement.MINOR] = 0 + base[SemVerIncrement.PATCH] = 0 + elif increment == SemVerIncrement.MINOR: + base[SemVerIncrement.MINOR] += 1 + base[SemVerIncrement.PATCH] = 0 + elif increment == SemVerIncrement.PATCH: + base[SemVerIncrement.PATCH] += 1 - return f"{base[MAJOR]}.{base[MINOR]}.{base[PATCH]}" + return f"{base[SemVerIncrement.MAJOR]}.{base[SemVerIncrement.MINOR]}.{base[SemVerIncrement.PATCH]}" def bump( self, - increment: Increment | None, + increment: SemVerIncrement | None, prerelease: Prerelease | None = None, prerelease_offset: int = 0, devrelease: int | None = None, @@ -272,12 +289,12 @@ def bump( base = self.increment_base(increment) else: base = f"{self.major}.{self.minor}.{self.micro}" - if increment == PATCH: + if increment == SemVerIncrement.PATCH: pass - elif increment == MINOR: + elif increment == SemVerIncrement.MINOR: if self.micro != 0: base = self.increment_base(increment) - elif increment == MAJOR: + elif increment == SemVerIncrement.MAJOR: if self.minor != 0 or self.micro != 0: base = self.increment_base(increment) dev_version = self.generate_devrelease(devrelease) diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index e15539d8a7..7b3203a981 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -13,6 +13,7 @@ import commitizen.commands.bump as bump from commitizen import cli, cmd, git, hooks +from commitizen.bump_rule import SemVerIncrement from commitizen.changelog_formats import ChangelogFormat from commitizen.cz.base import BaseCommitizen from commitizen.exceptions import ( @@ -1000,7 +1001,7 @@ def test_bump_with_pre_bump_hooks( new_version="0.2.0", new_tag_version="0.2.0", message="bump: version 0.1.0 → 0.2.0", - increment="MINOR", + increment=SemVerIncrement.MINOR, changelog_file_name=None, ), call( @@ -1012,7 +1013,7 @@ def test_bump_with_pre_bump_hooks( current_version="0.2.0", current_tag_version="0.2.0", message="bump: version 0.1.0 → 0.2.0", - increment="MINOR", + increment=SemVerIncrement.MINOR, changelog_file_name=None, ), ] diff --git a/tests/test_bump_find_increment.py b/tests/test_bump_find_increment.py deleted file mode 100644 index ff24ff17a7..0000000000 --- a/tests/test_bump_find_increment.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -CC: Conventional commits -SVE: Semantic version at the end -""" - -import pytest - -from commitizen import bump -from commitizen.cz.conventional_commits import ConventionalCommitsCz -from commitizen.git import GitCommit - -NONE_INCREMENT_CC = [ - "docs(README): motivation", - "ci: added travis", - "performance. Remove or disable the reimplemented linters", - "refactor that how this line starts", -] - -PATCH_INCREMENTS_CC = [ - "fix(setup.py): future is now required for every python version", - "docs(README): motivation", -] - -MINOR_INCREMENTS_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", - "perf: app is much faster", - "refactor: app is much faster", -] - -MAJOR_INCREMENTS_BREAKING_CHANGE_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "BREAKING CHANGE: `extends` key in config file is now used for extending other config files", # noqa - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_BREAKING_CHANGE_ALT_CC = [ - "feat(cli): added version", - "docs(README): motivation", - "BREAKING-CHANGE: `extends` key in config file is now used for extending other config files", # noqa - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_CC = [ - "feat(cli)!: added version", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_CC_SAMPLE_2 = [ - "feat(pipeline)!: some text with breaking change" -] - -MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_CC = [ - "chore!: drop support for Python 3.9", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_WITH_SCOPE_CC = [ - "chore(deps)!: drop support for Python 3.9", - "docs(README): motivation", - "fix(setup.py): future is now required for every python version", -] - -PATCH_INCREMENTS_SVE = ["readme motivation PATCH", "fix setup.py PATCH"] - -MINOR_INCREMENTS_SVE = [ - "readme motivation PATCH", - "fix setup.py PATCH", - "added version to cli MINOR", -] - -MAJOR_INCREMENTS_SVE = [ - "readme motivation PATCH", - "fix setup.py PATCH", - "added version to cli MINOR", - "extends key is used for other config files MAJOR", -] - -semantic_version_pattern = r"(MAJOR|MINOR|PATCH)" -semantic_version_map = {"MAJOR": "MAJOR", "MINOR": "MINOR", "PATCH": "PATCH"} - - -@pytest.mark.parametrize( - "messages, expected_type", - ( - (PATCH_INCREMENTS_CC, "PATCH"), - (MINOR_INCREMENTS_CC, "MINOR"), - (MAJOR_INCREMENTS_BREAKING_CHANGE_CC, "MAJOR"), - (MAJOR_INCREMENTS_BREAKING_CHANGE_ALT_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_OTHER_TYPE_WITH_SCOPE_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_CC, "MAJOR"), - (MAJOR_INCREMENTS_EXCLAMATION_CC_SAMPLE_2, "MAJOR"), - (NONE_INCREMENT_CC, None), - ), -) -def test_find_increment(messages, expected_type): - commits = [GitCommit(rev="test", title=message) for message in messages] - increment_type = bump.find_increment( - commits, - regex=ConventionalCommitsCz.bump_pattern, - increments_map=ConventionalCommitsCz.bump_map, - ) - assert increment_type == expected_type - - -@pytest.mark.parametrize( - "messages, expected_type", - ( - (PATCH_INCREMENTS_SVE, "PATCH"), - (MINOR_INCREMENTS_SVE, "MINOR"), - (MAJOR_INCREMENTS_SVE, "MAJOR"), - ), -) -def test_find_increment_sve(messages, expected_type): - commits = [GitCommit(rev="test", title=message) for message in messages] - increment_type = bump.find_increment( - commits, regex=semantic_version_pattern, increments_map=semantic_version_map - ) - assert increment_type == expected_type diff --git a/tests/test_bump_rule.py b/tests/test_bump_rule.py new file mode 100644 index 0000000000..e67c5da7c3 --- /dev/null +++ b/tests/test_bump_rule.py @@ -0,0 +1,648 @@ +import pytest + +from commitizen.bump_rule import ( + ConventionalCommitBumpRule, + CustomBumpRule, + SemVerIncrement, +) +from commitizen.defaults import ( + BUMP_MAP, + BUMP_MAP_MAJOR_VERSION_ZERO, + BUMP_PATTERN, +) +from commitizen.exceptions import NoPatternMapError + + +@pytest.fixture +def bump_rule(): + return ConventionalCommitBumpRule() + + +class TestConventionalCommitBumpRule: + def test_feat_commit(self, bump_rule): + assert ( + bump_rule.get_increment("feat: add new feature", False) + == SemVerIncrement.MINOR + ) + assert ( + bump_rule.get_increment("feat: add new feature", True) + == SemVerIncrement.MINOR + ) + + def test_fix_commit(self, bump_rule): + assert bump_rule.get_increment("fix: fix bug", False) == SemVerIncrement.PATCH + assert bump_rule.get_increment("fix: fix bug", True) == SemVerIncrement.PATCH + + def test_perf_commit(self, bump_rule): + assert ( + bump_rule.get_increment("perf: improve performance", False) + == SemVerIncrement.PATCH + ) + assert ( + bump_rule.get_increment("perf: improve performance", True) + == SemVerIncrement.PATCH + ) + + def test_refactor_commit(self, bump_rule): + assert ( + bump_rule.get_increment("refactor: restructure code", False) + == SemVerIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: restructure code", True) + == SemVerIncrement.PATCH + ) + + def test_breaking_change_with_bang(self, bump_rule): + assert ( + bump_rule.get_increment("feat!: breaking change", False) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat!: breaking change", True) + == SemVerIncrement.MINOR + ) + + def test_breaking_change_type(self, bump_rule): + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", False) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("BREAKING CHANGE: major change", True) + == SemVerIncrement.MINOR + ) + + def test_commit_with_scope(self, bump_rule): + assert ( + bump_rule.get_increment("feat(api): add new endpoint", False) + == SemVerIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui): fix button alignment", False) + == SemVerIncrement.PATCH + ) + + def test_commit_with_complex_scopes(self, bump_rule): + # Test with multiple word scopes + assert ( + bump_rule.get_increment("feat(user_management): add user roles", False) + == SemVerIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(database_connection): handle timeout", False) + == SemVerIncrement.PATCH + ) + + # Test with nested scopes + assert ( + bump_rule.get_increment("feat(api/auth): implement OAuth", False) + == SemVerIncrement.MINOR + ) + assert ( + bump_rule.get_increment("fix(ui/components): fix dropdown", False) + == SemVerIncrement.PATCH + ) + + # Test with breaking changes and scopes + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", False) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("feat(api)!: remove deprecated endpoints", True) + == SemVerIncrement.MINOR + ) + + # Test with BREAKING CHANGE and scopes + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", False + ) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment( + "BREAKING CHANGE(api): remove deprecated endpoints", True + ) + == SemVerIncrement.MINOR + ) + + def test_invalid_commit_message(self, bump_rule): + assert bump_rule.get_increment("invalid commit message", False) is None + assert bump_rule.get_increment("", False) is None + assert bump_rule.get_increment("feat", False) is None + + def test_other_commit_types(self, bump_rule): + # These commit types should not trigger any version bump + assert bump_rule.get_increment("docs: update documentation", False) is None + assert bump_rule.get_increment("style: format code", False) is None + assert bump_rule.get_increment("test: add unit tests", False) is None + assert bump_rule.get_increment("build: update build config", False) is None + assert bump_rule.get_increment("ci: update CI pipeline", False) is None + + def test_breaking_change_with_refactor(self, bump_rule): + """Test breaking changes with refactor type commit messages.""" + # Breaking change with refactor type + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("refactor!: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + + # Breaking change with refactor type and scope + assert ( + bump_rule.get_increment( + "refactor(api)!: remove deprecated endpoints", False + ) + == SemVerIncrement.MAJOR + ) + assert ( + bump_rule.get_increment("refactor(api)!: remove deprecated endpoints", True) + == SemVerIncrement.MINOR + ) + + # Regular refactor (should be SemVerIncrement.PATCH) + assert ( + bump_rule.get_increment("refactor: improve code structure", False) + == SemVerIncrement.PATCH + ) + assert ( + bump_rule.get_increment("refactor: improve code structure", True) + == SemVerIncrement.PATCH + ) + + +class TestFindIncrementByCallable: + @pytest.fixture + def get_increment(self, bump_rule): + return lambda x: bump_rule.get_increment(x, False) + + def test_single_commit(self, get_increment): + commit_messages = ["feat: add new feature"] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) + + def test_multiple_commits(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "docs: update readme", + ] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) + + def test_breaking_change(self, get_increment): + commit_messages = [ + "feat: new feature", + "feat!: breaking change", + ] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + == SemVerIncrement.MAJOR + ) + + def test_multi_line_commit(self, get_increment): + commit_messages = [ + "feat: new feature\n\nBREAKING CHANGE: major change", + ] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + == SemVerIncrement.MAJOR + ) + + def test_no_increment_needed(self, get_increment): + commit_messages = [ + "docs: update documentation", + "style: format code", + ] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) + + def test_empty_commits(self, get_increment): + commit_messages = [] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + is None + ) + + def test_major_version_zero(self): + bump_rule = ConventionalCommitBumpRule() + + commit_messages = [ + "feat!: breaking change", + "BREAKING CHANGE: major change", + ] + assert ( + SemVerIncrement.get_highest_by_messages( + commit_messages, lambda x: bump_rule.get_increment(x, True) + ) + == SemVerIncrement.MINOR + ) + + def test_mixed_commit_types(self, get_increment): + commit_messages = [ + "feat: new feature", + "fix: bug fix", + "perf: improve performance", + "refactor: restructure code", + ] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) + + def test_commit_with_scope(self, get_increment): + commit_messages = [ + "feat(api): add new endpoint", + "fix(ui): fix button alignment", + ] + assert ( + SemVerIncrement.get_highest_by_messages(commit_messages, get_increment) + == SemVerIncrement.MINOR + ) + + +class TestCustomBumpRule: + @pytest.fixture + def bump_pattern(self): + return r"^.*?\[(.*?)\].*$" + + @pytest.fixture + def bump_map(self): + return { + "MAJOR": SemVerIncrement.MAJOR, + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.PATCH, + } + + @pytest.fixture + def bump_map_major_version_zero(self): + return { + "MAJOR": SemVerIncrement.MINOR, # SemVerIncrement.MAJOR becomes SemVerIncrement.MINOR in version zero + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.PATCH, + } + + @pytest.fixture + def custom_bump_rule(self, bump_pattern, bump_map, bump_map_major_version_zero): + return CustomBumpRule(bump_pattern, bump_map, bump_map_major_version_zero) + + def test_major_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", False) + == SemVerIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", False) + == SemVerIncrement.MAJOR + ) + + def test_minor_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MINOR]", False) + == SemVerIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MINOR]", False) + == SemVerIncrement.MINOR + ) + + def test_patch_version(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [PATCH]", False) + == SemVerIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [PATCH]", False) + == SemVerIncrement.PATCH + ) + + def test_major_version_zero(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature [MAJOR]", True) + == SemVerIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix: bug fix [MAJOR]", True) + == SemVerIncrement.MINOR + ) + + def test_no_match(self, custom_bump_rule): + assert custom_bump_rule.get_increment("feat: add new feature", False) is None + assert custom_bump_rule.get_increment("fix: bug fix", False) is None + + def test_invalid_pattern(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", bump_map, bump_map_major_version_zero) + + def test_invalid_bump_map(self, bump_pattern): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, {}, {}) + + def test_invalid_bump_map_major_version_zero(self, bump_pattern, bump_map): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, bump_map, {}) + + def test_all_invalid(self): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", {}, {}) + + def test_none_values(self): + with pytest.raises(NoPatternMapError): + CustomBumpRule(None, {}, {}) + + def test_empty_pattern_with_valid_maps(self, bump_map, bump_map_major_version_zero): + with pytest.raises(NoPatternMapError): + CustomBumpRule("", bump_map, bump_map_major_version_zero) + + def test_empty_maps_with_valid_pattern(self, bump_pattern): + with pytest.raises(NoPatternMapError): + CustomBumpRule(bump_pattern, {}, {}) + + def test_complex_pattern(self): + pattern = r"^.*?\[(.*?)\].*?\[(.*?)\].*$" + bump_map = { + "MAJOR": SemVerIncrement.MAJOR, + "MINOR": SemVerIncrement.MINOR, + "PATCH": SemVerIncrement.PATCH, + } + rule = CustomBumpRule(pattern, bump_map, bump_map) + + assert ( + rule.get_increment( + "feat: add new feature [MAJOR] [MINOR]", + False, + ) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("fix: bug fix [MINOR] [PATCH]", False) + == SemVerIncrement.MINOR + ) + + def test_with_find_increment_by_callable(self, custom_bump_rule): + commit_messages = [ + "feat: add new feature [MAJOR]", + "fix: bug fix [PATCH]", + "docs: update readme [MINOR]", + ] + assert ( + SemVerIncrement.get_highest_by_messages( + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + ) + == SemVerIncrement.MAJOR + ) + + def test_flexible_bump_map(self, custom_bump_rule): + """Test that _find_highest_increment is used correctly in bump map processing.""" + # Test with multiple matching patterns + pattern = r"^((?Pmajor)|(?Pminor)|(?Ppatch))(?P\(.+\))?(?P!)?:" + bump_map = { + "major": SemVerIncrement.MAJOR, + "bang": SemVerIncrement.MAJOR, + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH, + } + bump_map_major_version_zero = { + "major": SemVerIncrement.MINOR, + "bang": SemVerIncrement.MINOR, + "minor": SemVerIncrement.MINOR, + "patch": SemVerIncrement.PATCH, + } + rule = CustomBumpRule(pattern, bump_map, bump_map_major_version_zero) + + # Test with multiple version tags + assert ( + rule.get_increment("major!: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("major!: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("major: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", False) + == SemVerIncrement.MAJOR + ) + assert ( + rule.get_increment("patch!: drop support for Python 2.7", True) + == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", False) + == SemVerIncrement.PATCH + ) + assert ( + rule.get_increment("patch: drop support for Python 2.7", True) + == SemVerIncrement.PATCH + ) + assert ( + rule.get_increment("minor: add new feature", False) == SemVerIncrement.MINOR + ) + assert ( + rule.get_increment("minor: add new feature", True) == SemVerIncrement.MINOR + ) + assert rule.get_increment("patch: fix bug", False) == SemVerIncrement.PATCH + assert rule.get_increment("patch: fix bug", True) == SemVerIncrement.PATCH + + +class TestCustomBumpRuleWithDefault: + @pytest.fixture + def custom_bump_rule(self): + return CustomBumpRule( + BUMP_PATTERN, + SemVerIncrement.safe_cast_dict(BUMP_MAP), + SemVerIncrement.safe_cast_dict(BUMP_MAP_MAJOR_VERSION_ZERO), + ) + + def test_breaking_change_with_bang(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat!: breaking change", False) + == SemVerIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("fix!: breaking change", False) + == SemVerIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("feat!: breaking change", True) + == SemVerIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix!: breaking change", True) + == SemVerIncrement.MINOR + ) + + def test_breaking_change_type(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("BREAKING CHANGE: major change", False) + == SemVerIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", False) + == SemVerIncrement.MAJOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING CHANGE: major change", True) + == SemVerIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("BREAKING-CHANGE: major change", True) + == SemVerIncrement.MINOR + ) + + def test_feat_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat: add new feature", False) + == SemVerIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("feat: add new feature", True) + == SemVerIncrement.MINOR + ) + + def test_fix_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("fix: fix bug", False) + == SemVerIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("fix: fix bug", True) + == SemVerIncrement.PATCH + ) + + def test_refactor_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("refactor: restructure code", False) + == SemVerIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("refactor: restructure code", True) + == SemVerIncrement.PATCH + ) + + def test_perf_commit(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("perf: improve performance", False) + == SemVerIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("perf: improve performance", True) + == SemVerIncrement.PATCH + ) + + def test_commit_with_scope(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("feat(api): add new endpoint", False) + == SemVerIncrement.MINOR + ) + assert ( + custom_bump_rule.get_increment("fix(ui): fix button alignment", False) + == SemVerIncrement.PATCH + ) + assert ( + custom_bump_rule.get_increment("refactor(core): restructure", False) + == SemVerIncrement.PATCH + ) + + def test_no_match(self, custom_bump_rule): + assert ( + custom_bump_rule.get_increment("docs: update documentation", False) is None + ) + assert custom_bump_rule.get_increment("style: format code", False) is None + assert custom_bump_rule.get_increment("test: add unit tests", False) is None + assert ( + custom_bump_rule.get_increment("build: update build config", False) is None + ) + assert custom_bump_rule.get_increment("ci: update CI pipeline", False) is None + + def test_with_find_increment_by_callable(self, custom_bump_rule): + commit_messages = [ + "feat!: breaking change", + "fix: bug fix", + "perf: improve performance", + ] + assert ( + SemVerIncrement.get_highest_by_messages( + commit_messages, lambda x: custom_bump_rule.get_increment(x, False) + ) + == SemVerIncrement.MAJOR + ) + + +class TestGetHighest: + def test_get_highest_with_major(self): + increments = [ + SemVerIncrement.PATCH, + SemVerIncrement.MINOR, + SemVerIncrement.MAJOR, + ] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MAJOR + + def test_get_highest_with_minor(self): + increments = [SemVerIncrement.PATCH, SemVerIncrement.MINOR, None] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MINOR + + def test_get_highest_with_patch(self): + increments = [SemVerIncrement.PATCH, None, None] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.PATCH + + def test_get_highest_with_none(self): + increments = [None, None, None] + assert SemVerIncrement.get_highest(increments) is None + + def test_get_highest_empty(self): + increments = [] + assert SemVerIncrement.get_highest(increments) is None + + def test_get_highest_mixed_order(self): + increments = [ + SemVerIncrement.MAJOR, + SemVerIncrement.PATCH, + SemVerIncrement.MINOR, + ] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MAJOR + + def test_get_highest_with_none_values(self): + increments = [None, SemVerIncrement.MINOR, None, SemVerIncrement.PATCH] + assert SemVerIncrement.get_highest(increments) == SemVerIncrement.MINOR + + +class TestSafeCast: + def test_safe_cast_valid_strings(self): + assert SemVerIncrement.safe_cast("MAJOR") == SemVerIncrement.MAJOR + assert SemVerIncrement.safe_cast("MINOR") == SemVerIncrement.MINOR + assert SemVerIncrement.safe_cast("PATCH") == SemVerIncrement.PATCH + + def test_safe_cast_invalid_strings(self): + assert SemVerIncrement.safe_cast("invalid") is None + assert SemVerIncrement.safe_cast("major") is None # case sensitive + assert SemVerIncrement.safe_cast("") is None + + def test_safe_cast_non_string_values(self): + assert SemVerIncrement.safe_cast(None) is None + assert SemVerIncrement.safe_cast(1) is None + assert SemVerIncrement.safe_cast(True) is None + assert SemVerIncrement.safe_cast([]) is None + assert SemVerIncrement.safe_cast({}) is None + assert ( + SemVerIncrement.safe_cast(SemVerIncrement.MAJOR) is None + ) # enum value itself diff --git a/tests/test_version_scheme_pep440.py b/tests/test_version_scheme_pep440.py index a983dad14a..e512dd2d39 100644 --- a/tests/test_version_scheme_pep440.py +++ b/tests/test_version_scheme_pep440.py @@ -3,184 +3,188 @@ import pytest -from commitizen.version_schemes import Pep440, VersionProtocol +from commitizen.bump_rule import SemVerIncrement +from commitizen.version_schemes import Pep440, Prerelease, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1.dev1"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("0.2.0", "MINOR", None, 0, None), "0.3.0"), - (("0.2.0", "MINOR", None, 0, 1), "0.3.0.dev1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1a0"), - (("0.3.1a0", None, "alpha", 0, None), "0.3.1a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1a1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, 1), "0.1.1.dev1"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, 1), "0.3.0.dev1"), + (("0.3.0", SemVerIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1a1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1a1"), + (("0.3.1a0", None, Prerelease.ALPHA, 1, None), "0.3.1a1"), (("0.3.1a0", None, None, 0, None), "0.3.1"), - (("0.3.1", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0a0"), - (("1.0.0a0", None, "alpha", 0, None), "1.0.0a1"), - (("1.0.0a1", None, "alpha", 0, None), "1.0.0a2"), - (("1.0.0a1", None, "alpha", 0, 1), "1.0.0a2.dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0a3.dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0a3.dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0b1"), - (("1.0.0b1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc0", None, "rc", 0, None), "1.0.0rc1"), - (("1.0.0rc0", None, "rc", 0, 1), "1.0.0rc1.dev1"), - (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", SemVerIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("1.0.0a0", None, Prerelease.ALPHA, 0, None), "1.0.0a1"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, None), "1.0.0a2"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, 1), "1.0.0a2.dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 1), "1.0.0a3.dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 0), "1.0.0a3.dev0"), + (("1.0.0a1", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0b0", None, Prerelease.BETA, 0, None), "1.0.0b1"), + (("1.0.0b1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc0", None, Prerelease.RC, 0, None), "1.0.0rc1"), + (("1.0.0rc0", None, Prerelease.RC, 0, 1), "1.0.0rc1.dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", SemVerIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", SemVerIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", SemVerIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", SemVerIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", SemVerIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", SemVerIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1b1", None, "alpha", 0, None), "0.1.1b2"), - (("0.1.1rc0", None, "alpha", 0, None), "0.1.1rc1"), - (("0.1.1rc0", None, "beta", 0, None), "0.1.1rc1"), + (("0.1.1b1", None, Prerelease.ALPHA, 0, None), "0.1.1b2"), + (("0.1.1rc0", None, Prerelease.ALPHA, 0, None), "0.1.1rc1"), + (("0.1.1rc0", None, Prerelease.BETA, 0, None), "0.1.1rc1"), ] weird_cases = [ - (("1.1", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1a0", None, "alpha", 0, None), "1.0.0a1"), - (("1a0", None, "alpha", 1, None), "1.0.0a1"), - (("1", None, "beta", 0, None), "1.0.0b0"), - (("1", None, "beta", 1, None), "1.0.0b1"), - (("1beta", None, "beta", 0, None), "1.0.0b1"), - (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0a2"), - (("1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", SemVerIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), + (("1a0", None, Prerelease.ALPHA, 0, None), "1.0.0a1"), + (("1a0", None, Prerelease.ALPHA, 1, None), "1.0.0a1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0b1"), + (("1beta", None, Prerelease.BETA, 0, None), "1.0.0b1"), + (("1.0.0alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0a2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc1+e20d7b57f3eb", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1a0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0a0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0a0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0a1"), - (("1.0.0a2", None, "beta", 0, None), "1.0.0b0"), - (("1.0.0a2", None, "beta", 1, None), "1.0.0b1"), - (("1.0.0beta1", None, "rc", 0, None), "1.0.0rc0"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0rc2"), + (("0.1.1", SemVerIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1a0"), + (("0.9.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0a1"), + (("1.0.0a2", None, Prerelease.BETA, 0, None), "1.0.0b0"), + (("1.0.0a2", None, Prerelease.BETA, 1, None), "1.0.0b1"), + (("1.0.0beta1", None, Prerelease.RC, 0, None), "1.0.0rc0"), + (("1.0.0rc1", None, Prerelease.RC, 0, None), "1.0.0rc2"), ] # additional pre-release tests run through various release scenarios prerelease_cases = [ # - (("3.3.3", "PATCH", "alpha", 0, None), "3.3.4a0"), - (("3.3.4a0", "PATCH", "alpha", 0, None), "3.3.4a1"), - (("3.3.4a1", "MINOR", "alpha", 0, None), "3.4.0a0"), - (("3.4.0a0", "PATCH", "alpha", 0, None), "3.4.0a1"), - (("3.4.0a1", "MINOR", "alpha", 0, None), "3.4.0a2"), - (("3.4.0a2", "MAJOR", "alpha", 0, None), "4.0.0a0"), - (("4.0.0a0", "PATCH", "alpha", 0, None), "4.0.0a1"), - (("4.0.0a1", "MINOR", "alpha", 0, None), "4.0.0a2"), - (("4.0.0a2", "MAJOR", "alpha", 0, None), "4.0.0a3"), + (("3.3.3", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a0"), + (("3.3.4a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.3.4a1"), + (("3.3.4a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a0"), + (("3.4.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "3.4.0a1"), + (("3.4.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.4.0a2"), + (("3.4.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), + (("4.0.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "4.0.0a1"), + (("4.0.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "4.0.0a2"), + (("4.0.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a3"), # - (("1.0.0", "PATCH", "alpha", 0, None), "1.0.1a0"), - (("1.0.1a0", "PATCH", "alpha", 0, None), "1.0.1a1"), - (("1.0.1a1", "MINOR", "alpha", 0, None), "1.1.0a0"), - (("1.1.0a0", "PATCH", "alpha", 0, None), "1.1.0a1"), - (("1.1.0a1", "MINOR", "alpha", 0, None), "1.1.0a2"), - (("1.1.0a2", "MAJOR", "alpha", 0, None), "2.0.0a0"), + (("1.0.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a0"), + (("1.0.1a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.0.1a1"), + (("1.0.1a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), # - (("1.0.0", "MINOR", "alpha", 0, None), "1.1.0a0"), - (("1.1.0a0", "PATCH", "alpha", 0, None), "1.1.0a1"), - (("1.1.0a1", "MINOR", "alpha", 0, None), "1.1.0a2"), - (("1.1.0a2", "PATCH", "alpha", 0, None), "1.1.0a3"), - (("1.1.0a3", "MAJOR", "alpha", 0, None), "2.0.0a0"), + (("1.0.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), + (("1.1.0a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a1"), + (("1.1.0a1", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a2"), + (("1.1.0a2", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "1.1.0a3"), + (("1.1.0a3", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), # - (("1.0.0", "MAJOR", "alpha", 0, None), "2.0.0a0"), - (("2.0.0a0", "MINOR", "alpha", 0, None), "2.0.0a1"), - (("2.0.0a1", "PATCH", "alpha", 0, None), "2.0.0a2"), - (("2.0.0a2", "MAJOR", "alpha", 0, None), "2.0.0a3"), - (("2.0.0a3", "MINOR", "alpha", 0, None), "2.0.0a4"), - (("2.0.0a4", "PATCH", "alpha", 0, None), "2.0.0a5"), - (("2.0.0a5", "MAJOR", "alpha", 0, None), "2.0.0a6"), + (("1.0.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a0"), + (("2.0.0a0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a1"), + (("2.0.0a1", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a2"), + (("2.0.0a2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a3"), + (("2.0.0a3", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0a4"), + (("2.0.0a4", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0a5"), + (("2.0.0a5", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "2.0.0a6"), # - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.0.0b1"), - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.0b1"), + (("2.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.0.0b1"), + (("2.0.0b0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.0b1"), # - (("1.0.1a0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1a0", "MINOR", None, 0, None), "1.1.0"), - (("1.0.1a0", "MAJOR", None, 0, None), "2.0.0"), + (("1.0.1a0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1a0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.0.1a0", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), # - (("1.1.0a0", "PATCH", None, 0, None), "1.1.0"), - (("1.1.0a0", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0a0", "MAJOR", None, 0, None), "2.0.0"), + (("1.1.0a0", SemVerIncrement.PATCH, None, 0, None), "1.1.0"), + (("1.1.0a0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0a0", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), # - (("2.0.0a0", "MINOR", None, 0, None), "2.0.0"), - (("2.0.0a0", "MAJOR", None, 0, None), "2.0.0"), - (("2.0.0a0", "PATCH", None, 0, None), "2.0.0"), + (("2.0.0a0", SemVerIncrement.MINOR, None, 0, None), "2.0.0"), + (("2.0.0a0", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), + (("2.0.0a0", SemVerIncrement.PATCH, None, 0, None), "2.0.0"), # (("3.0.0a1", None, None, 0, None), "3.0.0"), (("3.0.0b1", None, None, 0, None), "3.0.0"), (("3.0.0rc1", None, None, 0, None), "3.0.0"), # - (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("3.1.4", None, "beta", 0, None), "3.1.4b0"), - (("3.1.4", None, "rc", 0, None), "3.1.4rc0"), + (("3.1.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + (("3.1.4", None, Prerelease.BETA, 0, None), "3.1.4b0"), + (("3.1.4", None, Prerelease.RC, 0, None), "3.1.4rc0"), # - (("3.1.4", None, "alpha", 0, None), "3.1.4a0"), - (("3.1.4a0", "PATCH", "alpha", 0, None), "3.1.4a1"), # UNEXPECTED! - (("3.1.4a0", "MINOR", "alpha", 0, None), "3.2.0a0"), - (("3.1.4a0", "MAJOR", "alpha", 0, None), "4.0.0a0"), + (("3.1.4", None, Prerelease.ALPHA, 0, None), "3.1.4a0"), + ( + ("3.1.4a0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), + "3.1.4a1", + ), # UNEXPECTED! + (("3.1.4a0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "3.2.0a0"), + (("3.1.4a0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "4.0.0a0"), ] exact_cases = [ - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1b0"), + (("1.0.0a1", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1b0"), + (("1.0.0b0", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1b0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1rc0"), + (("1.0.0b1", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1rc0"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1rc0.dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, 1), "1.0.1rc0.dev1"), # with exact_increment=False: "1.0.0b0" - (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0b0"), + (("1.0.0a1", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0b0"), + (("1.0.0b0", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0b0"), # with exact_increment=False: "1.0.0b1" - (("1.0.0b0", "MINOR", "alpha", 0, None), "1.1.0a0"), + (("1.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "1.1.0a0"), # with exact_increment=False: "1.0.0rc0" - (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0rc0"), + (("1.0.0b1", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1" - (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0rc0"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0rc0"), # with exact_increment=False: "1.0.0rc1-dev1" - (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0rc0.dev1"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, 1), "1.1.0rc0.dev1"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + (("2.0.0b0", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + (("2.0.0b0", SemVerIncrement.MINOR, None, 0, None), "2.1.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + (("2.0.0b0", SemVerIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0a0"), + (("2.0.0b0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "3.0.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0a0"), + (("2.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.1.0a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1a0"), + (("2.0.0b0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.1a0"), ] diff --git a/tests/test_version_scheme_semver.py b/tests/test_version_scheme_semver.py index 8785717a34..f1eb43d11e 100644 --- a/tests/test_version_scheme_semver.py +++ b/tests/test_version_scheme_semver.py @@ -3,121 +3,122 @@ import pytest -from commitizen.version_schemes import SemVer, VersionProtocol +from commitizen.bump_rule import SemVerIncrement +from commitizen.version_schemes import Prerelease, SemVer, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev1"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("0.2.0", "MINOR", None, 0, None), "0.3.0"), - (("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-a0"), - (("0.3.1a0", None, "alpha", 0, None), "0.3.1-a1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-a1"), - (("0.3.1a0", None, "alpha", 1, None), "0.3.1-a1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, 1), "0.1.1-dev1"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, 1), "0.3.0-dev1"), + (("0.3.0", SemVerIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-a0"), + (("0.3.1a0", None, Prerelease.ALPHA, 0, None), "0.3.1-a1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1-a1"), + (("0.3.1a0", None, Prerelease.ALPHA, 1, None), "0.3.1-a1"), (("0.3.1a0", None, None, 0, None), "0.3.1"), - (("0.3.1", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-a0"), - (("1.0.0a0", None, "alpha", 0, None), "1.0.0-a1"), - (("1.0.0a1", None, "alpha", 0, None), "1.0.0-a2"), - (("1.0.0a1", None, "alpha", 0, 1), "1.0.0-a2-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 1), "1.0.0-a3-dev1"), - (("1.0.0a2.dev0", None, "alpha", 0, 0), "1.0.0-a3-dev0"), - (("1.0.0a1", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0b0", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0b1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc0", None, "rc", 0, None), "1.0.0-rc1"), - (("1.0.0rc0", None, "rc", 0, 1), "1.0.0-rc1-dev1"), - (("1.0.0rc0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0a3.dev0", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", SemVerIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("1.0.0a0", None, Prerelease.ALPHA, 0, None), "1.0.0-a1"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), + (("1.0.0a1", None, Prerelease.ALPHA, 0, 1), "1.0.0-a2-dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 1), "1.0.0-a3-dev1"), + (("1.0.0a2.dev0", None, Prerelease.ALPHA, 0, 0), "1.0.0-a3-dev0"), + (("1.0.0a1", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0b0", None, Prerelease.BETA, 0, None), "1.0.0-b1"), + (("1.0.0b1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc0", None, Prerelease.RC, 0, None), "1.0.0-rc1"), + (("1.0.0rc0", None, Prerelease.RC, 0, 1), "1.0.0-rc1-dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0a3.dev0", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", SemVerIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", SemVerIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", SemVerIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", SemVerIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", SemVerIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", SemVerIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1b1", None, "alpha", 0, None), "0.1.1-b2"), - (("0.1.1rc0", None, "alpha", 0, None), "0.1.1-rc1"), - (("0.1.1rc0", None, "beta", 0, None), "0.1.1-rc1"), + (("0.1.1b1", None, Prerelease.ALPHA, 0, None), "0.1.1-b2"), + (("0.1.1rc0", None, Prerelease.ALPHA, 0, None), "0.1.1-rc1"), + (("0.1.1rc0", None, Prerelease.BETA, 0, None), "0.1.1-rc1"), ] weird_cases = [ - (("1.1", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1a0", None, "alpha", 0, None), "1.0.0-a1"), - (("1a0", None, "alpha", 1, None), "1.0.0-a1"), - (("1", None, "beta", 0, None), "1.0.0-b0"), - (("1", None, "beta", 1, None), "1.0.0-b1"), - (("1beta", None, "beta", 0, None), "1.0.0-b1"), - (("1.0.0alpha1", None, "alpha", 0, None), "1.0.0-a2"), - (("1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", SemVerIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), + (("1a0", None, Prerelease.ALPHA, 0, None), "1.0.0-a1"), + (("1a0", None, Prerelease.ALPHA, 1, None), "1.0.0-a1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0-b1"), + (("1beta", None, Prerelease.BETA, 0, None), "1.0.0-b1"), + (("1.0.0alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc1+e20d7b57f3eb", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-a0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-a0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-a0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-a1"), - (("1.0.0a2", None, "beta", 0, None), "1.0.0-b0"), - (("1.0.0a2", None, "beta", 1, None), "1.0.0-b1"), - (("1.0.0beta1", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0rc1", None, "rc", 0, None), "1.0.0-rc2"), - (("1.0.0-a0", None, "rc", 0, None), "1.0.0-rc0"), - (("1.0.0-alpha1", None, "alpha", 0, None), "1.0.0-a2"), + (("0.1.1", SemVerIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-a0"), + (("0.9.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-a0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0-a1"), + (("1.0.0a2", None, Prerelease.BETA, 0, None), "1.0.0-b0"), + (("1.0.0a2", None, Prerelease.BETA, 1, None), "1.0.0-b1"), + (("1.0.0beta1", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0rc1", None, Prerelease.RC, 0, None), "1.0.0-rc2"), + (("1.0.0-a0", None, Prerelease.RC, 0, None), "1.0.0-rc0"), + (("1.0.0-alpha1", None, Prerelease.ALPHA, 0, None), "1.0.0-a2"), ] exact_cases = [ - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.0", "MINOR", None, 0, None), "1.1.0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.0", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", "PATCH", "beta", 0, None), "1.0.1-b0"), + (("1.0.0a1", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", "PATCH", "beta", 0, None), "1.0.1-b0"), + (("1.0.0b0", SemVerIncrement.PATCH, Prerelease.BETA, 0, None), "1.0.1-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", "PATCH", "rc", 0, None), "1.0.1-rc0"), + (("1.0.0b1", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "PATCH", "rc", 0, None), "1.0.1-rc0"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, None), "1.0.1-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", "PATCH", "rc", 0, 1), "1.0.1-rc0-dev1"), + (("1.0.0rc0", SemVerIncrement.PATCH, Prerelease.RC, 0, 1), "1.0.1-rc0-dev1"), # with exact_increment=False: "1.0.0-b0" - (("1.0.0a1", "MINOR", "beta", 0, None), "1.1.0-b0"), + (("1.0.0a1", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-b1" - (("1.0.0b0", "MINOR", "beta", 0, None), "1.1.0-b0"), + (("1.0.0b0", SemVerIncrement.MINOR, Prerelease.BETA, 0, None), "1.1.0-b0"), # with exact_increment=False: "1.0.0-rc0" - (("1.0.0b1", "MINOR", "rc", 0, None), "1.1.0-rc0"), + (("1.0.0b1", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1" - (("1.0.0rc0", "MINOR", "rc", 0, None), "1.1.0-rc0"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, None), "1.1.0-rc0"), # with exact_increment=False: "1.0.0-rc1-dev1" - (("1.0.0rc0", "MINOR", "rc", 0, 1), "1.1.0-rc0-dev1"), + (("1.0.0rc0", SemVerIncrement.MINOR, Prerelease.RC, 0, 1), "1.1.0-rc0-dev1"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MAJOR", None, 0, None), "3.0.0"), + (("2.0.0b0", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "MINOR", None, 0, None), "2.1.0"), + (("2.0.0b0", SemVerIncrement.MINOR, None, 0, None), "2.1.0"), # with exact_increment=False: "2.0.0" - (("2.0.0b0", "PATCH", None, 0, None), "2.0.1"), + (("2.0.0b0", SemVerIncrement.PATCH, None, 0, None), "2.0.1"), # same with exact_increment=False - (("2.0.0b0", "MAJOR", "alpha", 0, None), "3.0.0-a0"), + (("2.0.0b0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "3.0.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "MINOR", "alpha", 0, None), "2.1.0-a0"), + (("2.0.0b0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "2.1.0-a0"), # with exact_increment=False: "2.0.0b1" - (("2.0.0b0", "PATCH", "alpha", 0, None), "2.0.1-a0"), + (("2.0.0b0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "2.0.1-a0"), ] diff --git a/tests/test_version_scheme_semver2.py b/tests/test_version_scheme_semver2.py index d18a058a7c..1e1fe14523 100644 --- a/tests/test_version_scheme_semver2.py +++ b/tests/test_version_scheme_semver2.py @@ -3,84 +3,85 @@ import pytest -from commitizen.version_schemes import SemVer2, VersionProtocol +from commitizen.bump_rule import SemVerIncrement +from commitizen.version_schemes import Prerelease, SemVer2, VersionProtocol simple_flow = [ - (("0.1.0", "PATCH", None, 0, None), "0.1.1"), - (("0.1.0", "PATCH", None, 0, 1), "0.1.1-dev.1"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("0.2.0", "MINOR", None, 0, None), "0.3.0"), - (("0.2.0", "MINOR", None, 0, 1), "0.3.0-dev.1"), - (("0.3.0", "PATCH", None, 0, None), "0.3.1"), - (("0.3.0", "PATCH", "alpha", 0, None), "0.3.1-alpha.0"), - (("0.3.1-alpha.0", None, "alpha", 0, None), "0.3.1-alpha.1"), - (("0.3.0", "PATCH", "alpha", 1, None), "0.3.1-alpha.1"), - (("0.3.1-alpha.0", None, "alpha", 1, None), "0.3.1-alpha.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, None), "0.1.1"), + (("0.1.0", SemVerIncrement.PATCH, None, 0, 1), "0.1.1-dev.1"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, None), "0.3.0"), + (("0.2.0", SemVerIncrement.MINOR, None, 0, 1), "0.3.0-dev.1"), + (("0.3.0", SemVerIncrement.PATCH, None, 0, None), "0.3.1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.3.1-alpha.0"), + (("0.3.1-alpha.0", None, Prerelease.ALPHA, 0, None), "0.3.1-alpha.1"), + (("0.3.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 1, None), "0.3.1-alpha.1"), + (("0.3.1-alpha.0", None, Prerelease.ALPHA, 1, None), "0.3.1-alpha.1"), (("0.3.1-alpha.0", None, None, 0, None), "0.3.1"), - (("0.3.1", "PATCH", None, 0, None), "0.3.2"), - (("0.4.2", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"), - (("1.0.0-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), - (("1.0.0-alpha.1", None, "alpha", 0, 1), "1.0.0-alpha.2.dev.1"), - (("1.0.0-alpha.2.dev.0", None, "alpha", 0, 1), "1.0.0-alpha.3.dev.1"), - (("1.0.0-alpha.2.dev.0", None, "alpha", 0, 0), "1.0.0-alpha.3.dev.0"), - (("1.0.0-alpha.1", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0-beta.0", None, "beta", 0, None), "1.0.0-beta.1"), - (("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.0", None, "rc", 0, None), "1.0.0-rc.1"), - (("1.0.0-rc.0", None, "rc", 0, 1), "1.0.0-rc.1.dev.1"), - (("1.0.0-rc.0", "PATCH", None, 0, None), "1.0.0"), - (("1.0.0-alpha.3.dev.0", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0", "PATCH", None, 0, None), "1.0.1"), - (("1.0.1", "PATCH", None, 0, None), "1.0.2"), - (("1.0.2", "MINOR", None, 0, None), "1.1.0"), - (("1.1.0", "MINOR", None, 0, None), "1.2.0"), - (("1.2.0", "PATCH", None, 0, None), "1.2.1"), - (("1.2.1", "MAJOR", None, 0, None), "2.0.0"), + (("0.3.1", SemVerIncrement.PATCH, None, 0, None), "0.3.2"), + (("0.4.2", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("1.0.0-alpha.0", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.1"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, 1), "1.0.0-alpha.2.dev.1"), + (("1.0.0-alpha.2.dev.0", None, Prerelease.ALPHA, 0, 1), "1.0.0-alpha.3.dev.1"), + (("1.0.0-alpha.2.dev.0", None, Prerelease.ALPHA, 0, 0), "1.0.0-alpha.3.dev.0"), + (("1.0.0-alpha.1", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0-beta.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.1"), + (("1.0.0-beta.1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.0", None, Prerelease.RC, 0, None), "1.0.0-rc.1"), + (("1.0.0-rc.0", None, Prerelease.RC, 0, 1), "1.0.0-rc.1.dev.1"), + (("1.0.0-rc.0", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), + (("1.0.0-alpha.3.dev.0", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0", SemVerIncrement.PATCH, None, 0, None), "1.0.1"), + (("1.0.1", SemVerIncrement.PATCH, None, 0, None), "1.0.2"), + (("1.0.2", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1.1.0", SemVerIncrement.MINOR, None, 0, None), "1.2.0"), + (("1.2.0", SemVerIncrement.PATCH, None, 0, None), "1.2.1"), + (("1.2.1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), ] local_versions = [ - (("4.5.0+0.1.0", "PATCH", None, 0, None), "4.5.0+0.1.1"), - (("4.5.0+0.1.1", "MINOR", None, 0, None), "4.5.0+0.2.0"), - (("4.5.0+0.2.0", "MAJOR", None, 0, None), "4.5.0+1.0.0"), + (("4.5.0+0.1.0", SemVerIncrement.PATCH, None, 0, None), "4.5.0+0.1.1"), + (("4.5.0+0.1.1", SemVerIncrement.MINOR, None, 0, None), "4.5.0+0.2.0"), + (("4.5.0+0.2.0", SemVerIncrement.MAJOR, None, 0, None), "4.5.0+1.0.0"), ] # never bump backwards on pre-releases linear_prerelease_cases = [ - (("0.1.1-beta.1", None, "alpha", 0, None), "0.1.1-beta.2"), - (("0.1.1-rc.0", None, "alpha", 0, None), "0.1.1-rc.1"), - (("0.1.1-rc.0", None, "beta", 0, None), "0.1.1-rc.1"), + (("0.1.1-beta.1", None, Prerelease.ALPHA, 0, None), "0.1.1-beta.2"), + (("0.1.1-rc.0", None, Prerelease.ALPHA, 0, None), "0.1.1-rc.1"), + (("0.1.1-rc.0", None, Prerelease.BETA, 0, None), "0.1.1-rc.1"), ] weird_cases = [ - (("1.1", "PATCH", None, 0, None), "1.1.1"), - (("1", "MINOR", None, 0, None), "1.1.0"), - (("1", "MAJOR", None, 0, None), "2.0.0"), - (("1-alpha.0", None, "alpha", 0, None), "1.0.0-alpha.1"), - (("1-alpha.0", None, "alpha", 1, None), "1.0.0-alpha.1"), - (("1", None, "beta", 0, None), "1.0.0-beta.0"), - (("1", None, "beta", 1, None), "1.0.0-beta.1"), - (("1-beta", None, "beta", 0, None), "1.0.0-beta.1"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), - (("1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.1+e20d7b57f3eb", "PATCH", None, 0, None), "1.0.0"), + (("1.1", SemVerIncrement.PATCH, None, 0, None), "1.1.1"), + (("1", SemVerIncrement.MINOR, None, 0, None), "1.1.0"), + (("1", SemVerIncrement.MAJOR, None, 0, None), "2.0.0"), + (("1-alpha.0", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.1"), + (("1-alpha.0", None, Prerelease.ALPHA, 1, None), "1.0.0-alpha.1"), + (("1", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1", None, Prerelease.BETA, 1, None), "1.0.0-beta.1"), + (("1-beta", None, Prerelease.BETA, 0, None), "1.0.0-beta.1"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), + (("1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.1+e20d7b57f3eb", SemVerIncrement.PATCH, None, 0, None), "1.0.0"), ] # test driven development tdd_cases = [ - (("0.1.1", "PATCH", None, 0, None), "0.1.2"), - (("0.1.1", "MINOR", None, 0, None), "0.2.0"), - (("2.1.1", "MAJOR", None, 0, None), "3.0.0"), - (("0.9.0", "PATCH", "alpha", 0, None), "0.9.1-alpha.0"), - (("0.9.0", "MINOR", "alpha", 0, None), "0.10.0-alpha.0"), - (("0.9.0", "MAJOR", "alpha", 0, None), "1.0.0-alpha.0"), - (("0.9.0", "MAJOR", "alpha", 1, None), "1.0.0-alpha.1"), - (("1.0.0-alpha.2", None, "beta", 0, None), "1.0.0-beta.0"), - (("1.0.0-alpha.2", None, "beta", 1, None), "1.0.0-beta.1"), - (("1.0.0-beta.1", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-rc.1", None, "rc", 0, None), "1.0.0-rc.2"), - (("1.0.0-alpha.0", None, "rc", 0, None), "1.0.0-rc.0"), - (("1.0.0-alpha.1", None, "alpha", 0, None), "1.0.0-alpha.2"), + (("0.1.1", SemVerIncrement.PATCH, None, 0, None), "0.1.2"), + (("0.1.1", SemVerIncrement.MINOR, None, 0, None), "0.2.0"), + (("2.1.1", SemVerIncrement.MAJOR, None, 0, None), "3.0.0"), + (("0.9.0", SemVerIncrement.PATCH, Prerelease.ALPHA, 0, None), "0.9.1-alpha.0"), + (("0.9.0", SemVerIncrement.MINOR, Prerelease.ALPHA, 0, None), "0.10.0-alpha.0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 0, None), "1.0.0-alpha.0"), + (("0.9.0", SemVerIncrement.MAJOR, Prerelease.ALPHA, 1, None), "1.0.0-alpha.1"), + (("1.0.0-alpha.2", None, Prerelease.BETA, 0, None), "1.0.0-beta.0"), + (("1.0.0-alpha.2", None, Prerelease.BETA, 1, None), "1.0.0-beta.1"), + (("1.0.0-beta.1", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-rc.1", None, Prerelease.RC, 0, None), "1.0.0-rc.2"), + (("1.0.0-alpha.0", None, Prerelease.RC, 0, None), "1.0.0-rc.0"), + (("1.0.0-alpha.1", None, Prerelease.ALPHA, 0, None), "1.0.0-alpha.2"), ] diff --git a/tests/test_version_schemes.py b/tests/test_version_schemes.py index 8e2dae9027..ef646d6d2c 100644 --- a/tests/test_version_schemes.py +++ b/tests/test_version_schemes.py @@ -12,7 +12,35 @@ from commitizen.config.base_config import BaseConfig from commitizen.exceptions import VersionSchemeUnknown -from commitizen.version_schemes import Pep440, SemVer, get_version_scheme +from commitizen.version_schemes import Pep440, Prerelease, SemVer, get_version_scheme + + +class TestPrereleaseSafeCast: + def test_safe_cast_valid_strings(self): + assert Prerelease.safe_cast("ALPHA") == Prerelease.ALPHA + assert Prerelease.safe_cast("BETA") == Prerelease.BETA + assert Prerelease.safe_cast("RC") == Prerelease.RC + + def test_safe_cast_case_insensitive(self): + assert Prerelease.safe_cast("alpha") == Prerelease.ALPHA + assert Prerelease.safe_cast("beta") == Prerelease.BETA + assert Prerelease.safe_cast("rc") == Prerelease.RC + assert Prerelease.safe_cast("Alpha") == Prerelease.ALPHA + assert Prerelease.safe_cast("Beta") == Prerelease.BETA + assert Prerelease.safe_cast("Rc") == Prerelease.RC + + def test_safe_cast_invalid_strings(self): + assert Prerelease.safe_cast("invalid") is None + assert Prerelease.safe_cast("") is None + assert Prerelease.safe_cast("release") is None + + def test_safe_cast_non_string_values(self): + assert Prerelease.safe_cast(None) is None + assert Prerelease.safe_cast(1) is None + assert Prerelease.safe_cast(True) is None + assert Prerelease.safe_cast([]) is None + assert Prerelease.safe_cast({}) is None + assert Prerelease.safe_cast(Prerelease.ALPHA) is None # enum value itself def test_default_version_scheme_is_pep440(config: BaseConfig):