mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
judge: support binary output (#301)
This commit is contained in:
1 parent
24cd356f78
commit
2c01eaa967
8 files changed
+89
-72
No files matched your search
@@ -11,12 +11,12 @@ const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
||||
|
||||
interface CheckConfig {
|
||||
checker_type: string;
|
||||
stdin: string,
|
||||
stdout: string,
|
||||
user_stdout: string,
|
||||
user_stderr: string,
|
||||
score: number,
|
||||
stdin: CopyInFile,
|
||||
stdout: CopyInFile,
|
||||
user_stdout: CopyInFile,
|
||||
user_stderr: CopyInFile,
|
||||
copyIn?: Record<string, CopyInFile>,
|
||||
score: number,
|
||||
detail: boolean,
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ import { CopyInFile } from './sandbox/interface';
|
||||
import { parse } from './testlib';
|
||||
|
||||
interface CheckConfig {
|
||||
input: string,
|
||||
output: string,
|
||||
user_stdout: string,
|
||||
user_stderr: string,
|
||||
score: number,
|
||||
input: CopyInFile,
|
||||
output: CopyInFile,
|
||||
user_stdout: CopyInFile,
|
||||
user_stderr: CopyInFile,
|
||||
copyIn: Record<string, CopyInFile>,
|
||||
score: number,
|
||||
detail: boolean,
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ const checkers: Record<string, Checker> = {
|
||||
async default(config) {
|
||||
const { stdout } = await run('/usr/bin/diff -BZ usrout answer', {
|
||||
copyIn: {
|
||||
usrout: { src: config.user_stdout },
|
||||
answer: { src: config.output },
|
||||
usrout: config.user_stdout,
|
||||
answer: config.output,
|
||||
...config.copyIn,
|
||||
},
|
||||
});
|
||||
@@ -41,22 +41,24 @@ const checkers: Record<string, Checker> = {
|
||||
try {
|
||||
const pt = stdout.split('---');
|
||||
const u = pt[0].split('\n')[1];
|
||||
let usr = u.substr(2, u.length - 2).trim().split(' ');
|
||||
const usr = u.substring(2).trim().split(' ');
|
||||
const t = pt[1].split('\n')[1];
|
||||
let std = t.substr(2, t.length - 2).trim().split(' ');
|
||||
const std = t.substring(2).trim().split(' ');
|
||||
if (usr.length < std.length) message = 'Standard answer longer than user output.';
|
||||
else if (usr.length > std.length) message = 'User output longer than standard answer.';
|
||||
else {
|
||||
let usrString = usr[0];
|
||||
let stdString = std[0];
|
||||
for (const i in usr) {
|
||||
if (usr[i] !== std[i]) {
|
||||
usr = usr[i];
|
||||
std = std[i];
|
||||
usrString = usr[i];
|
||||
stdString = std[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (usr.length > 20) usr = `${usr.substring(0, 16)}...`;
|
||||
if (std.length > 20) std = `${std.substring(0, 16)}...`;
|
||||
message = { message: 'Read {0}, expect {1}.', params: [usr, std] };
|
||||
if (usr.length > 20) usrString = `${usrString.substring(0, 16)}...`;
|
||||
if (std.length > 20) stdString = `${stdString.substring(0, 16)}...`;
|
||||
message = { message: 'Read {0}, expect {1}.', params: [usrString, stdString] };
|
||||
}
|
||||
} catch (e) {
|
||||
message = stdout.substring(0, stdout.length - 1 <= 30 ? stdout.length - 1 : 30);
|
||||
@@ -74,8 +76,8 @@ const checkers: Record<string, Checker> = {
|
||||
async strict(config) {
|
||||
const { stdout } = await run('/usr/bin/diff usrout answer', {
|
||||
copyIn: {
|
||||
usrout: { src: config.user_stdout },
|
||||
answer: { src: config.output },
|
||||
usrout: config.user_stdout,
|
||||
answer: config.output,
|
||||
...config.copyIn,
|
||||
},
|
||||
});
|
||||
@@ -96,9 +98,9 @@ const checkers: Record<string, Checker> = {
|
||||
async hustoj(config) {
|
||||
const { code, stdout } = await run('${dir}/checker input answer usrout', {
|
||||
copyIn: {
|
||||
usrout: { src: config.user_stdout },
|
||||
answer: { src: config.output },
|
||||
input: { src: config.input },
|
||||
usrout: config.user_stdout,
|
||||
answer: config.output,
|
||||
input: config.input,
|
||||
...config.copyIn,
|
||||
},
|
||||
});
|
||||
@@ -121,9 +123,9 @@ const checkers: Record<string, Checker> = {
|
||||
async lemon(config) {
|
||||
const { files } = await run(`\${dir}/checker input usrout answer ${config.score} score message`, {
|
||||
copyIn: {
|
||||
usrout: { src: config.user_stdout },
|
||||
answer: { src: config.output },
|
||||
input: { src: config.input },
|
||||
usrout: config.user_stdout,
|
||||
answer: config.output,
|
||||
input: config.input,
|
||||
...config.copyIn,
|
||||
},
|
||||
copyOut: ['score', 'message'],
|
||||
@@ -147,8 +149,8 @@ const checkers: Record<string, Checker> = {
|
||||
async qduoj(config) {
|
||||
const { status, stdout } = await run('${dir}/checker input usrout', {
|
||||
copyIn: {
|
||||
usrout: { src: config.user_stdout },
|
||||
input: { src: config.input },
|
||||
usrout: config.user_stdout,
|
||||
input: config.input,
|
||||
...config.copyIn,
|
||||
},
|
||||
});
|
||||
@@ -173,9 +175,9 @@ const checkers: Record<string, Checker> = {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { status, stdout, stderr } = await run('${dir}/checker', {
|
||||
copyIn: {
|
||||
input: { src: config.input },
|
||||
user_out: { src: config.user_stdout },
|
||||
answer: { src: config.output },
|
||||
input: config.input,
|
||||
user_out: config.user_stdout,
|
||||
answer: config.output,
|
||||
code: { content: '' },
|
||||
...config.copyIn,
|
||||
},
|
||||
@@ -189,9 +191,9 @@ const checkers: Record<string, Checker> = {
|
||||
async testlib(config) {
|
||||
const { stderr, status } = await run('${dir}/checker ${dir}/in ${dir}/user_out ${dir}/answer', {
|
||||
copyIn: {
|
||||
in: { src: config.input },
|
||||
user_out: { src: config.user_stdout },
|
||||
answer: { src: config.output },
|
||||
in: config.input,
|
||||
user_out: config.user_stdout,
|
||||
answer: config.output,
|
||||
...config.copyIn,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,10 +3,11 @@ import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { CompileError } from './error';
|
||||
import { Execute } from './interface';
|
||||
import { del, run } from './sandbox';
|
||||
import { CopyInFile } from './sandbox/interface';
|
||||
import { compilerText } from './utils';
|
||||
|
||||
export = async function compile(
|
||||
lang: LangConfig, code: string, target: string, copyIn: any = {}, next?: Function,
|
||||
lang: LangConfig, code: string, target: string, copyIn: Record<string, CopyInFile> = {}, next?: Function,
|
||||
): Promise<Execute> {
|
||||
target = lang.target || target;
|
||||
copyIn[lang.code_file] = { content: code };
|
||||
|
||||
@@ -8,5 +8,5 @@ export interface Execute {
|
||||
export interface CompileErrorInfo {
|
||||
stdout?: string,
|
||||
stderr?: string,
|
||||
status?: string
|
||||
status?: number
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
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 { run } from '../sandbox';
|
||||
import { del, run } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { parseFilename } from '../utils';
|
||||
import {
|
||||
@@ -40,26 +38,25 @@ function judgeCase(c: Case, sid: string) {
|
||||
const { filename } = ctx.config;
|
||||
const copyIn = { ...ctx.execute.copyIn };
|
||||
if (filename) copyIn[`${filename}.in`] = c.input ? { src: c.input } : { content: '' };
|
||||
const copyOut = filename ? [`${filename}.out?`] : [];
|
||||
const copyOutCached = filename ? [`${filename}.out?`] : [];
|
||||
const stdin = filename ? null : c.input;
|
||||
const stdout = path.resolve(ctx.tmpdir, `${c.id}.out`);
|
||||
const stderr = path.resolve(ctx.tmpdir, `${c.id}.err`);
|
||||
const res = await run(
|
||||
ctx.execute.execute.replace(/\$\{name\}/g, 'code'),
|
||||
{
|
||||
stdin,
|
||||
stdout: filename ? null : stdout,
|
||||
stderr,
|
||||
copyIn,
|
||||
copyOut,
|
||||
copyOutCached,
|
||||
time: ctxSubtask.subtask.time * ctx.execute.time,
|
||||
memory: ctxSubtask.subtask.memory,
|
||||
cacheStdoutAndStderr: true,
|
||||
},
|
||||
);
|
||||
const { code, time_usage_ms, memory_usage_kb } = res;
|
||||
let { status } = res;
|
||||
if (res.files[`${filename}.out`] || !fs.existsSync(stdout)) {
|
||||
fs.writeFileSync(stdout, res.files[`${filename}.out`] || '');
|
||||
let stdout = { fileId: res.fileIds['stdout'] };
|
||||
const stderr = { fileId: res.fileIds['stderr'] };
|
||||
if (res.fileIds[`${filename}.out`]) {
|
||||
stdout = { fileId: res.fileIds[`${filename}.out`] };
|
||||
}
|
||||
let message: any = '';
|
||||
let score = 0;
|
||||
@@ -71,8 +68,8 @@ function judgeCase(c: Case, sid: string) {
|
||||
} else {
|
||||
[status, score, message] = await check({
|
||||
copyIn: ctx.checker.copyIn,
|
||||
stdin: c.input,
|
||||
stdout: c.output,
|
||||
stdin: { src: c.input },
|
||||
stdout: { src: c.output },
|
||||
user_stdout: stdout,
|
||||
user_stderr: stderr,
|
||||
checker_type: ctx.config.checker_type,
|
||||
@@ -84,10 +81,9 @@ function judgeCase(c: Case, sid: string) {
|
||||
if (code < 32) message = signals[code];
|
||||
else message = { message: 'Your program returned {0}.', params: [code] };
|
||||
}
|
||||
await Promise.all([
|
||||
fs.remove(stdout),
|
||||
fs.remove(stderr),
|
||||
]).catch(() => { /* Ignore file doesn't exist */ });
|
||||
await Promise.all(
|
||||
Object.values(res.fileIds).map((id) => del(id)),
|
||||
).catch(() => { /* Ignore file doesn't exist */ });
|
||||
if (runner && ctx.rerun && status === STATUS.STATUS_TIME_LIMIT_EXCEEDED) {
|
||||
ctx.rerun--;
|
||||
await runner(ctx, ctxSubtask);
|
||||
|
||||
@@ -97,10 +97,10 @@ export const judge = async (ctx: Context) => {
|
||||
} else {
|
||||
[status, , message] = await check({
|
||||
copyIn: copyInDir(path.resolve(ctx.tmpdir, 'checker')),
|
||||
stdin: input,
|
||||
stdout,
|
||||
user_stdout: stdout,
|
||||
user_stderr: stderr,
|
||||
stdin: { src: input },
|
||||
stdout: { src: stdout },
|
||||
user_stdout: { src: stdout },
|
||||
user_stderr: { src: stderr },
|
||||
checker_type: ctx.config.checker_type,
|
||||
score: 100,
|
||||
detail: ctx.config.detail,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Queue from 'p-queue';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import compile from '../compile';
|
||||
import { getConfig } from '../config';
|
||||
import { run } from '../sandbox';
|
||||
import { runPiped } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { parse } from '../testlib';
|
||||
import { findFileSync, parseFilename } from '../utils';
|
||||
@@ -22,20 +22,21 @@ function judgeCase(c: Case) {
|
||||
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_usage_ms, memory_usage_kb }, resInteractor] = await run([
|
||||
const [{ code, time_usage_ms, memory_usage_kb }, resInteractor] = await runPiped(
|
||||
{
|
||||
execute: ctx.executeUser.execute.replace(/\$\{name\}/g, 'code'),
|
||||
copyIn: ctx.executeUser.copyIn,
|
||||
time: ctxSubtask.subtask.time * ctx.executeUser.time,
|
||||
memory: ctxSubtask.subtask.memory,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
execute: `${ctx.executeInteractor.execute.replace(/\$\{name\}/g, 'interactor')} /w/in /w/tout /w/out`,
|
||||
copyIn: ctx.executeInteractor.copyIn,
|
||||
time: ctxSubtask.subtask.time * 2 * ctx.executeInteractor.time,
|
||||
memory: ctxSubtask.subtask.memory * 2,
|
||||
copyOut: ['/w/tout?'],
|
||||
},
|
||||
]);
|
||||
);
|
||||
// TODO handle tout (maybe pass to checker?)
|
||||
let status: number;
|
||||
let score = 0;
|
||||
|
||||
@@ -38,6 +38,19 @@ interface Parameter {
|
||||
copyIn?: Record<string, CopyInFile>;
|
||||
copyOut?: string[];
|
||||
copyOutCached?: string[];
|
||||
cacheStdoutAndStderr?: boolean;
|
||||
}
|
||||
|
||||
interface SandboxAdaptedResult {
|
||||
status: number;
|
||||
code: number;
|
||||
time_usage_ms: number;
|
||||
memory_usage_kb?: number;
|
||||
files: Record<string, string>;
|
||||
fileIds?: Record<string, string>;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
function checkStringArray(args: ParseEntry[]): args is string[] {
|
||||
@@ -58,12 +71,17 @@ function proc({
|
||||
memory = parseMemoryMB(getConfig('memoryMax')),
|
||||
processLimit = getConfig('processLimit'),
|
||||
stdin = '', copyIn = {}, copyOut = [], copyOutCached = [],
|
||||
cacheStdoutAndStderr = false,
|
||||
}: Parameter = {}): Cmd {
|
||||
if (!supportOptional) {
|
||||
copyOut = (copyOut as string[]).map((i) => (i.endsWith('?') ? i.substr(0, i.length - 1) : i));
|
||||
}
|
||||
const size = parseMemoryMB(getConfig('stdio_size'));
|
||||
const rate = getConfig('rate');
|
||||
const copyOutCachedCopy = [...copyOutCached];
|
||||
if (cacheStdoutAndStderr) {
|
||||
copyOutCachedCopy.push('stdout', 'stderr');
|
||||
}
|
||||
return {
|
||||
args: parseArgs(execute),
|
||||
env: getConfig('env').split('\n'),
|
||||
@@ -80,14 +98,14 @@ function proc({
|
||||
procLimit: processLimit,
|
||||
copyIn,
|
||||
copyOut,
|
||||
copyOutCached,
|
||||
copyOutCached: copyOutCachedCopy,
|
||||
};
|
||||
}
|
||||
|
||||
async function adaptResult(result: SandboxResult, params: Parameter) {
|
||||
const rate = getConfig('rate');
|
||||
async function adaptResult(result: SandboxResult, params: Parameter): Promise<SandboxAdaptedResult> {
|
||||
const rate = getConfig('rate') as number;
|
||||
// FIXME: Signalled?
|
||||
const ret: any = {
|
||||
const ret: SandboxAdaptedResult = {
|
||||
status: statusMap.get(result.status) || STATUS.STATUS_ACCEPTED,
|
||||
time_usage_ms: result.time / 1000000 / rate,
|
||||
memory_usage_kb: result.memory / 1024,
|
||||
@@ -107,14 +125,14 @@ async function adaptResult(result: SandboxResult, params: Parameter) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function runMultiple(execute: Parameter[]) {
|
||||
export async function runPiped(execute0: Parameter, execute1: Parameter): Promise<[SandboxAdaptedResult, SandboxAdaptedResult]> {
|
||||
let res: SandboxResult[];
|
||||
const size = parseMemoryMB(getConfig('stdio_size'));
|
||||
try {
|
||||
const body = {
|
||||
cmd: [
|
||||
proc(execute[0]),
|
||||
proc(execute[1]),
|
||||
proc(execute0),
|
||||
proc(execute1),
|
||||
],
|
||||
pipeMapping: [{
|
||||
in: { index: 0, fd: 1 },
|
||||
@@ -142,16 +160,15 @@ export async function runMultiple(execute: Parameter[]) {
|
||||
if (e instanceof FormatError) throw e;
|
||||
throw new SystemError('Sandbox Error', [e]);
|
||||
}
|
||||
return await Promise.all(res.map((r) => adaptResult(r, {})));
|
||||
return await Promise.all(res.map((r) => adaptResult(r, {}))) as [SandboxAdaptedResult, SandboxAdaptedResult];
|
||||
}
|
||||
|
||||
export async function del(fileId: string) {
|
||||
await new SandboxClient(getConfig('sandbox_host')).deleteFile(fileId);
|
||||
}
|
||||
|
||||
export async function run(execute: string | Parameter[], params?: Parameter) {
|
||||
export async function run(execute: string, params?: Parameter): Promise<SandboxAdaptedResult> {
|
||||
let result: SandboxResult;
|
||||
if (typeof execute === 'object') return await runMultiple(execute);
|
||||
try {
|
||||
const client = new SandboxClient(getConfig('sandbox_host'));
|
||||
if (!supportOptional) {
|
||||
|
||||
Reference in new issue
Block a user