Files
shaguabijia-app-server/tests/test_compare_alert_stuck_worker.py
T
guke 2e86d2ce27 test(compare-alert): 补 review 缺口——continue 分支回归 + <1s 边界
- read_stuck_points 最忙平台末帧损坏 → last fallthrough 到干净平台
  (重构最险分支的回归测试,原只在一次性验证脚本里)
- _fmt_last round 边界:500→<1s、999→停留1s
- continue 注释点明与旧版 _platform_stuck→None 等价

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 18:05:39 +08:00

214 lines
9.4 KiB
Python

"""build_hits 集成测:cancelled trace 优先/保底切换、failed 附卡点、限量。"""
import json
from pathlib import Path
from app.core.compare_alert_worker import _fmt_last, _fmt_stuck, build_hits
from app.services.trace_stuck import StuckPoint
class _Rec:
def __init__(self, **kw):
self.trace_id = kw.get("trace_id", "t")
self.status = kw.get("status", "cancelled")
self.total_ms = kw.get("total_ms")
self.step_count = kw.get("step_count")
self.fail_reason = kw.get("fail_reason")
self.information = kw.get("information")
self.app_version = kw.get("app_version")
self.created_at = kw.get("created_at")
self.trace_url = kw.get("trace_url")
self.user_id = kw.get("user_id")
def _frame(pdir: Path, idx: int, step: str, page: str) -> None:
pdir.mkdir(parents=True, exist_ok=True)
body = {"pipeline_step": step, "detected_page": page, "windows": [{"n": ["x" * 200]}]}
(pdir / f"step_{idx:03d}.json").write_text(
json.dumps(body, ensure_ascii=False), encoding="utf-8"
)
def _frame_ts(pdir: Path, idx: int, step: str, page: str, ts: str) -> None:
pdir.mkdir(parents=True, exist_ok=True)
body = {"pipeline_step": step, "detected_page": page, "timestamp": ts,
"windows": [{"n": ["x" * 200]}]}
(pdir / f"step_{idx:03d}.json").write_text(
json.dumps(body, ensure_ascii=False), encoding="utf-8"
)
_KW = dict(
stuck_threshold=15, max_tail=40, max_trace_reads=30,
cancelled_ms_threshold=90000, cancelled_step_threshold=30,
timeout_keywords=("超时",), unrecognized_keywords=("未识别",), biz_exclude_keywords=(),
)
def test_cancelled_stuck_reports_via_trace(tmp_path):
for i in range(18):
_frame(tmp_path / "20260804_x" / "meituan", i, "add_one_dish", "meal_detail_popup")
rec = _Rec(status="cancelled", trace_url="https://x/traces/20260804_x/",
total_ms=5000, step_count=3) # 保底不会中,靠 trace 判卡死
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert len(hits) == 1
assert hits[0].alert_type == "T5"
assert hits[0].reason == "深度放弃"
assert "美团·加菜" in hits[0].stuck_point
assert "" in hits[0].stuck_point
assert "meal_detail_popup" in hits[0].stuck_point # 卡点带末帧页面
def test_cancelled_readable_not_stuck_long_duration_reports(tmp_path):
# trace 可读但没判出原地卡点:仍过耗时/步数阈值兜底,超长(>90s)照报 T5;没卡点也用末帧标"退出前在哪屏"。
# (线上 trace 几乎总可读,若不回退则 total_ms 阈值形同虚设、超长放弃永不报——见 compare-fail-alert 排查。)
p = tmp_path / "20260804_y" / "eleme"
_frame(p, 0, "set_address", "home")
for i in range(1, 6):
_frame(p, i, "enter_store", "store")
rec = _Rec(status="cancelled", trace_url="https://x/traces/20260804_y/",
total_ms=95000, step_count=40)
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert len(hits) == 1
assert hits[0].alert_type == "T5"
assert hits[0].reason == "深度放弃"
assert hits[0].stuck_point == "饿了么·进店·store" # 无卡点 → 用末帧(平台·环节·页面)标退出前在哪屏
def test_cancelled_readable_not_stuck_short_no_report(tmp_path):
# trace 可读没卡点、且耗时/步数都没超阈值 → 正常早退,不报(兜底阈值把住,不误报)。
p = tmp_path / "20260804_ys" / "eleme"
_frame(p, 0, "set_address", "home")
for i in range(1, 6):
_frame(p, i, "enter_store", "store")
rec = _Rec(status="cancelled", trace_url="https://x/traces/20260804_ys/",
total_ms=5000, step_count=3)
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert hits == []
def test_cancelled_unreadable_falls_back(tmp_path):
rec = _Rec(status="cancelled", trace_url="https://x/traces/nope/",
total_ms=95000, step_count=3)
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert len(hits) == 1
assert hits[0].alert_type == "T5" and "深度放弃" in hits[0].reason
def test_no_work_log_dir_uses_fallback(tmp_path):
rec = _Rec(status="cancelled", trace_url="https://x/traces/y/",
total_ms=95000, step_count=3)
hits = build_hits([rec], work_log_dir="", **_KW)
assert len(hits) == 1 and "深度放弃" in hits[0].reason
def test_failed_gets_stuck_point_appended(tmp_path):
for i in range(20):
_frame(tmp_path / "20260804_f" / "meituan", i, "add_one_dish", "meal_detail_popup")
rec = _Rec(status="failed", fail_reason="启动超时",
trace_url="https://x/traces/20260804_f/")
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert len(hits) == 1
assert hits[0].alert_type == "T2"
assert "美团·加菜" in hits[0].stuck_point
assert "卡在" not in hits[0].reason
def test_max_trace_reads_zero_skips_trace(tmp_path):
for i in range(18):
_frame(tmp_path / "20260804_z" / "meituan", i, "add_one_dish", "meal_detail_popup")
rec = _Rec(status="cancelled", trace_url="https://x/traces/20260804_z/",
total_ms=95000, step_count=3)
kw = {**_KW, "max_trace_reads": 0}
hits = build_hits([rec], work_log_dir=str(tmp_path), **kw)
# 没读 trace → 回退保底 → deep(95s) → 深度放弃
assert len(hits) == 1 and "深度放弃" in hits[0].reason
def test_shared_reads_budget_across_branches(tmp_path):
# cancelled 和 failed 共用 max_trace_reads 预算;预算=1 时 cancelled 先消耗,failed 拿不到卡点
for i in range(18):
_frame(tmp_path / "20260804_c" / "meituan", i, "add_one_dish", "meal_detail_popup")
for i in range(20):
_frame(tmp_path / "20260804_d" / "meituan", i, "add_one_dish", "meal_detail_popup")
cancelled = _Rec(status="cancelled", trace_url="https://x/traces/20260804_c/",
total_ms=5000, step_count=3)
failed = _Rec(status="failed", fail_reason="启动超时",
trace_url="https://x/traces/20260804_d/")
kw = {**_KW, "max_trace_reads": 1}
hits = build_hits([cancelled, failed], work_log_dir=str(tmp_path), **kw)
assert len(hits) == 2
# cancelled 消耗了唯一预算 → 报卡死,卡点在 stuck_point
assert hits[0].alert_type == "T5" and hits[0].stuck_point is not None
# failed 超预算 → 仍报 T2,但 stuck_point 为 None
assert hits[1].alert_type == "T2" and hits[1].stuck_point is None
# ---- _fmt_stuck 单测 ----
def test_fmt_stuck_with_ms():
sp = StuckPoint(platform="meituan", pipeline_step="add_one_dish", frames=110, stuck_ms=32000)
assert _fmt_stuck(sp) == "美团·加菜 110帧/32s"
def test_fmt_stuck_without_ms():
sp = StuckPoint(platform="meituan", pipeline_step="add_one_dish", frames=110, stuck_ms=None)
assert _fmt_stuck(sp) == "美团·加菜 110帧"
# ---- _fmt_last 单测(末帧路径:环节·页面 + 停留,不显总帧数)----
def test_fmt_last_with_dwell():
sp = StuckPoint("meituan", "checkout", frames=480,
detected_page="checkout_page", dwell_ms=8000)
assert _fmt_last(sp) == "美团·结算·checkout_page 停留8s"
def test_fmt_last_sub_second():
sp = StuckPoint("meituan", "checkout", frames=480,
detected_page="checkout_page", dwell_ms=300)
assert _fmt_last(sp) == "美团·结算·checkout_page 停留<1s"
# <1s 阈值边界:round(500/1000)=0 → <1s;round(999/1000)=1 → 停留1s
sp500 = StuckPoint("meituan", "checkout", frames=480,
detected_page="checkout_page", dwell_ms=500)
assert _fmt_last(sp500) == "美团·结算·checkout_page 停留<1s"
sp999 = StuckPoint("meituan", "checkout", frames=480,
detected_page="checkout_page", dwell_ms=999)
assert _fmt_last(sp999) == "美团·结算·checkout_page 停留1s"
def test_fmt_last_without_dwell():
sp = StuckPoint("meituan", "checkout", frames=480,
detected_page="checkout_page", dwell_ms=None)
assert _fmt_last(sp) == "美团·结算·checkout_page"
# ---- 末帧路径带 dwell 集成 ----
def test_failed_stuck_point_has_dwell(tmp_path):
# failed 末帧 5 帧都在 checkout,ts :00→:08 → stuck_point 附「停留8s」
p = tmp_path / "20260807_f" / "meituan"
for i in range(5):
_frame_ts(p, i, "checkout", "checkout_page",
f"2026-08-07T12:00:{i * 2:02d}.000000")
rec = _Rec(status="failed", fail_reason="启动超时",
trace_url="https://x/traces/20260807_f/")
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert len(hits) == 1
assert hits[0].alert_type == "T2"
assert hits[0].stuck_point == "美团·结算·checkout_page 停留8s"
def test_cancelled_fallback_stuck_point_has_dwell(tmp_path):
# cancelled 超阈值(95s)但末段 5<15 不卡死 → 兜底,末帧带 ts → 附「停留8s」
p = tmp_path / "20260807_c" / "eleme"
_frame_ts(p, 0, "set_address", "home", "2026-08-07T12:00:00.000000")
for i in range(1, 6): # enter_store 5 帧 :02→:10
_frame_ts(p, i, "enter_store", "store",
f"2026-08-07T12:00:{i * 2:02d}.000000")
rec = _Rec(status="cancelled", trace_url="https://x/traces/20260807_c/",
total_ms=95000, step_count=40)
hits = build_hits([rec], work_log_dir=str(tmp_path), **_KW)
assert len(hits) == 1
assert hits[0].alert_type == "T5"
assert hits[0].stuck_point == "饿了么·进店·store 停留8s"