mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
judge: change config.detail to enum (#1000)
This commit is contained in:
1 parent
fb04aaacb9
commit
ed830e119e
13 files changed
+41
-29
No files matched your search
@@ -36,6 +36,8 @@ export interface SubtaskConfig {
|
||||
cases?: TestCaseConfig[];
|
||||
}
|
||||
|
||||
export type DetailType = 'full' | 'case' | 'none';
|
||||
|
||||
export interface ProblemConfigFile {
|
||||
type?: ProblemType;
|
||||
subType?: string;
|
||||
@@ -48,7 +50,7 @@ export interface ProblemConfigFile {
|
||||
num_processes?: number;
|
||||
user_extra_files?: string[];
|
||||
judge_extra_files?: string[];
|
||||
detail?: boolean;
|
||||
detail?: DetailType | boolean;
|
||||
answers?: Record<string, [string | string[], number]>;
|
||||
redirect?: string;
|
||||
cases?: TestCaseConfig[];
|
||||
|
||||
@@ -87,6 +87,9 @@ export default async function readCases(folder: string, cfg: ProblemConfigFile =
|
||||
throw new SystemError('Cannot parse testdata.', [e.message, ...(e.params || [])]);
|
||||
}
|
||||
}
|
||||
if (result.detail === true) result.detail = 'full';
|
||||
else if (result.detail === false) result.detail = 'case';
|
||||
else result.detail ||= 'full';
|
||||
result.subtasks = normalizeSubtasks(result.subtasks || [], checkFile, config.time, config.memory, false, timeRate, memoryRate);
|
||||
if (result.key && args.key !== result.key) throw new FormatError('Incorrect secret key');
|
||||
if (!result.key && !args.trusted) isValidConfig(result);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
import { DetailType, STATUS } from '@hydrooj/common';
|
||||
import { FormatError, SystemError } from './error';
|
||||
import { CopyInFile, runQueued } from './sandbox';
|
||||
import { parse } from './testlib';
|
||||
@@ -12,7 +12,7 @@ export interface CheckConfig {
|
||||
code: CopyInFile;
|
||||
copyIn: Record<string, CopyInFile>;
|
||||
score: number;
|
||||
detail: boolean;
|
||||
detail: DetailType;
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ elif [ -n "$result" ]; then
|
||||
fi
|
||||
`;
|
||||
|
||||
const getDefaultChecker = (strict: boolean) => async (config) => {
|
||||
const getDefaultChecker = (strict: boolean) => async (config: CheckConfig) => {
|
||||
const { code, stdout } = await runQueued(`/bin/bash compare.sh${strict ? '' : ' BZ'}`, {
|
||||
copyIn: {
|
||||
usrout: config.user_stdout,
|
||||
@@ -102,7 +102,7 @@ const getDefaultChecker = (strict: boolean) => async (config) => {
|
||||
message = `Checker returned with status ${code}`;
|
||||
} else if (stdout) {
|
||||
status = STATUS.STATUS_WRONG_ANSWER;
|
||||
if (config.detail && !strict) message = parseDiffMsg(stdout);
|
||||
if (config.detail === 'full') message = parseDiffMsg(stdout);
|
||||
} else status = STATUS.STATUS_ACCEPTED;
|
||||
if (message.length > 1024000) message = '';
|
||||
return {
|
||||
@@ -135,7 +135,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
||||
return {
|
||||
status,
|
||||
score: status === STATUS.STATUS_ACCEPTED ? config.score : 0,
|
||||
message: stdout,
|
||||
message: config.detail === 'full' ? stdout : '',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -168,7 +168,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
||||
const score = Math.floor(+files.score) || 0;
|
||||
return {
|
||||
score,
|
||||
message: files.message,
|
||||
message: config.detail === 'full' ? files.message : '',
|
||||
status: score === config.score
|
||||
? STATUS.STATUS_ACCEPTED
|
||||
: STATUS.STATUS_WRONG_ANSWER,
|
||||
@@ -193,7 +193,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
||||
return {
|
||||
status: st,
|
||||
score: (status === STATUS.STATUS_ACCEPTED) ? config.score : 0,
|
||||
message: stdout,
|
||||
message: config.detail === 'full' ? stdout : '',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -219,7 +219,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
||||
if (status !== STATUS.STATUS_ACCEPTED) throw new SystemError('Checker returned {0}.', [status]);
|
||||
const score = +stdout;
|
||||
status = score === 100 ? STATUS.STATUS_ACCEPTED : STATUS.STATUS_WRONG_ANSWER;
|
||||
return { status, score: Math.floor((score * config.score) / 100), message: stderr };
|
||||
return { status, score: Math.floor((score * config.score) / 100), message: config.detail === 'full' ? stderr : '' };
|
||||
},
|
||||
|
||||
async testlib(config) {
|
||||
@@ -252,7 +252,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
||||
message: `Checker exited with code ${code}`,
|
||||
};
|
||||
}
|
||||
return parse(stderr, config.score);
|
||||
return parse(stderr, config.score, config.detail);
|
||||
},
|
||||
|
||||
// https://www.kattis.com/problem-package-format/spec/2023-07-draft.html#output-validator
|
||||
@@ -285,7 +285,9 @@ const checkers: Record<string, Checker> = new Proxy({
|
||||
|
||||
const message = status === STATUS.STATUS_SYSTEM_ERROR
|
||||
? files['feedback_dir/judgeerror.txt'] || `Checker exited with code ${code}`
|
||||
: files['feedback_dir/teammessage.txt'] || files['feedback_dir/judgemessage.txt'] || '';
|
||||
: config.detail === 'full'
|
||||
? files['feedback_dir/teammessage.txt'] || files['feedback_dir/judgemessage.txt'] || ''
|
||||
: '';
|
||||
|
||||
return { status, score, message };
|
||||
},
|
||||
|
||||
@@ -32,7 +32,12 @@ export const JudgeSettings = Schema.object({
|
||||
host: Schema.any(),
|
||||
secret: Schema.string().description('Judge Token Secret').default(String.random(32)),
|
||||
disable: Schema.boolean().description('Disable builtin judge').default(false),
|
||||
detail: Schema.boolean().description('Show diff detail').default(true),
|
||||
detail: Schema.union([
|
||||
Schema.const('full'),
|
||||
Schema.const('case'),
|
||||
Schema.const('none'),
|
||||
Schema.transform(Schema.boolean(), (v) => (v ? 'full' : 'case')),
|
||||
]).description('Show diff detail').default('full'),
|
||||
performance: Schema.boolean().description('Performance mode').default(false),
|
||||
});
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['
|
||||
ctx.total_time += res.time;
|
||||
ctx.total_memory = Math.max(ctx.total_memory, res.memory);
|
||||
}
|
||||
ctx.next({ ...res ? { case: res } : {}, addProgress: 100 / ctx.config.count });
|
||||
if (ctx.config.detail !== 'none') {
|
||||
ctx.next({ ...res ? { case: res } : {}, addProgress: 100 / ctx.config.count });
|
||||
}
|
||||
}));
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
JudgeResultBody, type LangConfig, NormalizedSubtask, ProblemConfigFile,
|
||||
DetailType, JudgeResultBody, type LangConfig, NormalizedSubtask, ProblemConfigFile,
|
||||
} from '@hydrooj/common';
|
||||
import { CopyInFile } from './sandbox';
|
||||
import type { JudgeTask } from './task';
|
||||
@@ -14,11 +14,12 @@ export interface Execute {
|
||||
|
||||
export type NextFunction = (body: Partial<JudgeResultBody>) => Promise<void> | void;
|
||||
|
||||
export interface ParsedConfig extends Omit<ProblemConfigFile, 'time' | 'memory' | 'subtasks'> {
|
||||
export interface ParsedConfig extends Omit<ProblemConfigFile, 'time' | 'memory' | 'subtasks' | 'detail'> {
|
||||
count: number;
|
||||
time: number;
|
||||
memory: number;
|
||||
subtasks: NormalizedSubtask[];
|
||||
detail: DetailType;
|
||||
}
|
||||
|
||||
export { JudgeRequest } from '@hydrooj/common';
|
||||
@@ -28,5 +29,5 @@ export interface Session {
|
||||
getReporter: (task: JudgeTask) => { next: NextFunction, end: NextFunction };
|
||||
fetchFile: <T extends null | string>(namespace: T, files: Record<string, string>) => Promise<T extends null ? string : null>;
|
||||
postFile: (target: string, filename: string, file: string) => Promise<void>;
|
||||
config: { detail: boolean, host?: string, trusted?: boolean };
|
||||
config: { detail: DetailType, host?: string, trusted?: boolean };
|
||||
}
|
||||
@@ -46,7 +46,6 @@ function judgeCase(c: NormalizedCase) {
|
||||
let score = 0;
|
||||
let status = STATUS.STATUS_ACCEPTED;
|
||||
let message: any;
|
||||
const detail = ctx.config.detail ?? true;
|
||||
for (let i = 0; i < ctx.config.num_processes; i++) {
|
||||
const result = res[i + 1];
|
||||
time += result.time;
|
||||
@@ -55,7 +54,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
else if (result.memory > c.memory * 1024) status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
else if ((result.code && result.code !== 13 /* Broken Pipe */) || (result.code === 13 && !resManager.code)) {
|
||||
status = STATUS.STATUS_RUNTIME_ERROR;
|
||||
if (detail) {
|
||||
if (ctx.config.detail === 'full') {
|
||||
if (result.code < 32 && result.signalled) message = signals[result.code];
|
||||
else message = { message: 'Your program returned {0}.', params: [result.code] };
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ function judgeCase(c: NormalizedCase) {
|
||||
let { status } = res;
|
||||
let message: any = '';
|
||||
let score = 0;
|
||||
const detail = ctx.config.detail ?? true;
|
||||
if (status === STATUS.STATUS_ACCEPTED) {
|
||||
if (time > c.time) {
|
||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
@@ -44,7 +43,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
user_stdout: fileIds.stdout ? { fileId: fileIds.stdout } : { content: '' },
|
||||
user_stderr: fileIds.stderr ? { fileId: fileIds.stderr } : { content: '' },
|
||||
score: c.score,
|
||||
detail,
|
||||
detail: ctx.config.detail,
|
||||
env: {
|
||||
...ctx.env,
|
||||
HYDRO_TESTCASE: c.id.toString(),
|
||||
@@ -53,7 +52,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
},
|
||||
}));
|
||||
}
|
||||
} else if (status === STATUS.STATUS_RUNTIME_ERROR && code && detail) {
|
||||
} else if (status === STATUS.STATUS_RUNTIME_ERROR && code && ctx.config.detail === 'full') {
|
||||
if (code < 32 && signalled) message = signals[code];
|
||||
else message = { message: 'Your program returned {0}.', params: [code] };
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export async function judge(ctx: Context) {
|
||||
user_stderr: { fileId: res.fileIds.stderr },
|
||||
code: ctx.code,
|
||||
score: 100,
|
||||
detail: ctx.config.detail ?? true,
|
||||
detail: ctx.config.detail ?? 'full',
|
||||
env: { ...ctx.env, HYDRO_TESTCASE: '0' },
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -39,12 +39,11 @@ function judgeCase(c: NormalizedCase) {
|
||||
let status: number;
|
||||
let score = 0;
|
||||
let message: any = '';
|
||||
const detail = ctx.config.detail ?? true;
|
||||
if (time > c.time) {
|
||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
} else if (memory > c.memory * 1024) {
|
||||
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
} else if (detail && ((code && code !== 13/* Broken Pipe */) || (code === 13 && !resInteractor.code))) {
|
||||
} else if (ctx.config.detail === 'full' && ((code && code !== 13/* Broken Pipe */) || (code === 13 && !resInteractor.code))) {
|
||||
status = STATUS.STATUS_RUNTIME_ERROR;
|
||||
if (code < 32 && signalled) message = signals[code];
|
||||
else message = { message: 'Your program returned {0}.', params: [code] };
|
||||
|
||||
@@ -50,7 +50,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
user_stderr: { content: '' },
|
||||
code: ctx.code,
|
||||
score: c.score,
|
||||
detail: ctx.config.detail ?? true,
|
||||
detail: ctx.config.detail,
|
||||
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
import { DetailType, STATUS } from '@hydrooj/common';
|
||||
|
||||
const operation = /^\s*(status|score)\((\d+)\)\s*(.*)$/m;
|
||||
|
||||
export function parse(output: string, fullscore: number) {
|
||||
export function parse(output: string, fullscore: number, detail: DetailType) {
|
||||
let status = STATUS.STATUS_WRONG_ANSWER;
|
||||
let score = 0;
|
||||
let builder = (msg: string) => msg;
|
||||
@@ -51,5 +51,5 @@ export function parse(output: string, fullscore: number) {
|
||||
score = +val;
|
||||
}
|
||||
}
|
||||
return { status, score, message: builder(message) };
|
||||
return { status, score, message: builder(detail === 'full' ? message : '') };
|
||||
}
|
||||
@@ -86,7 +86,7 @@ const problemConfigSchema: JSONSchema7 = {
|
||||
cases: { $ref: '#/definitions/cases' },
|
||||
subtasks: { type: 'array', items: { $ref: '#/definitions/subtask' } },
|
||||
filename: { type: 'string' },
|
||||
detail: { type: 'boolean' },
|
||||
detail: { type: 'string', enum: ['full', 'case', 'none'] },
|
||||
time: { $ref: '#/definitions/time' },
|
||||
memory: { $ref: '#/definitions/memory' },
|
||||
score: { $ref: '#/definitions/score' },
|
||||
|
||||
Reference in new issue
Block a user