mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 09:46:28 +08:00
judge: 集成testlib,修复交互题功能
This commit is contained in:
1 parent
66243005e1
commit
1f0ae8f13b
8 files changed
+2933
-31
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/hydrojudge",
|
"name": "@hydrooj/hydrojudge",
|
||||||
"bin": "bin/hydrojudge.js",
|
"bin": "bin/hydrojudge.js",
|
||||||
"version": "2.3.19",
|
"version": "2.4.0",
|
||||||
"main": "package.json",
|
"main": "package.json",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export async function readYamlCases(folder: string, cfg: Dictionary<any> = {}, a
|
|||||||
const checkFile = ensureFile(folder);
|
const checkFile = ensureFile(folder);
|
||||||
config.checker_type = cfg.checker_type || 'default';
|
config.checker_type = cfg.checker_type || 'default';
|
||||||
if (cfg.checker) config.checker = checkFile(cfg.checker, 'Cannot find checker {0}.');
|
if (cfg.checker) config.checker = checkFile(cfg.checker, 'Cannot find checker {0}.');
|
||||||
|
if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.');
|
||||||
if (cfg.judge_extra_files) {
|
if (cfg.judge_extra_files) {
|
||||||
if (typeof cfg.judge_extra_files === 'string') {
|
if (typeof cfg.judge_extra_files === 'string') {
|
||||||
config.judge_extra_files = [checkFile(cfg.judge_extra_files, 'Cannot find judge extra file {0}.')];
|
config.judge_extra_files = [checkFile(cfg.judge_extra_files, 'Cannot find judge extra file {0}.')];
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
/* eslint-disable no-template-curly-in-string */
|
/* eslint-disable no-template-curly-in-string */
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import { resolve } from 'path';
|
||||||
import * as STATUS from './status';
|
import * as STATUS from './status';
|
||||||
|
import { parse } from './testlib';
|
||||||
import { run } from './sandbox';
|
import { run } from './sandbox';
|
||||||
import { SystemError } from './error';
|
import { SystemError } from './error';
|
||||||
|
|
||||||
@@ -178,6 +180,7 @@ const checkers: Record<string, Checker> = {
|
|||||||
async testlib(config) {
|
async testlib(config) {
|
||||||
const { stderr, status } = await run('${dir}/checker ${dir}/in ${dir}/user_out ${dir}/answer', {
|
const { stderr, status } = await run('${dir}/checker ${dir}/in ${dir}/user_out ${dir}/answer', {
|
||||||
copyIn: {
|
copyIn: {
|
||||||
|
'testlib.h': { src: resolve(__dirname, '../files/testlib.h') },
|
||||||
in: { src: config.input },
|
in: { src: config.input },
|
||||||
user_out: { src: config.user_stdout },
|
user_out: { src: config.user_stdout },
|
||||||
answer: { src: config.output },
|
answer: { src: config.output },
|
||||||
@@ -191,11 +194,7 @@ const checkers: Record<string, Checker> = {
|
|||||||
message: '',
|
message: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return parse(stderr, config.score);
|
||||||
status: stderr.startsWith('ok ') ? STATUS.STATUS_ACCEPTED : STATUS.STATUS_WRONG_ANSWER,
|
|
||||||
score: stderr.startsWith('ok ') ? config.score : 0,
|
|
||||||
message: stderr.split(' ').splice(1).join(' '),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import Queue from 'p-queue';
|
import Queue from 'p-queue';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import { resolve } from 'path';
|
||||||
import * as STATUS from '../status';
|
import * as STATUS from '../status';
|
||||||
|
import { parse } from '../testlib';
|
||||||
import { parseFilename } from '../utils';
|
import { parseFilename } from '../utils';
|
||||||
import { run } from '../sandbox';
|
import { run } from '../sandbox';
|
||||||
import compile from '../compile';
|
import compile from '../compile';
|
||||||
@@ -15,7 +17,9 @@ const Score = {
|
|||||||
|
|
||||||
function judgeCase(c) {
|
function judgeCase(c) {
|
||||||
return async (ctx, ctxSubtask) => {
|
return async (ctx, ctxSubtask) => {
|
||||||
ctx.executeInteractor.copyIn.stdans = { src: c.input };
|
ctx.executeInteractor.copyIn.in = { src: c.input };
|
||||||
|
ctx.executeInteractor.copyIn.out = { src: c.output };
|
||||||
|
ctx.executeInteractor.copyIn['testlib.h'] = { src: resolve(__dirname, '../../files/testlib.h') };
|
||||||
const [{ code, time_usage_ms, memory_usage_kb }, resInteractor] = await run([
|
const [{ code, time_usage_ms, memory_usage_kb }, resInteractor] = await run([
|
||||||
{
|
{
|
||||||
execute: ctx.executeUser.execute.replace(/\$\{name\}/g, 'code'),
|
execute: ctx.executeUser.execute.replace(/\$\{name\}/g, 'code'),
|
||||||
@@ -23,7 +27,7 @@ function judgeCase(c) {
|
|||||||
time_limit_ms: ctxSubtask.subtask.time_limit_ms,
|
time_limit_ms: ctxSubtask.subtask.time_limit_ms,
|
||||||
memory_limit_mb: ctxSubtask.subtask.memory_limit_mb,
|
memory_limit_mb: ctxSubtask.subtask.memory_limit_mb,
|
||||||
}, {
|
}, {
|
||||||
execute: ctx.executeInteractor.execute.replace(/\$\{name\}/g, 'code'),
|
execute: `${ctx.executeInteractor.execute.replace(/\$\{name\}/g, 'interactor')} /w/in /w/tout`,
|
||||||
copyIn: ctx.executeInteractor.copyIn,
|
copyIn: ctx.executeInteractor.copyIn,
|
||||||
time_limit_ms: ctxSubtask.subtask.time_limit_ms * 2,
|
time_limit_ms: ctxSubtask.subtask.time_limit_ms * 2,
|
||||||
memory_limit_mb: ctxSubtask.subtask.memory_limit_mb * 2,
|
memory_limit_mb: ctxSubtask.subtask.memory_limit_mb * 2,
|
||||||
@@ -40,7 +44,12 @@ function judgeCase(c) {
|
|||||||
status = STATUS.STATUS_RUNTIME_ERROR;
|
status = STATUS.STATUS_RUNTIME_ERROR;
|
||||||
if (code < 32) message = signals[code];
|
if (code < 32) message = signals[code];
|
||||||
else message = { message: 'Your program returned {0}.', params: [code] };
|
else message = { message: 'Your program returned {0}.', params: [code] };
|
||||||
} else[status, score, message] = resInteractor.files.stderr.split('\n');
|
} else {
|
||||||
|
const result = parse(resInteractor.files.stderr, ctx.config.score);
|
||||||
|
status = result.status;
|
||||||
|
score = result.score;
|
||||||
|
message = result.message;
|
||||||
|
}
|
||||||
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score);
|
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score);
|
||||||
ctxSubtask.status = Math.max(ctxSubtask.status, status);
|
ctxSubtask.status = Math.max(ctxSubtask.status, status);
|
||||||
ctx.total_time_usage_ms += time_usage_ms;
|
ctx.total_time_usage_ms += time_usage_ms;
|
||||||
|
|||||||
@@ -46,6 +46,28 @@ function proc({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function adaptResult(result, params) {
|
||||||
|
// FIXME: Signalled?
|
||||||
|
const ret: any = {
|
||||||
|
status: statusMap[result.status] || STATUS.STATUS_ACCEPTED,
|
||||||
|
time_usage_ms: result.time / 1000000,
|
||||||
|
memory_usage_kb: result.memory / 1024,
|
||||||
|
files: result.files,
|
||||||
|
code: result.exitStatus,
|
||||||
|
};
|
||||||
|
if (ret.time_usage_ms >= (params.time_limit_ms || 16000)) {
|
||||||
|
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
|
}
|
||||||
|
ret.files = result.files || {};
|
||||||
|
ret.fileIds = result.fileIds || {};
|
||||||
|
if (params.stdout) await fs.writeFile(params.stdout, ret.files.stdout || '');
|
||||||
|
else ret.stdout = ret.files.stdout || '';
|
||||||
|
if (params.stderr) await fs.writeFile(params.stderr, ret.files.stderr || '');
|
||||||
|
else ret.stderr = ret.files.stderr || '';
|
||||||
|
if (result.error) ret.error = result.error;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
export async function runMultiple(execute) {
|
export async function runMultiple(execute) {
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
@@ -70,7 +92,7 @@ export async function runMultiple(execute) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new SystemError('Sandbox Error');
|
throw new SystemError('Sandbox Error');
|
||||||
}
|
}
|
||||||
return res.data;
|
return await Promise.all(res.data.map((i) => adaptResult(i, {})));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function del(fileId) {
|
export async function del(fileId) {
|
||||||
@@ -91,24 +113,5 @@ export async function run(execute, params?) {
|
|||||||
// FIXME request body larger than maxBodyLength limit
|
// FIXME request body larger than maxBodyLength limit
|
||||||
throw new SystemError('Sandbox Error', e.message);
|
throw new SystemError('Sandbox Error', e.message);
|
||||||
}
|
}
|
||||||
// FIXME: Signalled?
|
return await adaptResult(result, params);
|
||||||
const ret: any = {
|
|
||||||
status: statusMap[result.status] || STATUS.STATUS_ACCEPTED,
|
|
||||||
time_usage_ms: result.time / 1000000,
|
|
||||||
memory_usage_kb: result.memory / 1024,
|
|
||||||
files: result.files,
|
|
||||||
code: result.exitStatus,
|
|
||||||
};
|
|
||||||
if (ret.time_usage_ms >= (params.time_limit_ms || 16000)) {
|
|
||||||
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
|
||||||
}
|
|
||||||
result.files = result.files || {};
|
|
||||||
if (params.stdout) await fs.writeFile(params.stdout, result.files.stdout || '');
|
|
||||||
else ret.stdout = result.files.stdout || '';
|
|
||||||
if (params.stderr) await fs.writeFile(params.stderr, result.files.stderr || '');
|
|
||||||
else ret.stderr = result.files.stderr || '';
|
|
||||||
if (result.error) ret.error = result.error;
|
|
||||||
ret.files = result.files;
|
|
||||||
ret.fileIds = result.fileIds || {};
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import * as STATUS from './status';
|
||||||
|
|
||||||
|
export function parse(output: string, fullscore: number) {
|
||||||
|
let status = STATUS.STATUS_WRONG_ANSWER;
|
||||||
|
let score = 0;
|
||||||
|
let message = '';
|
||||||
|
if (output.startsWith('ok ')) status = STATUS.STATUS_ACCEPTED;
|
||||||
|
else if (output.startsWith('wrong answer ')) {
|
||||||
|
message = output.split('wrong answer ')[1] || '';
|
||||||
|
score = fullscore;
|
||||||
|
} else if (output.startsWith('wrong output format ')) {
|
||||||
|
message = `PE ${output.split('wrong output format ')[1] || ''}`;
|
||||||
|
} else if (output.startsWith('unexpected eof ')) {
|
||||||
|
message = `UE ${output.split('unexpected eof ')[1] || ''}`;
|
||||||
|
} else if (output.startsWith('partially correct ')) {
|
||||||
|
message = `PC ${output.split('partially correct ')[1] || ''}`;
|
||||||
|
const p = +output.split('partially correct (')[1].split(')')[0];
|
||||||
|
score = Math.floor(fullscore * (p / 200));
|
||||||
|
} else if (output.startsWith('FAIL ')) {
|
||||||
|
message = output.split('FAIL ')[1] || '';
|
||||||
|
status = STATUS.STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
return { status, score, message };
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "2.18.4",
|
"version": "2.18.5",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "dist/loader.js",
|
"main": "dist/loader.js",
|
||||||
"typings": "dist/loader.d.ts",
|
"typings": "dist/loader.d.ts",
|
||||||
|
|||||||
Reference in new issue
Block a user