Files
pyflowx/tests/cli/test_gittool_tool.py
T
zhou e31646e281 feat: 新增envdev、gittool、msdownload、pymake工具模块及对应测试用例
新增四个业务工具模块:
1. envdev: 开发环境镜像源配置工具,支持Python/Conda/Rust镜像配置及Linux系统环境配置
2. gittool: Git操作工具,提供提交、初始化、清理、推送等快捷子命令
3. msdownload: ModelScope模型/数据集下载工具
4. pymake: 项目构建工具,覆盖构建、测试、发布等全流程开发操作

同时为每个工具模块编写了完整的注册验证与功能测试用例
2026-07-06 13:11:09 +08:00

268 lines
10 KiB
Python

"""Tests for ops.gittool 模块 (@px.tool 注册验证)."""
from __future__ import annotations
import subprocess
from pathlib import Path
from unittest.mock import patch
import pytest
import pyflowx as px
import pyflowx.ops.gittool
from pyflowx.ops import gittool
from pyflowx.tools import _TOOL_REGISTRY, get_tool
# ---------------------------------------------------------------------- #
# @px.tool 注册验证
# ---------------------------------------------------------------------- #
class TestGittoolRegistration:
"""``gittool`` 模块 ``@px.tool`` 注册验证."""
def test_gittool_registered(self) -> None:
"""gittool 应在 _TOOL_REGISTRY 中注册."""
assert "gittool" in _TOOL_REGISTRY
def test_visible_subcommands(self) -> None:
"""可见子命令: a, c, i, isub, p, pl."""
subs = px.list_subcommands("gittool")
assert "a" in subs
assert "c" in subs
assert "i" in subs
assert "isub" in subs
assert "p" in subs
assert "pl" in subs
def test_clean_is_hidden(self) -> None:
"""clean 应为 hidden (不在可见子命令中)."""
subs = px.list_subcommands("gittool")
assert "clean" not in subs
def test_clean_hidden_included_with_flag(self) -> None:
"""include_hidden=True 时 clean 应出现."""
subs = px.list_subcommands("gittool", include_hidden=True)
assert "clean" in subs
def test_c_needs_clean(self) -> None:
"""c 应依赖 clean."""
spec = get_tool("gittool", "c")
assert "clean" in spec.needs
def test_clean_has_cmd(self) -> None:
"""clean 应有 cmd."""
spec = get_tool("gittool", "clean")
assert spec.cmd is not None
assert "git" in spec.cmd
assert "clean" in spec.cmd
assert "-xfd" in spec.cmd
def test_clean_cmd_includes_excludes(self) -> None:
"""clean cmd 应包含排除项."""
spec = get_tool("gittool", "clean")
assert spec.cmd is not None
assert "-e" in spec.cmd
assert ".venv" in spec.cmd
def test_c_has_cmd(self) -> None:
"""c 应有 cmd."""
spec = get_tool("gittool", "c")
assert spec.cmd == ("git", "status", "--porcelain")
def test_p_has_cmd(self) -> None:
"""p 应有 cmd."""
spec = get_tool("gittool", "p")
assert spec.cmd == ("git", "push")
def test_pl_has_cmd(self) -> None:
"""pl 应有 cmd."""
spec = get_tool("gittool", "pl")
assert spec.cmd == ("git", "pull")
# ---------------------------------------------------------------------- #
# 辅助函数验证 (not_has_git_repo / has_files)
# ---------------------------------------------------------------------- #
class TestGittoolHelpers:
"""``gittool`` 模块辅助函数验证."""
def test_not_has_git_repo_true(self, tmp_path: Path) -> None:
"""无 .git 目录时 not_has_git_repo 应返回 True."""
with patch.object(Path, "cwd", return_value=tmp_path):
assert gittool.not_has_git_repo() is True
def test_not_has_git_repo_false(self, tmp_path: Path) -> None:
"""有 .git 目录时 not_has_git_repo 应返回 False."""
(tmp_path / ".git").mkdir()
with patch.object(Path, "cwd", return_value=tmp_path):
assert gittool.not_has_git_repo() is False
def test_has_files_false_when_no_changes(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""无未提交更改时 has_files 应返回 False."""
class _FakeResult:
stdout = ""
monkeypatch.setattr("subprocess.run", lambda *_, **__: _FakeResult())
assert gittool.has_files() is False
def test_has_files_true_when_changes(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""有未提交更改时 has_files 应返回 True."""
class _FakeResult:
stdout = " M test.txt\n"
monkeypatch.setattr("subprocess.run", lambda *_, **__: _FakeResult())
assert gittool.has_files() is True
def test_has_files_false_on_subprocess_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""subprocess.SubprocessError 时 has_files 应返回 False."""
def _raise(*args: object, **kwargs: object) -> None:
raise subprocess.SubprocessError("fail")
monkeypatch.setattr("subprocess.run", _raise)
assert gittool.has_files() is False
def test_has_files_false_on_oserror(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""OSError 时 has_files 应返回 False."""
def _raise(*args: object, **kwargs: object) -> None:
raise OSError("fail")
monkeypatch.setattr("subprocess.run", _raise)
assert gittool.has_files() is False
# ---------------------------------------------------------------------- #
# git_add_commit / git_init_add_commit 函数体验证
# ---------------------------------------------------------------------- #
class TestGittoolCommit:
"""``gittool`` 模块提交函数验证."""
def test_git_add_commit_no_changes(
self,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""无更改时 git_add_commit 应打印提示且不执行 git 命令."""
monkeypatch.setattr(gittool, "has_files", lambda: False)
calls: list[list[str]] = []
def _capture(*args: object, **kwargs: object) -> None:
calls.append(args[0]) # type: ignore[arg-type]
monkeypatch.setattr("subprocess.run", _capture)
gittool.git_add_commit()
assert calls == []
assert "没有文件需要提交" in capsys.readouterr().out
def test_git_add_commit_with_changes(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""有更改时 git_add_commit 应执行 git add + git commit."""
monkeypatch.setattr(gittool, "has_files", lambda: True)
calls: list[list[str]] = []
def _capture(*args: object, **kwargs: object) -> None:
calls.append(args[0]) # type: ignore[arg-type]
monkeypatch.setattr("subprocess.run", _capture)
gittool.git_add_commit(message="custom msg")
assert calls == [["git", "add", "."], ["git", "commit", "-m", "custom msg"]]
def test_git_init_add_commit_init_and_commit(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""无仓库且有更改时 git_init_add_commit 应 git init + add + commit."""
monkeypatch.setattr(gittool, "not_has_git_repo", lambda: True)
monkeypatch.setattr(gittool, "has_files", lambda: True)
calls: list[list[str]] = []
def _capture(*args: object, **kwargs: object) -> None:
calls.append(args[0]) # type: ignore[arg-type]
monkeypatch.setattr("subprocess.run", _capture)
gittool.git_init_add_commit()
assert calls == [["git", "init"], ["git", "add", "."], ["git", "commit", "-m", "init commit"]]
def test_git_init_add_commit_init_no_changes(
self,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""无仓库且无更改时 git_init_add_commit 应 git init + 打印提示."""
monkeypatch.setattr(gittool, "not_has_git_repo", lambda: True)
monkeypatch.setattr(gittool, "has_files", lambda: False)
calls: list[list[str]] = []
def _capture(*args: object, **kwargs: object) -> None:
calls.append(args[0]) # type: ignore[arg-type]
monkeypatch.setattr("subprocess.run", _capture)
gittool.git_init_add_commit()
assert calls == [["git", "init"]]
assert "没有文件需要提交" in capsys.readouterr().out
def test_git_init_add_commit_no_init_with_changes(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""有仓库且有更改时 git_init_add_commit 应 add + commit (不 init)."""
monkeypatch.setattr(gittool, "not_has_git_repo", lambda: False)
monkeypatch.setattr(gittool, "has_files", lambda: True)
calls: list[list[str]] = []
def _capture(*args: object, **kwargs: object) -> None:
calls.append(args[0]) # type: ignore[arg-type]
monkeypatch.setattr("subprocess.run", _capture)
gittool.git_init_add_commit()
assert calls == [["git", "add", "."], ["git", "commit", "-m", "init commit"]]
def test_git_init_add_commit_no_init_no_changes(
self,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""有仓库且无更改时 git_init_add_commit 应仅打印提示 (不 init, 不 add/commit)."""
monkeypatch.setattr(gittool, "not_has_git_repo", lambda: False)
monkeypatch.setattr(gittool, "has_files", lambda: False)
calls: list[list[str]] = []
def _capture(*args: object, **kwargs: object) -> None:
calls.append(args[0]) # type: ignore[arg-type]
monkeypatch.setattr("subprocess.run", _capture)
gittool.git_init_add_commit()
assert calls == []
assert "没有文件需要提交" in capsys.readouterr().out
# ---------------------------------------------------------------------- #
# init_sub_dirs 函数验证
# ---------------------------------------------------------------------- #
class TestGittoolInitSubDirs:
"""``init_sub_dirs`` 函数验证."""
def test_init_sub_dirs_runs_graph_for_each_subdir(
self,
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""init_sub_dirs 应为每个子目录构建并运行图 (文件被跳过)."""
(tmp_path / "subdir1").mkdir()
(tmp_path / "subdir2").mkdir()
(tmp_path / "file.txt").write_text("test")
monkeypatch.setattr(Path, "cwd", lambda: tmp_path)
runs: list[object] = []
monkeypatch.setattr(gittool.px, "run", runs.append)
gittool.init_sub_dirs()
assert len(runs) == 2
def test_init_sub_dirs_no_subdirs(
self,
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""无子目录时 init_sub_dirs 不应运行任何图."""
(tmp_path / "file.txt").write_text("test")
monkeypatch.setattr(Path, "cwd", lambda: tmp_path)
runs: list[object] = []
monkeypatch.setattr(gittool.px, "run", runs.append)
gittool.init_sub_dirs()
assert runs == []