Files
pyflowx/tests/cli/test_llm.py
T
zhou f7fb95af83 refactor: 重构项目架构,移除YAML编排与旧注册系统
1.  将所有YAML配置的工具迁移到`@px.tool`装饰器实现
2.  拆分`llm`模块为`msdownload`和`sglang`子模块
3.  移除废弃的`registry.py`、`yaml_loader.py`和`yamlrun.py`模块
4.  清理项目依赖,移除PyYAML相关包
5.  更新文档与测试用例适配新架构
2026-07-06 13:04:57 +08:00

170 lines
6.8 KiB
Python

"""Tests for ops.msdownload / ops.sglang 模块 (ModelScope 下载/SGLang 服务)."""
from __future__ import annotations
import subprocess
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from pyflowx.conditions import Constants
from pyflowx.ops.msdownload import msdownload_run
from pyflowx.ops.sglang import install_sglang, run_sglang
# ---------------------------------------------------------------------- #
# msdownload_run
# ---------------------------------------------------------------------- #
class TestMsdownloadRun:
"""``msdownload_run`` 函数测试."""
def test_empty_name_does_nothing(self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
"""name 为空时应直接返回."""
called: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: called.append(cmd))
msdownload_run("")
captured = capsys.readouterr()
assert "name 不能为空" in captured.out
assert called == []
def test_default_download_dir(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""未提供 download_dir 时默认使用 ~/.models/<name 最后一段>."""
monkeypatch.setattr(Path, "home", lambda: tmp_path)
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
msdownload_run("Qwen/Qwen2.5-Coder")
expected_dir = tmp_path / ".models" / "Qwen2.5-Coder"
assert expected_dir.exists()
assert ran_cmds[0] == [
"uvx",
"modelscope",
"download",
"--model",
"Qwen/Qwen2.5-Coder",
"--local_dir",
str(expected_dir),
]
def test_custom_download_dir(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""提供 download_dir 时应使用指定目录."""
custom_dir = tmp_path / "custom"
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
msdownload_run("Qwen/Qwen2.5", "dataset", str(custom_dir))
assert custom_dir.exists()
assert ran_cmds[0][3] == "--dataset"
assert str(custom_dir) in ran_cmds[0]
def test_dataset_type(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""target_type=dataset 时应传递 --dataset."""
monkeypatch.setattr(Path, "home", lambda: tmp_path)
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
msdownload_run("foo/bar", "dataset")
assert "--dataset" in ran_cmds[0]
# ---------------------------------------------------------------------- #
# install_sglang
# ---------------------------------------------------------------------- #
class TestInstallSglang:
"""``install_sglang`` 函数测试."""
def test_already_installed_skips(self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
"""sglang 已安装时应跳过."""
monkeypatch.setattr("shutil.which", lambda _cmd: "/usr/bin/sglang")
called: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: called.append(cmd))
install_sglang()
captured = capsys.readouterr()
assert "已安装" in captured.out
assert called == []
def test_not_installed_runs_uv_install(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""sglang 未安装时应执行 uv install sglang[all]."""
monkeypatch.setattr("shutil.which", lambda _cmd: None)
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
install_sglang()
assert ran_cmds == [["uv", "install", "sglang[all]"]]
# ---------------------------------------------------------------------- #
# run_sglang
# ---------------------------------------------------------------------- #
class TestRunSglang:
"""``run_sglang`` 函数测试."""
def test_model_dir_not_exist_skips(
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
"""模型目录不存在时应跳过."""
monkeypatch.setattr(Path, "exists", lambda _self: False)
called: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: called.append(cmd))
run_sglang(model="/nonexistent/path")
captured = capsys.readouterr()
assert "模型目录不存在" in captured.out
assert called == []
def test_windows_uses_python(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""Windows 上应使用 python."""
monkeypatch.setattr(Constants, "IS_WINDOWS", True)
monkeypatch.setattr(Path, "expanduser", lambda _self: tmp_path)
monkeypatch.setattr(Path, "exists", lambda _self: True)
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
run_sglang(model=str(tmp_path))
assert ran_cmds[0][0] == "python"
assert "-m" in ran_cmds[0]
assert "sglang.launch_server" in ran_cmds[0]
def test_non_windows_uses_python3(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""非 Windows 上应使用 python3."""
monkeypatch.setattr(Constants, "IS_WINDOWS", False)
monkeypatch.setattr(Path, "expanduser", lambda _self: tmp_path)
monkeypatch.setattr(Path, "exists", lambda _self: True)
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
run_sglang(model=str(tmp_path), port=9000, ctx_len=4096, mem_fraction=0.5, host="127.0.0.1", log_level="debug")
cmd = ran_cmds[0]
assert cmd[0] == "python3"
assert "--port" in cmd
assert "9000" in cmd
assert "4096" in cmd
assert "0.5" in cmd
assert "127.0.0.1" in cmd
assert "debug" in cmd
def test_command_includes_qwen_parser(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""命令应包含 --tool-call-parser qwen."""
monkeypatch.setattr(Constants, "IS_WINDOWS", True)
monkeypatch.setattr(Path, "expanduser", lambda _self: tmp_path)
monkeypatch.setattr(Path, "exists", lambda _self: True)
ran_cmds: list[list[str]] = []
monkeypatch.setattr(subprocess, "run", lambda cmd, **_: ran_cmds.append(cmd) or MagicMock())
run_sglang(model=str(tmp_path))
cmd = ran_cmds[0]
assert "--tool-call-parser" in cmd
qwen_idx = cmd.index("--tool-call-parser")
assert cmd[qwen_idx + 1] == "qwen"