mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
judge: provide score & subtaskId
This commit is contained in:
1 parent
60aa0e1611
commit
a585c13a72
19 files changed
+118
-186
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/hydrojudge",
|
"name": "@hydrooj/hydrojudge",
|
||||||
"bin": "bin/hydrojudge.js",
|
"bin": "bin/hydrojudge.js",
|
||||||
"version": "2.19.4",
|
"version": "2.20.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",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export = async function compile(
|
|||||||
);
|
);
|
||||||
if (status !== STATUS.STATUS_ACCEPTED) throw new CompileError({ status, stdout, stderr });
|
if (status !== STATUS.STATUS_ACCEPTED) throw new CompileError({ status, stdout, stderr });
|
||||||
if (!fileIds[target]) throw new CompileError({ stderr: 'Executable file not found.' });
|
if (!fileIds[target]) throw new CompileError({ stderr: 'Executable file not found.' });
|
||||||
if (next) next({ compiler_text: compilerText(stdout, stderr) });
|
if (next) next({ compilerText: compilerText(stdout, stderr) });
|
||||||
return {
|
return {
|
||||||
execute,
|
execute,
|
||||||
copyIn: { ...copyIn, [target]: { fileId: fileIds[target] } },
|
copyIn: { ...copyIn, [target]: { fileId: fileIds[target] } },
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import PQueue from 'p-queue';
|
|||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
|
import type { JudgeResultBody } from 'hydrooj';
|
||||||
import readCases from '../cases';
|
import readCases from '../cases';
|
||||||
import { getConfig } from '../config';
|
import { getConfig } from '../config';
|
||||||
import { CompileError, FormatError, SystemError } from '../error';
|
import { CompileError, FormatError, SystemError } from '../error';
|
||||||
import judge from '../judge';
|
import judge from '../judge';
|
||||||
import log from '../log';
|
import log from '../log';
|
||||||
import { CopyInFile } from '../sandbox/interface';
|
import type { CopyInFile } from '../sandbox/interface';
|
||||||
import * as sysinfo from '../sysinfo';
|
import * as sysinfo from '../sysinfo';
|
||||||
import * as tmpfs from '../tmpfs';
|
import * as tmpfs from '../tmpfs';
|
||||||
import {
|
import {
|
||||||
@@ -82,21 +83,21 @@ class JudgeTask {
|
|||||||
await this.doSubmission(startPromise);
|
await this.doSubmission(startPromise);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CompileError) {
|
if (e instanceof CompileError) {
|
||||||
this.next({ compiler_text: compilerText(e.stdout, e.stderr) });
|
this.next({ compilerText: compilerText(e.stdout, e.stderr) });
|
||||||
this.end({
|
this.end({
|
||||||
status: STATUS.STATUS_COMPILE_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
status: STATUS.STATUS_COMPILE_ERROR, score: 0, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
} else if (e instanceof FormatError) {
|
} else if (e instanceof FormatError) {
|
||||||
this.next({ judge_text: 'Testdata configuration incorrect.' });
|
this.next({ message: 'Testdata configuration incorrect.' });
|
||||||
this.next({ judge_text: { message: e.message, params: e.params } });
|
this.next({ message: { message: e.message, params: e.params } });
|
||||||
this.end({
|
this.end({
|
||||||
status: STATUS.STATUS_FORMAT_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
status: STATUS.STATUS_FORMAT_ERROR, score: 0, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.error(e);
|
log.error(e);
|
||||||
this.next({ message: { message: e.message, params: e.params || [], ...argv.options.debug ? { stack: e.stack } : {} } });
|
this.next({ message: { message: e.message, params: e.params || [], ...argv.options.debug ? { stack: e.stack } : {} } });
|
||||||
this.end({
|
this.end({
|
||||||
status: STATUS.STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
status: STATUS.STATUS_SYSTEM_ERROR, score: 0, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -132,38 +133,21 @@ class JudgeTask {
|
|||||||
await judge[type].judge(this, startPromise);
|
await judge[type].judge(this, startPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
next(data, id?: number) {
|
next(data: Partial<JudgeResultBody>, id?: number) {
|
||||||
log.debug('Next: %d %o', id, data);
|
log.debug('Next: %d %o', id, data);
|
||||||
data.key = 'next';
|
data.key = 'next';
|
||||||
data.rid = new ObjectID(this.rid);
|
data.rid = new ObjectID(this.rid);
|
||||||
if (data.time_ms) data.time = data.time_ms;
|
|
||||||
if (data.memory_kb) data.memory = data.memory_kb;
|
|
||||||
if (data.compiler_text) data.compilerText = data.compiler_text;
|
|
||||||
if (data.judge_text) data.message = data.judge_text;
|
|
||||||
delete data.time_ms;
|
|
||||||
delete data.memory_kb;
|
|
||||||
delete data.compiler_text;
|
|
||||||
delete data.judge_text;
|
|
||||||
if (data.case) {
|
if (data.case) {
|
||||||
data.case = {
|
data.case.id ||= id;
|
||||||
id,
|
data.case.message ||= '';
|
||||||
status: data.case.status,
|
|
||||||
time: data.case.time_ms || data.case.time,
|
|
||||||
memory: data.case.memory_kb || data.case.memory,
|
|
||||||
message: data.case.message || data.case.judgeText || data.case.judge_text || '',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
this.ws.send(JSON.stringify(data));
|
this.ws.send(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
end(data) {
|
end(data: Partial<JudgeResultBody>) {
|
||||||
log.info('End: %o', data);
|
log.info('End: %o', data);
|
||||||
data.key = 'end';
|
data.key = 'end';
|
||||||
data.rid = new ObjectID(this.request.rid);
|
data.rid = this.request.rid;
|
||||||
data.time = data.time_ms ?? data.time;
|
|
||||||
delete data.time_ms;
|
|
||||||
data.memory = data.memory_kb ?? data.memory;
|
|
||||||
delete data.memory_kb;
|
|
||||||
this.ws.send(JSON.stringify(data));
|
this.ws.send(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +205,7 @@ export default class Hydro {
|
|||||||
}
|
}
|
||||||
if (filenames.length) {
|
if (filenames.length) {
|
||||||
log.info(`Getting problem data: ${this.config.host}/${source}`);
|
log.info(`Getting problem data: ${this.config.host}/${source}`);
|
||||||
if (next) next({ judge_text: 'Syncing testdata, please wait...' });
|
if (next) next({ message: 'Syncing testdata, please wait...' });
|
||||||
await this.ensureLogin();
|
await this.ensureLogin();
|
||||||
const res = await this.axios.post(`/d/${domainId}/judge/files`, {
|
const res = await this.axios.post(`/d/${domainId}/judge/files`, {
|
||||||
pid: +pid,
|
pid: +pid,
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import { Logger } from '../log';
|
|||||||
import { del, run } from '../sandbox';
|
import { del, run } from '../sandbox';
|
||||||
import { CmdFile } from '../sandbox/interface';
|
import { CmdFile } from '../sandbox/interface';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parseFilename } from '../utils';
|
import { NormalizedCase, NormalizedSubtask, parseFilename } from '../utils';
|
||||||
import {
|
import { Context, ContextSubTask } from './interface';
|
||||||
Case, Context, ContextSubTask, SubTask,
|
|
||||||
} from './interface';
|
|
||||||
|
|
||||||
const Score = {
|
const Score = {
|
||||||
sum: (a: number, b: number) => (a + b),
|
sum: (a: number, b: number) => (a + b),
|
||||||
@@ -21,18 +19,18 @@ const Score = {
|
|||||||
|
|
||||||
const logger = new Logger('judge/default');
|
const logger = new Logger('judge/default');
|
||||||
|
|
||||||
function judgeCase(c: Case, sid: string) {
|
function judgeCase(c: NormalizedCase, sid: string) {
|
||||||
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
||||||
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
||||||
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|
||||||
|| ((ctxSubtask.subtask.if || []).filter((i: string) => ctx.failed[i]).length)
|
|| ((ctxSubtask.subtask.if || []).filter((i) => ctx.failed[i]).length)
|
||||||
) {
|
) {
|
||||||
ctx.next({
|
ctx.next({
|
||||||
case: {
|
case: {
|
||||||
status: STATUS.STATUS_CANCELED,
|
status: STATUS.STATUS_CANCELED,
|
||||||
score: 0,
|
score: 0,
|
||||||
time_ms: 0,
|
time: 0,
|
||||||
memory_kb: 0,
|
memory: 0,
|
||||||
message: '',
|
message: '',
|
||||||
},
|
},
|
||||||
addProgress: 100 / ctx.config.count,
|
addProgress: 100 / ctx.config.count,
|
||||||
@@ -100,10 +98,11 @@ function judgeCase(c: Case, sid: string) {
|
|||||||
ctx.total_memory_usage_kb = Math.max(ctx.total_memory_usage_kb, memory_usage_kb);
|
ctx.total_memory_usage_kb = Math.max(ctx.total_memory_usage_kb, memory_usage_kb);
|
||||||
ctx.next({
|
ctx.next({
|
||||||
case: {
|
case: {
|
||||||
|
subtaskId: ctxSubtask.subtask.id,
|
||||||
status,
|
status,
|
||||||
score: 0,
|
score,
|
||||||
time_ms: time_usage_ms,
|
time: time_usage_ms,
|
||||||
memory_kb: memory_usage_kb,
|
memory: memory_usage_kb,
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
addProgress: 100 / ctx.config.count,
|
addProgress: 100 / ctx.config.count,
|
||||||
@@ -126,7 +125,7 @@ function judgeCase(c: Case, sid: string) {
|
|||||||
env: ctx.env,
|
env: ctx.env,
|
||||||
});
|
});
|
||||||
const out = r.stdout.toString();
|
const out = r.stdout.toString();
|
||||||
if (out.length) ctx.next({ compiler_text: out.substring(0, 1024) });
|
if (out.length) ctx.next({ compilerText: out.substring(0, 1024) });
|
||||||
if (process.env.DEV) console.log(r);
|
if (process.env.DEV) console.log(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.info('Failed to run analysis');
|
logger.info('Failed to run analysis');
|
||||||
@@ -137,7 +136,7 @@ function judgeCase(c: Case, sid: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function judgeSubtask(subtask: SubTask, sid: string) {
|
function judgeSubtask(subtask: NormalizedSubtask, sid: string) {
|
||||||
return async (ctx: Context) => {
|
return async (ctx: Context) => {
|
||||||
subtask.type = subtask.type || 'min';
|
subtask.type = subtask.type || 'min';
|
||||||
const ctxSubtask = {
|
const ctxSubtask = {
|
||||||
@@ -219,7 +218,7 @@ export const judge = async (ctx: Context, startPromise = Promise.resolve()) => {
|
|||||||
ctx.end({
|
ctx.end({
|
||||||
status: ctx.total_status,
|
status: ctx.total_status,
|
||||||
score: ctx.total_score,
|
score: ctx.total_score,
|
||||||
time_ms: Math.floor(ctx.total_time_usage_ms * 1000000) / 1000000,
|
time: Math.floor(ctx.total_time_usage_ms * 1000000) / 1000000,
|
||||||
memory_kb: ctx.total_memory_usage_kb,
|
memory: ctx.total_memory_usage_kb,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -5,10 +5,10 @@ import { getConfig } from '../config';
|
|||||||
import { runPiped } from '../sandbox';
|
import { runPiped } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parse } from '../testlib';
|
import { parse } from '../testlib';
|
||||||
import { findFileSync, parseFilename } from '../utils';
|
|
||||||
import {
|
import {
|
||||||
Case, Context, ContextSubTask, SubTask,
|
findFileSync, NormalizedCase, NormalizedSubtask, parseFilename,
|
||||||
} from './interface';
|
} from '../utils';
|
||||||
|
import { Context, ContextSubTask } from './interface';
|
||||||
|
|
||||||
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
||||||
const Score = {
|
const Score = {
|
||||||
@@ -17,7 +17,7 @@ const Score = {
|
|||||||
min: Math.min,
|
min: Math.min,
|
||||||
};
|
};
|
||||||
|
|
||||||
function judgeCase(c: Case) {
|
function judgeCase(c: NormalizedCase) {
|
||||||
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
||||||
ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' };
|
ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' };
|
||||||
ctx.executeInteractor.copyIn.out = c.output ? { src: c.output } : { content: '' };
|
ctx.executeInteractor.copyIn.out = c.output ? { src: c.output } : { content: '' };
|
||||||
@@ -63,10 +63,11 @@ function judgeCase(c: Case) {
|
|||||||
ctx.next({
|
ctx.next({
|
||||||
status: STATUS.STATUS_JUDGING,
|
status: STATUS.STATUS_JUDGING,
|
||||||
case: {
|
case: {
|
||||||
|
subtaskId: ctxSubtask.subtask.id,
|
||||||
status,
|
status,
|
||||||
score,
|
score,
|
||||||
time_ms: time_usage_ms,
|
time: time_usage_ms,
|
||||||
memory_kb: memory_usage_kb,
|
memory: memory_usage_kb,
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
progress: Math.floor((c.id * 100) / ctx.config.count),
|
progress: Math.floor((c.id * 100) / ctx.config.count),
|
||||||
@@ -74,7 +75,7 @@ function judgeCase(c: Case) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function judgeSubtask(subtask: SubTask) {
|
function judgeSubtask(subtask: NormalizedSubtask) {
|
||||||
return async (ctx: Context) => {
|
return async (ctx: Context) => {
|
||||||
subtask.type = subtask.type || 'min';
|
subtask.type = subtask.type || 'min';
|
||||||
const ctxSubtask = {
|
const ctxSubtask = {
|
||||||
@@ -134,7 +135,7 @@ export const judge = async (ctx: Context, startPromise = Promise.resolve()) => {
|
|||||||
ctx.end({
|
ctx.end({
|
||||||
status: ctx.total_status,
|
status: ctx.total_status,
|
||||||
score: ctx.total_score,
|
score: ctx.total_score,
|
||||||
time_ms: ctx.total_time_usage_ms,
|
time: ctx.total_time_usage_ms,
|
||||||
memory_kb: ctx.total_memory_usage_kb,
|
memory: ctx.total_memory_usage_kb,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,31 +1,15 @@
|
|||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
import type { NormalizedSubtask } from '@hydrooj/utils/lib/common';
|
||||||
|
import type { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||||
|
import type { JudgeResultBody } from 'hydrooj';
|
||||||
import { Execute } from '../interface';
|
import { Execute } from '../interface';
|
||||||
import { CopyInFile } from '../sandbox/interface';
|
import { CopyInFile } from '../sandbox/interface';
|
||||||
|
|
||||||
export type Context = JudgeTaskInterface & RuntimeContext;
|
export type Context = JudgeTaskInterface & RuntimeContext;
|
||||||
|
|
||||||
export interface JudgeTaskInterface {
|
export interface JudgeTaskInterface {
|
||||||
next(arg0: {
|
next(data: Partial<JudgeResultBody>, id?: number): void;
|
||||||
compiler_text?: string;
|
end(data: Partial<JudgeResultBody>): void;
|
||||||
status?: number;
|
|
||||||
message?: string;
|
|
||||||
progress?: number;
|
|
||||||
addProgress?: number;
|
|
||||||
case?: {
|
|
||||||
status: number;
|
|
||||||
score?: number;
|
|
||||||
time_ms: number;
|
|
||||||
memory_kb: number;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
}, arg1?: number): void;
|
|
||||||
end(arg0: {
|
|
||||||
status: number;
|
|
||||||
score: number;
|
|
||||||
time_ms: number;
|
|
||||||
memory_kb: number;
|
|
||||||
}): void;
|
|
||||||
getLang: (name: string) => LangConfig;
|
getLang: (name: string) => LangConfig;
|
||||||
|
|
||||||
stat: Record<string, Date>;
|
stat: Record<string, Date>;
|
||||||
@@ -56,22 +40,20 @@ export interface RuntimeContext {
|
|||||||
executeUser?: Execute;
|
executeUser?: Execute;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraFile = string[];
|
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
type: string;
|
type: string;
|
||||||
subType?: string;
|
subType?: string;
|
||||||
time: string;
|
time: string;
|
||||||
memory: string;
|
memory: string;
|
||||||
|
|
||||||
subtasks?: SubTask[];
|
subtasks?: NormalizedSubtask[];
|
||||||
count?: number;
|
count?: number;
|
||||||
checker_type?: string;
|
checker_type?: string;
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
|
|
||||||
judge_extra_files?: ExtraFile;
|
judge_extra_files?: string[];
|
||||||
user_extra_files?: ExtraFile;
|
user_extra_files?: string[];
|
||||||
template?: Record<string, [string, string]>;
|
template?: Record<string, [string, string]>;
|
||||||
|
|
||||||
checker?: string;
|
checker?: string;
|
||||||
@@ -86,24 +68,8 @@ export interface Config {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubTask {
|
|
||||||
time: number;
|
|
||||||
memory: number;
|
|
||||||
type: 'sum' | 'max' | 'min';
|
|
||||||
score: number;
|
|
||||||
cases: Record<string, Case>;
|
|
||||||
if: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContextSubTask {
|
export interface ContextSubTask {
|
||||||
subtask: SubTask;
|
subtask: NormalizedSubtask;
|
||||||
score: number;
|
score: number;
|
||||||
status: number;
|
status: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Case {
|
|
||||||
id: number;
|
|
||||||
input: string;
|
|
||||||
output: string;
|
|
||||||
score: number;
|
|
||||||
}
|
|
||||||
@@ -29,11 +29,11 @@ export async function judge({
|
|||||||
status: totalStatus,
|
status: totalStatus,
|
||||||
progress: (100 * (+i + 1)) / config.outputs.length,
|
progress: (100 * (+i + 1)) / config.outputs.length,
|
||||||
case: {
|
case: {
|
||||||
status, score, time_ms: 0, memory_kb: 0, message: '',
|
status, score, time: 0, memory: 0, message: '',
|
||||||
},
|
},
|
||||||
}, +i + 1);
|
}, +i + 1);
|
||||||
}
|
}
|
||||||
return end({
|
return end({
|
||||||
status: totalStatus, score: totalScore, time_ms: 0, memory_kb: 0,
|
status: totalStatus, score: totalScore, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ import { Context } from './interface';
|
|||||||
const failure = (status: number, message?: string) => ({
|
const failure = (status: number, message?: string) => ({
|
||||||
status,
|
status,
|
||||||
score: 0,
|
score: 0,
|
||||||
time_ms: 0,
|
time: 0,
|
||||||
memory_kb: 0,
|
memory: 0,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,9 +88,11 @@ export const judge = async (ctx: Context) => {
|
|||||||
ctx.next({
|
ctx.next({
|
||||||
status,
|
status,
|
||||||
case: {
|
case: {
|
||||||
|
subtaskId: 0,
|
||||||
status,
|
status,
|
||||||
time_ms: time_usage_ms,
|
score: 100,
|
||||||
memory_kb: memory_usage_kb,
|
time: time_usage_ms,
|
||||||
|
memory: memory_usage_kb,
|
||||||
message: message.join('\n').substring(0, 102400),
|
message: message.join('\n').substring(0, 102400),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -115,7 +117,7 @@ export const judge = async (ctx: Context) => {
|
|||||||
memory: 256,
|
memory: 256,
|
||||||
});
|
});
|
||||||
const out = r.stdout.toString();
|
const out = r.stdout.toString();
|
||||||
if (out.length) ctx.next({ compiler_text: out.substring(0, 1024) });
|
if (out.length) ctx.next({ compilerText: out.substring(0, 1024) });
|
||||||
if (process.env.DEV) console.log(r);
|
if (process.env.DEV) console.log(r);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.info('Failed to run analysis');
|
logger.info('Failed to run analysis');
|
||||||
@@ -128,7 +130,7 @@ export const judge = async (ctx: Context) => {
|
|||||||
ctx.end({
|
ctx.end({
|
||||||
status,
|
status,
|
||||||
score: status === STATUS.STATUS_ACCEPTED ? 100 : 0,
|
score: status === STATUS.STATUS_ACCEPTED ? 100 : 0,
|
||||||
time_ms: Math.floor(time_usage_ms * 1000000) / 1000000,
|
time: Math.floor(time_usage_ms * 1000000) / 1000000,
|
||||||
memory_kb: memory_usage_kb,
|
memory: memory_usage_kb,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -5,10 +5,8 @@ import { check, compileChecker } from '../check';
|
|||||||
import { getConfig } from '../config';
|
import { getConfig } from '../config';
|
||||||
import { FormatError } from '../error';
|
import { FormatError } from '../error';
|
||||||
import { del, run } from '../sandbox';
|
import { del, run } from '../sandbox';
|
||||||
import { parseFilename } from '../utils';
|
import { NormalizedCase, NormalizedSubtask, parseFilename } from '../utils';
|
||||||
import {
|
import { Context, ContextSubTask } from './interface';
|
||||||
Case, Context, ContextSubTask, SubTask,
|
|
||||||
} from './interface';
|
|
||||||
|
|
||||||
const Score = {
|
const Score = {
|
||||||
sum: (a: number, b: number) => (a + b),
|
sum: (a: number, b: number) => (a + b),
|
||||||
@@ -16,18 +14,18 @@ const Score = {
|
|||||||
min: Math.min,
|
min: Math.min,
|
||||||
};
|
};
|
||||||
|
|
||||||
function judgeCase(c: Case, sid: string) {
|
function judgeCase(c: NormalizedCase, sid: string) {
|
||||||
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
||||||
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
||||||
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|
||||||
|| ((ctxSubtask.subtask.if || []).filter((i: string) => ctx.failed[i]).length)
|
|| ((ctxSubtask.subtask.if || []).filter((i) => ctx.failed[i]).length)
|
||||||
) {
|
) {
|
||||||
ctx.next({
|
ctx.next({
|
||||||
case: {
|
case: {
|
||||||
status: STATUS.STATUS_CANCELED,
|
status: STATUS.STATUS_CANCELED,
|
||||||
score: 0,
|
score: 0,
|
||||||
time_ms: 0,
|
time: 0,
|
||||||
memory_kb: 0,
|
memory: 0,
|
||||||
message: '',
|
message: '',
|
||||||
},
|
},
|
||||||
addProgress: 100 / ctx.config.count,
|
addProgress: 100 / ctx.config.count,
|
||||||
@@ -87,8 +85,8 @@ function judgeCase(c: Case, sid: string) {
|
|||||||
case: {
|
case: {
|
||||||
status,
|
status,
|
||||||
score: 0,
|
score: 0,
|
||||||
time_ms: 0,
|
time: 0,
|
||||||
memory_kb: 0,
|
memory: 0,
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
addProgress: 100 / ctx.config.count,
|
addProgress: 100 / ctx.config.count,
|
||||||
@@ -96,7 +94,7 @@ function judgeCase(c: Case, sid: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function judgeSubtask(subtask: SubTask, sid: string) {
|
function judgeSubtask(subtask: NormalizedSubtask, sid: string) {
|
||||||
return async (ctx: Context) => {
|
return async (ctx: Context) => {
|
||||||
subtask.type = subtask.type || 'min';
|
subtask.type = subtask.type || 'min';
|
||||||
const ctxSubtask = {
|
const ctxSubtask = {
|
||||||
@@ -160,7 +158,7 @@ export const judge = async (ctx: Context, startPromise = Promise.resolve()) => {
|
|||||||
ctx.end({
|
ctx.end({
|
||||||
status: ctx.total_status,
|
status: ctx.total_status,
|
||||||
score: ctx.total_score,
|
score: ctx.total_score,
|
||||||
time_ms: 0,
|
time: 0,
|
||||||
memory_kb: 0,
|
memory: 0,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -7,6 +7,7 @@ import fs from 'fs-extra';
|
|||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
import { ObjectID } from 'mongodb';
|
import { ObjectID } from 'mongodb';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
|
import type { JudgeResultBody } from 'hydrooj';
|
||||||
import { Logger } from 'hydrooj/src/logger';
|
import { Logger } from 'hydrooj/src/logger';
|
||||||
import * as monitor from 'hydrooj/src/service/monitor';
|
import * as monitor from 'hydrooj/src/service/monitor';
|
||||||
import readCases from './cases';
|
import readCases from './cases';
|
||||||
@@ -108,34 +109,18 @@ async function postInit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getNext(that) {
|
function getNext(that) {
|
||||||
return (data, id = 0) => {
|
return (data: Partial<JudgeResultBody>, id = 0) => {
|
||||||
logger.debug('Next: %d %o', id, data);
|
logger.debug('Next: %d %o', id, data);
|
||||||
data.rid = new ObjectID(that.rid);
|
data.rid = new ObjectID(that.rid);
|
||||||
if (data.time_ms) data.time = data.time_ms;
|
if (data.case) data.case.id ||= id;
|
||||||
if (data.memory_kb) data.memory = data.memory_kb;
|
|
||||||
if (data.compiler_text) data.compilerText = data.compiler_text;
|
|
||||||
delete data.time_ms;
|
|
||||||
delete data.memory_kb;
|
|
||||||
delete data.compiler_text;
|
|
||||||
if (data.case) {
|
|
||||||
data.case = {
|
|
||||||
id,
|
|
||||||
status: data.case.status,
|
|
||||||
time: data.case.time_ms || data.case.time,
|
|
||||||
memory: data.case.memory_kb || data.case.memory,
|
|
||||||
message: data.case.message || data.case.judgeText || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_judge.next(data);
|
_judge.next(data);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEnd(rid: string) {
|
function getEnd(rid: string) {
|
||||||
return (data) => {
|
return (data: Partial<JudgeResultBody>) => {
|
||||||
data.key = 'end';
|
data.key = 'end';
|
||||||
data.rid = new ObjectID(rid);
|
data.rid = new ObjectID(rid);
|
||||||
data.time = data.time_ms ?? data.time;
|
|
||||||
data.memory = data.memory_kb ?? data.memory;
|
|
||||||
logger.info('End: status=%d score=%d time=%dms memory=%dkb', data.status, data.score, data.time, data.memory);
|
logger.info('End: status=%d score=%d time=%dms memory=%dkb', data.status, data.score, data.time, data.memory);
|
||||||
_judge.end(data);
|
_judge.end(data);
|
||||||
};
|
};
|
||||||
@@ -160,8 +145,8 @@ async function postInit() {
|
|||||||
config: any;
|
config: any;
|
||||||
env: any;
|
env: any;
|
||||||
input?: string;
|
input?: string;
|
||||||
next: (data: any, id?: number) => void;
|
next: ReturnType<typeof getNext>;
|
||||||
end: (data: any) => void;
|
end: ReturnType<typeof getEnd>;
|
||||||
tmpdir: string;
|
tmpdir: string;
|
||||||
clean: (() => Promise<any>)[];
|
clean: (() => Promise<any>)[];
|
||||||
folder: string;
|
folder: string;
|
||||||
@@ -204,20 +189,20 @@ async function postInit() {
|
|||||||
await this.submission();
|
await this.submission();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CompileError) {
|
if (e instanceof CompileError) {
|
||||||
this.next({ compiler_text: compilerText(e.stdout, e.stderr) });
|
this.next({ compilerText: compilerText(e.stdout, e.stderr) });
|
||||||
this.end({
|
this.end({
|
||||||
status: STATUS.STATUS_COMPILE_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
status: STATUS.STATUS_COMPILE_ERROR, score: 0, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
} else if (e instanceof FormatError) {
|
} else if (e instanceof FormatError) {
|
||||||
this.next({ message: { message: e.message, params: e.params } });
|
this.next({ message: { message: e.message, params: e.params } });
|
||||||
this.end({
|
this.end({
|
||||||
status: STATUS.STATUS_FORMAT_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
status: STATUS.STATUS_FORMAT_ERROR, score: 0, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
this.next({ message: { message: e.message, params: e.params, ...process.env.DEV ? { stack: e.stack } : {} } });
|
this.next({ message: { message: e.message, params: e.params, ...process.env.DEV ? { stack: e.stack } : {} } });
|
||||||
this.end({
|
this.end({
|
||||||
status: STATUS.STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
status: STATUS.STATUS_SYSTEM_ERROR, score: 0, time: 0, memory: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "3.13.4",
|
"version": "3.13.5",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "src/loader",
|
"main": "src/loader",
|
||||||
"module": "src/loader",
|
"module": "src/loader",
|
||||||
|
|||||||
@@ -41,19 +41,19 @@ export async function postJudge(rdoc: RecordDoc) {
|
|||||||
await bus.serial('record/judge', rdoc, updated);
|
await bus.serial('record/judge', rdoc, updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function next(body: JudgeResultBody) {
|
export async function next(body: Partial<JudgeResultBody>) {
|
||||||
body.rid = new ObjectID(body.rid);
|
body.rid = new ObjectID(body.rid);
|
||||||
let rdoc = await record.get(body.rid);
|
let rdoc = await record.get(body.rid);
|
||||||
if (!rdoc) return;
|
if (!rdoc) return;
|
||||||
const $set: Partial<RecordDoc> = {};
|
const $set: Partial<RecordDoc> = {};
|
||||||
const $push: any = {};
|
const $push: any = {};
|
||||||
if (body.case) {
|
if (body.case) {
|
||||||
const c: TestCase = {
|
const c: Required<TestCase> = {
|
||||||
|
...body.case,
|
||||||
id: body.case.id || 0,
|
id: body.case.id || 0,
|
||||||
memory: body.case.memory,
|
subtaskId: body.case.subtaskId || 0,
|
||||||
time: body.case.time,
|
score: body.case.score || 0,
|
||||||
message: body.case.message || '',
|
message: body.case.message || '',
|
||||||
status: body.case.status,
|
|
||||||
};
|
};
|
||||||
rdoc.testCases.push(c);
|
rdoc.testCases.push(c);
|
||||||
$push.testCases = c;
|
$push.testCases = c;
|
||||||
@@ -75,7 +75,7 @@ export async function next(body: JudgeResultBody) {
|
|||||||
bus.broadcast('record/change', rdoc!, $set, $push);
|
bus.broadcast('record/change', rdoc!, $set, $push);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function end(body: JudgeResultBody) {
|
export async function end(body: Partial<JudgeResultBody>) {
|
||||||
if (body.rid) body.rid = new ObjectID(body.rid);
|
if (body.rid) body.rid = new ObjectID(body.rid);
|
||||||
let rdoc = await record.get(body.rid);
|
let rdoc = await record.get(body.rid);
|
||||||
if (!rdoc) return;
|
if (!rdoc) return;
|
||||||
|
|||||||
@@ -289,7 +289,6 @@ export class ProblemRandomHandler extends ProblemHandler {
|
|||||||
const category = flattenDeep(decodeURIComponent(qs).split(' ')
|
const category = flattenDeep(decodeURIComponent(qs).split(' ')
|
||||||
.filter((i) => i.startsWith('category:'))
|
.filter((i) => i.startsWith('category:'))
|
||||||
.map((i) => i.split('category:')[1]?.split(',')));
|
.map((i) => i.split('category:')[1]?.split(',')));
|
||||||
console.log(qs, category);
|
|
||||||
const q = buildQuery(this.user);
|
const q = buildQuery(this.user);
|
||||||
if (category.length) q.$and = category.map((tag) => ({ tag }));
|
if (category.length) q.$and = category.map((tag) => ({ tag }));
|
||||||
await bus.serial('problem/list', q, this);
|
await bus.serial('problem/list', q, this);
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class RecordDetailHandler extends Handler {
|
|||||||
time: 0,
|
time: 0,
|
||||||
memory: 0,
|
memory: 0,
|
||||||
testCases: [{
|
testCases: [{
|
||||||
status: 9, score: 0, time: 0, memory: 0, message: 'score canceled',
|
id: 0, subtaskId: 0, status: 9, score: 0, time: 0, memory: 0, message: 'score canceled',
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
const [latest] = await Promise.all([
|
const [latest] = await Promise.all([
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type fs from 'fs';
|
|||||||
import type { Dictionary, NumericDictionary } from 'lodash';
|
import type { Dictionary, NumericDictionary } from 'lodash';
|
||||||
import type { ItemBucketMetadata } from 'minio';
|
import type { ItemBucketMetadata } from 'minio';
|
||||||
import type { Cursor, ObjectID } from 'mongodb';
|
import type { Cursor, ObjectID } from 'mongodb';
|
||||||
import type { Readable, Writable } from 'stream';
|
|
||||||
import type { ProblemDoc } from './model/problem';
|
import type { ProblemDoc } from './model/problem';
|
||||||
|
|
||||||
type document = typeof import('./model/document');
|
type document = typeof import('./model/document');
|
||||||
@@ -208,6 +207,7 @@ export interface SampleContentNode {
|
|||||||
sectionTitle: string,
|
sectionTitle: string,
|
||||||
payload: [string, string],
|
payload: [string, string],
|
||||||
}
|
}
|
||||||
|
// TODO drop contentNode support
|
||||||
export type ContentNode = PlainContentNode | TextContentNode | SampleContentNode;
|
export type ContentNode = PlainContentNode | TextContentNode | SampleContentNode;
|
||||||
export type Content = string | ContentNode[] | Record<string, ContentNode[]>;
|
export type Content = string | ContentNode[] | Record<string, ContentNode[]>;
|
||||||
|
|
||||||
@@ -272,6 +272,8 @@ export interface ProblemStatusDoc extends StatusDoc {
|
|||||||
|
|
||||||
export interface TestCase {
|
export interface TestCase {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
subtaskId?: number;
|
||||||
|
score?: number;
|
||||||
time: number;
|
time: number;
|
||||||
memory: number;
|
memory: number;
|
||||||
status: number;
|
status: number;
|
||||||
@@ -288,9 +290,9 @@ export interface RecordDoc {
|
|||||||
score: number;
|
score: number;
|
||||||
memory: number;
|
memory: number;
|
||||||
time: number;
|
time: number;
|
||||||
judgeTexts: string[];
|
judgeTexts: (string | JudgeMessage)[];
|
||||||
compilerTexts: string[];
|
compilerTexts: string[];
|
||||||
testCases: TestCase[];
|
testCases: Required<TestCase>[];
|
||||||
rejudged: boolean;
|
rejudged: boolean;
|
||||||
source?: string;
|
source?: string;
|
||||||
/** judge uid */
|
/** judge uid */
|
||||||
@@ -497,24 +499,25 @@ export interface Script {
|
|||||||
validate: any,
|
validate: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JudgeMessage {
|
||||||
|
message: string;
|
||||||
|
params?: string[];
|
||||||
|
stack?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JudgeResultBody {
|
export interface JudgeResultBody {
|
||||||
|
key: string;
|
||||||
domainId: string;
|
domainId: string;
|
||||||
rid: ObjectID;
|
rid: ObjectID;
|
||||||
judger?: number;
|
judger?: number;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
addProgress?: number;
|
addProgress?: number;
|
||||||
case?: {
|
case?: TestCase,
|
||||||
id?: number;
|
|
||||||
status: number;
|
|
||||||
time: number;
|
|
||||||
memory: number;
|
|
||||||
message?: string;
|
|
||||||
},
|
|
||||||
status?: number;
|
status?: number;
|
||||||
score?: number;
|
score?: number;
|
||||||
time?: number;
|
time?: number;
|
||||||
memory?: number;
|
memory?: number;
|
||||||
message?: string;
|
message?: string | JudgeMessage;
|
||||||
compilerText?: string,
|
compilerText?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,19 +530,6 @@ export interface Task {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadStream extends Writable {
|
|
||||||
id: ObjectID;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HydroFileSystem {
|
|
||||||
openUploadStream: (filename: string) => UploadStream
|
|
||||||
openDownloadStream: (_id: ObjectID) => Readable
|
|
||||||
// TODO
|
|
||||||
find: (...args: any[]) => any
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PathComponent = [string, string, Dictionary<any>?, boolean?];
|
|
||||||
|
|
||||||
export interface BaseService {
|
export interface BaseService {
|
||||||
started: boolean;
|
started: boolean;
|
||||||
error?: Error | string;
|
error?: Error | string;
|
||||||
@@ -637,7 +627,6 @@ export interface Model {
|
|||||||
export interface Service {
|
export interface Service {
|
||||||
bus: typeof import('./service/bus'),
|
bus: typeof import('./service/bus'),
|
||||||
db: typeof import('./service/db'),
|
db: typeof import('./service/db'),
|
||||||
fs: HydroFileSystem,
|
|
||||||
monitor: typeof import('./service/monitor'),
|
monitor: typeof import('./service/monitor'),
|
||||||
server: typeof import('./service/server'),
|
server: typeof import('./service/server'),
|
||||||
storage: typeof import('./service/storage'),
|
storage: typeof import('./service/storage'),
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ app.on('error', (error) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
wsServer.on('error', (error) => {
|
wsServer.on('error', (error) => {
|
||||||
console.log(123, error);
|
console.log('Websocket server error:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ignoredLimit = `,${argv.options.ignoredLimit},`;
|
const ignoredLimit = `,${argv.options.ignoredLimit},`;
|
||||||
|
|||||||
@@ -200,9 +200,13 @@ const tasks = {
|
|||||||
},
|
},
|
||||||
record: async (doc) => {
|
record: async (doc) => {
|
||||||
if (doc.hidden) return null;
|
if (doc.hidden) return null;
|
||||||
const testCases: TestCase[] = [];
|
const testCases: Required<TestCase>[] = [];
|
||||||
|
let i = 1;
|
||||||
for (const c of doc.cases || []) {
|
for (const c of doc.cases || []) {
|
||||||
testCases.push({
|
testCases.push({
|
||||||
|
id: i++,
|
||||||
|
subtaskId: 0,
|
||||||
|
score: 0,
|
||||||
status: c.status,
|
status: c.status,
|
||||||
time: c.time_ms || c.time,
|
time: c.time_ms || c.time,
|
||||||
memory: c.memory_kb || c.memory,
|
memory: c.memory_kb || c.memory,
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ interface MatchRule {
|
|||||||
|
|
||||||
const SubtaskMatcher: MatchRule[] = [
|
const SubtaskMatcher: MatchRule[] = [
|
||||||
{
|
{
|
||||||
regex: /^([^\d]*)(\d+).(in|txt)$/,
|
regex: /^([^\d]*(?:\d+[a-zA-Z]+)*)(\d+).(in|txt)$/,
|
||||||
output: [
|
output: [
|
||||||
(a) => `${a[1] + a[2]}.out`,
|
(a) => `${a[1] + a[2]}.out`,
|
||||||
(a) => `${a[1] + a[2]}.ans`,
|
(a) => `${a[1] + a[2]}.ans`,
|
||||||
@@ -260,9 +260,14 @@ export function readSubtasksFromFiles(files: string[], config) {
|
|||||||
return Object.values(subtask);
|
return Object.values(subtask);
|
||||||
}
|
}
|
||||||
|
|
||||||
type NormalizedCase = Required<ParsedCase>;
|
export interface NormalizedCase extends Required<ParsedCase> {
|
||||||
interface NormalizedSubtask extends Required<ParsedSubtask> {
|
time: number;
|
||||||
|
memory: number;
|
||||||
|
}
|
||||||
|
export interface NormalizedSubtask extends Required<ParsedSubtask> {
|
||||||
cases: NormalizedCase[];
|
cases: NormalizedCase[];
|
||||||
|
time: number;
|
||||||
|
memory: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeSubtasks(
|
export function normalizeSubtasks(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/utils",
|
"name": "@hydrooj/utils",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "hydrooj utils",
|
"description": "hydrooj utils",
|
||||||
"main": "package.json",
|
"main": "package.json",
|
||||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||||
|
|||||||
Reference in new issue
Block a user