judge: provide score & subtaskId

This commit is contained in:
undefined committed 2022-05-13 00:00:55 +08:00
1 parent 60aa0e1611
commit a585c13a72
19 files changed
+118 -186

No files matched your search

+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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] } },
+14 -30
View File
@@ -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,
+14 -15
View File
@@ -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,
}); });
}; };
+10 -9
View File
@@ -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,
}); });
}; };
+9 -43
View File
@@ -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;
}
+2 -2
View File
@@ -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,
}); });
} }
+9 -7
View File
@@ -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,
}); });
}; };
+11 -13
View File
@@ -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,
}); });
}; };
+10 -25
View File
@@ -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 -1
View File
@@ -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",
+6 -6
View File
@@ -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;
-1
View File
@@ -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);
+1 -1
View File
@@ -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([
+14 -25
View File
@@ -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'),
+1 -1
View File
@@ -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},`;
+5 -1
View File
@@ -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,
+8 -3
View File
@@ -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 -1
View File
@@ -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",