You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/hydrojudge/src/judge/interactive.ts

102 lines
4.1 KiB
TypeScript

import { basename } from 'path';
import { STATUS } from '@hydrooj/utils/lib/status';
import compile, { compileInteractor } from '../compile';
import { runFlow } from '../flow';
import { Execute } from '../interface';
import { runPiped } from '../sandbox';
import signals from '../signals';
import { parse } from '../testlib';
import { findFileSync, 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: '' };
ctx.executeInteractor.copyIn.out = c.output ? { src: c.output } : { content: '' };
const [{ code, time, memory }, resInteractor] = await runPiped(
{
execute: ctx.executeUser.execute,
copyIn: ctx.executeUser.copyIn,
time: c.time * ctx.executeUser.time,
memory: c.memory,
},
{
execute: `${ctx.executeInteractor.execute} /w/in /w/tout /w/out`,
copyIn: ctx.executeInteractor.copyIn,
time: c.time * 2 * ctx.executeInteractor.time,
memory: c.memory * 2,
3 years ago
copyOut: ['/w/tout?'],
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
},
);
3 years ago
// TODO handle tout (maybe pass to checker?)
let status: number;
let score = 0;
let message: any = '';
if (time > c.time * ctx.executeUser.time) {
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
} else if (memory > c.memory * 1024) {
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
3 years ago
} else if ((code && code !== 13/* Broken Pipe */) || (code === 13 && !resInteractor.code)) {
status = STATUS.STATUS_RUNTIME_ERROR;
if (code < 32) message = signals[code];
else message = { message: 'Your program returned {0}.', params: [code] };
} else {
const result = parse(resInteractor.stderr, c.score);
status = result.status;
score = result.score;
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,
});
};
}
export const judge = async (ctx: Context) => await runFlow(ctx, {
compile: async () => {
const markCleanup = (i: Execute) => {
ctx.clean.push(i.clean);
return i;
};
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,
};
for (const file of ctx.config.judge_extra_files) {
interactorFiles[basename(file)] = { src: file };
}
[ctx.executeUser, ctx.executeInteractor] = await Promise.all([
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,
});