judge: change config.detail to enum (#1000)

This commit is contained in:
undefined authored and GitHub committed 2025-06-26 02:55:38 +01:00
1 parent fb04aaacb9
commit ed830e119e
13 files changed
+41 -29

No files matched your search

+3 -1
View File
@@ -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[];
+3
View File
@@ -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);
+12 -10
View File
@@ -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 };
},
+6 -1
View File
@@ -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),
});
+3 -1
View File
@@ -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 {
+4 -3
View File
@@ -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] };
}
+2 -3
View File
@@ -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] };
}
+1 -1
View File
@@ -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' },
}));
}
+1 -2
View File
@@ -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() },
}));
}
+3 -3
View File
@@ -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' },