diff --git a/packages/common/types.ts b/packages/common/types.ts index a1300ff86..f57c49dc2 100644 --- a/packages/common/types.ts +++ b/packages/common/types.ts @@ -107,7 +107,7 @@ export interface RecordPayload extends RecordJudgeInfo { source?: string; progress?: number; /** pretest */ - input?: string; + input?: string | string[]; /** hack target rid */ hackTarget?: string; /** 0 if pretest&script */ diff --git a/packages/hydrojudge/src/judge/generate.ts b/packages/hydrojudge/src/judge/generate.ts index 613879cfe..9b864cdce 100644 --- a/packages/hydrojudge/src/judge/generate.ts +++ b/packages/hydrojudge/src/judge/generate.ts @@ -29,7 +29,7 @@ export const judge = async (ctx: JudgeTask) => { await using res = await runQueued( `${executeGenerator.execute} ${i}`, { - stdin: { content: ctx.input || '' }, + stdin: { content: (ctx.input || [])[i - 1] || '' }, copyIn: executeGenerator.copyIn, copyOut: ['stderr'], copyOutCached: ['stdout'], diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index 3dc8b5e78..87b9b71d5 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -1,19 +1,17 @@ -import { STATUS } from '@hydrooj/common'; +import { NormalizedCase, STATUS } from '@hydrooj/common'; import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; +import { runFlow } from '../flow'; import { runQueued } from '../sandbox'; import signals from '../signals'; -import { JudgeTask } from '../task'; +import { Context } from './interface'; -export const judge = async (ctx: JudgeTask) => { - ctx.next({ status: STATUS.STATUS_COMPILING }); - const execute = await ctx.compile(ctx.lang, ctx.code); - ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 }); +const judgeCase = (c: NormalizedCase) => async (ctx: Context) => { const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang); const res = await runQueued( - execute.execute, + ctx.execute.execute, { - stdin: { content: ctx.input }, - copyIn: execute.copyIn, + stdin: { content: c.input }, + copyIn: ctx.execute.copyIn, // Allow 2x limits for better debugging time: parseTimeMS(ctx.config.time || '1s') * 2, memory: parseMemoryMB(ctx.config.memory || '256m'), @@ -21,7 +19,7 @@ export const judge = async (ctx: JudgeTask) => { addressSpaceLimit: address_space_limit, processLimit: process_limit, }, - `judge[${ctx.rid}]`, + `pretest[${c.id}]<${ctx.rid}>`, 1, ); const { @@ -39,27 +37,42 @@ export const judge = async (ctx: JudgeTask) => { else message.push(`ExitCode: ${code}`); } message.push(res.stdout, res.stderr); - ctx.next({ - status, - time: Math.floor(time * 1000000) / 1000000, - memory, - score: status === STATUS.STATUS_ACCEPTED ? 100 : 0, - case: { - subtaskId: 0, - status, - score: 100, - time, - memory, - message: message.join('\n').substring(0, 102400), - }, - }); - if ([STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) { - await ctx.runAnalysis(execute, { content: ctx.input }); + if (!ctx.analysis && [STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) { + ctx.analysis = true; + await ctx.runAnalysis(ctx.execute, { content: c.input }); } - ctx.end({ + return { + id: c.id, + subtaskId: 1, status, - time: Math.floor(time * 1000000) / 1000000, + score: 1, + time, memory, - score: status === STATUS.STATUS_ACCEPTED ? 100 : 0, + message: message.join('\n').substring(0, 102400), + }; +}; + +export const judge = async (ctx: Context) => { + ctx.config.subtasks = [{ + id: 1, + type: 'sum', + score: 100, + time: ctx.config.time, + memory: ctx.config.memory, + if: [], + cases: ctx.input.map((i, idx) => ({ + id: idx + 1, + time: ctx.config.time, + memory: ctx.config.memory, + input: i, + output: '', + score: 1, + })), + }]; + await runFlow(ctx, { + compile: async () => { + ctx.execute = await ctx.compile(ctx.lang, ctx.code); + }, + judgeCase, }); }; diff --git a/packages/hydrojudge/src/task.ts b/packages/hydrojudge/src/task.ts index a77064274..aec0d46a1 100644 --- a/packages/hydrojudge/src/task.ts +++ b/packages/hydrojudge/src/task.ts @@ -30,7 +30,7 @@ export class JudgeTask { rid: string; lang: string; code: CopyInFile; - input?: string; + input?: string[]; finished: boolean = false; clean: (() => Promise)[] = []; data: FileInfo[]; @@ -67,7 +67,7 @@ export class JudgeTask { this.source = this.request.source; this.meta = this.request.meta; this.files = this.request.files; - this.input = this.request.input; + this.input = Array.isArray(this.request.input) ? this.request.input : [this.request.input || '']; let tid = this.request.contest?.toString() || ''; if (tid === '000000000000000000000000') tid = ''; this.env = { diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 269d06277..0b5ac01fc 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -103,7 +103,7 @@ class SystemScriptHandler extends SystemHandler { if (typeof global.Hydro.script[id].validate === 'function') { args = global.Hydro.script[id].validate(args); } - const rid = await record.add(domainId, -1, this.user._id, '-', id, false, { input: raw, type: 'pretest' }); + const rid = await record.add(domainId, -1, this.user._id, '-', id, false, { input: [raw], type: 'pretest' }); const c = new JudgeResultCallbackContext(this.ctx, { type: 'judge', domainId, rid }); c.next({ message: `Running script: ${id} `, status: STATUS.STATUS_JUDGING }); const start = Date.now(); diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index e2fa80edf..dc3fa7a2a 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -483,9 +483,9 @@ export class ProblemSubmitHandler extends ProblemDetailHandler { @param('lang', Types.Name) @param('code', Types.String, true) @param('pretest', Types.Boolean) - @param('input', Types.String, true) + @param('input', Types.ArrayOf(Types.String), true) @param('tid', Types.ObjectId, true) - async post(domainId: string, lang: string, code: string, pretest = false, input = '', tid?: ObjectId) { + async post(domainId: string, lang: string, code: string, pretest = false, input: string[] = [], tid?: ObjectId) { const config = this.pdoc.config; if (typeof config === 'string' || config === null) throw new ProblemConfigError(); if (['submit_answer', 'objective'].includes(config.type)) { @@ -498,6 +498,7 @@ export class ProblemSubmitHandler extends ProblemDetailHandler { if (!['default', 'remote_judge'].includes(this.response.body.pdoc.config?.type)) { throw new ProblemNotAllowPretestError('type'); } + if (!input.length) throw new ValidationError('input'); } await this.limitRate('add_record', 60, system.get('limit.submission_user'), '{{user}}'); await this.limitRate('add_record', 60, pretest ? system.get('limit.pretest') : system.get('limit.submission')); diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 5386525e4..7024f1340 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -131,7 +131,7 @@ export default class RecordModel { lang: string, code: string, addTask: boolean, args: { contest?: ObjectId; - input?: string; + input?: string[]; files?: Record; hackTarget?: ObjectId; type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate'; @@ -165,7 +165,7 @@ export default class RecordModel { args.type = 'judge'; data.rejudged = true; } else if (args.type === 'pretest') { - data.input = args.input || ''; + data.input = args.input || []; isContest = false; data.contest = RecordModel.RECORD_PRETEST; } else if (args.type === 'generate') { diff --git a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx index e722a2bb9..6316b5ab4 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx @@ -39,7 +39,7 @@ const mapDispatchToProps = (dispatch) => ({ const req = request.post(UiContext.postSubmitUrl, { lang: props.editorLang, code: props.editorCode, - input: props.pretestInput, + input: [props.pretestInput], pretest: true, }); dispatch({