L.Leverage Learn从第一行代码,到读懂真实后端查看项目源码

OJ 评测系统 / 第 23 课

评测排错与验证任务

根据状态、日志和证据区分 CE、SE 与卡住的提交,并完成一项不触碰生产服务的测试改动。

◷ 预计 20 分钟 · 面向初学者

排错先问“提交停在哪一步”,再根据状态查对应证据。COMPILING 长时间不变,先确认任务是否被 Worker 领取、编译容器是否启动以及是否有清理/超时记录;JUDGING 长时间不变,检查当前 attempt 是否仍有效、测试点读取是否阻塞、运行容器是否退出。也要查看队列积压和 Worker 日志;单看浏览器里的 PENDING 无法区分排队、Worker 停机还是派发故障。

CE 和 SE 需要分开。CE 是评测引擎确认用户源代码编译失败,编译器 stderr 会进入编译错误信息;SE 是沙箱启动失败、镜像不可用、Docker 命令失败、测试数据缺失或其他基础设施错误。若所有提交都在编译前转 SE,优先检查镜像名、Docker daemon、Worker 日志和部署资源,而不是让学生改代码。若只有一份程序是 CE,比较其编译输出与语言/编译器配置。RE 则是程序运行后以非零退出码结束;TLE、MLE、OLE 应结合沙箱终止原因、时间预算、OOM 状态和输出限制判断。

可以沿着这些证据顺序缩小范围:

提交记录与 judgeAttempt
  → 队列任务是否存在/被消费
  → Worker 阶段日志(编译 / 测试点 / 结算)
  → 容器创建与退出状态
  → SQL 终态与 Redis 排名发布

不同测试只能证明不同范围。对输出比较器、attempt 校验等纯函数,单元测试运行快且不启动 Docker;给 JudgeEngine 注入 mock sandbox,可以检查状态映射、边界和错误路径,但 mock 并不能证明真实容器能创建。真实沙箱集成测试验证镜像、Docker 参数与进程行为,需要隔离且允许启动 Docker 的测试环境;端到端测试还能覆盖队列和数据库,但成本更高,也容易依赖外部服务。没有安装或配置 Docker 时,不要把 mock 测试描述成沙箱验证,更不要为排错在主应用进程直接执行用户代码。

Capstone:给标准输出比较器补边界测试

在后端仓库的现有单元测试结构中,为公开 judge-engine.ts 的 equalOutput 补一组只调用纯函数的测试。先查看该仓库当前测试框架和命令,再把测试放在现有对应测试目录;无需启动 Docker、队列、数据库或 Redis。覆盖以下断言:"7\n" 与 "7" 相等;行尾空格和多余末尾空行可忽略;行首空格必须保留,因此 " 7\n" 与 "7\n" 不相等。

测试断言应对应输入字符串实际包含换行符,而不是两个字符“反斜杠”和“n”;在 JavaScript 测试代码里可以用 \n 转义表示换行。把一条规则写成一条清楚的期望,失败时能定位差异。此次任务只改测试,不必为了覆盖边界而重写比较器,也不需要在课程学习环境中安装 Docker。

该版本的目标文件是 src/modules/judge-runtime/judge-engine.spec.ts。若文件已导入 equalOutput,直接添加这些断言即可,不要重复声明导入:

it('只忽略行尾空白和末尾空行', () => {
  expect(equalOutput('7\n', '7')).toBe(true);
  expect(equalOutput('7  \n\n', '7\n')).toBe(true);
  expect(equalOutput(' 7\n', '7\n')).toBe(false);
});

在后端仓库根目录安装其依赖后,运行固定版本支持的定向单测命令:

pnpm test:unit --runInBand --runTestsByPath src/modules/judge-runtime/judge-engine.spec.ts

测试只调用比较器。导入引擎模块可能加载沙箱类定义,但不会因此创建 Docker 容器,不能把这次通过称为沙箱验证。

**验收条件:**现有项目支持的定向单测命令通过;三类边界断言均有测试;测试没有导入 DockerSandbox、启动服务或运行用户代码;实现对行首空格的行为未被放宽。提交任务后记录所运行的命令和结果。测试失败时先确认期望是否符合源码比较规则,不要为让测试通过而改动无关评测逻辑。

小练习

mock sandbox 的 JudgeEngine 单测通过,能否据此断定生产机器的 Docker 沙箱可启动?如果 SQL 已写入终态但排名旧,应查哪一段?

**答案:**不能;mock 没有创建真实容器,需在允许 Docker 的隔离环境做集成测试。SQL 终态已经提交后,应检查 finalize 之后的 Redis 排名发布和重试,不应再次累计 SQL 统计。

去真实仓库看看

固定公开提交 de34b268e75f293e904d7734c194a4ff2467b7a8:

互动练习:定位一次评测失败

提交显示“编译成功”,第 1 个测试点通过,第 2 个测试点的输出与标准答案不同;没有超时或运行异常。下一步最合理的结论是什么?

选择一个答案

✓
这一课,你已经能……

先按评测阶段和证据定位故障,再用适合的单元、沙箱或端到端测试验证修复范围。