judge: enhanced scorer

This commit is contained in:
undefined committed 2022-09-06 13:02:46 +08:00
1 parent 7c0f80e5a0
commit 53ea4e758a
5 files changed
+47 -83

No files matched your search

+21 -12
View File
@@ -1,5 +1,6 @@
import Queue from 'p-queue'; import Queue from 'p-queue';
import { STATUS } from '@hydrooj/utils/lib/status'; import { STATUS } from '@hydrooj/utils/lib/status';
import type { JudgeResultBody } from 'hydrooj';
import { getConfig } from './config'; import { getConfig } from './config';
import { FormatError } from './error'; import { FormatError } from './error';
import { Context, ContextSubTask } from './judge/interface'; import { Context, ContextSubTask } from './judge/interface';
@@ -7,11 +8,17 @@ import { NormalizedCase, NormalizedSubtask } from './utils';
interface Task { interface Task {
compile: () => Promise<void>; compile: () => Promise<void>;
judgeCase: (c: NormalizedCase, sid: string) => ( judgeCase: (c: NormalizedCase) => (
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<any> (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<JudgeResultBody['case']>
) )
} }
const Score = {
sum: (a: number, b: number) => (a + b),
max: Math.max,
min: Math.min,
};
function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['judgeCase']) { function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['judgeCase']) {
return async (ctx: Context) => { return async (ctx: Context) => {
subtask.type ||= 'min'; subtask.type ||= 'min';
@@ -24,15 +31,13 @@ function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['
}; };
const cases = []; const cases = [];
for (const cid in subtask.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 () => { cases.push(ctx.queue.add(async () => {
if (ctx.errored const res = (ctx.errored
|| (subtask.type === 'min' && ctxSubtask.score === 0) || (subtask.type === 'min' && ctxSubtask.score === 0)
|| (subtask.type === 'max' && ctxSubtask.score === subtask.score) || (subtask.type === 'max' && ctxSubtask.score === subtask.score)
|| (subtask.if || []).filter((i) => ctx.failed[i]).length || (subtask.if || []).filter((i) => ctx.failed[i]).length)
) { ? {
ctx.next({
case: {
id: subtask.cases[cid].id, id: subtask.cases[cid].id,
status: STATUS.STATUS_CANCELED, status: STATUS.STATUS_CANCELED,
subtaskId: subtask.id, subtaskId: subtask.id,
@@ -40,10 +45,14 @@ function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['
time: 0, time: 0,
memory: 0, memory: 0,
message: '', message: '',
}, } : await runner(ctx, ctxSubtask, runner);
addProgress: 100 / ctx.config.count, if (!res) return;
}); ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, res.score);
} else await runner(ctx, ctxSubtask, runner); 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 { try {
+12 -28
View File
@@ -10,15 +10,9 @@ import signals from '../signals';
import { NormalizedCase } from '../utils'; import { NormalizedCase } from '../utils';
import { Context, ContextSubTask } from './interface'; 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'); const logger = new Logger('judge/default');
function judgeCase(c: NormalizedCase, sid: string) { function judgeCase(c: NormalizedCase) {
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => { return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
const { filename } = ctx.config; const { filename } = ctx.config;
const copyIn = { ...ctx.execute.copyIn }; const copyIn = { ...ctx.execute.copyIn };
@@ -70,28 +64,9 @@ function judgeCase(c: NormalizedCase, sid: string) {
).catch(() => { /* Ignore file doesn't exist */ }); ).catch(() => { /* Ignore file doesn't exist */ });
if (runner && ctx.rerun && c.time <= 5000 && status === STATUS.STATUS_TIME_LIMIT_EXCEEDED) { if (runner && ctx.rerun && c.time <= 5000 && status === STATUS.STATUS_TIME_LIMIT_EXCEEDED) {
ctx.rerun--; ctx.rerun--;
await runner(ctx, ctxSubtask); return await runner(ctx, ctxSubtask);
return;
} }
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); if (!ctx.request.rejudged && [STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
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)) {
const langConfig = ctx.session.getLang(ctx.lang); const langConfig = ctx.session.getLang(ctx.lang);
if (langConfig.analysis && !ctx.analysis) { if (langConfig.analysis && !ctx.analysis) {
ctx.analysis = true; 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,
};
}; };
} }
+4 -23
View File
@@ -6,16 +6,9 @@ import { Execute } from '../interface';
import { runPiped } from '../sandbox'; import { runPiped } from '../sandbox';
import signals from '../signals'; import signals from '../signals';
import { parse } from '../testlib'; import { parse } from '../testlib';
import { findFileSync, NormalizedCase } from '../utils'; import { NormalizedCase } from '../utils';
import { Context, ContextSubTask } from './interface'; 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) { function judgeCase(c: NormalizedCase) {
return async (ctx: Context, ctxSubtask: ContextSubTask) => { return async (ctx: Context, ctxSubtask: ContextSubTask) => {
ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' }; ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' };
@@ -55,13 +48,7 @@ function judgeCase(c: NormalizedCase) {
message = result.message; message = result.message;
if (resInteractor.code && !(resInteractor.stderr || '').trim().length) message += ` (Interactor exited with code ${resInteractor.code})`; if (resInteractor.code && !(resInteractor.stderr || '').trim().length) message += ` (Interactor exited with code ${resInteractor.code})`;
} }
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); return {
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, id: c.id,
subtaskId: ctxSubtask.subtask.id, subtaskId: ctxSubtask.subtask.id,
status, status,
@@ -69,9 +56,7 @@ function judgeCase(c: NormalizedCase) {
time, time,
memory, memory,
message, message,
}, };
addProgress: 100 / ctx.config.count,
});
}; };
} }
@@ -84,10 +69,7 @@ export const judge = async (ctx: Context) => await runFlow(ctx, {
const userExtraFiles = Object.fromEntries( const userExtraFiles = Object.fromEntries(
(ctx.config.user_extra_files || []).map((i) => [basename(i), { src: i }]), (ctx.config.user_extra_files || []).map((i) => [basename(i), { src: i }]),
); );
const interactorFiles = { const interactorFiles = { user_code: ctx.code };
'testlib.h': { src: testlibSrc },
user_code: ctx.code,
};
for (const file of ctx.config.judge_extra_files) { for (const file of ctx.config.judge_extra_files) {
interactorFiles[basename(file)] = { src: file }; 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), compile(ctx.session.getLang(ctx.lang), ctx.code, userExtraFiles, ctx.next).then(markCleanup),
compileInteractor(ctx.session.getLang, ctx.config.interactor, interactorFiles).then(markCleanup), compileInteractor(ctx.session.getLang, ctx.config.interactor, interactorFiles).then(markCleanup),
]); ]);
ctx.clean.push(ctx.executeUser.clean, ctx.executeInteractor.clean);
}, },
judgeCase, judgeCase,
}); });
+4 -2
View File
@@ -18,13 +18,14 @@ export async function judge({
answers = yaml.load(answer) as any; answers = yaml.load(answer) as any;
assert(typeof answers === 'object'); assert(typeof answers === 'object');
} catch (e) { } catch (e) {
return end({ end({
status: STATUS.STATUS_WRONG_ANSWER, status: STATUS.STATUS_WRONG_ANSWER,
score: 0, score: 0,
message: 'Unable to parse answer.', message: 'Unable to parse answer.',
time: 0, time: 0,
memory: 0, memory: 0,
}); });
return null;
} }
let totalScore = 0; let totalScore = 0;
let totalStatus = 0; let totalStatus = 0;
@@ -107,7 +108,8 @@ export async function judge({
} }
} }
} }
return end({ end({
status: totalStatus, score: totalScore, time: 0, memory: 0, status: totalStatus, score: totalScore, time: 0, memory: 0,
}); });
return null;
} }
+6 -18
View File
@@ -6,16 +6,10 @@ import { compileChecker } from '../compile';
import { runFlow } from '../flow'; import { runFlow } from '../flow';
import { del, run } from '../sandbox'; import { del, run } from '../sandbox';
import { NormalizedCase } from '../utils'; import { NormalizedCase } from '../utils';
import { Context, ContextSubTask } from './interface'; import { Context } from './interface';
const Score = { function judgeCase(c: NormalizedCase) {
sum: (a: number, b: number) => (a + b), return async (ctx: Context) => {
max: Math.max,
min: Math.min,
};
function judgeCase(c: NormalizedCase, sid: string) {
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
const chars = /[a-zA-Z0-9_.-]/; const chars = /[a-zA-Z0-9_.-]/;
const name = (ctx.config.filename && /^[a-zA-Z0-9-_#.]+$/.test(ctx.config.filename)) const name = (ctx.config.filename && /^[a-zA-Z0-9-_#.]+$/.test(ctx.config.filename))
? ctx.config.filename.replace('#', c.id.toString()) ? 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 */ }); await Promise.all(fileIds.map(del)).catch(() => { /* Ignore file doesn't exist */ });
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); return {
ctxSubtask.status = Math.max(ctxSubtask.status, status);
if (ctxSubtask.status > STATUS.STATUS_ACCEPTED) ctx.failed[sid] = true;
ctx.next({
case: {
id: c.id, id: c.id,
status, status,
score: 0, score,
time: 0, time: 0,
memory: 0, memory: 0,
message, message,
}, };
addProgress: 100 / ctx.config.count,
});
}; };
} }