test(submissions): verify exact performance cohort boundaries
This commit is contained in:
@@ -5,9 +5,14 @@
|
||||
|
||||
当前版本为 **0.0.1**:沙箱已有受控隔离、C/C++ 编译及 native 执行;平台已实现真实身份、
|
||||
不可变题目、工件与幂等提交创建。事务 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 不写业务数据库。
|
||||
真实服务链与安全条件必须在独立 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 {
|
||||
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]
|
||||
fn grouping_ignores_submission_identity_and_separates_epoch_and_language() {
|
||||
let bytes =
|
||||
|
||||
Reference in New Issue
Block a user