test(panelcache): 子代理审查加固 — 去 flaky + 补遥测差值覆盖

三处审查驱动的测试改进:
- 并发计数测试放宽 reuse_count==4 为 reuse+hit==4 (保留"只扫盘1次"核心不变量),
  消除慢调度下 leader 已写缓存致某线程走 hit 分支的 flaky。
- _FakeEngine 零值 dict 改从 PanelCache().stats() 取键 —— 字段重命名时桩自动跟随,
  杜绝 test 绿而生产 cache_stats KeyError 的契约漂移。
- 新增 test_walkforward_cache_telemetry_computes_deltas: 递增桩验证 scans/hits/
  reuses/秒数 = after-before, 覆盖此前 _FakeEngine 恒返 0 掩盖的差值/顺序逻辑。
This commit is contained in:
im47cn
2026-07-12 13:52:49 +08:00
parent db535c8004
commit 482904b591
2 changed files with 36 additions and 3 deletions
+32 -2
View File
@@ -136,11 +136,14 @@ class _FakeOptimizer:
return {"best_params": {"p": cfg.start.month}, "best_score": 2.0, "results": [], "n_completed": 1}
_ZERO_CACHE_STATS = {"compute_seconds": 0.0, "compute_count": 0, "hit_count": 0, "reuse_count": 0}
# 从真实 PanelCache 取字段模板 —— 字段被重命名时本桩自动跟随, 避免 test 绿而生产 KeyError。
from app.backtest.engine import PanelCache
_ZERO_CACHE_STATS = {k: type(v)() for k, v in PanelCache().stats().items()}
class _FakeEngine:
"""最小引擎桩: 仅提供 WF 遥测所需的 cache_stats。"""
"""最小引擎桩: 仅提供 WF 遥测所需的 cache_stats (字段同源自 PanelCache.stats)"""
def cache_stats(self):
return dict(_ZERO_CACHE_STATS)
@@ -182,6 +185,33 @@ def test_walkforward_optimizes_train_applies_oos():
assert svc.calls[0]["start"] >= opt.train_ranges[0][1]
class _CountingEngine:
"""首尾两次 cache_stats 返回不同值, 用于验证 WF 遥测差值/顺序计算 (非全零掩盖)。"""
def __init__(self):
self._n = 0
def cache_stats(self):
self._n += 1
if self._n == 1: # run 开头快照 (before)
return {"compute_seconds": 1.0, "compute_count": 2, "hit_count": 0, "reuse_count": 0}
return {"compute_seconds": 3.5, "compute_count": 7, "hit_count": 4, "reuse_count": 3} # after
def test_walkforward_cache_telemetry_computes_deltas():
"""cache_telemetry 用首尾快照差值: scans/hits/reuses/秒数 = after - before, 且方向正确。"""
opt, svc = _FakeOptimizer(), _FakeService()
svc.engine = _CountingEngine()
wf = WalkForwardService(opt, svc, strategy_engine=None)
out = wf.run(_wf_cfg())
tel = out["cache_telemetry"]
assert tel["scans"] == 5 # 7 - 2, 顺序写反会得 -5
assert tel["hits"] == 4 # 4 - 0
assert tel["single_flight_reuses"] == 3 # 3 - 0
assert abs(tel["load_panel_seconds"] - 2.5) < 1e-9 # 3.5 - 1.0
assert tel["load_panel_pct"] >= 0.0 # 扫盘耗时 / WF总耗时, 非负
def test_walkforward_reports_degradation():
opt, svc = _FakeOptimizer(), _FakeService()
wf = WalkForwardService(opt, svc, strategy_engine=None)
+4 -1
View File
@@ -187,8 +187,11 @@ def test_panel_cache_stats_counts_scans_hits_reuses():
t.join()
s = cache.stats()
# 核心不变量: 5 线程并发同 key 只扫盘 1 次 (args1 首次 + args2 一次 = 2)。
assert s["compute_count"] == 2, "并发同 key 应只扫盘 1 次"
assert s["reuse_count"] == 4, "其余 4 个跟随者应计为复用"
# 其余 4 线程要么 single-flight 复用, 要么(慢调度下 leader 已写缓存)命中 ——
# 二者之和恒为 4。不锁定 reuse/hit 具体分配, 避免时序 flaky。
assert s["reuse_count"] + (s["hit_count"] - 1) == 4, "4 个非 leader 线程应复用或命中"
def test_job_key_includes_asset_type_and_is_consistent():