judge: submit_answer

This commit is contained in:
undefined committed 2022-04-03 15:02:32 +08:00
1 parent 5c4221778e
commit 0cb5afa169
18 files changed
+191 -82

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@hydrooj/hydrojudge",
"bin": "bin/hydrojudge.js",
"version": "2.15.5",
"version": "2.16.0",
"main": "package.json",
"author": "undefined <i@undefined.moe>",
"repository": "https://github.com/hydro-dev/Hydro.git",
+1 -3
View File
@@ -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);
}
+3 -3
View File
@@ -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,
};
+26 -4
View File
@@ -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;
+32 -25
View File
@@ -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 };
}
+2 -2
View 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,
);
})(),
+2 -1
View File
@@ -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[];
+32 -25
View File
@@ -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) {
+25 -3
View File
@@ -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,