feat: add soak runner with budget guards and invariant scoreboard
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user