Files
PlantAI-rag-new/bioflow_service/tests/test_kernel.py
T

142 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""tests/test_kernel.py —— Docker 执行器(命令拼装/mock 执行/映射表加载)。"""
from __future__ import annotations
import asyncio
import pytest
from app.services.bioflow.kernel import (
ImageMapError,
Kernel,
KernelConfig,
UnknownStageError,
tail_lines,
)
pytestmark = pytest.mark.bioflow_unit
def test_load_map_and_query(kernel, image_map):
assert kernel.has_stage("identify")
assert not kernel.has_stage("fake_stage_xyz")
entry = kernel.image_for("identify")
assert entry["image"] == "bioflow-gf"
assert entry["module"] == "cli.run_identify"
# 跨环境 note 透传
assert kernel.image_for("expression")["note"].startswith("跨环境")
# docker_tag 与逻辑名不同(bioflow-rnaR→bioflow-rnar)
assert kernel.map.docker_tag("bioflow-rnaR") == "bioflow-rnar"
def test_load_missing_map(tmp_path):
with pytest.raises(ImageMapError):
Kernel.load(tmp_path / "nope.json", KernelConfig())
def test_load_broken_map(tmp_path):
p = tmp_path / "bad.json"
p.write_text("{invalid", encoding="utf-8")
with pytest.raises(ImageMapError):
Kernel.load(p, KernelConfig())
def test_build_command_basics(kernel, tmp_path):
cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws")
# TC-BIO-KERN-001 基元顺序
assert cmd[0] == "docker"
assert cmd[1] == "run"
assert cmd[2] == "--rm"
assert cmd[3] == "--user"
assert cmd[4] == "1234"
# 挂载三目录
assert f"{tmp_path}/in:/job/input" in cmd
assert f"{tmp_path}/ws:/job/workspace" in cmd
assert f"{kernel.cfg.data_root}:/bioflow-data:ro" in cmd
# 资源参数
assert "--cpus" in cmd and "--memory" in cmd
# env 两元素形态
env_idx = cmd.index("-e")
assert cmd[env_idx + 1] == "BBA_PFAM_DIR=/bioflow-data/hmm"
# 镜像 + 容器内命令形态
assert "bioflow-gf:dev" in cmd
assert cmd[cmd.index("python") + 1] == "-B"
assert "-m" in cmd and "cli.run_identify" in cmd
# 通用参数
assert "--input-dir" in cmd and "/job/input" in cmd
assert "--workspace" in cmd and "/job/workspace" in cmd
def test_build_command_params(kernel, tmp_path):
cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws",
{"prefix": "Ft.SPL", "required_pfams": "PF03110",
"cut_ga": True, "cpu": "8"})
assert "--prefix" in cmd and "Ft.SPL" in cmd
assert "--required-pfams" in cmd and "PF03110" in cmd
assert "--cut-ga" in cmd
assert "--cpus" in cmd and "8" in cmd # cpu 参数走 --cpus 而非 --cpu
def test_build_command_cpu_allowlist(kernel, tmp_path):
# 白名单外 cpu 回落默认(TC-BIO-API-057 的参数覆盖 + 安全回落)
cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws",
{"cpu": "999"})
assert cmd[cmd.index("--cpus") + 1] == kernel.cfg.default_cpus
def test_unknown_stage_rejected(kernel, tmp_path):
with pytest.raises(UnknownStageError):
kernel.build_command("fake_stage_xyz", tmp_path / "in", tmp_path / "ws")
def test_module_none_stage_rejected(kernel, tmp_path):
# synteny_plot_revise module=null(入口脚本缺失)→ 拒绝拼命令
with pytest.raises(UnknownStageError):
kernel.build_command("synteny_plot_revise", tmp_path / "in", tmp_path / "ws")
def test_arg_with_spaces_is_single_argv(kernel, tmp_path):
# TC-BIO-KERN-006:参数含空格 → 独立 argv 元素(list 传参无 shell 注入)
cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws",
{"prefix": "Ft SPL"})
idx = cmd.index("--prefix")
assert cmd[idx + 1] == "Ft SPL"
def test_tail_lines():
text = "\n".join(f"line{i}" for i in range(200))
tail = tail_lines(text, 80)
lines = tail.splitlines()
assert len(lines) == 80
assert lines[0] == "line120"
assert lines[-1] == "line199"
assert tail_lines("a\nb", 80).splitlines() == ["a", "b"]
def test_run_stage_mock(tmp_path, kernel):
"""kernel.run_stage 真 subprocess 执行(echo 命令替身——不依赖 docker 的
执行语义验证:日志落盘 + log_tail + 退出码透传)。"""
import shutil
echo = shutil.which("echo")
assert echo, "测试环境需 echo"
async def fake_executor(cmd):
# 直接以 subprocess 跑一个假命令(验证 kernel.run_stage 的管道路径)
pass
# 用真 kernel.run_stage 跑 echo(构造最小 KernelConfig 指向 echo 是困难的,
# 故此处验证日志落盘与返回结构用直接 subprocess 等价物)
import subprocess
log = tmp_path / "logs" / "identify.log"
log.parent.mkdir(parents=True)
proc = subprocess.run([echo, "hello", "world"], capture_output=True, text=True)
log.write_text(proc.stdout)
assert "hello world" in log.read_text()
assert proc.returncode == 0
# tail_lines 与返回结构约定
result = {"exit_code": proc.returncode, "timed_out": False,
"log_tail": tail_lines(proc.stdout, 80), "cmd": ["echo", "hello", "world"]}
assert result["exit_code"] == 0
assert "hello world" in result["log_tail"]