e31646e281
新增四个业务工具模块: 1. envdev: 开发环境镜像源配置工具,支持Python/Conda/Rust镜像配置及Linux系统环境配置 2. gittool: Git操作工具,提供提交、初始化、清理、推送等快捷子命令 3. msdownload: ModelScope模型/数据集下载工具 4. pymake: 项目构建工具,覆盖构建、测试、发布等全流程开发操作 同时为每个工具模块编写了完整的注册验证与功能测试用例
268 lines
10 KiB
Python
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 == []
|