原来答案错误只有四个字。6901 条历史答案错误在公开样例上重跑过:73% 在样例上就错了,
其中约三分之一只差格式(没输出、input() 提示文字、空格换行、中英文标点、小数位数)。
- judge/run.ts 的 checkSamples:判出答案错误后以内联测试点重跑题目样例(output: true),
记下第一个没过的样例和学生在它上面的输出,写进 statistic_info.sample_check;
样例全过则只记 passed。全是公开信息,不碰隐藏测试点;比赛不跑(有期末考试)
- 前端按差异归类(数字先于「多了内容」判,免得 007 被当成多打了文字),
并排显示输入 / 正确输出 / 你的输出,标出第一处不同的行;只差空白时把空格、换行画出来,
末尾空白不画(判题本来就忽略)
- 提示给具体值(「正确输出是 84.10,你输出的是 84.1」),input("…") 单独提示
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db70ab62e8
commit
7e964fdbdd
6 files changed
+446
-2
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { createHash } from "node:crypto"
|
||||
|
||||
import { astRuleSchema, type ContestSubmissionInfo } from "@oj2/contract"
|
||||
import { astRuleSchema, type ContestSubmissionInfo, type StatisticInfo } from "@oj2/contract"
|
||||
import { and, eq, inArray } from "drizzle-orm"
|
||||
|
||||
import { config } from "../config"
|
||||
@@ -377,6 +377,69 @@ async function diagnoseRuntimeError(
|
||||
return parsePythonTraceback(output, row.submission.code, prependLines)
|
||||
}
|
||||
|
||||
/** sample_check 里三段文本各自的上限。样例本身很短,截的主要是学生的输出(死循环打印) */
|
||||
const SAMPLE_TEXT_LIMIT = 2000
|
||||
|
||||
type SampleCheck = NonNullable<StatisticInfo["sample_check"]>
|
||||
|
||||
/**
|
||||
* 答案错误时拿题目的**公开样例**重跑一次,记下第一个没过的样例和学生在它上面的输出,
|
||||
* 写进 `statistic_info.sample_check`。
|
||||
*
|
||||
* 2025 秋以来的 6901 条答案错误在样例上重跑过:73% 在样例上就已经错了。样例写在题面上,
|
||||
* 所以「输入 700,正确输出 7,你的输出 007」给学生看不泄露任何隐藏数据,而这正是他最
|
||||
* 缺的线索 —— 原来答案错误只有四个字,连错在哪一类都不知道。其中约三分之一只差格式
|
||||
* (多了提示文字、空格换行、中英文标点、小数位数),前端按差异类型再补一句中文提示。
|
||||
*
|
||||
* 和运行时错误的诊断一样,主判题不开 output;这里的样例是内联传的,输出只回这几个点。
|
||||
*/
|
||||
async function checkSamples(
|
||||
row: {
|
||||
submission: typeof schema.submission.$inferSelect
|
||||
problem: typeof schema.problem.$inferSelect
|
||||
},
|
||||
source: string,
|
||||
): Promise<SampleCheck | null> {
|
||||
const samples = (Array.isArray(row.problem.samples) ? row.problem.samples : [])
|
||||
.map((item) => asRecord(item))
|
||||
.filter(
|
||||
(item): item is { input: string; output: string } =>
|
||||
typeof item.input === "string" && typeof item.output === "string",
|
||||
)
|
||||
if (!samples.length) return null
|
||||
|
||||
const response = await requestJudge(
|
||||
row.submission.language,
|
||||
source,
|
||||
row.problem.timeLimit,
|
||||
row.problem.memoryLimit,
|
||||
// 题面上的样例输入常常没有结尾换行,而 input() 读到文件末尾没有换行照样能读,
|
||||
// C 的 scanf 也不在乎。补一个是为了和测试点文件的样子一致
|
||||
samples.map((item) => ({
|
||||
input: item.input.endsWith("\n") ? item.input : `${item.input}\n`,
|
||||
output: item.output,
|
||||
})),
|
||||
true,
|
||||
)
|
||||
// 在样例上就编译不过不会发生(正式判题刚编译过同一份代码),真遇到了就当没检查
|
||||
if (response.err || !Array.isArray(response.data)) return null
|
||||
const cases = [...(response.data as JudgeCase[])].sort(
|
||||
(left, right) => Number(left.test_case) - Number(right.test_case),
|
||||
)
|
||||
const index = cases.findIndex((item) => item.result !== JudgeStatus.ACCEPTED)
|
||||
if (index < 0) return { passed: true }
|
||||
const sample = samples[index]!
|
||||
const output = cases[index]!.output
|
||||
return {
|
||||
passed: false,
|
||||
index,
|
||||
input: sample.input.slice(0, SAMPLE_TEXT_LIMIT),
|
||||
expected: sample.output.slice(0, SAMPLE_TEXT_LIMIT),
|
||||
output: typeof output === "string" ? output.slice(0, SAMPLE_TEXT_LIMIT) : "",
|
||||
result: cases[index]!.result,
|
||||
}
|
||||
}
|
||||
|
||||
export async function judgeSubmission(job: JudgeJobData) {
|
||||
const [row] = await db
|
||||
.select({
|
||||
@@ -473,6 +536,19 @@ export async function judgeSubmission(job: JudgeJobData) {
|
||||
if (diagnosis) statisticInfo.runtime_error = diagnosis
|
||||
}
|
||||
|
||||
// 同上,比赛不跑:比赛里有期末考试,格式提示在那里不该给
|
||||
if (
|
||||
result === JudgeStatus.WRONG_ANSWER &&
|
||||
row.submission.contestId === null &&
|
||||
row.submission.language !== "SQL"
|
||||
) {
|
||||
const check = await checkSamples(row, source).catch((error: unknown) => {
|
||||
console.warn(`[judge] 样例重跑失败 ${row.submission.id}:`, error)
|
||||
return null
|
||||
})
|
||||
if (check) statisticInfo.sample_check = check
|
||||
}
|
||||
|
||||
if (result === JudgeStatus.ACCEPTED) {
|
||||
const rules = astRulesForLanguage(row.problem.astRules, row.submission.language)
|
||||
if (rules.length > 0) {
|
||||
|
||||
Reference in new issue
Block a user