From 53ea4e758ac502bf62044fa959487a06e3d1e505 Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 4 Sep 2022 16:33:22 +0800 Subject: [PATCH] judge: enhanced scorer --- packages/hydrojudge/src/flow.ts | 47 +++++++++++-------- packages/hydrojudge/src/judge/default.ts | 40 +++++----------- packages/hydrojudge/src/judge/interactive.ts | 41 +++++----------- packages/hydrojudge/src/judge/objective.ts | 6 ++- .../hydrojudge/src/judge/submit_answer.ts | 34 +++++--------- 5 files changed, 66 insertions(+), 102 deletions(-) diff --git a/packages/hydrojudge/src/flow.ts b/packages/hydrojudge/src/flow.ts index b2083769e..a0184e117 100644 --- a/packages/hydrojudge/src/flow.ts +++ b/packages/hydrojudge/src/flow.ts @@ -1,5 +1,6 @@ import Queue from 'p-queue'; import { STATUS } from '@hydrooj/utils/lib/status'; +import type { JudgeResultBody } from 'hydrooj'; import { getConfig } from './config'; import { FormatError } from './error'; import { Context, ContextSubTask } from './judge/interface'; @@ -7,11 +8,17 @@ import { NormalizedCase, NormalizedSubtask } from './utils'; interface Task { compile: () => Promise; - judgeCase: (c: NormalizedCase, sid: string) => ( - (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise + judgeCase: (c: NormalizedCase) => ( + (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise ) } +const Score = { + sum: (a: number, b: number) => (a + b), + max: Math.max, + min: Math.min, +}; + function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['judgeCase']) { return async (ctx: Context) => { subtask.type ||= 'min'; @@ -24,26 +31,28 @@ function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task[' }; const cases = []; for (const cid in subtask.cases) { - const runner = judgeCase(subtask.cases[cid], subtask.id.toString() ?? sid); + const runner = judgeCase(subtask.cases[cid]); cases.push(ctx.queue.add(async () => { - if (ctx.errored + const res = (ctx.errored || (subtask.type === 'min' && ctxSubtask.score === 0) || (subtask.type === 'max' && ctxSubtask.score === subtask.score) - || (subtask.if || []).filter((i) => ctx.failed[i]).length - ) { - ctx.next({ - case: { - id: subtask.cases[cid].id, - status: STATUS.STATUS_CANCELED, - subtaskId: subtask.id, - score: 0, - time: 0, - memory: 0, - message: '', - }, - addProgress: 100 / ctx.config.count, - }); - } else await runner(ctx, ctxSubtask, runner); + || (subtask.if || []).filter((i) => ctx.failed[i]).length) + ? { + id: subtask.cases[cid].id, + status: STATUS.STATUS_CANCELED, + subtaskId: subtask.id, + score: 0, + time: 0, + memory: 0, + message: '', + } : await runner(ctx, ctxSubtask, runner); + if (!res) return; + ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, res.score); + ctxSubtask.status = Math.max(ctxSubtask.status, res.status); + if (ctxSubtask.status > STATUS.STATUS_ACCEPTED) ctx.failed[sid] = true; + ctx.total_time += res.time; + ctx.total_memory = Math.max(ctx.total_memory, res.memory); + ctx.next({ case: res, addProgress: 100 / ctx.config.count }); })); } try { diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index ffe15040d..d2620b904 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -10,15 +10,9 @@ import signals from '../signals'; import { NormalizedCase } from '../utils'; import { Context, ContextSubTask } from './interface'; -const Score = { - sum: (a: number, b: number) => (a + b), - max: Math.max, - min: Math.min, -}; - const logger = new Logger('judge/default'); -function judgeCase(c: NormalizedCase, sid: string) { +function judgeCase(c: NormalizedCase) { return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => { const { filename } = ctx.config; const copyIn = { ...ctx.execute.copyIn }; @@ -70,28 +64,9 @@ function judgeCase(c: NormalizedCase, sid: string) { ).catch(() => { /* Ignore file doesn't exist */ }); if (runner && ctx.rerun && c.time <= 5000 && status === STATUS.STATUS_TIME_LIMIT_EXCEEDED) { ctx.rerun--; - await runner(ctx, ctxSubtask); - return; + return await runner(ctx, ctxSubtask); } - ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); - ctxSubtask.status = Math.max(ctxSubtask.status, status); - if (ctxSubtask.status > STATUS.STATUS_ACCEPTED) ctx.failed[sid] = true; - ctx.total_time += time; - ctx.total_memory = Math.max(ctx.total_memory, memory); - ctx.next({ - case: { - id: c.id, - subtaskId: ctxSubtask.subtask.id, - status, - score, - time, - memory, - message, - }, - addProgress: 100 / ctx.config.count, - }); - if (ctx.request.rejudged) return; // Skip analysis for rejudged submissions - if ([STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) { + if (!ctx.request.rejudged && [STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) { const langConfig = ctx.session.getLang(ctx.lang); if (langConfig.analysis && !ctx.analysis) { ctx.analysis = true; @@ -117,6 +92,15 @@ function judgeCase(c: NormalizedCase, sid: string) { } } } + return { + id: c.id, + subtaskId: ctxSubtask.subtask.id, + status, + score, + time, + memory, + message, + }; }; } diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index dbe7a0a63..2e5ec6f01 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -6,16 +6,9 @@ import { Execute } from '../interface'; import { runPiped } from '../sandbox'; import signals from '../signals'; import { parse } from '../testlib'; -import { findFileSync, NormalizedCase } from '../utils'; +import { NormalizedCase } from '../utils'; import { Context, ContextSubTask } from './interface'; -const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h'); -const Score = { - sum: (a: number, b: number) => (a + b), - max: Math.max, - min: Math.min, -}; - function judgeCase(c: NormalizedCase) { return async (ctx: Context, ctxSubtask: ContextSubTask) => { ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' }; @@ -55,23 +48,15 @@ function judgeCase(c: NormalizedCase) { message = result.message; if (resInteractor.code && !(resInteractor.stderr || '').trim().length) message += ` (Interactor exited with code ${resInteractor.code})`; } - ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); - ctxSubtask.status = Math.max(ctxSubtask.status, status); - ctx.total_time += time; - ctx.total_memory = Math.max(ctx.total_memory, memory); - ctx.next({ - status: STATUS.STATUS_JUDGING, - case: { - id: c.id, - subtaskId: ctxSubtask.subtask.id, - status, - score, - time, - memory, - message, - }, - addProgress: 100 / ctx.config.count, - }); + return { + id: c.id, + subtaskId: ctxSubtask.subtask.id, + status, + score, + time, + memory, + message, + }; }; } @@ -84,10 +69,7 @@ export const judge = async (ctx: Context) => await runFlow(ctx, { const userExtraFiles = Object.fromEntries( (ctx.config.user_extra_files || []).map((i) => [basename(i), { src: i }]), ); - const interactorFiles = { - 'testlib.h': { src: testlibSrc }, - user_code: ctx.code, - }; + const interactorFiles = { user_code: ctx.code }; for (const file of ctx.config.judge_extra_files) { interactorFiles[basename(file)] = { src: file }; } @@ -95,7 +77,6 @@ export const judge = async (ctx: Context) => await runFlow(ctx, { compile(ctx.session.getLang(ctx.lang), ctx.code, userExtraFiles, ctx.next).then(markCleanup), compileInteractor(ctx.session.getLang, ctx.config.interactor, interactorFiles).then(markCleanup), ]); - ctx.clean.push(ctx.executeUser.clean, ctx.executeInteractor.clean); }, judgeCase, }); diff --git a/packages/hydrojudge/src/judge/objective.ts b/packages/hydrojudge/src/judge/objective.ts index 2826c581d..a0d00cfe3 100644 --- a/packages/hydrojudge/src/judge/objective.ts +++ b/packages/hydrojudge/src/judge/objective.ts @@ -18,13 +18,14 @@ export async function judge({ answers = yaml.load(answer) as any; assert(typeof answers === 'object'); } catch (e) { - return end({ + end({ status: STATUS.STATUS_WRONG_ANSWER, score: 0, message: 'Unable to parse answer.', time: 0, memory: 0, }); + return null; } let totalScore = 0; let totalStatus = 0; @@ -107,7 +108,8 @@ export async function judge({ } } } - return end({ + end({ status: totalStatus, score: totalScore, time: 0, memory: 0, }); + return null; } diff --git a/packages/hydrojudge/src/judge/submit_answer.ts b/packages/hydrojudge/src/judge/submit_answer.ts index 29b51cb57..db31a483c 100644 --- a/packages/hydrojudge/src/judge/submit_answer.ts +++ b/packages/hydrojudge/src/judge/submit_answer.ts @@ -6,16 +6,10 @@ import { compileChecker } from '../compile'; import { runFlow } from '../flow'; import { del, run } from '../sandbox'; import { NormalizedCase } from '../utils'; -import { Context, ContextSubTask } from './interface'; +import { Context } from './interface'; -const Score = { - sum: (a: number, b: number) => (a + b), - max: Math.max, - min: Math.min, -}; - -function judgeCase(c: NormalizedCase, sid: string) { - return async (ctx: Context, ctxSubtask: ContextSubTask) => { +function judgeCase(c: NormalizedCase) { + return async (ctx: Context) => { const chars = /[a-zA-Z0-9_.-]/; const name = (ctx.config.filename && /^[a-zA-Z0-9-_#.]+$/.test(ctx.config.filename)) ? ctx.config.filename.replace('#', c.id.toString()) @@ -61,20 +55,14 @@ function judgeCase(c: NormalizedCase, sid: string) { })); } await Promise.all(fileIds.map(del)).catch(() => { /* Ignore file doesn't exist */ }); - ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); - ctxSubtask.status = Math.max(ctxSubtask.status, status); - if (ctxSubtask.status > STATUS.STATUS_ACCEPTED) ctx.failed[sid] = true; - ctx.next({ - case: { - id: c.id, - status, - score: 0, - time: 0, - memory: 0, - message, - }, - addProgress: 100 / ctx.config.count, - }); + return { + id: c.id, + status, + score, + time: 0, + memory: 0, + message, + }; }; }