mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
judge: use common workflow
This commit is contained in:
1 parent
1394af0591
commit
7bf2e73561
4 files changed
+171
-251
No files matched your search
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user