test(submissions): verify exact performance cohort boundaries

This commit is contained in:
2026-10-11 07:45:56 +08:00
parent bc5cd12056
commit c8cac766c7
3 changed files with 56 additions and 1 deletions
+6 -1
View File
@@ -5,9 +5,14 @@
当前版本为 **0.0.1**:沙箱已有受控隔离、C/C++ 编译及 native 执行;平台已实现真实身份、 当前版本为 **0.0.1**:沙箱已有受控隔离、C/C++ 编译及 native 执行;平台已实现真实身份、
不可变题目、工件与幂等提交创建。事务 outbox、持久化 Coordinator、普通 Worker 和受控 不可变题目、工件与幂等提交创建。事务 outbox、持久化 Coordinator、普通 Worker 和受控
helper 工厂已接通 C/C++ 编译一次、多测试点评测;登录、题库、提交和个人成绩页面 helper 工厂已接通 C17/C++20、固定 CPython 3.13 与 OpenJDK 21 的普通题评测;登录、题库、提交和个人成绩页面
使用真实接口。训练、比赛、社区等扩展页面仍为开发预览。 使用真实接口。训练、比赛、社区等扩展页面仍为开发预览。
题目工作台支持当前版本的“本题我的提交”、右侧详情与完整个人详情。
性能展示读取受验证最终 AC 的 CPU 总和与内存峰值,只比较同版本、语言、精确工具链、
预算与可信测量环境的最近有效记录。缺测、旧环境无法验证或其他记录不足五条时明确显示
不可比较;不会填入示例成绩。口径、查询边界和权限见[提交模块说明](backend/src/kf_oj/modules/submissions/README.md)。
## 工程划分 ## 工程划分
| 目录 | 职责 | | 目录 | 职责 |
@@ -23,3 +23,23 @@ submission、queued evaluation 和 outbox;同键同内容复用既有提交,
Coordinator 通过正式 claim 创建短期领取权,Worker 不写业务数据库。 Coordinator 通过正式 claim 创建短期领取权,Worker 不写业务数据库。
真实服务链与安全条件必须在独立 VM 验收;数据库 fixture 的租约或合成报告只能 真实服务链与安全条件必须在独立 VM 验收;数据库 fixture 的租约或合成报告只能
验证控制规则、授权和投影,不能作为真实程序执行/停止证明。 验证控制规则、授权和投影,不能作为真实程序执行/停止证明。
`performance.py` 提供本人提交的只读匿名聚合。Coordinator 在受验证最终 AC 的同一事务
写入不可变 `evaluation_performance`;平台只有 SELECT 权限。比较组绑定不可变版本、
语言、精确镜像/工具链、编译与运行预算,以及运维核验后配置的测量环境纪元。
没有原纪元的旧尝试不会通过当前配置被补造可信性,仍保留个人历史与正常成绩。
单条受控回填与纪元变更规则见[协调器说明](../../../../../judge/crates/coordinator/README.md)。
历史可按 `problem_version_id` 与可选 wire `language` 过滤,始终带当前账号条件。
`GET /api/v1/submissions/{submission_id}/performance` 每次先检查会话和所有权;
匿名拒绝、其他主体与不存在记录同为不可用,私有响应为 `no-store`。
缓存命中仍查归属。每进程最多一个聚合查询,不排等待队列;SQL 期限两秒、请求总期限
三秒、锁等待至多半秒。聚合 DTO 缓存三十秒且最多128项,新 AC 的索引水位改变即失效。
缓存不保存逐条样本数组,不向浏览器发送其他账号的身份、源码或逐条用量。
每指标在同一个只读可重复读快照内,排除当前提交,取最近最多10,000条非缺测的其他
有效 AC,响应带快照、最早样本及截断标志。少于五条时仅显示本次真实用量。
CPU 为测试点运行 CPU 微秒之和,内存为测试点运行峰值字节的最大值,不包括编译。
用量与桶边界以十进制字符串传输;零合法、缺测不补零。严格优于数按原整数的 `>`
计算,同值独立计数。响应至多24个连续桶,只有最后上界包含端点;浏览器仅格式化单位
和绘制坐标,比例以服务端严格计数除以样本数,不按人数统计。
@@ -91,6 +91,36 @@ pub async fn record(
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn grouping_separates_exact_image_version_and_execution_budgets() {
let bytes = include_bytes!(
"../../../../tests/fixtures/contracts-v2/judge-manifest.valid-python313.json"
);
let value: Value = serde_json::from_slice(bytes).unwrap();
let original: JobManifest = serde_json::from_value(value.clone()).unwrap();
let epoch = "a".repeat(64);
let original_group = comparison_group(&original, &epoch).unwrap();
for (pointer, replacement) in [
("/toolchain/image_digest", json!("b".repeat(64))),
(
"/problem_version_id",
json!("33333333-3333-4333-8333-333333333333"),
),
("/run_limits/cpu_time_us", json!(500_001)),
("/compile_limits/cpu_time_us", json!(5_000_001)),
("/job_wall_time_us", json!(120_000_001)),
] {
let mut changed = value.clone();
*changed.pointer_mut(pointer).unwrap() = replacement;
let manifest: JobManifest = serde_json::from_value(changed).unwrap();
assert_ne!(
original_group,
comparison_group(&manifest, &epoch).unwrap(),
"{pointer} must isolate the cohort"
);
}
}
#[test] #[test]
fn grouping_ignores_submission_identity_and_separates_epoch_and_language() { fn grouping_ignores_submission_identity_and_separates_epoch_and_language() {
let bytes = let bytes =