from __future__ import annotations import sys import uuid from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING, NamedTuple import pytest from commitizen import cli, cmd, exceptions, git from commitizen.cmd import Command if TYPE_CHECKING: from unittest.mock import Mock from freezegun.api import FrozenDateTimeFactory from pytest_mock import MockerFixture from commitizen.version_schemes import Increment, Prerelease class VersionSchemeTestArgs(NamedTuple): current_version: str increment: Increment | None prerelease: Prerelease | None prerelease_offset: int devrelease: int | None @dataclass class UtilFixture: """ An helper fixture providing most common operations for tests. git and cli operations grant control over time. """ mocker: MockerFixture monkeypatch: pytest.MonkeyPatch freezer: FrozenDateTimeFactory def __post_init__(self): self.patch_env() def create_file_and_commit( self, message: str, filename: str | None = None, committer_date: str | None = None, ) -> None: """Create a file and commit it.""" if not filename: filename = str(uuid.uuid4()) Path(filename).touch() c = cmd.run("git add .") if c.return_code != 0: raise exceptions.CommitError(c.err) c = git.commit(message, committer_date=committer_date) if c.return_code != 0: raise exceptions.CommitError(c.err) self.tick() def create_branch(self, name: str) -> None: """Create a new branch.""" c = cmd.run(f"git branch {name}") if c.return_code != 0: raise exceptions.GitCommandError(c.err) def switch_branch(self, branch: str) -> None: """Switch to given branch.""" c = cmd.run(f"git switch {branch}") if c.return_code != 0: raise exceptions.GitCommandError(c.err) def merge_branch(self, branch: str) -> None: """Merge given branch into current branch.""" c = cmd.run(f"git merge {branch}") if c.return_code != 0: raise exceptions.GitCommandError(c.err) self.tick() def get_current_branch(self) -> str: """Get current git branch name.""" c = cmd.run("git rev-parse --abbrev-ref HEAD") if c.return_code != 0: raise exceptions.GitCommandError(c.err) return c.out def create_tag( self, tag: str, message: str | None = None, annotated: bool | None = None ) -> None: """Create a git tag.""" c = git.tag( tag, annotated=(annotated is True or message is not None), msg=message ) if c.return_code != 0: raise exceptions.CommitError(c.err) self.tick() def run_cli(self, *args: str) -> None: """Execute commitizen CLI with given args.""" self.mocker.patch.object(sys, "argv", ["cz", *args]) cli.main() self.tick() def patch_env(self) -> None: """Patch environment variables to sync with FreezeGun time.""" self.monkeypatch.setenv("DATE", datetime.now().isoformat()) self.monkeypatch.setenv("GIT_AUTHOR_DATE", datetime.now().isoformat()) self.monkeypatch.setenv("GIT_COMMITTER_DATE", datetime.now().isoformat()) def tick(self) -> None: """Advance time by 1 second.""" self.freezer.tick() self.patch_env() def mock_cmd(self, out: str = "", err: str = "", return_code: int = 0) -> Mock: """Mock cmd.run command.""" return_value = Command(out, err, b"", b"", return_code) return self.mocker.patch("commitizen.cmd.run", return_value=return_value) @pytest.fixture def util( monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture, freezer: FrozenDateTimeFactory, ) -> UtilFixture: """An helper fixture""" return UtilFixture(mocker=mocker, monkeypatch=monkeypatch, freezer=freezer)