diff --git a/tests/unit/test_soak_corpus.py b/tests/unit/test_soak_corpus.py index 90d05b7..d95ce33 100644 --- a/tests/unit/test_soak_corpus.py +++ b/tests/unit/test_soak_corpus.py @@ -242,3 +242,12 @@ class TestLiveInvariantsAndCaps: inv_fault_errors_present(rows, fault_source_names=[]) # 未配故障源直接通过 with pytest.raises(AssertionError): inv_fault_errors_present([_row("c")], fault_source_names=["bad_1"]) + + +class TestAnyErrors: + def test_presence_and_absence(self): + from tools.soak.scoreboard import inv_any_errors + + inv_any_errors([_row("a", error="TransientError: x"), _row("b")]) + with pytest.raises(AssertionError, match="故障未生效"): + inv_any_errors([_row("c")]) diff --git a/tools/soak/run_soak.py b/tools/soak/run_soak.py index c6f5e5a..c3f9302 100644 --- a/tools/soak/run_soak.py +++ b/tools/soak/run_soak.py @@ -198,12 +198,11 @@ def _scoreboard(args: argparse.Namespace, env: dict[str, str]) -> None: asyncio.run(_live_checks(args, env, verdicts)) else: verdicts.append(("记账归零/gate 可再准入", "SKIP — memory 后端跨进程不可查")) - # 不变量 2c: P5/P6 且配置了故障源 → 故障必须真实发生 + # 不变量 2c: P5/P6 故障混编池下错误必须真实发生。紧闸源被限流闸跳过 + # 不产错误行、故障名单无法从配置泛化推断 → 钉全局存在性,按源归因 + # 交报告"错误分布"人工核对(findings §4 条 2 的比例校准留待 P5 常态化) if args.scenario in ("P5", "P6"): - fault_names = [ - name for name in rpm_conf if rpm_conf[name] <= 10 - ] # 紧闸源;坏 key/黑洞源由错误分布人工核对(findings §4 条 2 校准留待 P5 实跑) - _check("故障混编生效(P5/P6)", sb.inv_fault_errors_present, rows, fault_source_names=fault_names) + _check("故障混编生效(P5/P6)", sb.inv_any_errors, rows) rate = sb.structured_success_rate(rows, session_suffix="-p3") verdicts.append(("P3 结构化成功率", f"{rate:.3f}(基线首跑建立)")) report = sb.render_report(args.run_id, rows, verdicts) diff --git a/tools/soak/scoreboard.py b/tools/soak/scoreboard.py index e6a06c8..b7a47e1 100644 --- a/tools/soak/scoreboard.py +++ b/tools/soak/scoreboard.py @@ -179,3 +179,12 @@ def inv_fault_errors_present(rows: list[Row], *, fault_source_names: list[str]) return fault_errors = [r for r in rows if r.get("error") and r["source_name"] in fault_source_names] assert fault_errors, f"故障源 {fault_source_names} 零错误行——故障混编未生效" + + +def inv_any_errors(rows: list[Row]) -> None: + """不变量 2c 的兜底形态(P5/P6): 故障源混编池下错误行必然存在。 + + 紧闸源被限流闸跳过不产错误行,故障名单无法从配置泛化推断——按源归因 + 交报告"错误分布"人工核对;此处只钉全局存在性。 + """ + assert any(r.get("error") for r in rows), "P5/P6 故障混编池零错误行——故障未生效"