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
+66 -102

No files matched your search

+28 -19
View File
@@ -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<void>;
judgeCase: (c: NormalizedCase, sid: string) => (
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<any>
judgeCase: (c: NormalizedCase) => (
(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']) {
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 {
+12 -28
View File
@@ -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,
};
};
}
+11 -30
View File
@@ -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,
});
+4 -2
View File
@@ -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;
}
+11 -23
View File
@@ -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,
};
};
}