diff --git a/packages/common/types.ts b/packages/common/types.ts index 1132b71ab7..4d123bd402 100644 --- a/packages/common/types.ts +++ b/packages/common/types.ts @@ -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; redirect?: string; cases?: TestCaseConfig[]; diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index 8c7e225a98..ce7cb39875 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -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); diff --git a/packages/hydrojudge/src/checkers.ts b/packages/hydrojudge/src/checkers.ts index a51fa1f2af..0e88325865 100644 --- a/packages/hydrojudge/src/checkers.ts +++ b/packages/hydrojudge/src/checkers.ts @@ -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; score: number; - detail: boolean; + detail: DetailType; env?: Record; } @@ -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 = 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 = 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 = 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 = 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 = 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 = 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 }; }, diff --git a/packages/hydrojudge/src/config.ts b/packages/hydrojudge/src/config.ts index b3d4d0c540..72a2f86158 100644 --- a/packages/hydrojudge/src/config.ts +++ b/packages/hydrojudge/src/config.ts @@ -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), }); diff --git a/packages/hydrojudge/src/flow.ts b/packages/hydrojudge/src/flow.ts index 8824346285..2ef44b7e3a 100644 --- a/packages/hydrojudge/src/flow.ts +++ b/packages/hydrojudge/src/flow.ts @@ -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 { diff --git a/packages/hydrojudge/src/interface.ts b/packages/hydrojudge/src/interface.ts index 1d2c48a5b2..e85bfd1bc3 100644 --- a/packages/hydrojudge/src/interface.ts +++ b/packages/hydrojudge/src/interface.ts @@ -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) => Promise | void; -export interface ParsedConfig extends Omit { +export interface ParsedConfig extends Omit { 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: (namespace: T, files: Record) => Promise; postFile: (target: string, filename: string, file: string) => Promise; - config: { detail: boolean, host?: string, trusted?: boolean }; + config: { detail: DetailType, host?: string, trusted?: boolean }; } diff --git a/packages/hydrojudge/src/judge/communication.ts b/packages/hydrojudge/src/judge/communication.ts index ebb7416d9e..c74bf4f0c3 100644 --- a/packages/hydrojudge/src/judge/communication.ts +++ b/packages/hydrojudge/src/judge/communication.ts @@ -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] }; } diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index 9eb25e38bb..31f60e279c 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -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] }; } diff --git a/packages/hydrojudge/src/judge/hack.ts b/packages/hydrojudge/src/judge/hack.ts index 0cfe998265..504b652da9 100644 --- a/packages/hydrojudge/src/judge/hack.ts +++ b/packages/hydrojudge/src/judge/hack.ts @@ -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' }, })); } diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index d279961212..16929cbb95 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -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] }; diff --git a/packages/hydrojudge/src/judge/submit_answer.ts b/packages/hydrojudge/src/judge/submit_answer.ts index 949c7efff0..96be657aed 100644 --- a/packages/hydrojudge/src/judge/submit_answer.ts +++ b/packages/hydrojudge/src/judge/submit_answer.ts @@ -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() }, })); } diff --git a/packages/hydrojudge/src/testlib.ts b/packages/hydrojudge/src/testlib.ts index 6d198556ed..7e76f360de 100644 --- a/packages/hydrojudge/src/testlib.ts +++ b/packages/hydrojudge/src/testlib.ts @@ -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 : '') }; } diff --git a/packages/ui-default/components/monaco/schema/problemconfig.ts b/packages/ui-default/components/monaco/schema/problemconfig.ts index d608ebf3a5..f5f156c662 100644 --- a/packages/ui-default/components/monaco/schema/problemconfig.ts +++ b/packages/ui-default/components/monaco/schema/problemconfig.ts @@ -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' },