feat: add soak runner with budget guards and invariant scoreboard

This commit is contained in:
2026-07-21 01:23:40 -04:00
parent cca7071dbd
commit 9aa97a61ae
3 changed files with 416 additions and 0 deletions
+63
View File
@@ -129,3 +129,66 @@ class TestWeightedMix:
def test_invalid_weights_rejected(self):
with pytest.raises(ValueError):
weighted_mix({"P1": 0.0}, lambda: 0.5)
# ═══════════ T11: 记分板硬不变量(纯函数) ═══════════
from tools.soak.scoreboard import ( # noqa: E402
inv_call_ids_unique,
inv_rows_match_calls,
inv_rpm_never_exceeded,
inv_rss_stable,
structured_success_rate,
)
def _row(call_id, *, source="s1", created_at="2026-07-20T10:00:00", error=None, session="r-p3"):
return {
"call_id": call_id,
"source_name": source,
"created_at": created_at,
"error": error,
"session_id": session,
}
class TestInvariants:
def test_rows_match_calls(self):
rows = [_row("a"), _row("b")]
inv_rows_match_calls(rows, expected_calls=2)
with pytest.raises(AssertionError):
inv_rows_match_calls(rows, expected_calls=3)
def test_call_ids_unique(self):
inv_call_ids_unique([_row("a"), _row("b")])
with pytest.raises(AssertionError):
inv_call_ids_unique([_row("a"), _row("a")])
def test_rpm_minute_bucket(self):
# 固定分钟窗口口径(与限流器 int(sec/60) 同源;滑动窗会对合法的
# 跨窗背靠背流量误报,不采用)
rows = [
_row("a", created_at="2026-07-20T10:00:01"),
_row("b", created_at="2026-07-20T10:00:59"),
_row("c", created_at="2026-07-20T10:01:01"),
]
inv_rpm_never_exceeded(rows, {"s1": 2})
rows.append(_row("d", created_at="2026-07-20T10:00:30"))
with pytest.raises(AssertionError):
inv_rpm_never_exceeded(rows, {"s1": 2})
def test_rpm_ignores_unlimited_sources(self):
inv_rpm_never_exceeded([_row(str(i)) for i in range(100)], {"s1": 0})
def test_rss_stable(self):
inv_rss_stable([100.0, 105.0, 110.0], max_growth_mb=50.0)
with pytest.raises(AssertionError):
inv_rss_stable([100.0, 400.0], max_growth_mb=50.0)
def test_structured_success_rate(self):
rows = [
_row("a"),
_row("b", error="ResultInvalidError: x"),
_row("c", session="r-p1"), # 非 P3 剔除
]
assert structured_success_rate(rows, session_suffix="-p3") == pytest.approx(0.5)