Files
pyflowx/tests/cli/test_bumpversion.py
T
zhou 4fd1d70b58 test: 修复跨平台路径断言不兼容问题
1. 调整测试用例中的路径比较逻辑,统一替换反斜杠为正斜杠适配Windows平台
2. 将原生echo/true/false命令替换为跨平台的python执行命令,避免环境依赖问题
2026-07-05 22:18:16 +08:00

193 lines
8.3 KiB
Python

"""Tests for ops.bumpversion module."""
from __future__ import annotations
import subprocess
from pathlib import Path
import pytest
from pyflowx.ops import bumpversion
@pytest.fixture(autouse=True)
def auto_use_tmp_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""自动使用临时路径."""
monkeypatch.chdir(tmp_path)
# ---------------------------------------------------------------------- #
# bump_file_version
# ---------------------------------------------------------------------- #
class TestBumpFileVersion:
"""Test bump_file_version function."""
@pytest.mark.parametrize(
("part", "expected"),
[("patch", "1.2.4"), ("minor", "1.3.0"), ("major", "2.0.0")],
)
def test_bump_pyproject(self, tmp_path: Path, part: str, expected: str) -> None:
"""pyproject.toml 三种 part 递增."""
f = tmp_path / "pyproject.toml"
f.write_text('version = "1.2.3"', encoding="utf-8")
assert bumpversion.bump_file_version(f, part) == expected # type: ignore[arg-type]
assert f'version = "{expected}"' in f.read_text(encoding="utf-8")
@pytest.mark.parametrize(
("part", "expected"),
[("patch", "1.2.4"), ("minor", "1.3.0"), ("major", "2.0.0")],
)
def test_bump_init_py(self, tmp_path: Path, part: str, expected: str) -> None:
"""__init__.py 三种 part 递增."""
f = tmp_path / "__init__.py"
f.write_text('__version__ = "1.2.3"', encoding="utf-8")
assert bumpversion.bump_file_version(f, part) == expected # type: ignore[arg-type]
assert f'__version__ = "{expected}"' in f.read_text(encoding="utf-8")
def test_prerelease_and_build_metadata_stripped(self, tmp_path: Path) -> None:
"""prerelease 和 build metadata 应被清除."""
f = tmp_path / "pyproject.toml"
f.write_text('version = "1.2.3-alpha.1+build.123"', encoding="utf-8")
assert bumpversion.bump_file_version(f, "patch") == "1.2.4"
content = f.read_text(encoding="utf-8")
assert "alpha" not in content
assert "build" not in content
def test_dependencies_not_modified(self, tmp_path: Path) -> None:
"""只更新 project version, 不动 dependencies 中的版本号."""
f = tmp_path / "pyproject.toml"
f.write_text(
'[project]\nversion = "1.0.0"\ndependencies = ["lib >= 2.0.0", "other >= 3.0.0"]\n',
encoding="utf-8",
)
assert bumpversion.bump_file_version(f, "patch") == "1.0.1"
content = f.read_text(encoding="utf-8")
assert 'version = "1.0.1"' in content
assert "lib >= 2.0.0" in content
assert "other >= 3.0.0" in content
def test_no_version_pattern_returns_none(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""未匹配到版本号模式返回 None (支持类型但无版本 / 不支持的文件类型)."""
f1 = tmp_path / "__init__.py"
f1.write_text("# no version here", encoding="utf-8")
assert bumpversion.bump_file_version(f1, "patch") is None
assert "未找到版本号模式" in capsys.readouterr().out
f2 = tmp_path / "test.txt"
f2.write_text("no version here", encoding="utf-8")
assert bumpversion.bump_file_version(f2, "patch") is None
def test_read_directory_raises(self, tmp_path: Path) -> None:
"""读取目录 (名为 __init__.py) 应抛异常."""
f = tmp_path / "__init__.py"
f.mkdir()
with pytest.raises(Exception): # noqa: B017
bumpversion.bump_file_version(f, "patch")
def test_write_failure_raises(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""写入失败应抛 OSError."""
f = tmp_path / "__init__.py"
f.write_text('__version__ = "1.0.0"', encoding="utf-8")
def raise_oserror(*_args: object, **_kwargs: object) -> None:
raise OSError("write failed")
monkeypatch.setattr(Path, "write_text", raise_oserror)
with pytest.raises(OSError, match="write failed"):
bumpversion.bump_file_version(f, "patch")
# ---------------------------------------------------------------------- #
# bump_project_version (核心 bug 修复: 不同步文件统一同步)
# ---------------------------------------------------------------------- #
class TestBumpProjectVersion:
"""Test bump_project_version function."""
@staticmethod
def _mock_subprocess(monkeypatch: pytest.MonkeyPatch) -> list[list[str]]:
"""Mock subprocess.run, 返回调用记录列表."""
calls: list[list[str]] = []
def fake_run(cmd: list[str], **_kwargs: object) -> subprocess.CompletedProcess[bytes]:
calls.append(cmd)
return subprocess.CompletedProcess(cmd, 0, b"", b"")
monkeypatch.setattr(subprocess, "run", fake_run)
return calls
def test_unsynced_files_synchronized(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
"""核心 bug 修复: 不同步的文件应统一同步到同一新版本号.
场景: __init__.py = 0.4.5, pyproject.toml = 0.3.5 (历史不同步)
期望: bump patch 后两者都变为 0.4.6 (取最大值 0.4.5 作为基准 +1)
"""
init_file = tmp_path / "src" / "pkg" / "__init__.py"
init_file.parent.mkdir(parents=True)
init_file.write_text('__version__ = "0.4.5"', encoding="utf-8")
pyproj = tmp_path / "pyproject.toml"
pyproj.write_text('version = "0.3.5"', encoding="utf-8")
calls = self._mock_subprocess(monkeypatch)
result = bumpversion.bump_project_version("patch")
assert result == "0.4.6"
assert '__version__ = "0.4.6"' in init_file.read_text(encoding="utf-8")
assert 'version = "0.4.6"' in pyproj.read_text(encoding="utf-8")
out = capsys.readouterr().out
assert "基准版本: 0.4.5" in out
assert "新版本: 0.4.6" in out
add_calls = [c for c in calls if c[:2] == ["git", "add"]]
assert len(add_calls) == 1
# 跨平台: Windows 上 Path 转换为反斜杠, 统一用正斜杠比较
init_path = str(init_file.relative_to(tmp_path)).replace("\\", "/")
assert init_path in [arg.replace("\\", "/") for arg in add_calls[0]]
assert "pyproject.toml" in add_calls[0]
assert "." not in add_calls[0][2:]
tag_calls = [c for c in calls if c[:2] == ["git", "tag"]]
assert len(tag_calls) == 1
assert "v0.4.6" in tag_calls[0]
def test_no_files_returns_none(self, capsys: pytest.CaptureFixture[str]) -> None:
"""无版本号文件返回 None."""
assert bumpversion.bump_project_version("patch") is None
assert "未找到包含版本号的文件" in capsys.readouterr().out
def test_files_without_version_returns_none(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
"""有文件但所有文件都无版本号返回 None."""
f = tmp_path / "__init__.py"
f.write_text("# no version here", encoding="utf-8")
self._mock_subprocess(monkeypatch)
assert bumpversion.bump_project_version("patch") is None
assert "未能从任何文件读取版本号" in capsys.readouterr().out
def test_no_tag_skips_tag_creation(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""no_tag=True 跳过 tag 创建."""
pyproj = tmp_path / "pyproject.toml"
pyproj.write_text('version = "1.0.0"', encoding="utf-8")
calls = self._mock_subprocess(monkeypatch)
assert bumpversion.bump_project_version("patch", no_tag=True) == "1.0.1"
assert not any(c[:2] == ["git", "tag"] for c in calls)
def test_ignored_dirs_excluded(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
""".venv 等忽略目录中的版本号文件不被处理."""
venv_init = tmp_path / ".venv" / "lib" / "pkg" / "__init__.py"
venv_init.parent.mkdir(parents=True)
venv_init.write_text('__version__ = "0.1.0"', encoding="utf-8")
pyproj = tmp_path / "pyproject.toml"
pyproj.write_text('version = "1.0.0"', encoding="utf-8")
self._mock_subprocess(monkeypatch)
assert bumpversion.bump_project_version("patch") == "1.0.1"
assert venv_init.read_text(encoding="utf-8") == '__version__ = "0.1.0"'