mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
move delarations to @hydrooj/common
This commit is contained in:
1 parent
100ccf718f
commit
4a33816a37
49 files changed
+496
-465
No files matched your search
@@ -8,7 +8,7 @@ import Koa from 'koa';
|
|||||||
import Body from 'koa-body';
|
import Body from 'koa-body';
|
||||||
import Compress from 'koa-compress';
|
import Compress from 'koa-compress';
|
||||||
import { Shorty } from 'shorty.js';
|
import { Shorty } from 'shorty.js';
|
||||||
import { WebSocketServer } from 'ws';
|
import { WebSocket, WebSocketServer } from 'ws';
|
||||||
import {
|
import {
|
||||||
Counter, errorMessage, isClass, Logger, parseMemoryMB,
|
Counter, errorMessage, isClass, Logger, parseMemoryMB,
|
||||||
} from '@hydrooj/utils/lib/utils';
|
} from '@hydrooj/utils/lib/utils';
|
||||||
@@ -71,6 +71,7 @@ export interface HydroResponse {
|
|||||||
etag?: string;
|
etag?: string;
|
||||||
attachment: (name: string, stream?: any) => void;
|
attachment: (name: string, stream?: any) => void;
|
||||||
addHeader: (name: string, value: string) => void;
|
addHeader: (name: string, value: string) => void;
|
||||||
|
serializer: typeof serializer;
|
||||||
}
|
}
|
||||||
export type RendererFunction = (name: string, context: Record<string, any>) => string | Promise<string>;
|
export type RendererFunction = (name: string, context: Record<string, any>) => string | Promise<string>;
|
||||||
type HydroContext = {
|
type HydroContext = {
|
||||||
@@ -170,7 +171,7 @@ export class HandlerCommon {
|
|||||||
|
|
||||||
renderHTML(templateName: string, args: Record<string, any>) {
|
renderHTML(templateName: string, args: Record<string, any>) {
|
||||||
const type = templateName.split('.')[1];
|
const type = templateName.split('.')[1];
|
||||||
const engine = this.ctx.server.renderers[type] || (() => JSON.stringify(args, serializer(false, this)));
|
const engine = this.ctx.server.renderers[type] || (() => JSON.stringify(args, this.response.serializer(false, this)));
|
||||||
return engine(templateName, {
|
return engine(templateName, {
|
||||||
handler: this,
|
handler: this,
|
||||||
UserContext: this.user,
|
UserContext: this.user,
|
||||||
@@ -265,7 +266,7 @@ export class ConnectionHandler extends HandlerCommon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send(data: any) {
|
send(data: any) {
|
||||||
let payload = JSON.stringify(data, serializer(false, this));
|
let payload = JSON.stringify(data, this.response.serializer(false, this));
|
||||||
if (this.compression) {
|
if (this.compression) {
|
||||||
if (this.counter > 1000) this.resetCompression();
|
if (this.counter > 1000) this.resetCompression();
|
||||||
payload = this.compression.deflate(payload);
|
payload = this.compression.deflate(payload);
|
||||||
@@ -327,6 +328,7 @@ export interface WebServiceConfig {
|
|||||||
host?: string;
|
host?: string;
|
||||||
xff?: string;
|
xff?: string;
|
||||||
xhost?: string;
|
xhost?: string;
|
||||||
|
serializer?: typeof serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebService extends Service {
|
export class WebService extends Service {
|
||||||
@@ -441,7 +443,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`);
|
|||||||
func: (t) => this.handleHttp(t, NotFoundHandler, () => true),
|
func: (t) => this.handleHttp(t, NotFoundHandler, () => true),
|
||||||
},
|
},
|
||||||
]));
|
]));
|
||||||
this.addLayer('base', base(logger, this.config.xff, this.config.xhost));
|
this.addLayer('base', base(logger, this.config.xff, this.config.xhost, this.config.serializer));
|
||||||
wsServer.on('connection', async (socket, request) => {
|
wsServer.on('connection', async (socket, request) => {
|
||||||
socket.on('error', (err) => {
|
socket.on('error', (err) => {
|
||||||
logger.warn('Websocket Error: %s', err.message);
|
logger.warn('Websocket Error: %s', err.message);
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import { findFileSync, parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export async function readYamlCases(cfg: Record<string, any> = {}, checkFile = (s: string, errMsg: string) => s) {
|
||||||
|
const config: any = {
|
||||||
|
checker_type: cfg.checker_type || 'default',
|
||||||
|
judge_extra_files: [],
|
||||||
|
user_extra_files: [],
|
||||||
|
};
|
||||||
|
if (cfg.type === 'objective') {
|
||||||
|
if (cfg.checker || cfg.interactor || cfg.validator) {
|
||||||
|
throw new Error('You cannot use checker, interactor or validator for objective questions');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cfg.checker) {
|
||||||
|
if (!cfg.checker.includes('.')) {
|
||||||
|
config.checker = findFileSync(`@hydrooj/hydrojudge/vendor/testlib/checkers/${cfg.checker}.cpp`, false);
|
||||||
|
}
|
||||||
|
config.checker ||= checkFile(cfg.checker, 'Cannot find checker {0}.');
|
||||||
|
}
|
||||||
|
if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.');
|
||||||
|
if (cfg.manager) config.manager = checkFile(cfg.manager, 'Cannot find Manager {0}.');
|
||||||
|
if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.');
|
||||||
|
for (const n of ['judge', 'user']) {
|
||||||
|
const conf = cfg[`${n}_extra_files`];
|
||||||
|
if (!conf) continue;
|
||||||
|
if (conf instanceof Array) {
|
||||||
|
config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`));
|
||||||
|
} else throw new Error(`Invalid ${n}_extra_files config.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cfg.cases?.length) {
|
||||||
|
config.subtasks = [{
|
||||||
|
cases: cfg.cases,
|
||||||
|
type: 'sum',
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
if (cfg.time) config.time = parseTimeMS(cfg.time);
|
||||||
|
if (cfg.memory) config.memory = parseMemoryMB(cfg.memory);
|
||||||
|
return Object.assign(cfg, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertIniConfig(ini: string) {
|
||||||
|
const f = ini.split('\n');
|
||||||
|
const count = parseInt(f[0], 10);
|
||||||
|
const res = { subtasks: [] };
|
||||||
|
for (let i = 1; i <= count; i++) {
|
||||||
|
if (!f[i]?.trim()) throw new Error('Testdata count incorrect.');
|
||||||
|
const [input, output, time, score, memory] = f[i].split('|');
|
||||||
|
const cur = {
|
||||||
|
cases: [{ input, output }],
|
||||||
|
score: parseInt(score, 10),
|
||||||
|
time: `${time}s`,
|
||||||
|
memory: '256m',
|
||||||
|
};
|
||||||
|
if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`;
|
||||||
|
res.subtasks.push(cur);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MatchRule {
|
||||||
|
regex: RegExp;
|
||||||
|
output: (a: RegExpExecArray) => string[];
|
||||||
|
id: (a: RegExpExecArray) => number;
|
||||||
|
subtask: (a: RegExpExecArray) => number;
|
||||||
|
preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum';
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubtaskMatcher: MatchRule[] = [
|
||||||
|
{
|
||||||
|
regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/,
|
||||||
|
output: (a) => ['out', 'ans']
|
||||||
|
.flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`])
|
||||||
|
.flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')])
|
||||||
|
.concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null),
|
||||||
|
id: (a) => +a[4],
|
||||||
|
subtask: (a) => +(a[3] || 1),
|
||||||
|
preferredScorerType: (a) => (a[3] ? 'min' : 'sum'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /^([^\d]*)\.(in|IN)(\d+)$/,
|
||||||
|
output: (a) => [
|
||||||
|
`${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`,
|
||||||
|
`${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`,
|
||||||
|
].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]),
|
||||||
|
id: (a) => +a[2],
|
||||||
|
subtask: () => 1,
|
||||||
|
preferredScorerType: () => 'sum',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/,
|
||||||
|
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`),
|
||||||
|
id: (a) => +a[4],
|
||||||
|
subtask: (a) => +a[2],
|
||||||
|
preferredScorerType: () => 'min',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/,
|
||||||
|
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`),
|
||||||
|
id: (a) => +a[2],
|
||||||
|
subtask: () => 1,
|
||||||
|
preferredScorerType: () => 'sum',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function* getScore(totalScore: number, count: number) {
|
||||||
|
const base = Math.floor(totalScore / count);
|
||||||
|
const extra = count - (totalScore % count);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
if (i >= extra) yield base + 1;
|
||||||
|
else yield base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParsedCase {
|
||||||
|
id?: number;
|
||||||
|
time?: number | string;
|
||||||
|
memory?: number | string;
|
||||||
|
score?: number;
|
||||||
|
input?: string;
|
||||||
|
output?: string;
|
||||||
|
}
|
||||||
|
interface ParsedSubtask {
|
||||||
|
cases: ParsedCase[];
|
||||||
|
type: 'min' | 'max' | 'sum';
|
||||||
|
time?: number | string;
|
||||||
|
memory?: number | string;
|
||||||
|
score?: number;
|
||||||
|
id?: number;
|
||||||
|
if?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readSubtasksFromFiles(files: string[], config) {
|
||||||
|
const subtask: Record<number, ParsedSubtask> = {};
|
||||||
|
for (const s of config.subtasks || []) if (s.id) subtask[s.id] = s;
|
||||||
|
for (const file of files) {
|
||||||
|
let match = false;
|
||||||
|
for (const rule of SubtaskMatcher) {
|
||||||
|
const data = rule.regex.exec(file);
|
||||||
|
if (!data) continue;
|
||||||
|
const sid = rule.subtask(data);
|
||||||
|
const c = { input: file, output: '', id: rule.id(data) };
|
||||||
|
const type = rule.preferredScorerType(data);
|
||||||
|
const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i);
|
||||||
|
for (const output of outputs) {
|
||||||
|
if (output === file) continue;
|
||||||
|
if (output === '/dev/null' || files.includes(output)) {
|
||||||
|
match = true;
|
||||||
|
c.output = output;
|
||||||
|
if (!subtask[sid]) {
|
||||||
|
subtask[sid] = {
|
||||||
|
time: config.time,
|
||||||
|
memory: config.memory,
|
||||||
|
type,
|
||||||
|
cases: [c],
|
||||||
|
id: sid,
|
||||||
|
};
|
||||||
|
} else if (!subtask[sid].cases) subtask[sid].cases = [c];
|
||||||
|
else subtask[sid].cases.push(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.values(subtask);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedCase extends Required<ParsedCase> {
|
||||||
|
time: number;
|
||||||
|
memory: number;
|
||||||
|
}
|
||||||
|
export interface NormalizedSubtask extends Required<ParsedSubtask> {
|
||||||
|
cases: NormalizedCase[];
|
||||||
|
time: number;
|
||||||
|
memory: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeSubtasks(
|
||||||
|
subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string,
|
||||||
|
time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false,
|
||||||
|
timeRate = 1, memoryRate = 1,
|
||||||
|
): NormalizedSubtask[] {
|
||||||
|
subtasks.sort((a, b) => (a.id - b.id));
|
||||||
|
const subtaskScore = getScore(
|
||||||
|
Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0),
|
||||||
|
subtasks.filter((i) => !i.score).length,
|
||||||
|
);
|
||||||
|
return subtasks.map((s, id) => {
|
||||||
|
s.cases.sort((a, b) => (a.id - b.id));
|
||||||
|
const score = s.score || subtaskScore.next().value as number;
|
||||||
|
const caseScore = getScore(
|
||||||
|
Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0),
|
||||||
|
s.cases.filter((i) => !i.score).length,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
id: id + 1,
|
||||||
|
type: 'min',
|
||||||
|
if: [],
|
||||||
|
...s,
|
||||||
|
score,
|
||||||
|
time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate,
|
||||||
|
memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate,
|
||||||
|
cases: s.cases.map((c, index) => ({
|
||||||
|
id: index + 1,
|
||||||
|
...c,
|
||||||
|
score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score),
|
||||||
|
time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate,
|
||||||
|
memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate,
|
||||||
|
input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null',
|
||||||
|
output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null',
|
||||||
|
})) as NormalizedCase[],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './lang';
|
||||||
|
export * from './status';
|
||||||
|
export * from './types';
|
||||||
|
export * from './cases';
|
||||||
File renamed without changes.
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "@hydrooj/common",
|
||||||
|
"main": "./index.ts",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@hydrooj/utils": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,16 @@ export const STATUS_CODES: Record<STATUS, string> = {
|
|||||||
[STATUS.STATUS_HACK_UNSUCCESSFUL]: 'fail',
|
[STATUS.STATUS_HACK_UNSUCCESSFUL]: 'fail',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NORMAL_STATUS: STATUS[] = [
|
||||||
|
STATUS.STATUS_ACCEPTED,
|
||||||
|
STATUS.STATUS_WRONG_ANSWER,
|
||||||
|
STATUS.STATUS_TIME_LIMIT_EXCEEDED,
|
||||||
|
STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
|
||||||
|
STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
|
||||||
|
STATUS.STATUS_RUNTIME_ERROR,
|
||||||
|
STATUS.STATUS_COMPILE_ERROR,
|
||||||
|
];
|
||||||
|
|
||||||
export function getScoreColor(score: number | string): string {
|
export function getScoreColor(score: number | string): string {
|
||||||
if (score === null || score === undefined || !Number.isFinite(+score)) return '#000000';
|
if (score === null || score === undefined || !Number.isFinite(+score)) return '#000000';
|
||||||
return [
|
return [
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { describe, it } from 'node:test';
|
import { describe, it } from 'node:test';
|
||||||
import { readSubtasksFromFiles } from '../lib/common';
|
import { readSubtasksFromFiles } from '../cases';
|
||||||
|
|
||||||
interface Info {
|
interface Info {
|
||||||
subtask: number;
|
subtask: number;
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
export type CompilableSource = string | {
|
||||||
|
file: string;
|
||||||
|
lang: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum ProblemType {
|
||||||
|
Default = 'default',
|
||||||
|
SubmitAnswer = 'submit_answer',
|
||||||
|
Interactive = 'interactive',
|
||||||
|
Communication = 'communication',
|
||||||
|
Objective = 'objective',
|
||||||
|
Remote = 'remote_judge',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestCaseConfig {
|
||||||
|
input: string;
|
||||||
|
output: string;
|
||||||
|
time?: string;
|
||||||
|
memory?: string;
|
||||||
|
score?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SubtaskType {
|
||||||
|
min = 'min',
|
||||||
|
max = 'max',
|
||||||
|
sum = 'sum',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubtaskConfig {
|
||||||
|
time?: string;
|
||||||
|
memory?: string;
|
||||||
|
score?: number;
|
||||||
|
if?: number[];
|
||||||
|
id?: number;
|
||||||
|
type?: SubtaskType;
|
||||||
|
cases?: TestCaseConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProblemConfigFile {
|
||||||
|
type?: ProblemType;
|
||||||
|
subType?: string;
|
||||||
|
target?: string;
|
||||||
|
score?: number;
|
||||||
|
time?: string;
|
||||||
|
memory?: string;
|
||||||
|
filename?: string;
|
||||||
|
checker_type?: string;
|
||||||
|
num_processes?: number;
|
||||||
|
user_extra_files?: string[];
|
||||||
|
judge_extra_files?: string[];
|
||||||
|
detail?: boolean;
|
||||||
|
answers?: Record<string, [string | string[], number]>;
|
||||||
|
redirect?: string;
|
||||||
|
cases?: TestCaseConfig[];
|
||||||
|
subtasks?: SubtaskConfig[];
|
||||||
|
langs?: string[];
|
||||||
|
checker?: CompilableSource;
|
||||||
|
interactor?: CompilableSource;
|
||||||
|
manager?: CompilableSource;
|
||||||
|
validator?: CompilableSource;
|
||||||
|
time_limit_rate?: Record<string, number>;
|
||||||
|
memory_limit_rate?: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileInfo {
|
||||||
|
/** storage path */
|
||||||
|
_id: string,
|
||||||
|
/** filename */
|
||||||
|
name: string,
|
||||||
|
/** file size (in bytes) */
|
||||||
|
size: number,
|
||||||
|
etag: string,
|
||||||
|
lastModified: Date,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JudgeMeta {
|
||||||
|
problemOwner: number;
|
||||||
|
hackRejudge?: string;
|
||||||
|
rejudge?: boolean;
|
||||||
|
// FIXME stricter types
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecordPayload {
|
||||||
|
domainId: string;
|
||||||
|
pid: number;
|
||||||
|
uid: number;
|
||||||
|
lang: string;
|
||||||
|
code: string;
|
||||||
|
score: number;
|
||||||
|
memory: number;
|
||||||
|
time: number;
|
||||||
|
judgeTexts: (string | JudgeMessage)[];
|
||||||
|
compilerTexts: string[];
|
||||||
|
testCases: Required<TestCase>[];
|
||||||
|
rejudged: boolean;
|
||||||
|
source?: string;
|
||||||
|
/** judge uid */
|
||||||
|
judger: number;
|
||||||
|
judgeAt: Date;
|
||||||
|
status: number;
|
||||||
|
progress?: number;
|
||||||
|
/** pretest */
|
||||||
|
input?: string;
|
||||||
|
/** hack target rid */
|
||||||
|
hackTarget?: string;
|
||||||
|
/** 0 if pretest&script */
|
||||||
|
contest?: string;
|
||||||
|
|
||||||
|
files?: Record<string, string>
|
||||||
|
subtasks?: Record<number, SubtaskResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JudgeRequest extends Omit<RecordPayload, 'testCases'> {
|
||||||
|
priority: number;
|
||||||
|
type: 'judge' | 'generate';
|
||||||
|
rid: string;
|
||||||
|
config: ProblemConfigFile;
|
||||||
|
meta: JudgeMeta;
|
||||||
|
data: FileInfo[];
|
||||||
|
source: string;
|
||||||
|
trusted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestCase {
|
||||||
|
id?: number;
|
||||||
|
subtaskId?: number;
|
||||||
|
score?: number;
|
||||||
|
time: number;
|
||||||
|
memory: number;
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JudgeMessage {
|
||||||
|
message: string;
|
||||||
|
params?: string[];
|
||||||
|
stack?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubtaskResult {
|
||||||
|
type: SubtaskType;
|
||||||
|
score: number;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JudgeResultBody {
|
||||||
|
key: string;
|
||||||
|
domainId: string;
|
||||||
|
rid: string;
|
||||||
|
judger?: number;
|
||||||
|
progress?: number;
|
||||||
|
addProgress?: number;
|
||||||
|
case?: TestCase;
|
||||||
|
cases?: TestCase[];
|
||||||
|
status?: number;
|
||||||
|
score?: number;
|
||||||
|
/** in miliseconds */
|
||||||
|
time?: number;
|
||||||
|
/** in kilobytes */
|
||||||
|
memory?: number;
|
||||||
|
message?: string | JudgeMessage;
|
||||||
|
compilerText?: string;
|
||||||
|
nop?: boolean;
|
||||||
|
subtasks?: Record<number, SubtaskResult>;
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
changeErrorType, fs, normalizeSubtasks, readSubtasksFromFiles, yaml,
|
convertIniConfig, LangConfig,
|
||||||
|
normalizeSubtasks, ProblemConfigFile, readSubtasksFromFiles, readYamlCases,
|
||||||
|
} from '@hydrooj/common';
|
||||||
|
import {
|
||||||
|
changeErrorType, fs, yaml,
|
||||||
} from '@hydrooj/utils';
|
} from '@hydrooj/utils';
|
||||||
import readYamlCases, { convertIniConfig } from '@hydrooj/utils/lib/cases';
|
|
||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
|
||||||
import { ProblemConfigFile } from 'hydrooj';
|
|
||||||
import { getConfig } from './config';
|
import { getConfig } from './config';
|
||||||
import { FormatError, SystemError } from './error';
|
import { FormatError, SystemError } from './error';
|
||||||
import { NextFunction, ParsedConfig } from './interface';
|
import { NextFunction, ParsedConfig } from './interface';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-template-curly-in-string */
|
/* eslint-disable no-template-curly-in-string */
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
import { FormatError, SystemError } from './error';
|
import { FormatError, SystemError } from './error';
|
||||||
import { CopyInFile, runQueued } from './sandbox';
|
import { CopyInFile, runQueued } from './sandbox';
|
||||||
import { parse } from './testlib';
|
import { parse } from './testlib';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
import { LangConfig, STATUS } from '@hydrooj/common';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
|
||||||
import { CompileError } from './error';
|
import { CompileError } from './error';
|
||||||
import { Execute } from './interface';
|
import { Execute } from './interface';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STATUS_TEXTS } from '@hydrooj/utils/lib/status';
|
import { STATUS_TEXTS } from '@hydrooj/common';
|
||||||
|
|
||||||
interface CompileErrorInfo {
|
interface CompileErrorInfo {
|
||||||
stdout?: string,
|
stdout?: string,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Queue from 'p-queue';
|
import Queue from 'p-queue';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import {
|
||||||
import type { JudgeResultBody } from 'hydrooj';
|
JudgeResultBody, NormalizedCase, NormalizedSubtask, STATUS,
|
||||||
|
} from '@hydrooj/common';
|
||||||
import { getConfig } from './config';
|
import { getConfig } from './config';
|
||||||
import { FormatError } from './error';
|
import { FormatError } from './error';
|
||||||
import { Context, ContextSubTask } from './judge/interface';
|
import { Context, ContextSubTask } from './judge/interface';
|
||||||
import { NormalizedCase, NormalizedSubtask } from './utils';
|
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
compile: () => Promise<void>;
|
compile: () => Promise<void>;
|
||||||
|
|||||||
@@ -36,18 +36,16 @@ const session: Session = {
|
|||||||
t._callbackAwait ||= Promise.resolve();
|
t._callbackAwait ||= Promise.resolve();
|
||||||
return (data: Partial<JudgeResultBody>) => {
|
return (data: Partial<JudgeResultBody>) => {
|
||||||
logger.debug('Next: %o', data);
|
logger.debug('Next: %o', data);
|
||||||
data.rid = new ObjectId(t.rid);
|
|
||||||
if (data.case) data.case.message ||= '';
|
if (data.case) data.case.message ||= '';
|
||||||
t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.next({ ...data, domainId: t.request.domainId }));
|
t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.next({ ...data, rid: t.rid, domainId: t.request.domainId }));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getEnd(t: Context) {
|
getEnd(t: Context) {
|
||||||
t._callbackAwait ||= Promise.resolve();
|
t._callbackAwait ||= Promise.resolve();
|
||||||
return (data: Partial<JudgeResultBody>) => {
|
return (data: Partial<JudgeResultBody>) => {
|
||||||
data.key = 'end';
|
data.key = 'end';
|
||||||
data.rid = new ObjectId(t.rid);
|
|
||||||
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);
|
||||||
t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.end({ ...data, domainId: t.request.domainId }));
|
t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.end({ ...data, rid: t.rid, domainId: t.request.domainId }));
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getLang(lang: string, doThrow = true) {
|
getLang(lang: string, doThrow = true) {
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import AdmZip from 'adm-zip';
|
|||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
|
import { JudgeResultBody, parseLang, STATUS } from '@hydrooj/common';
|
||||||
import { fs, noop } from '@hydrooj/utils';
|
import { fs, noop } from '@hydrooj/utils';
|
||||||
import { parseLang } from '@hydrooj/utils/lib/lang';
|
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
|
||||||
import type { JudgeResultBody } from 'hydrooj';
|
|
||||||
import { getConfig } from '../config';
|
import { getConfig } from '../config';
|
||||||
import { FormatError, SystemError } from '../error';
|
import { FormatError, SystemError } from '../error';
|
||||||
import { Session } from '../interface';
|
import { Session } from '../interface';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
import { LangConfig } from '@hydrooj/common';
|
||||||
import { runQueued } from './sandbox';
|
import { runQueued } from './sandbox';
|
||||||
|
|
||||||
export async function compilerVersions(langs: Record<string, LangConfig>) {
|
export async function compilerVersions(langs: Record<string, LangConfig>) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { LangConfig } from '@hydrooj/utils/lib/lang';
|
import {
|
||||||
import { NormalizedSubtask } from '@hydrooj/utils/lib/utils';
|
JudgeResultBody, type LangConfig, NormalizedSubtask, ProblemConfigFile,
|
||||||
import type { JudgeRequest as OrigJudgeRequest, ObjectId } from 'hydrooj';
|
} from '@hydrooj/common';
|
||||||
import { JudgeResultBody, ProblemConfigFile } from 'hydrooj';
|
|
||||||
import { CopyInFile } from './sandbox';
|
import { CopyInFile } from './sandbox';
|
||||||
import type { JudgeTask } from './task';
|
import type { JudgeTask } from './task';
|
||||||
|
|
||||||
@@ -20,10 +19,7 @@ export interface ParsedConfig extends Omit<ProblemConfigFile, 'time' | 'memory'
|
|||||||
subtasks: NormalizedSubtask[];
|
subtasks: NormalizedSubtask[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace ObjectId to string
|
export { JudgeRequest } from '@hydrooj/common';
|
||||||
export type JudgeRequest = {
|
|
||||||
[K in keyof OrigJudgeRequest]: OrigJudgeRequest[K] extends ObjectId ? string : OrigJudgeRequest[K];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
getLang: (name: string) => LangConfig;
|
getLang: (name: string) => LangConfig;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
import { Parameter, runPiped } from '../sandbox';
|
import { Parameter, runPiped } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { NormalizedCase } from '../utils';
|
|
||||||
import { Context, ContextSubTask } from './interface';
|
import { Context, ContextSubTask } from './interface';
|
||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
function judgeCase(c: NormalizedCase) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||||
import checkers from '../checkers';
|
import checkers from '../checkers';
|
||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
import { del, runQueued } from '../sandbox';
|
import { del, runQueued } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { NormalizedCase } from '../utils';
|
|
||||||
import { Context, ContextSubTask } from './interface';
|
import { Context, ContextSubTask } from './interface';
|
||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
function judgeCase(c: NormalizedCase) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
import { SystemError } from '../error';
|
import { SystemError } from '../error';
|
||||||
import { CopyInFile, runQueued } from '../sandbox';
|
import { CopyInFile, runQueued } from '../sandbox';
|
||||||
import client from '../sandbox/client';
|
import client from '../sandbox/client';
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable no-sequences */
|
/* eslint-disable no-sequences */
|
||||||
import { fs } from '@hydrooj/utils';
|
import { STATUS } from '@hydrooj/common';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { fs, parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
|
||||||
import checkers from '../checkers';
|
import checkers from '../checkers';
|
||||||
import { del, runQueued } from '../sandbox';
|
import { del, runQueued } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parseMemoryMB, parseTimeMS } from '../utils';
|
|
||||||
import { Context } from './interface';
|
import { Context } from './interface';
|
||||||
|
|
||||||
export async function judge(ctx: Context) {
|
export async function judge(ctx: Context) {
|
||||||
@@ -13,7 +12,7 @@ export async function judge(ctx: Context) {
|
|||||||
ctx.compile(ctx.lang, ctx.code),
|
ctx.compile(ctx.lang, ctx.code),
|
||||||
ctx.compileLocalFile('checker', ctx.config.checker, ctx.config.checker_type),
|
ctx.compileLocalFile('checker', ctx.config.checker, ctx.config.checker_type),
|
||||||
ctx.compileLocalFile('validator', ctx.config.validator),
|
ctx.compileLocalFile('validator', ctx.config.validator),
|
||||||
ctx.session.fetchFile(ctx.files.hack),
|
ctx.session.fetchFile(null, { [ctx.files.hack]: '' }),
|
||||||
]);
|
]);
|
||||||
ctx.clean.push(() => fs.unlink(input));
|
ctx.clean.push(() => fs.unlink(input));
|
||||||
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
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 { NormalizedCase } from '../utils';
|
|
||||||
import { Context, ContextSubTask } from './interface';
|
import { Context, ContextSubTask } from './interface';
|
||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
function judgeCase(c: NormalizedCase) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import type { NormalizedSubtask } from '@hydrooj/utils/lib/common';
|
import { NormalizedSubtask } from '@hydrooj/common';
|
||||||
import { Execute } from '../interface';
|
import { Execute } from '../interface';
|
||||||
import { JudgeTask } from '../task';
|
import { JudgeTask } from '../task';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import { STATUS } from '@hydrooj/common';
|
||||||
import { fs, yaml } from '@hydrooj/utils';
|
import { fs, yaml } from '@hydrooj/utils';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
|
||||||
import { FormatError } from '../error';
|
import { FormatError } from '../error';
|
||||||
import { Context } from './interface';
|
import { Context } from './interface';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
|
import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
|
||||||
import { runQueued } from '../sandbox';
|
import { runQueued } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { JudgeTask } from '../task';
|
import { JudgeTask } from '../task';
|
||||||
import { parseMemoryMB, parseTimeMS } from '../utils';
|
|
||||||
|
|
||||||
export const judge = async (ctx: JudgeTask) => {
|
export const judge = async (ctx: JudgeTask) => {
|
||||||
ctx.stat.judge = new Date();
|
ctx.stat.judge = new Date();
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||||
import { fs } from '@hydrooj/utils';
|
import { fs } from '@hydrooj/utils';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
|
||||||
import checkers from '../checkers';
|
import checkers from '../checkers';
|
||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
import { del, runQueued } from '../sandbox';
|
import { del, runQueued } from '../sandbox';
|
||||||
import { NormalizedCase } from '../utils';
|
|
||||||
import { Context } from './interface';
|
import { Context } from './interface';
|
||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
function judgeCase(c: NormalizedCase) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import cac from 'cac';
|
|||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { gte } from 'semver';
|
import { gte } from 'semver';
|
||||||
import { ParseEntry } from 'shell-quote';
|
import { ParseEntry } from 'shell-quote';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
import * as sysinfo from '@hydrooj/utils/lib/sysinfo';
|
import * as sysinfo from '@hydrooj/utils/lib/sysinfo';
|
||||||
import { getConfig } from './config';
|
import { getConfig } from './config';
|
||||||
import { FormatError, SystemError } from './error';
|
import { FormatError, SystemError } from './error';
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { basename, join } from 'path';
|
import { basename, join } from 'path';
|
||||||
|
import {
|
||||||
|
CompilableSource, FileInfo, JudgeMeta, JudgeResultBody, STATUS, TestCase,
|
||||||
|
} from '@hydrooj/common';
|
||||||
import { findFileSync, fs } from '@hydrooj/utils';
|
import { findFileSync, fs } from '@hydrooj/utils';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
|
||||||
import type {
|
|
||||||
CompilableSource, FileInfo, JudgeMeta, JudgeResultBody, TestCase,
|
|
||||||
} from 'hydrooj';
|
|
||||||
import readCases from './cases';
|
import readCases from './cases';
|
||||||
import checkers from './checkers';
|
import checkers from './checkers';
|
||||||
import compile from './compile';
|
import compile from './compile';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
|
|
||||||
const operation = /^\s*(status|score)\((\d+)\)\s*(.*)$/m;
|
const operation = /^\s*(status|score)\((\d+)\)\s*(.*)$/m;
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import { omit } from 'lodash';
|
|||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import sanitize from 'sanitize-filename';
|
import sanitize from 'sanitize-filename';
|
||||||
|
import { JudgeResultBody, ProblemConfigFile, TestCase } from '@hydrooj/common';
|
||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
import {
|
import {
|
||||||
FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError,
|
FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import {
|
import { RecordDoc, Task } from '../interface';
|
||||||
JudgeResultBody, ProblemConfigFile, RecordDoc, Task, TestCase,
|
|
||||||
} from '../interface';
|
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import * as builtin from '../model/builtin';
|
import * as builtin from '../model/builtin';
|
||||||
import { PERM, STATUS } from '../model/builtin';
|
import { PERM, STATUS } from '../model/builtin';
|
||||||
@@ -73,11 +72,10 @@ function processPayload(body: Partial<JudgeResultBody>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function next(body: Partial<JudgeResultBody>) {
|
export async function next(body: Partial<JudgeResultBody>) {
|
||||||
body.rid = new ObjectId(body.rid); // TODO remove this
|
|
||||||
const {
|
const {
|
||||||
$set, $push, $unset, $inc,
|
$set, $push, $unset, $inc,
|
||||||
} = processPayload(body);
|
} = processPayload(body);
|
||||||
const rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset, $inc);
|
const rdoc = await record.update(body.domainId, new ObjectId(body.rid), $set, $push, $unset, $inc);
|
||||||
if (rdoc) bus.broadcast('record/change', rdoc, $set, $push, body);
|
if (rdoc) bus.broadcast('record/change', rdoc, $set, $push, body);
|
||||||
return rdoc;
|
return rdoc;
|
||||||
}
|
}
|
||||||
@@ -139,12 +137,11 @@ bus.on('record/judge', async (rdoc, updated, pdoc) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function end(body: Partial<JudgeResultBody>) {
|
export async function end(body: Partial<JudgeResultBody>) {
|
||||||
body.rid = new ObjectId(body.rid); // TODO remove this
|
|
||||||
const { $set, $push } = processPayload(body);
|
const { $set, $push } = processPayload(body);
|
||||||
const $unset: any = { progress: '' };
|
const $unset: any = { progress: '' };
|
||||||
$set.judgeAt = new Date();
|
$set.judgeAt = new Date();
|
||||||
$set.judger = body.judger ?? 1;
|
$set.judger = body.judger ?? 1;
|
||||||
const rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset);
|
const rdoc = await record.update(body.domainId, new ObjectId(body.rid), $set, $push, $unset);
|
||||||
if (rdoc) bus.broadcast('record/change', rdoc, null, null, body); // trigger a full update
|
if (rdoc) bus.broadcast('record/change', rdoc, null, null, body); // trigger a full update
|
||||||
await postJudge(rdoc);
|
await postJudge(rdoc);
|
||||||
return rdoc;
|
return rdoc;
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class SystemScriptHandler extends SystemHandler {
|
|||||||
const time = new Date().getTime() - start;
|
const time = new Date().getTime() - start;
|
||||||
judge.end({
|
judge.end({
|
||||||
domainId,
|
domainId,
|
||||||
rid,
|
rid: rid.toHexString(),
|
||||||
status: STATUS.STATUS_ACCEPTED,
|
status: STATUS.STATUS_ACCEPTED,
|
||||||
message: inspect(ret, false, 10, true),
|
message: inspect(ret, false, 10, true),
|
||||||
judger: 1,
|
judger: 1,
|
||||||
@@ -128,7 +128,7 @@ class SystemScriptHandler extends SystemHandler {
|
|||||||
logger.error(err);
|
logger.error(err);
|
||||||
judge.end({
|
judge.end({
|
||||||
domainId,
|
domainId,
|
||||||
rid,
|
rid: rid.toHexString(),
|
||||||
status: STATUS.STATUS_SYSTEM_ERROR,
|
status: STATUS.STATUS_SYSTEM_ERROR,
|
||||||
message: `${err.message} \n${(err as any).params || []} \n${err.stack} `,
|
message: `${err.message} \n${(err as any).params || []} \n${err.stack} `,
|
||||||
judger: 1,
|
judger: 1,
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import { CredentialDeviceType } from '@simplewebauthn/types';
|
|||||||
import type fs from 'fs';
|
import type fs from 'fs';
|
||||||
import type { Dictionary, NumericDictionary } from 'lodash';
|
import type { Dictionary, NumericDictionary } from 'lodash';
|
||||||
import type { Binary, FindCursor, ObjectId } from 'mongodb';
|
import type { Binary, FindCursor, ObjectId } from 'mongodb';
|
||||||
|
import type {
|
||||||
|
FileInfo, RecordPayload,
|
||||||
|
} from '@hydrooj/common/types';
|
||||||
import type { Context } from './context';
|
import type { Context } from './context';
|
||||||
import type { DocStatusType } from './model/document';
|
import type { DocStatusType } from './model/document';
|
||||||
import type { ProblemDoc } from './model/problem';
|
import type { ProblemDoc } from './model/problem';
|
||||||
import type { Handler } from './service/server';
|
import type { Handler } from './service/server';
|
||||||
|
|
||||||
|
export * from '@hydrooj/common/types';
|
||||||
|
|
||||||
type document = typeof import('./model/document');
|
type document = typeof import('./model/document');
|
||||||
|
|
||||||
export interface System {
|
export interface System {
|
||||||
@@ -142,76 +147,6 @@ export interface BaseUser {
|
|||||||
}
|
}
|
||||||
export type BaseUserDict = Record<number, BaseUser>;
|
export type BaseUserDict = Record<number, BaseUser>;
|
||||||
|
|
||||||
export interface FileInfo {
|
|
||||||
/** storage path */
|
|
||||||
_id: string,
|
|
||||||
/** filename */
|
|
||||||
name: string,
|
|
||||||
/** file size (in bytes) */
|
|
||||||
size: number,
|
|
||||||
etag: string,
|
|
||||||
lastModified: Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TestCaseConfig {
|
|
||||||
input: string;
|
|
||||||
output: string;
|
|
||||||
time?: string;
|
|
||||||
memory?: string;
|
|
||||||
score?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProblemType {
|
|
||||||
Default = 'default',
|
|
||||||
SubmitAnswer = 'submit_answer',
|
|
||||||
Interactive = 'interactive',
|
|
||||||
Communication = 'communication',
|
|
||||||
Objective = 'objective',
|
|
||||||
Remote = 'remote_judge',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SubtaskType {
|
|
||||||
min = 'min',
|
|
||||||
max = 'max',
|
|
||||||
sum = 'sum',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SubtaskConfig {
|
|
||||||
time?: string;
|
|
||||||
memory?: string;
|
|
||||||
score?: number;
|
|
||||||
if?: number[];
|
|
||||||
id?: number;
|
|
||||||
type?: SubtaskType;
|
|
||||||
cases?: TestCaseConfig[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProblemConfigFile {
|
|
||||||
type?: ProblemType;
|
|
||||||
subType?: string;
|
|
||||||
target?: string;
|
|
||||||
score?: number;
|
|
||||||
time?: string;
|
|
||||||
memory?: string;
|
|
||||||
filename?: string;
|
|
||||||
checker_type?: string;
|
|
||||||
checker?: string;
|
|
||||||
interactor?: string;
|
|
||||||
manager?: string;
|
|
||||||
num_processes?: number;
|
|
||||||
user_extra_files?: string[];
|
|
||||||
judge_extra_files?: string[];
|
|
||||||
detail?: boolean;
|
|
||||||
answers?: Record<string, [string | string[], number]>;
|
|
||||||
redirect?: string;
|
|
||||||
cases?: TestCaseConfig[];
|
|
||||||
subtasks?: SubtaskConfig[];
|
|
||||||
langs?: string[];
|
|
||||||
validator?: string;
|
|
||||||
time_limit_rate?: Record<string, number>;
|
|
||||||
memory_limit_rate?: Record<string, number>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProblemConfig {
|
export interface ProblemConfig {
|
||||||
redirect?: [string, string];
|
redirect?: [string, string];
|
||||||
count: number;
|
count: number;
|
||||||
@@ -284,46 +219,11 @@ export interface ProblemStatusDoc extends StatusDocBase {
|
|||||||
star?: boolean;
|
star?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TestCase {
|
export type RecordDoc = {
|
||||||
id?: number;
|
[K in keyof RecordPayload]: K extends 'hackTarget' | 'contest' ? ObjectId : RecordPayload[K];
|
||||||
subtaskId?: number;
|
} & {
|
||||||
score?: number;
|
|
||||||
time: number;
|
|
||||||
memory: number;
|
|
||||||
status: number;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecordDoc {
|
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
domainId: string;
|
};
|
||||||
pid: number;
|
|
||||||
uid: number;
|
|
||||||
lang: string;
|
|
||||||
code: string;
|
|
||||||
score: number;
|
|
||||||
memory: number;
|
|
||||||
time: number;
|
|
||||||
judgeTexts: (string | JudgeMessage)[];
|
|
||||||
compilerTexts: string[];
|
|
||||||
testCases: Required<TestCase>[];
|
|
||||||
rejudged: boolean;
|
|
||||||
source?: string;
|
|
||||||
/** judge uid */
|
|
||||||
judger: number;
|
|
||||||
judgeAt: Date;
|
|
||||||
status: number;
|
|
||||||
progress?: number;
|
|
||||||
/** pretest */
|
|
||||||
input?: string;
|
|
||||||
/** hack target rid */
|
|
||||||
hackTarget?: ObjectId;
|
|
||||||
/** 0 if pretest&script */
|
|
||||||
contest?: ObjectId;
|
|
||||||
|
|
||||||
files?: Record<string, string>
|
|
||||||
subtasks?: Record<number, SubtaskResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecordStatDoc {
|
export interface RecordStatDoc {
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
@@ -335,24 +235,6 @@ export interface RecordStatDoc {
|
|||||||
length: number;
|
length: number;
|
||||||
lang: string;
|
lang: string;
|
||||||
}
|
}
|
||||||
export interface JudgeMeta {
|
|
||||||
problemOwner: number;
|
|
||||||
hackRejudge?: string;
|
|
||||||
rejudge?: boolean;
|
|
||||||
// FIXME stricter types
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JudgeRequest extends Omit<RecordDoc, '_id' | 'testCases'> {
|
|
||||||
priority: number;
|
|
||||||
type: 'judge' | 'generate';
|
|
||||||
rid: ObjectId;
|
|
||||||
config: ProblemConfigFile;
|
|
||||||
meta: JudgeMeta;
|
|
||||||
data: FileInfo[];
|
|
||||||
source: string;
|
|
||||||
trusted: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScoreboardNode {
|
export interface ScoreboardNode {
|
||||||
type: 'string' | 'rank' | 'user' | 'email' | 'record' | 'records' | 'problem' | 'solved' | 'time' | 'total_score';
|
type: 'string' | 'rank' | 'user' | 'email' | 'record' | 'records' | 'problem' | 'solved' | 'time' | 'total_score';
|
||||||
@@ -569,39 +451,6 @@ export interface Script {
|
|||||||
validate: any,
|
validate: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JudgeMessage {
|
|
||||||
message: string;
|
|
||||||
params?: string[];
|
|
||||||
stack?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SubtaskResult {
|
|
||||||
type: SubtaskType;
|
|
||||||
score: number;
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JudgeResultBody {
|
|
||||||
key: string;
|
|
||||||
domainId: string;
|
|
||||||
rid: ObjectId;
|
|
||||||
judger?: number;
|
|
||||||
progress?: number;
|
|
||||||
addProgress?: number;
|
|
||||||
case?: TestCase;
|
|
||||||
cases?: TestCase[];
|
|
||||||
status?: number;
|
|
||||||
score?: number;
|
|
||||||
/** in miliseconds */
|
|
||||||
time?: number;
|
|
||||||
/** in kilobytes */
|
|
||||||
memory?: number;
|
|
||||||
message?: string | JudgeMessage;
|
|
||||||
compilerText?: string;
|
|
||||||
nop?: boolean;
|
|
||||||
subtasks?: Record<number, SubtaskResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -683,6 +532,13 @@ export interface ContestBalloonDoc {
|
|||||||
sentAt?: Date;
|
sentAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LockDoc {
|
||||||
|
_id: ObjectId;
|
||||||
|
key: string;
|
||||||
|
lockAt: Date;
|
||||||
|
daemonId: string;
|
||||||
|
}
|
||||||
|
|
||||||
declare module './service/db' {
|
declare module './service/db' {
|
||||||
interface Collections {
|
interface Collections {
|
||||||
'blacklist': BlacklistDoc;
|
'blacklist': BlacklistDoc;
|
||||||
@@ -712,6 +568,7 @@ declare module './service/db' {
|
|||||||
'opcount': OpCountDoc;
|
'opcount': OpCountDoc;
|
||||||
'schedule': Schedule;
|
'schedule': Schedule;
|
||||||
'contest.balloon': ContestBalloonDoc;
|
'contest.balloon': ContestBalloonDoc;
|
||||||
|
'lock': LockDoc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { load } from 'js-yaml';
|
import { load } from 'js-yaml';
|
||||||
import readYamlCases from '@hydrooj/utils/lib/cases';
|
import {
|
||||||
import type { ProblemConfig, ProblemConfigFile } from '../interface';
|
normalizeSubtasks, ProblemConfigFile, readYamlCases,
|
||||||
import { normalizeSubtasks, parseMemoryMB, parseTimeMS } from '../utils';
|
} from '@hydrooj/common';
|
||||||
|
import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
|
||||||
|
import type { ProblemConfig } from '../interface';
|
||||||
|
|
||||||
export async function parseConfig(config: string | ProblemConfigFile = {}) {
|
export async function parseConfig(config: string | ProblemConfigFile = {}) {
|
||||||
const cfg: ProblemConfigFile = typeof config === 'string'
|
const cfg: ProblemConfigFile = typeof config === 'string'
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import {
|
|||||||
getScoreColor, STATUS, STATUS_CODES, STATUS_SHORT_TEXTS,
|
getScoreColor, STATUS, STATUS_CODES, STATUS_SHORT_TEXTS,
|
||||||
STATUS_TEXTS, USER_GENDER_FEMALE, USER_GENDER_ICONS, USER_GENDER_MALE,
|
STATUS_TEXTS, USER_GENDER_FEMALE, USER_GENDER_ICONS, USER_GENDER_MALE,
|
||||||
USER_GENDER_OTHER, USER_GENDER_RANGE, USER_GENDERS,
|
USER_GENDER_OTHER, USER_GENDER_RANGE, USER_GENDERS,
|
||||||
} from '@hydrooj/utils/lib/status';
|
} from '@hydrooj/common';
|
||||||
|
|
||||||
export * from '@hydrooj/utils/lib/status';
|
export * from '@hydrooj/common/status';
|
||||||
|
|
||||||
export const PERM = {
|
export const PERM = {
|
||||||
PERM_NONE: 0n,
|
PERM_NONE: 0n,
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import yaml from 'js-yaml';
|
|||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { Filter, ObjectId } from 'mongodb';
|
import { Filter, ObjectId } from 'mongodb';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
|
import { ProblemConfigFile } from '@hydrooj/common';
|
||||||
import { Logger, size, streamToBuffer } from '@hydrooj/utils/lib/utils';
|
import { Logger, size, streamToBuffer } from '@hydrooj/utils/lib/utils';
|
||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error';
|
import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error';
|
||||||
import type {
|
import type {
|
||||||
Document, ProblemConfigFile, ProblemDict, ProblemStatusDoc, User,
|
Document, ProblemDict, ProblemStatusDoc, User,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
import { parseConfig } from '../lib/testdataConfig';
|
import { parseConfig } from '../lib/testdataConfig';
|
||||||
import * as bus from '../service/bus';
|
import * as bus from '../service/bus';
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import {
|
|||||||
Filter, FindOptions, MatchKeysAndValues,
|
Filter, FindOptions, MatchKeysAndValues,
|
||||||
ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter,
|
ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter,
|
||||||
} from 'mongodb';
|
} from 'mongodb';
|
||||||
|
import { ProblemConfigFile } from '@hydrooj/common';
|
||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
import { ProblemNotFoundError } from '../error';
|
import { ProblemNotFoundError } from '../error';
|
||||||
import {
|
import { JudgeMeta, RecordDoc } from '../interface';
|
||||||
JudgeMeta, ProblemConfigFile, RecordDoc,
|
|
||||||
} from '../interface';
|
|
||||||
import db from '../service/db';
|
import db from '../service/db';
|
||||||
import { MaybeArray, NumberKeys } from '../typeutils';
|
import { MaybeArray, NumberKeys } from '../typeutils';
|
||||||
import { ArgMethod, buildProjection, Time } from '../utils';
|
import { ArgMethod, buildProjection, Time } from '../utils';
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import * as cordis from 'cordis';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { Dictionary } from 'lodash';
|
import { Dictionary } from 'lodash';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
import { LangConfig, parseLang } from '@hydrooj/utils/lib/lang';
|
import { LangConfig, parseLang } from '@hydrooj/common';
|
||||||
import { retry } from '@hydrooj/utils/lib/utils';
|
import { retry } from '@hydrooj/utils';
|
||||||
import { Context, Service } from '../context';
|
import { Context, Service } from '../context';
|
||||||
import { Setting as _Setting } from '../interface';
|
import { Setting as _Setting } from '../interface';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as status from '@hydrooj/utils/lib/status';
|
import * as status from '@hydrooj/common/status';
|
||||||
import { findFileSync } from '@hydrooj/utils/lib/utils';
|
import { findFileSync } from '@hydrooj/utils/lib/utils';
|
||||||
import {
|
import {
|
||||||
avatar, Context, fs, PERM, PRIV, STATUS, yaml,
|
avatar, Context, fs, PERM, PRIV, STATUS, yaml,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Button, Classes, Icon, Popover, TreeNode,
|
Button, Classes, Icon, Popover, TreeNode,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common';
|
import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/common';
|
||||||
import { TestCaseConfig } from 'hydrooj';
|
import { TestCaseConfig } from 'hydrooj';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Button, Classes, Dialog, DialogBody, DialogFooter, Icon,
|
Button, Classes, Dialog, DialogBody, DialogFooter, Icon,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { readSubtasksFromFiles } from '@hydrooj/utils/lib/common';
|
import { readSubtasksFromFiles } from '@hydrooj/common';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { useSelector, useStore } from 'react-redux';
|
import { useSelector, useStore } from 'react-redux';
|
||||||
import { i18n } from 'vj/utils';
|
import { i18n } from 'vj/utils';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STATUS_TEXTS } from '@hydrooj/utils/lib/status';
|
import { STATUS_TEXTS } from '@hydrooj/common';
|
||||||
|
|
||||||
export default function reducer(state = {
|
export default function reducer(state = {
|
||||||
input: '',
|
input: '',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common';
|
import { normalizeSubtasks, readSubtasksFromFiles, SubtaskType } from '@hydrooj/common';
|
||||||
import type { SubtaskType } from 'hydrooj/src/interface';
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { map } from 'lodash';
|
import { map } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getScoreColor } from '@hydrooj/utils/lib/status';
|
import { getScoreColor } from '@hydrooj/common';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { NamedPage } from 'vj/misc/Page';
|
import { NamedPage } from 'vj/misc/Page';
|
||||||
import { getAvailableLangs, tpl } from 'vj/utils';
|
import { getAvailableLangs, tpl } from 'vj/utils';
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { findFileSync, parseMemoryMB, parseTimeMS } from './utils';
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
export default async function readYamlCases(cfg: Record<string, any> = {}, checkFile = (s: string, errMsg: string) => s) {
|
|
||||||
const config: any = {
|
|
||||||
checker_type: cfg.checker_type || 'default',
|
|
||||||
judge_extra_files: [],
|
|
||||||
user_extra_files: [],
|
|
||||||
};
|
|
||||||
if (cfg.type === 'objective') {
|
|
||||||
if (cfg.checker || cfg.interactor || cfg.validator) {
|
|
||||||
throw new Error('You cannot use checker, interactor or validator for objective questions');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (cfg.checker) {
|
|
||||||
if (!cfg.checker.includes('.')) {
|
|
||||||
config.checker = findFileSync(`@hydrooj/hydrojudge/vendor/testlib/checkers/${cfg.checker}.cpp`, false);
|
|
||||||
}
|
|
||||||
config.checker ||= checkFile(cfg.checker, 'Cannot find checker {0}.');
|
|
||||||
}
|
|
||||||
if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.');
|
|
||||||
if (cfg.manager) config.manager = checkFile(cfg.manager, 'Cannot find Manager {0}.');
|
|
||||||
if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.');
|
|
||||||
for (const n of ['judge', 'user']) {
|
|
||||||
const conf = cfg[`${n}_extra_files`];
|
|
||||||
if (!conf) continue;
|
|
||||||
if (conf instanceof Array) {
|
|
||||||
config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`));
|
|
||||||
} else throw new Error(`Invalid ${n}_extra_files config.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cfg.cases?.length) {
|
|
||||||
config.subtasks = [{
|
|
||||||
cases: cfg.cases,
|
|
||||||
type: 'sum',
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
if (cfg.time) config.time = parseTimeMS(cfg.time);
|
|
||||||
if (cfg.memory) config.memory = parseMemoryMB(cfg.memory);
|
|
||||||
return Object.assign(cfg, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertIniConfig(ini: string) {
|
|
||||||
const f = ini.split('\n');
|
|
||||||
const count = parseInt(f[0], 10);
|
|
||||||
const res = { subtasks: [] };
|
|
||||||
for (let i = 1; i <= count; i++) {
|
|
||||||
if (!f[i]?.trim()) throw new Error('Testdata count incorrect.');
|
|
||||||
const [input, output, time, score, memory] = f[i].split('|');
|
|
||||||
const cur = {
|
|
||||||
cases: [{ input, output }],
|
|
||||||
score: parseInt(score, 10),
|
|
||||||
time: `${time}s`,
|
|
||||||
memory: '256m',
|
|
||||||
};
|
|
||||||
if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`;
|
|
||||||
res.subtasks.push(cur);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
@@ -236,159 +236,3 @@ export function sortFiles(files: Record<string, any>[] | string[], key = '_id')
|
|||||||
});
|
});
|
||||||
return result.map((x) => (isString ? x.name : (delete x._weights && x)));
|
return result.map((x) => (isString ? x.name : (delete x._weights && x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MatchRule {
|
|
||||||
regex: RegExp;
|
|
||||||
output: (a: RegExpExecArray) => string[];
|
|
||||||
id: (a: RegExpExecArray) => number;
|
|
||||||
subtask: (a: RegExpExecArray) => number;
|
|
||||||
preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum';
|
|
||||||
}
|
|
||||||
|
|
||||||
const SubtaskMatcher: MatchRule[] = [
|
|
||||||
{
|
|
||||||
regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/,
|
|
||||||
output: (a) => ['out', 'ans']
|
|
||||||
.flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`])
|
|
||||||
.flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')])
|
|
||||||
.concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null),
|
|
||||||
id: (a) => +a[4],
|
|
||||||
subtask: (a) => +(a[3] || 1),
|
|
||||||
preferredScorerType: (a) => (a[3] ? 'min' : 'sum'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
regex: /^([^\d]*)\.(in|IN)(\d+)$/,
|
|
||||||
output: (a) => [
|
|
||||||
`${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`,
|
|
||||||
`${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`,
|
|
||||||
].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]),
|
|
||||||
id: (a) => +a[2],
|
|
||||||
subtask: () => 1,
|
|
||||||
preferredScorerType: () => 'sum',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/,
|
|
||||||
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`),
|
|
||||||
id: (a) => +a[4],
|
|
||||||
subtask: (a) => +a[2],
|
|
||||||
preferredScorerType: () => 'min',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/,
|
|
||||||
output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`),
|
|
||||||
id: (a) => +a[2],
|
|
||||||
subtask: () => 1,
|
|
||||||
preferredScorerType: () => 'sum',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function* getScore(totalScore: number, count: number) {
|
|
||||||
const base = Math.floor(totalScore / count);
|
|
||||||
const extra = count - (totalScore % count);
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
if (i >= extra) yield base + 1;
|
|
||||||
else yield base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParsedCase {
|
|
||||||
id?: number;
|
|
||||||
time?: number | string;
|
|
||||||
memory?: number | string;
|
|
||||||
score?: number;
|
|
||||||
input?: string;
|
|
||||||
output?: string;
|
|
||||||
}
|
|
||||||
interface ParsedSubtask {
|
|
||||||
cases: ParsedCase[];
|
|
||||||
type: 'min' | 'max' | 'sum';
|
|
||||||
time?: number | string;
|
|
||||||
memory?: number | string;
|
|
||||||
score?: number;
|
|
||||||
id?: number;
|
|
||||||
if?: number[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readSubtasksFromFiles(files: string[], config) {
|
|
||||||
const subtask: Record<number, ParsedSubtask> = {};
|
|
||||||
for (const s of config.subtasks || []) if (s.id) subtask[s.id] = s;
|
|
||||||
for (const file of files) {
|
|
||||||
let match = false;
|
|
||||||
for (const rule of SubtaskMatcher) {
|
|
||||||
const data = rule.regex.exec(file);
|
|
||||||
if (!data) continue;
|
|
||||||
const sid = rule.subtask(data);
|
|
||||||
const c = { input: file, output: '', id: rule.id(data) };
|
|
||||||
const type = rule.preferredScorerType(data);
|
|
||||||
const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i);
|
|
||||||
for (const output of outputs) {
|
|
||||||
if (output === file) continue;
|
|
||||||
if (output === '/dev/null' || files.includes(output)) {
|
|
||||||
match = true;
|
|
||||||
c.output = output;
|
|
||||||
if (!subtask[sid]) {
|
|
||||||
subtask[sid] = {
|
|
||||||
time: config.time,
|
|
||||||
memory: config.memory,
|
|
||||||
type,
|
|
||||||
cases: [c],
|
|
||||||
id: sid,
|
|
||||||
};
|
|
||||||
} else if (!subtask[sid].cases) subtask[sid].cases = [c];
|
|
||||||
else subtask[sid].cases.push(c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (match) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Object.values(subtask);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NormalizedCase extends Required<ParsedCase> {
|
|
||||||
time: number;
|
|
||||||
memory: number;
|
|
||||||
}
|
|
||||||
export interface NormalizedSubtask extends Required<ParsedSubtask> {
|
|
||||||
cases: NormalizedCase[];
|
|
||||||
time: number;
|
|
||||||
memory: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeSubtasks(
|
|
||||||
subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string,
|
|
||||||
time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false,
|
|
||||||
timeRate = 1, memoryRate = 1,
|
|
||||||
): NormalizedSubtask[] {
|
|
||||||
subtasks.sort((a, b) => (a.id - b.id));
|
|
||||||
const subtaskScore = getScore(
|
|
||||||
Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0),
|
|
||||||
subtasks.filter((i) => !i.score).length,
|
|
||||||
);
|
|
||||||
return subtasks.map((s, id) => {
|
|
||||||
s.cases.sort((a, b) => (a.id - b.id));
|
|
||||||
const score = s.score || subtaskScore.next().value as number;
|
|
||||||
const caseScore = getScore(
|
|
||||||
Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0),
|
|
||||||
s.cases.filter((i) => !i.score).length,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
id: id + 1,
|
|
||||||
type: 'min',
|
|
||||||
if: [],
|
|
||||||
...s,
|
|
||||||
score,
|
|
||||||
time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate,
|
|
||||||
memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate,
|
|
||||||
cases: s.cases.map((c, index) => ({
|
|
||||||
id: index + 1,
|
|
||||||
...c,
|
|
||||||
score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score),
|
|
||||||
time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate,
|
|
||||||
memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate,
|
|
||||||
input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null',
|
|
||||||
output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null',
|
|
||||||
})) as NormalizedCase[],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
import { LangConfig, STATUS } from '@hydrooj/common';
|
||||||
import {
|
import {
|
||||||
Context, db, DomainModel, JudgeHandler, Logger, ProblemModel, RecordModel, Service, SettingModel,
|
Context, db, DomainModel, JudgeHandler, Logger,
|
||||||
sleep, STATUS, SystemModel, TaskModel, Time, yaml,
|
ProblemModel, RecordModel, Service, SettingModel,
|
||||||
|
sleep, SystemModel, TaskModel, Time, yaml,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
|
import { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
|
||||||
import providers from './providers/index';
|
import providers from './providers/index';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
import { JudgeResultBody, LangConfig } from '@hydrooj/common';
|
||||||
import { JudgeResultBody } from 'hydrooj';
|
|
||||||
|
|
||||||
export interface RemoteAccount {
|
export interface RemoteAccount {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/common';
|
||||||
|
|
||||||
export function normalize(key: string) {
|
export function normalize(key: string) {
|
||||||
return key.toUpperCase().replace(/ /g, '_');
|
return key.toUpperCase().replace(/ /g, '_');
|
||||||
|
|||||||
Reference in new issue
Block a user