mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
judge: submit_answer
This commit is contained in:
1 parent
5c4221778e
commit
0cb5afa169
18 files changed
+191
-82
No files matched your search
@@ -1,4 +1,3 @@
|
||||
import fs from 'fs-extra';
|
||||
import { findFileSync } from '@hydrooj/utils/lib/utils';
|
||||
import checkers from './checkers';
|
||||
import compile from './compile';
|
||||
@@ -19,7 +18,6 @@ export async function check(config: CheckConfig): Promise<[number, number, strin
|
||||
export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: any): Promise<Execute> {
|
||||
if (!checkers[checkerType]) throw new SystemError('Unknown checker type {0}.', [checkerType]);
|
||||
if (checkerType === 'testlib') copyIn['testlib.h'] = { src: testlibSrc };
|
||||
const file = await fs.readFile(checker);
|
||||
const s = checker.replace('@', '.').split('.');
|
||||
let lang;
|
||||
let langId = s.pop();
|
||||
@@ -30,5 +28,5 @@ export async function compileChecker(getLang: Function, checkerType: string, che
|
||||
}
|
||||
if (!lang) throw new SystemError('Unknown checker language.');
|
||||
// TODO cache compiled checker
|
||||
return await compile(lang, file.toString(), copyIn);
|
||||
return await compile(lang, { src: checker }, copyIn);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { CopyInFile } from './sandbox/interface';
|
||||
import { compilerText } from './utils';
|
||||
|
||||
export = async function compile(
|
||||
lang: LangConfig, code: string, copyIn: Record<string, CopyInFile> = {}, next?: Function,
|
||||
lang: LangConfig, code: CopyInFile, copyIn: Record<string, CopyInFile> = {}, next?: Function,
|
||||
): Promise<Execute> {
|
||||
const target = lang.target || 'foo';
|
||||
const execute = copyIn['execute.sh'] ? '/bin/bash execute.sh' : lang.execute;
|
||||
@@ -18,7 +18,7 @@ export = async function compile(
|
||||
} = await run(
|
||||
copyIn['compile.sh'] ? '/bin/bash compile.sh' : lang.compile,
|
||||
{
|
||||
copyIn: { ...copyIn, [lang.code_file]: { content: code } },
|
||||
copyIn: { ...copyIn, [lang.code_file]: code },
|
||||
copyOutCached: [target],
|
||||
env: { HYDRO_LANG: lang.key },
|
||||
},
|
||||
@@ -35,7 +35,7 @@ export = async function compile(
|
||||
}
|
||||
return {
|
||||
execute,
|
||||
copyIn: { ...copyIn, [lang.code_file]: { content: code } },
|
||||
copyIn: { ...copyIn, [lang.code_file]: code },
|
||||
clean: () => Promise.resolve(null),
|
||||
time,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getConfig } from '../config';
|
||||
import { CompileError, FormatError, SystemError } from '../error';
|
||||
import judge from '../judge';
|
||||
import log from '../log';
|
||||
import { CopyInFile } from '../sandbox/interface';
|
||||
import * as sysinfo from '../sysinfo';
|
||||
import * as tmpfs from '../tmpfs';
|
||||
import {
|
||||
@@ -31,10 +32,10 @@ class JudgeTask {
|
||||
source: string;
|
||||
rid: string;
|
||||
lang: string;
|
||||
code: string;
|
||||
code: CopyInFile;
|
||||
tmpdir: string;
|
||||
input?: string;
|
||||
clean: Function[];
|
||||
clean: (() => Promise<any>)[];
|
||||
data: any[];
|
||||
folder: string;
|
||||
config: any;
|
||||
@@ -57,7 +58,7 @@ class JudgeTask {
|
||||
this.stat.handle = new Date();
|
||||
this.rid = this.request.rid;
|
||||
this.lang = this.request.lang;
|
||||
this.code = this.request.code;
|
||||
this.code = { content: this.request.code };
|
||||
this.config = this.request.config;
|
||||
this.input = this.request.input;
|
||||
this.data = this.request.data;
|
||||
@@ -100,7 +101,7 @@ class JudgeTask {
|
||||
}
|
||||
} finally {
|
||||
Lock.release(`${this.host}/${this.source}/${this.rid}`);
|
||||
for (const clean of this.clean) await clean().catch(noop);
|
||||
for (const clean of this.clean) await clean()?.catch(noop);
|
||||
tmpfs.umount(this.tmpdir);
|
||||
fs.removeSync(this.tmpdir);
|
||||
}
|
||||
@@ -109,6 +110,12 @@ class JudgeTask {
|
||||
async doSubmission(startPromise = Promise.resolve()) {
|
||||
this.stat.cache_start = new Date();
|
||||
this.folder = await this.session.cacheOpen(this.source, this.data, this.next);
|
||||
if ((this.code as any).content.startsWith('@@hydro_submission_file@@')) {
|
||||
const id = (this.code as any).content.split('@@hydro_submission_file@@')[1]?.split('#')?.[0];
|
||||
const target = await this.session.fetchCodeFile(id);
|
||||
this.code = { src: target };
|
||||
this.clean.push(() => fs.remove(target));
|
||||
}
|
||||
this.stat.read_cases = new Date();
|
||||
this.config = await readCases(
|
||||
this.folder,
|
||||
@@ -247,6 +254,21 @@ export default class Hydro {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async fetchCodeFile(name: string) {
|
||||
const res = await this.axios.post('judge/code', { id: name });
|
||||
const f = await this.axios.get(res.data.url, { responseType: 'stream' })
|
||||
.catch((e) => new Error(`DownloadFail(${name}): ${e.message}`));
|
||||
if (f instanceof Error) throw f;
|
||||
const target = path.join('/tmp/hydro/judge', name.replace(/\//g, '_'));
|
||||
const w = fs.createWriteStream(target);
|
||||
f.data.pipe(w);
|
||||
await new Promise((resolve, reject) => {
|
||||
w.on('finish', resolve);
|
||||
w.on('error', (e) => reject(new Error(`DownloadFail(${name}): ${e.message}`)));
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
getLang(name: string, doThrow = true) {
|
||||
if (this.language[name]) return this.language[name];
|
||||
if (name === 'cpp' && this.language.cc) return this.language.cc;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { check, compileChecker } from '../check';
|
||||
import compile from '../compile';
|
||||
import { getConfig } from '../config';
|
||||
import { CompileError, FormatError } from '../error';
|
||||
import { Logger } from '../log';
|
||||
import { del, run } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { parseFilename } from '../utils';
|
||||
@@ -17,6 +18,8 @@ const Score = {
|
||||
min: Math.min,
|
||||
};
|
||||
|
||||
const logger = new Logger('judge/default');
|
||||
|
||||
function judgeCase(c: Case, sid: string) {
|
||||
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
||||
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
||||
@@ -91,28 +94,6 @@ function judgeCase(c: Case, sid: string) {
|
||||
await runner(ctx, ctxSubtask);
|
||||
return;
|
||||
}
|
||||
if ([STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
|
||||
const langConfig = ctx.getLang(ctx.lang);
|
||||
if (langConfig.analysis && !ctx.analysis) {
|
||||
ctx.analysis = true;
|
||||
run(langConfig.analysis, {
|
||||
copyIn: {
|
||||
...copyIn,
|
||||
input: stdin ? { src: stdin } : { content: '' },
|
||||
[langConfig.code_file || 'foo']: { content: ctx.code },
|
||||
compile: { content: langConfig.compile || '' },
|
||||
execute: { content: langConfig.execute || '' },
|
||||
},
|
||||
time: 5000,
|
||||
memory: 256,
|
||||
env: ctx.env,
|
||||
}).then((r) => {
|
||||
const out = r.stdout.toString();
|
||||
if (out.length) ctx.next({ compiler_text: out.substring(0, 1024) });
|
||||
if (process.env.DEV) console.log(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -128,6 +109,32 @@ function judgeCase(c: Case, sid: string) {
|
||||
},
|
||||
addProgress: 100 / ctx.config.count,
|
||||
}, c.id);
|
||||
if ([STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
|
||||
const langConfig = ctx.getLang(ctx.lang);
|
||||
if (langConfig.analysis && !ctx.analysis) {
|
||||
ctx.analysis = true;
|
||||
try {
|
||||
const r = await run(langConfig.analysis, {
|
||||
copyIn: {
|
||||
...copyIn,
|
||||
input: stdin ? { src: stdin } : { content: '' },
|
||||
[langConfig.code_file || 'foo']: ctx.code,
|
||||
compile: { content: langConfig.compile || '' },
|
||||
execute: { content: langConfig.execute || '' },
|
||||
},
|
||||
time: 5000,
|
||||
memory: 256,
|
||||
env: ctx.env,
|
||||
});
|
||||
const out = r.stdout.toString();
|
||||
if (out.length) ctx.next({ compiler_text: out.substring(0, 1024) });
|
||||
if (process.env.DEV) console.log(r);
|
||||
} catch (e) {
|
||||
logger.info('Failed to run analysis');
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,10 +165,10 @@ function judgeSubtask(subtask: SubTask, sid: string) {
|
||||
export const judge = async (ctx: Context, startPromise = Promise.resolve()) => {
|
||||
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
|
||||
startPromise.then(() => ctx.next({ status: STATUS.STATUS_COMPILING }));
|
||||
if (ctx.config.template) {
|
||||
if (ctx.config.template && 'content' in ctx.code) {
|
||||
if (ctx.config.template[ctx.lang]) {
|
||||
const tpl = ctx.config.template[ctx.lang];
|
||||
ctx.code = tpl[0] + ctx.code + tpl[1];
|
||||
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([
|
||||
@@ -176,7 +183,7 @@ export const judge = async (ctx: Context, startPromise = Promise.resolve()) => {
|
||||
if (['default', 'strict'].includes(ctx.config.checker_type || 'default')) {
|
||||
return { execute: '', copyIn: {}, clean: () => Promise.resolve(null) };
|
||||
}
|
||||
const copyIn = { user_code: { content: ctx.code } };
|
||||
const copyIn = { user_code: ctx.code };
|
||||
for (const file of ctx.config.judge_extra_files) {
|
||||
copyIn[parseFilename(file)] = { src: file };
|
||||
}
|
||||
|
||||
@@ -108,14 +108,14 @@ export const judge = async (ctx: Context, startPromise = Promise.resolve()) => {
|
||||
(() => {
|
||||
const copyIn = {
|
||||
'testlib.h': { src: testlibSrc },
|
||||
user_code: { content: ctx.code },
|
||||
user_code: ctx.code,
|
||||
};
|
||||
for (const file of ctx.config.judge_extra_files) {
|
||||
copyIn[parseFilename(file)] = { src: file };
|
||||
}
|
||||
return compile(
|
||||
ctx.getLang(parseFilename(ctx.config.interactor).split('.')[1].replace('@', '.')),
|
||||
fs.readFileSync(ctx.config.interactor).toString(),
|
||||
{ src: ctx.config.interactor },
|
||||
copyIn,
|
||||
);
|
||||
})(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PQueue from 'p-queue';
|
||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { Execute } from '../interface';
|
||||
import { CopyInFile } from '../sandbox/interface';
|
||||
|
||||
export type Context = JudgeTaskInterface & RuntimeContext;
|
||||
|
||||
@@ -29,7 +30,7 @@ export interface JudgeTaskInterface {
|
||||
|
||||
stat: Record<string, Date>;
|
||||
lang: string;
|
||||
code: string;
|
||||
code: CopyInFile;
|
||||
tmpdir: string;
|
||||
input?: string;
|
||||
clean: Function[];
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from 'fs-extra';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import compile from '../compile';
|
||||
import { CompileError } from '../error';
|
||||
import { Logger } from '../log';
|
||||
import { run } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { compilerText, parseMemoryMB, parseTimeMS } from '../utils';
|
||||
@@ -16,6 +17,8 @@ const failure = (status: number, message?: string) => ({
|
||||
message,
|
||||
});
|
||||
|
||||
const logger = new Logger('judge/run');
|
||||
|
||||
export const judge = async (ctx: Context) => {
|
||||
ctx.stat.judge = new Date();
|
||||
ctx.next({ status: STATUS.STATUS_COMPILING });
|
||||
@@ -80,31 +83,6 @@ export const judge = async (ctx: Context) => {
|
||||
if (code < 32) message.push(`ExitCode: ${code} (${signals[code]})`);
|
||||
else message.push(`ExitCode: ${code}`);
|
||||
}
|
||||
if ([STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
|
||||
const langConfig = ctx.getLang(ctx.lang);
|
||||
if (langConfig.analysis) {
|
||||
ctx.analysis = true;
|
||||
run(langConfig.analysis, {
|
||||
copyIn: {
|
||||
...copyIn,
|
||||
input: { src: stdin },
|
||||
[langConfig.code_file || 'foo']: { content: ctx.code },
|
||||
compile: { content: langConfig.compile || '' },
|
||||
execute: { content: langConfig.execute || '' },
|
||||
},
|
||||
env: {
|
||||
...ctx.env,
|
||||
HYDRO_PRETEST: 'true',
|
||||
},
|
||||
time: 5000,
|
||||
memory: 256,
|
||||
}).then((r) => {
|
||||
const out = r.stdout.toString();
|
||||
if (out.length) ctx.next({ compiler_text: out.substring(0, 1024) });
|
||||
if (process.env.DEV) console.log(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
message.push(fs.readFileSync(stdout).toString());
|
||||
message.push(fs.readFileSync(stderr).toString());
|
||||
ctx.next({
|
||||
@@ -116,6 +94,35 @@ export const judge = async (ctx: Context) => {
|
||||
message: message.join('\n').substring(0, 102400),
|
||||
},
|
||||
});
|
||||
if ([STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
|
||||
const langConfig = ctx.getLang(ctx.lang);
|
||||
if (langConfig.analysis) {
|
||||
try {
|
||||
ctx.analysis = true;
|
||||
const r = await run(langConfig.analysis, {
|
||||
copyIn: {
|
||||
...copyIn,
|
||||
input: { src: stdin },
|
||||
[langConfig.code_file || 'foo']: ctx.code,
|
||||
compile: { content: langConfig.compile || '' },
|
||||
execute: { content: langConfig.execute || '' },
|
||||
},
|
||||
env: {
|
||||
...ctx.env,
|
||||
HYDRO_PRETEST: 'true',
|
||||
},
|
||||
time: 5000,
|
||||
memory: 256,
|
||||
});
|
||||
const out = r.stdout.toString();
|
||||
if (out.length) ctx.next({ compiler_text: out.substring(0, 1024) });
|
||||
if (process.env.DEV) console.log(r);
|
||||
} catch (e) {
|
||||
logger.info('Failed to run analysis');
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.stat.done = new Date();
|
||||
if (process.env.DEV) ctx.next({ message: JSON.stringify(ctx.stat) });
|
||||
ctx.end({
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { readFile } from 'fs-extra';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { Context } from './interface';
|
||||
|
||||
// TODO drop this format in next major version
|
||||
// NOT RECOMMENDED TO USE
|
||||
export async function judge({
|
||||
next, end, config, code,
|
||||
}: Context) {
|
||||
next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
||||
code = code.replace(/\r/g, '');
|
||||
const outputs = code.split('\n');
|
||||
const answer = ('src' in code)
|
||||
? await readFile(code.src, 'utf-8')
|
||||
: ('content' in code)
|
||||
? code.content.toString().replace(/\r/g, '')
|
||||
: '';
|
||||
const outputs = answer.split('\n');
|
||||
let totalScore = 0;
|
||||
let totalStatus = 0;
|
||||
for (const i in config.outputs) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as monitor from 'hydrooj/src/service/monitor';
|
||||
import readCases from './cases';
|
||||
import { getConfig } from './config';
|
||||
import { CompileError, FormatError, SystemError } from './error';
|
||||
import { CopyInFile } from './sandbox/interface';
|
||||
import * as sysinfo from './sysinfo';
|
||||
import * as tmpfs from './tmpfs';
|
||||
import { compilerText, md5 } from './utils';
|
||||
@@ -74,6 +75,21 @@ async function postInit() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCodeFile(name: string) {
|
||||
const res = await this.axios.post('judge/code', { id: name });
|
||||
const f = await this.axios.get(res.body.link, { responseType: 'stream' })
|
||||
.catch((e) => new Error(`DownloadFail(${name}): ${e.message}`));
|
||||
if (f instanceof Error) throw f;
|
||||
const target = path.join('/tmp/hydro/judge', name.replace(/\//g, '_'));
|
||||
const w = fs.createWriteStream(target);
|
||||
f.data.pipe(w);
|
||||
await new Promise((resolve, reject) => {
|
||||
w.on('finish', resolve);
|
||||
w.on('error', (e) => reject(new Error(`DownloadFail(${name}): ${e.message}`)));
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
async function cacheOpen(source: string, files: any[]) {
|
||||
const filePath = path.join(getConfig('cache_dir'), source);
|
||||
await fs.ensureDir(filePath);
|
||||
@@ -148,7 +164,7 @@ async function postInit() {
|
||||
source: string;
|
||||
rid: string;
|
||||
lang: string;
|
||||
code: string;
|
||||
code: CopyInFile;
|
||||
data: any[];
|
||||
config: any;
|
||||
env: any;
|
||||
@@ -156,7 +172,7 @@ async function postInit() {
|
||||
next: (data: any, id?: number) => void;
|
||||
end: (data: any) => void;
|
||||
tmpdir: string;
|
||||
clean: any[];
|
||||
clean: (() => Promise<any>)[];
|
||||
folder: string;
|
||||
getLang: (lang: string) => any;
|
||||
|
||||
@@ -173,7 +189,7 @@ async function postInit() {
|
||||
this.stat.handle = new Date();
|
||||
this.rid = this.request.rid.toString();
|
||||
this.lang = this.request.lang;
|
||||
this.code = this.request.code;
|
||||
this.code = { content: this.request.code };
|
||||
this.data = this.request.data;
|
||||
this.source = this.request.source;
|
||||
this.config = this.request.config;
|
||||
@@ -223,6 +239,12 @@ async function postInit() {
|
||||
async submission() {
|
||||
this.stat.cache_start = new Date();
|
||||
this.folder = await cacheOpen(this.source, this.data);
|
||||
if ((this.code as any).content.startsWith('@@hydro_submission_file@@')) {
|
||||
const id = (this.code as any).content.split('@@hydro_submission_file@@')[1]?.split('#')?.[0];
|
||||
const target = await fetchCodeFile(id);
|
||||
this.code = { src: target };
|
||||
this.clean.push(() => fs.remove(target));
|
||||
}
|
||||
this.stat.read_cases = new Date();
|
||||
this.config = await readCases(
|
||||
this.folder,
|
||||
|
||||
Reference in new issue
Block a user