fix: 修复补跑历史日期的静默空转与日报日期错位 (P0-3/P0-4)
- run_step: 补跑保护——历史日期跳过 crawler(首页只含当天内容)并告警,复用已有 raw 数据 - run_step: report 步骤改用传入的 date_str,不再硬编码 date.today() - 新增 4 个补跑相关测试,修正 3 个既有测试适配新行为 - 全量 259 passed(3 个 crawler 基线失败与本次无关)
This commit is contained in:
@@ -266,3 +266,83 @@ def test_once_rejects_resume_with_steps(monkeypatch: pytest.MonkeyPatch) -> None
|
||||
date="20260616")
|
||||
rc = rs._once(args)
|
||||
assert rc == 2
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# P0-3: 补跑模式跳过抓取 / P0-4: report 使用传入日期
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_crawler_backfill_skips_past_date(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""补跑历史日期时跳过 crawler(首页只含当天内容),不执行子进程。"""
|
||||
from scheduler import pipeline
|
||||
|
||||
def _fail_run(cmd, timeout=None): # noqa: ARG001
|
||||
raise AssertionError("补跑模式不应执行子进程")
|
||||
|
||||
monkeypatch.setattr(pipeline.subprocess, "run", _fail_run)
|
||||
sr = pipeline.run_step("crawler", "20260101")
|
||||
assert sr.success is True
|
||||
assert "跳过" in sr.tail_msg
|
||||
assert sr.elapsed_sec == 0.0
|
||||
|
||||
|
||||
def test_crawler_today_runs_normally(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""当天日期正常执行 crawler(不受补跑保护影响)。"""
|
||||
from datetime import date
|
||||
|
||||
from scheduler import pipeline
|
||||
|
||||
called: list[str] = []
|
||||
|
||||
def _fake_run(cmd, timeout=None): # noqa: ARG001
|
||||
called.append(" ".join(cmd))
|
||||
return SimpleNamespace(returncode=0)
|
||||
|
||||
monkeypatch.setattr(pipeline.subprocess, "run", _fake_run)
|
||||
sr = pipeline.run_step("crawler", date.today().strftime("%Y%m%d"))
|
||||
assert sr.success is True
|
||||
assert called and "run_crawler" in called[0]
|
||||
|
||||
|
||||
def test_report_step_uses_date_str(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""report 步骤必须使用传入的 date_str,而非 date.today()(P0-4)。"""
|
||||
from scheduler import pipeline
|
||||
from scheduler import reporter
|
||||
|
||||
received: list[str] = []
|
||||
|
||||
def _fake_generate(day_str, upload=True): # noqa: ARG001
|
||||
received.append(day_str)
|
||||
return 12345
|
||||
|
||||
monkeypatch.setattr(reporter, "generate_report", _fake_generate)
|
||||
sr = pipeline.run_step("report", "20260101")
|
||||
assert sr.success is True
|
||||
assert received == ["20260101"]
|
||||
|
||||
|
||||
def test_pipeline_backfill_skips_crawler_keeps_rest(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""补跑历史日期:全链路中仅 crawler 跳过,其余步骤照常执行。"""
|
||||
from scheduler import pipeline
|
||||
|
||||
calls: list[str] = []
|
||||
|
||||
def _fake_run(cmd, timeout=None): # noqa: ARG001
|
||||
name = next(c.split(".")[-1] for c in cmd if "scripts.run_" in c)
|
||||
calls.append(name)
|
||||
return SimpleNamespace(returncode=0)
|
||||
|
||||
monkeypatch.setattr(pipeline.subprocess, "run", _fake_run)
|
||||
state_path = tmp_path / "state.json"
|
||||
result = pipeline.run_pipeline(
|
||||
"20260101",
|
||||
steps=["crawler", "extractor", "dedup"],
|
||||
state_path=state_path,
|
||||
)
|
||||
# crawler 跳过(未执行子进程),extractor/dedup 正常
|
||||
assert calls == ["run_extractor", "run_dedup"]
|
||||
assert all(s.success for s in result.steps)
|
||||
state = pipeline._load_pipeline_state(state_path)
|
||||
assert state["20260101"]["crawler"]["status"] == "ok"
|
||||
|
||||
+10
-3
@@ -56,9 +56,12 @@ def test_pipeline_result_partial_failure() -> None:
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_run_step_success() -> None:
|
||||
# crawler 补跑保护:仅当天日期才执行子进程,故用今天的日期
|
||||
from datetime import date
|
||||
today = date.today().strftime("%Y%m%d")
|
||||
with patch("subprocess.run", return_value=_mock_proc(returncode=0,
|
||||
stderr="INFO | 完成: 成功 20/20")):
|
||||
sr = run_step("crawler", "20260616")
|
||||
sr = run_step("crawler", today)
|
||||
assert sr.success is True
|
||||
assert sr.exit_code == 0
|
||||
|
||||
@@ -109,6 +112,8 @@ def test_run_pipeline_all_success() -> None:
|
||||
|
||||
def test_run_pipeline_continues_on_failure() -> None:
|
||||
"""中间步骤失败,后续继续执行(不阻断)。"""
|
||||
from datetime import date
|
||||
today = date.today().strftime("%Y%m%d") # crawler 补跑保护:需当天日期才执行
|
||||
call_count = {"n": 0}
|
||||
|
||||
def _side_effect(*args, **kwargs):
|
||||
@@ -118,7 +123,7 @@ def test_run_pipeline_continues_on_failure() -> None:
|
||||
return _mock_proc(returncode=0, stderr="ok")
|
||||
|
||||
with patch("subprocess.run", side_effect=_side_effect):
|
||||
result = run_pipeline("20260616", steps=["crawler", "extractor", "dedup", "llm"])
|
||||
result = run_pipeline(today, steps=["crawler", "extractor", "dedup", "llm"])
|
||||
assert len(result.steps) == 4
|
||||
# extractor 失败,但后续仍执行
|
||||
assert result.steps[1].success is False
|
||||
@@ -126,7 +131,9 @@ def test_run_pipeline_continues_on_failure() -> None:
|
||||
|
||||
|
||||
def test_run_pipeline_custom_steps() -> None:
|
||||
from datetime import date
|
||||
today = date.today().strftime("%Y%m%d")
|
||||
with patch("subprocess.run", return_value=_mock_proc(returncode=0, stderr="ok")):
|
||||
result = run_pipeline("20260616", steps=["crawler", "extractor"])
|
||||
result = run_pipeline(today, steps=["crawler", "extractor"])
|
||||
assert len(result.steps) == 2
|
||||
assert result.all_success is True
|
||||
|
||||
Reference in New Issue
Block a user