judge: use common workflow

This commit is contained in:
undefined committed 2022-09-06 13:02:46 +08:00
1 parent 1394af0591
commit 7bf2e73561
4 files changed
+171 -251

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
import Queue from 'p-queue';
import { STATUS } from '@hydrooj/utils/lib/status';
import { getConfig } from './config';
import { CompileError, FormatError } from './error';
import { Context, ContextSubTask } from './judge/interface';
import { NormalizedCase, NormalizedSubtask } from './utils';
interface Task {
compile: () => Promise<void>;
judgeCase: (c: NormalizedCase, sid: string) => (
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<any>
)
}
function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['judgeCase']) {
return async (ctx: Context) => {
subtask.type = subtask.type || 'min';
const ctxSubtask = {
subtask,
status: 0,
score: subtask.type === 'min'
? subtask.score
: 0,
};
const cases = [];
for (const cid in subtask.cases) {
const runner = judgeCase(subtask.cases[cid], subtask.id.toString() ?? sid);
cases.push(ctx.queue.add(async () => {
if (ctx.errored
|| (ctxSubtask.subtask.type === 'min' && ctxSubtask.score === 0)
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|| ((ctxSubtask.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);
}));
}
await Promise.all(cases).catch((e) => {
ctx.errored = true;
throw e;
});
ctx.total_status = Math.max(ctx.total_status, ctxSubtask.status);
return ctxSubtask.score;
};
}
export const runFlow = async (ctx: Context, task: Task) => {
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
ctx.next({ status: STATUS.STATUS_COMPILING });
if (ctx.config.template && 'content' in ctx.code) {
if (ctx.config.template[ctx.lang]) {
const tpl = ctx.config.template[ctx.lang];
ctx.code.content = tpl[0] + ctx.code.content + tpl[1];
} else throw new CompileError('Language not supported by provided templates');
}
await task.compile();
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
const tasks = [];
ctx.total_status = 0;
ctx.total_score = 0;
ctx.total_memory_usage_kb = 0;
ctx.total_time_usage_ms = 0;
ctx.rerun = getConfig('rerun') || 0;
ctx.queue = new Queue({ concurrency: getConfig('singleTaskParallelism') });
ctx.failed = {};
for (const sid in ctx.config.subtasks) tasks.push(judgeSubtask(ctx.config.subtasks[sid], sid, task.judgeCase)(ctx));
const scores = await Promise.all(tasks);
for (const sid in ctx.config.subtasks) {
let effective = true;
for (const required of ctx.config.subtasks[sid].if || []) {
if (ctx.failed[required.toString()]) effective = false;
}
if (effective) ctx.total_score += scores[sid];
}
ctx.stat.done = new Date();
if (process.env.DEV) ctx.next({ message: JSON.stringify(ctx.stat) });
ctx.end({
status: ctx.total_status,
score: ctx.total_score,
time: Math.floor(ctx.total_time_usage_ms * 1000000) / 1000000,
memory: ctx.total_memory_usage_kb,
});
};
+27 -103
View File
@@ -1,13 +1,11 @@
import Queue from 'p-queue';
import { STATUS } from '@hydrooj/utils/lib/status';
import { check, compileChecker } from '../check';
import compile from '../compile';
import { getConfig } from '../config';
import { CompileError, FormatError } from '../error';
import { runFlow } from '../flow';
import { Logger } from '../log';
import { CmdFile, del, run } from '../sandbox';
import signals from '../signals';
import { NormalizedCase, NormalizedSubtask, parseFilename } from '../utils';
import { NormalizedCase, parseFilename } from '../utils';
import { Context, ContextSubTask } from './interface';
const Score = {
@@ -20,25 +18,6 @@ const logger = new Logger('judge/default');
function judgeCase(c: NormalizedCase, sid: string) {
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
if (ctx.errored
|| (ctxSubtask.subtask.type === 'min' && ctxSubtask.score === 0)
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|| ((ctxSubtask.subtask.if || []).filter((i) => ctx.failed[i]).length)
) {
ctx.next({
case: {
id: c.id,
status: STATUS.STATUS_CANCELED,
subtaskId: ctxSubtask.subtask.id,
score: 0,
time: 0,
memory: 0,
message: '',
},
addProgress: 100 / ctx.config.count,
});
return;
}
const { filename } = ctx.config;
const copyIn = { ...ctx.execute.copyIn };
if (filename) copyIn[`${filename}.in`] = c.input ? { src: c.input } : { content: '' };
@@ -140,84 +119,29 @@ function judgeCase(c: NormalizedCase, sid: string) {
};
}
function judgeSubtask(subtask: NormalizedSubtask, sid: string) {
return async (ctx: Context) => {
subtask.type = subtask.type || 'min';
const ctxSubtask = {
subtask,
status: 0,
score: subtask.type === 'min'
? subtask.score
: 0,
};
const cases = [];
for (const cid in subtask.cases) {
const runner = judgeCase(subtask.cases[cid], subtask.id.toString() ?? sid);
cases.push(ctx.queue.add(() => runner(ctx, ctxSubtask, runner)));
}
await Promise.all(cases).catch((e) => {
ctx.errored = true;
throw e;
});
ctx.total_status = Math.max(ctx.total_status, ctxSubtask.status);
return ctxSubtask.score;
};
}
export const judge = async (ctx: Context) => {
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
ctx.next({ status: STATUS.STATUS_COMPILING });
if (ctx.config.template && 'content' in ctx.code) {
if (ctx.config.template[ctx.lang]) {
const tpl = ctx.config.template[ctx.lang];
ctx.code.content = tpl[0] + ctx.code.content + tpl[1];
} else throw new CompileError('Language not supported by provided templates');
}
[ctx.execute, ctx.checker] = await Promise.all([
compile(
ctx.session.getLang(ctx.lang), ctx.code,
Object.fromEntries(
(ctx.config.user_extra_files || []).map((i) => [i.split('/').pop(), { src: i }]),
),
ctx.next,
),
compileChecker(
ctx.session.getLang,
ctx.config.checker_type,
ctx.config.checker,
{
user_code: ctx.code,
...Object.fromEntries(
(ctx.config.judge_extra_files || []).map((i) => [parseFilename(i), { src: i }]),
export const judge = async (ctx: Context) => await runFlow(ctx, {
compile: async () => {
[ctx.execute, ctx.checker] = await Promise.all([
compile(
ctx.session.getLang(ctx.lang), ctx.code,
Object.fromEntries(
(ctx.config.user_extra_files || []).map((i) => [i.split('/').pop(), { src: i }]),
),
},
),
]);
ctx.clean.push(ctx.execute.clean, ctx.checker.clean);
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
const tasks = [];
ctx.total_status = 0;
ctx.total_score = 0;
ctx.total_memory_usage_kb = 0;
ctx.total_time_usage_ms = 0;
ctx.rerun = getConfig('rerun') || 0;
ctx.queue = new Queue({ concurrency: getConfig('singleTaskParallelism') });
ctx.failed = {};
for (const sid in ctx.config.subtasks) tasks.push(judgeSubtask(ctx.config.subtasks[sid], sid)(ctx));
const scores = await Promise.all(tasks);
for (const sid in ctx.config.subtasks) {
let effective = true;
for (const required of ctx.config.subtasks[sid].if || []) {
if (ctx.failed[required.toString()]) effective = false;
}
if (effective) ctx.total_score += scores[sid];
}
ctx.stat.done = new Date();
if (process.env.DEV) ctx.next({ message: JSON.stringify(ctx.stat) });
ctx.end({
status: ctx.total_status,
score: ctx.total_score,
time: Math.floor(ctx.total_time_usage_ms * 1000000) / 1000000,
memory: ctx.total_memory_usage_kb,
});
};
ctx.next,
),
compileChecker(
ctx.session.getLang,
ctx.config.checker_type,
ctx.config.checker,
{
user_code: ctx.code,
...Object.fromEntries(
(ctx.config.judge_extra_files || []).map((i) => [parseFilename(i), { src: i }]),
),
},
),
]);
ctx.clean.push(ctx.execute.clean, ctx.checker.clean);
},
judgeCase,
});
+31 -66
View File
@@ -1,12 +1,11 @@
import Queue from 'p-queue';
import { STATUS } from '@hydrooj/utils/lib/status';
import compile from '../compile';
import { getConfig } from '../config';
import { runFlow } from '../flow';
import { runPiped } from '../sandbox';
import signals from '../signals';
import { parse } from '../testlib';
import {
findFileSync, NormalizedCase, NormalizedSubtask, parseFilename,
findFileSync, NormalizedCase, parseFilename,
} from '../utils';
import { Context, ContextSubTask } from './interface';
@@ -76,66 +75,32 @@ function judgeCase(c: NormalizedCase) {
};
}
function judgeSubtask(subtask: NormalizedSubtask) {
return async (ctx: Context) => {
subtask.type = subtask.type || 'min';
const ctxSubtask = {
subtask,
status: 0,
score: subtask.type === 'min'
? subtask.score
: 0,
};
const cases = [];
for (const cid in subtask.cases) {
cases.push(ctx.queue.add(() => judgeCase(subtask.cases[cid])(ctx, ctxSubtask)));
}
await Promise.all(cases);
ctx.total_status = Math.max(ctx.total_status, ctxSubtask.status);
ctx.total_score += ctxSubtask.score;
};
}
export const judge = async (ctx: Context) => {
ctx.next({ status: STATUS.STATUS_COMPILING });
[ctx.executeUser, ctx.executeInteractor] = await Promise.all([
(() => {
const copyIn = {};
for (const file of ctx.config.user_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return compile(ctx.session.getLang(ctx.lang), ctx.code, copyIn, ctx.next);
})(),
(() => {
const copyIn = {
'testlib.h': { src: testlibSrc },
user_code: ctx.code,
};
for (const file of ctx.config.judge_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return compile(
ctx.session.getLang(parseFilename(ctx.config.interactor).split('.')[1].replace('@', '.')),
{ src: ctx.config.interactor },
copyIn,
);
})(),
]);
ctx.clean.push(ctx.executeUser.clean, ctx.executeInteractor.clean);
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
const tasks = [];
ctx.total_status = ctx.total_score = ctx.total_memory_usage_kb = ctx.total_time_usage_ms = 0;
ctx.queue = new Queue({ concurrency: getConfig('parallelism') });
for (const sid in ctx.config.subtasks) {
tasks.push(judgeSubtask(ctx.config.subtasks[sid])(ctx));
}
await Promise.all(tasks);
ctx.stat.done = new Date();
if (process.env.DEV) ctx.next({ message: JSON.stringify(ctx.stat) });
ctx.end({
status: ctx.total_status,
score: ctx.total_score,
time: ctx.total_time_usage_ms,
memory: ctx.total_memory_usage_kb,
});
};
export const judge = async (ctx: Context) => await runFlow(ctx, {
compile: async () => {
[ctx.executeUser, ctx.executeInteractor] = await Promise.all([
(() => {
const copyIn = {};
for (const file of ctx.config.user_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return compile(ctx.session.getLang(ctx.lang), ctx.code, copyIn, ctx.next);
})(),
(() => {
const copyIn = {
'testlib.h': { src: testlibSrc },
user_code: ctx.code,
};
for (const file of ctx.config.judge_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return compile(
ctx.session.getLang(parseFilename(ctx.config.interactor).split('.')[1].replace('@', '.')),
{ src: ctx.config.interactor },
copyIn,
);
})(),
]);
ctx.clean.push(ctx.executeUser.clean, ctx.executeInteractor.clean);
},
judgeCase,
});
+19 -82
View File
@@ -1,11 +1,9 @@
import { readFile } from 'fs-extra';
import Queue from 'p-queue';
import { STATUS } from '@hydrooj/utils/lib/status';
import { check, compileChecker } from '../check';
import { getConfig } from '../config';
import { FormatError } from '../error';
import { runFlow } from '../flow';
import { del, run } from '../sandbox';
import { NormalizedCase, NormalizedSubtask, parseFilename } from '../utils';
import { NormalizedCase, parseFilename } from '../utils';
import { Context, ContextSubTask } from './interface';
const Score = {
@@ -16,23 +14,6 @@ const Score = {
function judgeCase(c: NormalizedCase, sid: string) {
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|| ((ctxSubtask.subtask.if || []).filter((i) => ctx.failed[i]).length)
) {
ctx.next({
case: {
id: c.id,
status: STATUS.STATUS_CANCELED,
score: 0,
time: 0,
memory: 0,
message: '',
},
addProgress: 100 / ctx.config.count,
});
return;
}
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())
@@ -96,67 +77,23 @@ function judgeCase(c: NormalizedCase, sid: string) {
};
}
function judgeSubtask(subtask: NormalizedSubtask, sid: string) {
return async (ctx: Context) => {
subtask.type = subtask.type || 'min';
const ctxSubtask = {
subtask,
status: 0,
score: subtask.type === 'min'
? subtask.score
: 0,
};
const cases = [];
for (const cid in subtask.cases) {
const runner = judgeCase(subtask.cases[cid], sid);
cases.push(ctx.queue.add(() => runner(ctx, ctxSubtask)));
}
await Promise.all(cases).catch((e) => {
ctx.errored = true;
throw e;
});
ctx.total_status = Math.max(ctx.total_status, ctxSubtask.status);
return ctxSubtask.score;
};
}
export const judge = async (ctx: Context) => {
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
ctx.next({ status: STATUS.STATUS_COMPILING });
ctx.checker = await (() => {
const copyIn = { user_code: ctx.code };
for (const file of ctx.config.judge_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return compileChecker(
ctx.session.getLang,
ctx.config.checker_type,
ctx.config.checker,
copyIn,
);
})();
ctx.clean.push(ctx.checker.clean);
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
const tasks = [];
ctx.total_status = 0;
ctx.total_score = 0;
ctx.queue = new Queue({ concurrency: getConfig('parallelism') });
ctx.failed = {};
for (const sid in ctx.config.subtasks) tasks.push(judgeSubtask(ctx.config.subtasks[sid], sid)(ctx));
const scores = await Promise.all(tasks);
for (const sid in ctx.config.subtasks) {
let effective = true;
for (const required of ctx.config.subtasks[sid].if || []) {
if (ctx.failed[required.toString()]) effective = false;
}
if (effective) ctx.total_score += scores[sid];
}
ctx.stat.done = new Date();
if (process.env.DEV) ctx.next({ message: JSON.stringify(ctx.stat) });
ctx.end({
status: ctx.total_status,
score: ctx.total_score,
time: 0,
memory: 0,
await runFlow(ctx, {
compile: async () => {
ctx.checker = await (() => {
const copyIn = { user_code: ctx.code };
for (const file of ctx.config.judge_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return compileChecker(
ctx.session.getLang,
ctx.config.checker_type,
ctx.config.checker,
copyIn,
);
})();
ctx.clean.push(ctx.checker.clean);
},
judgeCase,
});
};