mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
judge: handle optional stdin and redirection
This commit is contained in:
1 parent
5dfb81c6c3
commit
9d0aeebe12
5 files changed
+59
-72
No files matched your search
@@ -5,7 +5,7 @@ import compile, { compileChecker } from '../compile';
|
|||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
import { Execute } from '../interface';
|
import { Execute } from '../interface';
|
||||||
import { Logger } from '../log';
|
import { Logger } from '../log';
|
||||||
import { CmdFile, del, run } from '../sandbox';
|
import { del, run } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { NormalizedCase } from '../utils';
|
import { NormalizedCase } from '../utils';
|
||||||
import { Context, ContextSubTask } from './interface';
|
import { Context, ContextSubTask } from './interface';
|
||||||
@@ -14,27 +14,21 @@ const logger = new Logger('judge/default');
|
|||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
function judgeCase(c: NormalizedCase) {
|
||||||
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
||||||
const { filename } = ctx.config;
|
|
||||||
const copyIn = { ...ctx.execute.copyIn };
|
|
||||||
if (filename) copyIn[`${filename}.in`] = c.input ? { src: c.input } : { content: '' };
|
|
||||||
const copyOutCached = filename ? [`${filename}.out?`] : [];
|
|
||||||
const stdin = filename ? null : { src: c.input };
|
|
||||||
const res = await run(
|
const res = await run(
|
||||||
ctx.execute.execute,
|
ctx.execute.execute,
|
||||||
{
|
{
|
||||||
stdin,
|
stdin: { src: c.input },
|
||||||
copyIn,
|
copyIn: ctx.execute.copyIn,
|
||||||
copyOutCached,
|
filename: ctx.config.filename,
|
||||||
time: c.time * ctx.execute.time,
|
time: c.time * ctx.execute.time,
|
||||||
memory: c.memory,
|
memory: c.memory,
|
||||||
cacheStdoutAndStderr: true,
|
cacheStdoutAndStderr: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { code, time, memory } = res;
|
const {
|
||||||
|
code, time, memory, fileIds,
|
||||||
|
} = res;
|
||||||
let { status } = res;
|
let { status } = res;
|
||||||
let stdout: CmdFile = { fileId: res.fileIds[filename ? `${filename}.out` : 'stdout'] };
|
|
||||||
const stderr = { fileId: res.fileIds['stderr'] };
|
|
||||||
if (!stdout.fileId) stdout = { content: '' };
|
|
||||||
let message: any = '';
|
let message: any = '';
|
||||||
let score = 0;
|
let score = 0;
|
||||||
if (status === STATUS.STATUS_ACCEPTED) {
|
if (status === STATUS.STATUS_ACCEPTED) {
|
||||||
@@ -48,8 +42,8 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
copyIn: ctx.checker.copyIn || {},
|
copyIn: ctx.checker.copyIn || {},
|
||||||
input: { src: c.input },
|
input: { src: c.input },
|
||||||
output: { src: c.output },
|
output: { src: c.output },
|
||||||
user_stdout: stdout,
|
user_stdout: fileIds.stdout ? { fileId: fileIds.stdout } : { content: '' },
|
||||||
user_stderr: stderr,
|
user_stderr: fileIds.stderr ? { fileId: fileIds.stderr } : { content: '' },
|
||||||
score: c.score,
|
score: c.score,
|
||||||
detail: ctx.config.detail ?? true,
|
detail: ctx.config.detail ?? true,
|
||||||
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
|
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
|
||||||
@@ -73,8 +67,8 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
try {
|
try {
|
||||||
const r = await run(langConfig.analysis, {
|
const r = await run(langConfig.analysis, {
|
||||||
copyIn: {
|
copyIn: {
|
||||||
...copyIn,
|
...ctx.execute.copyIn,
|
||||||
input: stdin || { content: '' },
|
input: { src: c.input },
|
||||||
[langConfig.code_file || 'foo']: ctx.code,
|
[langConfig.code_file || 'foo']: ctx.code,
|
||||||
compile: { content: langConfig.compile || '' },
|
compile: { content: langConfig.compile || '' },
|
||||||
execute: { content: langConfig.execute || '' },
|
execute: { content: langConfig.execute || '' },
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { STATUS } from '@hydrooj/utils/lib/status';
|
|||||||
import checkers from '../checkers';
|
import checkers from '../checkers';
|
||||||
import compile, { compileChecker, compileValidator } from '../compile';
|
import compile, { compileChecker, compileValidator } from '../compile';
|
||||||
import { Execute } from '../interface';
|
import { Execute } from '../interface';
|
||||||
import { CmdFile, del, run } from '../sandbox';
|
import { del, run } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parseMemoryMB, parseTimeMS } from '../utils';
|
import { parseMemoryMB, parseTimeMS } from '../utils';
|
||||||
import { Context } from './interface';
|
import { Context } from './interface';
|
||||||
@@ -35,29 +35,22 @@ export async function judge(ctx: Context) {
|
|||||||
memory: parseMemoryMB(ctx.config.memory || '256m'),
|
memory: parseMemoryMB(ctx.config.memory || '256m'),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log(validateResult);
|
|
||||||
if (validateResult.status !== STATUS.STATUS_ACCEPTED) {
|
if (validateResult.status !== STATUS.STATUS_ACCEPTED) {
|
||||||
const message = `${validateResult.stdout || ''}\n${validateResult.stderr || ''}`.trim();
|
const message = `${validateResult.stdout || ''}\n${validateResult.stderr || ''}`.trim();
|
||||||
return ctx.end({ status: STATUS.STATUS_FORMAT_ERROR, message });
|
return ctx.end({ status: STATUS.STATUS_FORMAT_ERROR, message });
|
||||||
}
|
}
|
||||||
const copyIn = { ...execute.copyIn };
|
|
||||||
const { filename } = ctx.config;
|
|
||||||
if (filename) copyIn[`${filename}.in`] = { src: input };
|
|
||||||
const res = await run(
|
const res = await run(
|
||||||
execute.execute,
|
execute.execute,
|
||||||
{
|
{
|
||||||
stdin: filename ? null : { src: input },
|
stdin: { src: input },
|
||||||
copyIn,
|
copyIn: execute.copyIn,
|
||||||
copyOutCached: [filename ? `${filename}.out?` : 'stdout', 'stderr'],
|
|
||||||
time: parseTimeMS(ctx.config.time || '1s') * execute.time,
|
time: parseTimeMS(ctx.config.time || '1s') * execute.time,
|
||||||
memory: parseMemoryMB(ctx.config.memory || '256m'),
|
memory: parseMemoryMB(ctx.config.memory || '256m'),
|
||||||
|
cacheStdoutAndStderr: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { code, time, memory } = res;
|
const { code, time, memory } = res;
|
||||||
let { status } = res;
|
let { status } = res;
|
||||||
let stdout: CmdFile = { fileId: res.fileIds[filename ? `${filename}.out` : 'stdout'] };
|
|
||||||
console.log(res);
|
|
||||||
if (!stdout.fileId) stdout = { content: '' };
|
|
||||||
let message: any = '';
|
let message: any = '';
|
||||||
if (status === STATUS.STATUS_ACCEPTED) {
|
if (status === STATUS.STATUS_ACCEPTED) {
|
||||||
if (time > ctx.config.time * execute.time) {
|
if (time > ctx.config.time * execute.time) {
|
||||||
@@ -70,8 +63,8 @@ export async function judge(ctx: Context) {
|
|||||||
copyIn: checker.copyIn || {},
|
copyIn: checker.copyIn || {},
|
||||||
input: { src: input },
|
input: { src: input },
|
||||||
output: { content: '' },
|
output: { content: '' },
|
||||||
user_stdout: stdout,
|
user_stdout: res.fileIds.stdout ? { fileId: res.fileIds.stdout } : { content: '' },
|
||||||
user_stderr: { fileId: res.fileIds['stderr'] },
|
user_stderr: { fileId: res.fileIds.stderr },
|
||||||
score: 100,
|
score: 100,
|
||||||
detail: ctx.config.detail ?? true,
|
detail: ctx.config.detail ?? true,
|
||||||
env: { ...ctx.env, HYDRO_TESTCASE: '0' },
|
env: { ...ctx.env, HYDRO_TESTCASE: '0' },
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import path from 'path';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
import compile from '../compile';
|
import compile from '../compile';
|
||||||
import { CompileError } from '../error';
|
import { CompileError } from '../error';
|
||||||
@@ -48,18 +46,11 @@ export const judge = async (ctx: Context) => {
|
|||||||
}
|
}
|
||||||
ctx.clean.push(ctx.execute.clean);
|
ctx.clean.push(ctx.execute.clean);
|
||||||
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
||||||
const copyIn = { ...ctx.execute.copyIn };
|
|
||||||
const { filename } = ctx.config;
|
|
||||||
if (filename) copyIn[`${filename}.in`] = { content: ctx.input };
|
|
||||||
const copyOut = filename ? [`${filename}.out?`] : [];
|
|
||||||
const stdout = path.resolve(ctx.tmpdir, '0.out');
|
|
||||||
const res = await run(
|
const res = await run(
|
||||||
ctx.execute.execute,
|
ctx.execute.execute,
|
||||||
{
|
{
|
||||||
stdin: filename ? null : { content: ctx.input },
|
stdin: { content: ctx.input },
|
||||||
stdout: filename ? null : stdout,
|
copyIn: ctx.execute.copyIn,
|
||||||
copyIn,
|
|
||||||
copyOut,
|
|
||||||
// Allow 2x limits for better debugging
|
// Allow 2x limits for better debugging
|
||||||
time: parseTimeMS(ctx.config.time || '1s') * ctx.execute.time * 2,
|
time: parseTimeMS(ctx.config.time || '1s') * ctx.execute.time * 2,
|
||||||
memory: parseMemoryMB(ctx.config.memory || '128m'),
|
memory: parseMemoryMB(ctx.config.memory || '128m'),
|
||||||
@@ -67,7 +58,6 @@ export const judge = async (ctx: Context) => {
|
|||||||
);
|
);
|
||||||
const { code, time, memory } = res;
|
const { code, time, memory } = res;
|
||||||
let { status } = res;
|
let { status } = res;
|
||||||
if (!fs.existsSync(stdout)) fs.writeFileSync(stdout, '');
|
|
||||||
const message: string[] = [];
|
const message: string[] = [];
|
||||||
if (time > parseTimeMS(ctx.config.time || '1s') * ctx.execute.time) {
|
if (time > parseTimeMS(ctx.config.time || '1s') * ctx.execute.time) {
|
||||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
@@ -78,7 +68,7 @@ export const judge = async (ctx: Context) => {
|
|||||||
if (code < 32) message.push(`ExitCode: ${code} (${signals[code]})`);
|
if (code < 32) message.push(`ExitCode: ${code} (${signals[code]})`);
|
||||||
else message.push(`ExitCode: ${code}`);
|
else message.push(`ExitCode: ${code}`);
|
||||||
}
|
}
|
||||||
message.push(fs.readFileSync(stdout).toString());
|
message.push(res.stdout);
|
||||||
message.push(res.stderr);
|
message.push(res.stderr);
|
||||||
ctx.next({
|
ctx.next({
|
||||||
status,
|
status,
|
||||||
@@ -98,7 +88,7 @@ export const judge = async (ctx: Context) => {
|
|||||||
ctx.analysis = true;
|
ctx.analysis = true;
|
||||||
const r = await run(langConfig.analysis, {
|
const r = await run(langConfig.analysis, {
|
||||||
copyIn: {
|
copyIn: {
|
||||||
...copyIn,
|
...ctx.execute.copyIn,
|
||||||
input: { content: ctx.input },
|
input: { content: ctx.input },
|
||||||
[langConfig.code_file || 'foo']: ctx.code,
|
[langConfig.code_file || 'foo']: ctx.code,
|
||||||
compile: { content: langConfig.compile || '' },
|
compile: { content: langConfig.compile || '' },
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { STATUS } from '@hydrooj/utils/lib/status';
|
|||||||
import { getConfig } from './config';
|
import { getConfig } from './config';
|
||||||
import { FormatError, SystemError } from './error';
|
import { FormatError, SystemError } from './error';
|
||||||
import { Logger } from './log';
|
import { Logger } from './log';
|
||||||
import { SandboxClient } from './sandbox/client';
|
import client from './sandbox/client';
|
||||||
import {
|
import {
|
||||||
Cmd, CopyIn, CopyInFile, SandboxResult, SandboxStatus,
|
Cmd, CopyIn, CopyInFile, SandboxResult, SandboxStatus,
|
||||||
} from './sandbox/interface';
|
} from './sandbox/interface';
|
||||||
@@ -41,6 +41,8 @@ interface Parameter {
|
|||||||
copyOutCached?: string[];
|
copyOutCached?: string[];
|
||||||
cacheStdoutAndStderr?: boolean;
|
cacheStdoutAndStderr?: boolean;
|
||||||
env?: Record<string, string>;
|
env?: Record<string, string>;
|
||||||
|
/** redirect stdin & stdout */
|
||||||
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SandboxAdaptedResult {
|
interface SandboxAdaptedResult {
|
||||||
@@ -69,29 +71,24 @@ function parseArgs(execute: string): string[] {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
function proc({
|
function proc(params: Parameter): Cmd {
|
||||||
execute = '',
|
const copyOut = supportOptional
|
||||||
time = 16000,
|
? params.copyOut
|
||||||
memory = parseMemoryMB(getConfig('memoryMax')),
|
: params.copyOut.map((i) => (i.endsWith('?') ? i.substring(0, i.length - 1) : i));
|
||||||
processLimit = getConfig('processLimit'),
|
|
||||||
stdin = { content: '' }, copyIn = {}, copyOut = [], copyOutCached = [],
|
|
||||||
cacheStdoutAndStderr = false,
|
|
||||||
env = {},
|
|
||||||
}: Parameter = {}): Cmd {
|
|
||||||
if (!supportOptional) {
|
|
||||||
copyOut = copyOut.map((i) => (i.endsWith('?') ? i.substring(0, i.length - 1) : i));
|
|
||||||
}
|
|
||||||
const size = parseMemoryMB(getConfig('stdio_size'));
|
const size = parseMemoryMB(getConfig('stdio_size'));
|
||||||
const rate = getConfig('rate');
|
const rate = getConfig('rate');
|
||||||
const copyOutCachedCopy = [...copyOutCached];
|
const copyOutCached = [...(params.copyOutCached || [])];
|
||||||
if (cacheStdoutAndStderr) {
|
if (params.cacheStdoutAndStderr) copyOutCached.push(params.filename ? `${params.filename}.out` : 'stdout', 'stderr');
|
||||||
copyOutCachedCopy.push('stdout', 'stderr');
|
const copyIn = { ...(params.copyIn || {}) };
|
||||||
}
|
const stdin = params.stdin || { content: '' };
|
||||||
|
if (params.filename) copyIn[`${params.filename}.in`] = stdin;
|
||||||
|
const time = params.time || 16000;
|
||||||
|
const memory = params.memory || parseMemoryMB(getConfig('memoryMax'));
|
||||||
return {
|
return {
|
||||||
args: parseArgs(execute),
|
args: parseArgs(params.execute || ''),
|
||||||
env: [...getConfig('env').split('\n'), ...Object.keys(env).map((i) => `${i}=${env[i].replace(/=/g, '\\=')}`)],
|
env: [...getConfig('env').split('\n'), ...Object.entries(params.env || {}).map(([k, v]) => `${k}=${v.replace(/=/g, '\\=')}`)],
|
||||||
files: [
|
files: [
|
||||||
stdin,
|
params.filename ? { content: '' } : stdin,
|
||||||
{ name: 'stdout', max: Math.floor(1024 * 1024 * size) },
|
{ name: 'stdout', max: Math.floor(1024 * 1024 * size) },
|
||||||
{ name: 'stderr', max: Math.floor(1024 * 1024 * size) },
|
{ name: 'stderr', max: Math.floor(1024 * 1024 * size) },
|
||||||
],
|
],
|
||||||
@@ -100,10 +97,10 @@ function proc({
|
|||||||
memoryLimit: Math.floor(memory * 1024 * 1024),
|
memoryLimit: Math.floor(memory * 1024 * 1024),
|
||||||
strictMemoryLimit: getConfig('strict_memory'),
|
strictMemoryLimit: getConfig('strict_memory'),
|
||||||
// stackLimit: memory * 1024 * 1024,
|
// stackLimit: memory * 1024 * 1024,
|
||||||
procLimit: processLimit,
|
procLimit: params.processLimit || getConfig('processLimit'),
|
||||||
copyIn,
|
copyIn,
|
||||||
copyOut,
|
copyOut,
|
||||||
copyOutCached: copyOutCachedCopy,
|
copyOutCached,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +117,12 @@ async function adaptResult(result: SandboxResult, params: Parameter): Promise<Sa
|
|||||||
if (ret.time >= (params.time || 16000)) {
|
if (ret.time >= (params.time || 16000)) {
|
||||||
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
}
|
}
|
||||||
|
const outname = params.filename ? `${params.filename}.out` : 'stdout';
|
||||||
ret.files = result.files || {};
|
ret.files = result.files || {};
|
||||||
ret.fileIds = result.fileIds || {};
|
ret.fileIds = result.fileIds || {};
|
||||||
if (params.stdout) await fs.writeFile(params.stdout, ret.files.stdout || '');
|
if (ret.fileIds[outname]) ret.fileIds.stdout = ret.fileIds[outname];
|
||||||
else ret.stdout = ret.files.stdout || '';
|
if (params.stdout) await fs.writeFile(params.stdout, ret.files[outname] || '');
|
||||||
|
else ret.stdout = ret.files[outname] || '';
|
||||||
if (params.stderr) await fs.writeFile(params.stderr, ret.files.stderr || result.error || '');
|
if (params.stderr) await fs.writeFile(params.stderr, ret.files.stderr || result.error || '');
|
||||||
else ret.stderr = ret.files.stderr || result.error || '';
|
else ret.stderr = ret.files.stderr || result.error || '';
|
||||||
if (result.error) ret.error = result.error;
|
if (result.error) ret.error = result.error;
|
||||||
@@ -159,7 +158,7 @@ export async function runPiped(execute0: Parameter, execute1: Parameter): Promis
|
|||||||
body.cmd[1].files[1] = null;
|
body.cmd[1].files[1] = null;
|
||||||
const id = callId++;
|
const id = callId++;
|
||||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
||||||
res = await new SandboxClient(getConfig('sandbox_host')).run(body);
|
res = await client.run(body);
|
||||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res));
|
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof FormatError || e instanceof SystemError) throw e;
|
if (e instanceof FormatError || e instanceof SystemError) throw e;
|
||||||
@@ -170,13 +169,12 @@ export async function runPiped(execute0: Parameter, execute1: Parameter): Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function del(fileId: string) {
|
export async function del(fileId: string) {
|
||||||
await new SandboxClient(getConfig('sandbox_host')).deleteFile(fileId);
|
await client.deleteFile(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run(execute: string, params?: Parameter): Promise<SandboxAdaptedResult> {
|
export async function run(execute: string, params?: Parameter): Promise<SandboxAdaptedResult> {
|
||||||
let result: SandboxResult;
|
let result: SandboxResult;
|
||||||
try {
|
try {
|
||||||
const client = new SandboxClient(getConfig('sandbox_host'));
|
|
||||||
if (!supportOptional) {
|
if (!supportOptional) {
|
||||||
const res = await client.version();
|
const res = await client.version();
|
||||||
supportOptional = res.copyOutOptional;
|
supportOptional = res.copyOutOptional;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
|
import { getConfig } from '../config';
|
||||||
import { SandboxRequest, SandboxResult, SandboxVersion } from './interface';
|
import { SandboxRequest, SandboxResult, SandboxVersion } from './interface';
|
||||||
|
|
||||||
export class SandboxClient {
|
class SandboxClient {
|
||||||
constructor(public baseURL: string) {
|
constructor(public baseURL: string) {
|
||||||
if (baseURL.endsWith('/')) this.baseURL = baseURL.substring(0, baseURL.length - 1);
|
if (baseURL.endsWith('/')) this.baseURL = baseURL.substring(0, baseURL.length - 1);
|
||||||
}
|
}
|
||||||
@@ -26,3 +27,14 @@ export class SandboxClient {
|
|||||||
return superagent.get(`${this.baseURL}/version`).then((res) => res.body);
|
return superagent.get(`${this.baseURL}/version`).then((res) => res.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const client = new Proxy({} as SandboxClient, {
|
||||||
|
get(self, key) {
|
||||||
|
let url = getConfig('sandbox_host');
|
||||||
|
if (url.endsWith('/')) url = url.substring(0, url.length - 1);
|
||||||
|
const c = new SandboxClient(url);
|
||||||
|
return c[key].bind(c);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default client;
|
||||||
Reference in new issue
Block a user