Initial commit
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""M7 定时任务测试。
|
||||
|
||||
使用 mock subprocess.run,不依赖真实脚本执行。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from scheduler import PipelineResult, StepResult, run_pipeline, run_step
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# fixtures
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def _mock_proc(returncode: int = 0, stderr: str = "") -> MagicMock:
|
||||
p = MagicMock()
|
||||
p.returncode = returncode
|
||||
p.stderr = stderr
|
||||
p.stdout = "ok"
|
||||
return p
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# StepResult 模型
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_step_result_defaults() -> None:
|
||||
sr = StepResult(name="crawler", success=True, elapsed_sec=12.3)
|
||||
assert sr.name == "crawler"
|
||||
assert sr.success is True
|
||||
assert sr.elapsed_sec == 12.3
|
||||
assert sr.exit_code is None
|
||||
|
||||
|
||||
def test_pipeline_result_all_success() -> None:
|
||||
pr = PipelineResult(steps=[
|
||||
StepResult(name="crawler", success=True, elapsed_sec=1),
|
||||
StepResult(name="extractor", success=True, elapsed_sec=2),
|
||||
])
|
||||
assert pr.all_success is True
|
||||
|
||||
|
||||
def test_pipeline_result_partial_failure() -> None:
|
||||
pr = PipelineResult(steps=[
|
||||
StepResult(name="crawler", success=True, elapsed_sec=1),
|
||||
StepResult(name="extractor", success=False, elapsed_sec=0),
|
||||
])
|
||||
assert pr.all_success is False
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# run_step
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_run_step_success() -> None:
|
||||
with patch("subprocess.run", return_value=_mock_proc(returncode=0,
|
||||
stderr="INFO | 完成: 成功 20/20")):
|
||||
sr = run_step("crawler", "20260616")
|
||||
assert sr.success is True
|
||||
assert sr.exit_code == 0
|
||||
|
||||
|
||||
def test_run_step_failure() -> None:
|
||||
with patch("subprocess.run", return_value=_mock_proc(returncode=1)):
|
||||
sr = run_step("extractor", "20260616")
|
||||
assert sr.success is False
|
||||
assert sr.exit_code == 1
|
||||
assert "rc=1" in sr.tail_msg
|
||||
|
||||
|
||||
def test_run_step_timeout() -> None:
|
||||
with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd=["uv"], timeout=10)):
|
||||
sr = run_step("llm", "20260616")
|
||||
assert sr.success is False
|
||||
assert "超时" in sr.tail_msg
|
||||
|
||||
|
||||
def test_run_step_exception() -> None:
|
||||
with patch("subprocess.run", side_effect=OSError("磁盘满")):
|
||||
sr = run_step("embedding", "20260616")
|
||||
assert sr.success is False
|
||||
assert "磁盘满" in sr.tail_msg
|
||||
|
||||
|
||||
def test_run_step_unknown_name() -> None:
|
||||
sr = run_step("nonexistent", "20260616")
|
||||
assert sr.success is False
|
||||
assert "未知" in sr.tail_msg
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# run_pipeline
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_run_pipeline_all_success() -> None:
|
||||
with (
|
||||
patch("subprocess.run", return_value=_mock_proc(returncode=0)),
|
||||
patch("scheduler.reporter.generate_report", return_value=Path("/tmp/r.html")),
|
||||
):
|
||||
result = run_pipeline("20260616", steps=["crawler", "extractor", "dedup"])
|
||||
assert len(result.steps) == 3
|
||||
assert result.all_success is True
|
||||
assert result.started_at is not None
|
||||
assert result.finished_at is not None
|
||||
|
||||
|
||||
def test_run_pipeline_continues_on_failure() -> None:
|
||||
"""中间步骤失败,后续继续执行(不阻断)。"""
|
||||
call_count = {"n": 0}
|
||||
|
||||
def _side_effect(*args, **kwargs):
|
||||
call_count["n"] += 1
|
||||
if call_count["n"] == 2: # extractor 失败
|
||||
return _mock_proc(returncode=1, stderr="GNE 提取异常")
|
||||
return _mock_proc(returncode=0, stderr="ok")
|
||||
|
||||
with patch("subprocess.run", side_effect=_side_effect):
|
||||
result = run_pipeline("20260616", steps=["crawler", "extractor", "dedup", "llm"])
|
||||
assert len(result.steps) == 4
|
||||
# extractor 失败,但后续仍执行
|
||||
assert result.steps[1].success is False
|
||||
assert result.steps[2].success is True
|
||||
|
||||
|
||||
def test_run_pipeline_custom_steps() -> None:
|
||||
with patch("subprocess.run", return_value=_mock_proc(returncode=0, stderr="ok")):
|
||||
result = run_pipeline("20260616", steps=["crawler", "extractor"])
|
||||
assert len(result.steps) == 2
|
||||
assert result.all_success is True
|
||||
Reference in New Issue
Block a user