mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +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 Compress from 'koa-compress';
|
||||
import { Shorty } from 'shorty.js';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { WebSocket, WebSocketServer } from 'ws';
|
||||
import {
|
||||
Counter, errorMessage, isClass, Logger, parseMemoryMB,
|
||||
} from '@hydrooj/utils/lib/utils';
|
||||
@@ -71,6 +71,7 @@ export interface HydroResponse {
|
||||
etag?: string;
|
||||
attachment: (name: string, stream?: any) => void;
|
||||
addHeader: (name: string, value: string) => void;
|
||||
serializer: typeof serializer;
|
||||
}
|
||||
export type RendererFunction = (name: string, context: Record<string, any>) => string | Promise<string>;
|
||||
type HydroContext = {
|
||||
@@ -170,7 +171,7 @@ export class HandlerCommon {
|
||||
|
||||
renderHTML(templateName: string, args: Record<string, any>) {
|
||||
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, {
|
||||
handler: this,
|
||||
UserContext: this.user,
|
||||
@@ -265,7 +266,7 @@ export class ConnectionHandler extends HandlerCommon {
|
||||
}
|
||||
|
||||
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.counter > 1000) this.resetCompression();
|
||||
payload = this.compression.deflate(payload);
|
||||
@@ -327,6 +328,7 @@ export interface WebServiceConfig {
|
||||
host?: string;
|
||||
xff?: string;
|
||||
xhost?: string;
|
||||
serializer?: typeof serializer;
|
||||
}
|
||||
|
||||
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),
|
||||
},
|
||||
]));
|
||||
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) => {
|
||||
socket.on('error', (err) => {
|
||||
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',
|
||||
};
|
||||
|
||||
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 {
|
||||
if (score === null || score === undefined || !Number.isFinite(+score)) return '#000000';
|
||||
return [
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from 'chai';
|
||||
import { describe, it } from 'node:test';
|
||||
import { readSubtasksFromFiles } from '../lib/common';
|
||||
import { readSubtasksFromFiles } from '../cases';
|
||||
|
||||
interface Info {
|
||||
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 */
|
||||
import path from 'path';
|
||||
import {
|
||||
changeErrorType, fs, normalizeSubtasks, readSubtasksFromFiles, yaml,
|
||||
convertIniConfig, LangConfig,
|
||||
normalizeSubtasks, ProblemConfigFile, readSubtasksFromFiles, readYamlCases,
|
||||
} from '@hydrooj/common';
|
||||
import {
|
||||
changeErrorType, fs, yaml,
|
||||
} 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 { FormatError, SystemError } from './error';
|
||||
import { NextFunction, ParsedConfig } from './interface';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* 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 { CopyInFile, runQueued } from './sandbox';
|
||||
import { parse } from './testlib';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { LangConfig, STATUS } from '@hydrooj/common';
|
||||
import { CompileError } from './error';
|
||||
import { Execute } from './interface';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { STATUS_TEXTS } from '@hydrooj/utils/lib/status';
|
||||
import { STATUS_TEXTS } from '@hydrooj/common';
|
||||
|
||||
interface CompileErrorInfo {
|
||||
stdout?: string,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Queue from 'p-queue';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import type { JudgeResultBody } from 'hydrooj';
|
||||
import {
|
||||
JudgeResultBody, NormalizedCase, NormalizedSubtask, STATUS,
|
||||
} from '@hydrooj/common';
|
||||
import { getConfig } from './config';
|
||||
import { FormatError } from './error';
|
||||
import { Context, ContextSubTask } from './judge/interface';
|
||||
import { NormalizedCase, NormalizedSubtask } from './utils';
|
||||
|
||||
interface Task {
|
||||
compile: () => Promise<void>;
|
||||
|
||||
@@ -36,18 +36,16 @@ const session: Session = {
|
||||
t._callbackAwait ||= Promise.resolve();
|
||||
return (data: Partial<JudgeResultBody>) => {
|
||||
logger.debug('Next: %o', data);
|
||||
data.rid = new ObjectId(t.rid);
|
||||
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) {
|
||||
t._callbackAwait ||= Promise.resolve();
|
||||
return (data: Partial<JudgeResultBody>) => {
|
||||
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);
|
||||
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) {
|
||||
|
||||
@@ -4,10 +4,8 @@ import AdmZip from 'adm-zip';
|
||||
import PQueue from 'p-queue';
|
||||
import superagent from 'superagent';
|
||||
import WebSocket from 'ws';
|
||||
import { JudgeResultBody, parseLang, STATUS } from '@hydrooj/common';
|
||||
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 { FormatError, SystemError } from '../error';
|
||||
import { Session } from '../interface';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { LangConfig } from '@hydrooj/common';
|
||||
import { runQueued } from './sandbox';
|
||||
|
||||
export async function compilerVersions(langs: Record<string, LangConfig>) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { NormalizedSubtask } from '@hydrooj/utils/lib/utils';
|
||||
import type { JudgeRequest as OrigJudgeRequest, ObjectId } from 'hydrooj';
|
||||
import { JudgeResultBody, ProblemConfigFile } from 'hydrooj';
|
||||
import {
|
||||
JudgeResultBody, type LangConfig, NormalizedSubtask, ProblemConfigFile,
|
||||
} from '@hydrooj/common';
|
||||
import { CopyInFile } from './sandbox';
|
||||
import type { JudgeTask } from './task';
|
||||
|
||||
@@ -20,10 +19,7 @@ export interface ParsedConfig extends Omit<ProblemConfigFile, 'time' | 'memory'
|
||||
subtasks: NormalizedSubtask[];
|
||||
}
|
||||
|
||||
// replace ObjectId to string
|
||||
export type JudgeRequest = {
|
||||
[K in keyof OrigJudgeRequest]: OrigJudgeRequest[K] extends ObjectId ? string : OrigJudgeRequest[K];
|
||||
};
|
||||
export { JudgeRequest } from '@hydrooj/common';
|
||||
|
||||
export interface Session {
|
||||
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 { Parameter, runPiped } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { NormalizedCase } from '../utils';
|
||||
import { Context, ContextSubTask } from './interface';
|
||||
|
||||
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 { runFlow } from '../flow';
|
||||
import { del, runQueued } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { NormalizedCase } from '../utils';
|
||||
import { Context, ContextSubTask } from './interface';
|
||||
|
||||
function judgeCase(c: NormalizedCase) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { tmpdir } from 'os';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
import { SystemError } from '../error';
|
||||
import { CopyInFile, runQueued } from '../sandbox';
|
||||
import client from '../sandbox/client';
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/* eslint-disable no-sequences */
|
||||
import { fs } from '@hydrooj/utils';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
import { fs, parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
|
||||
import checkers from '../checkers';
|
||||
import { del, runQueued } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { parseMemoryMB, parseTimeMS } from '../utils';
|
||||
import { Context } from './interface';
|
||||
|
||||
export async function judge(ctx: Context) {
|
||||
@@ -13,7 +12,7 @@ export async function judge(ctx: Context) {
|
||||
ctx.compile(ctx.lang, ctx.code),
|
||||
ctx.compileLocalFile('checker', ctx.config.checker, ctx.config.checker_type),
|
||||
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.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 { runPiped } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { parse } from '../testlib';
|
||||
import { NormalizedCase } from '../utils';
|
||||
import { Context, ContextSubTask } from './interface';
|
||||
|
||||
function judgeCase(c: NormalizedCase) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import PQueue from 'p-queue';
|
||||
import type { NormalizedSubtask } from '@hydrooj/utils/lib/common';
|
||||
import { NormalizedSubtask } from '@hydrooj/common';
|
||||
import { Execute } from '../interface';
|
||||
import { JudgeTask } from '../task';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from 'assert';
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
import { fs, yaml } from '@hydrooj/utils';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { FormatError } from '../error';
|
||||
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 signals from '../signals';
|
||||
import { JudgeTask } from '../task';
|
||||
import { parseMemoryMB, parseTimeMS } from '../utils';
|
||||
|
||||
export const judge = async (ctx: JudgeTask) => {
|
||||
ctx.stat.judge = new Date();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||
import { fs } from '@hydrooj/utils';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import checkers from '../checkers';
|
||||
import { runFlow } from '../flow';
|
||||
import { del, runQueued } from '../sandbox';
|
||||
import { NormalizedCase } from '../utils';
|
||||
import { Context } from './interface';
|
||||
|
||||
function judgeCase(c: NormalizedCase) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import cac from 'cac';
|
||||
import PQueue from 'p-queue';
|
||||
import { gte } from 'semver';
|
||||
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 { getConfig } from './config';
|
||||
import { FormatError, SystemError } from './error';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { basename, join } from 'path';
|
||||
import {
|
||||
CompilableSource, FileInfo, JudgeMeta, JudgeResultBody, STATUS, TestCase,
|
||||
} from '@hydrooj/common';
|
||||
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 checkers from './checkers';
|
||||
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;
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import { omit } from 'lodash';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import PQueue from 'p-queue';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import { JudgeResultBody, ProblemConfigFile, TestCase } from '@hydrooj/common';
|
||||
import { Context } from '../context';
|
||||
import {
|
||||
FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError,
|
||||
} from '../error';
|
||||
import {
|
||||
JudgeResultBody, ProblemConfigFile, RecordDoc, Task, TestCase,
|
||||
} from '../interface';
|
||||
import { RecordDoc, Task } from '../interface';
|
||||
import { Logger } from '../logger';
|
||||
import * as builtin 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>) {
|
||||
body.rid = new ObjectId(body.rid); // TODO remove this
|
||||
const {
|
||||
$set, $push, $unset, $inc,
|
||||
} = 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);
|
||||
return rdoc;
|
||||
}
|
||||
@@ -139,12 +137,11 @@ bus.on('record/judge', async (rdoc, updated, pdoc) => {
|
||||
});
|
||||
|
||||
export async function end(body: Partial<JudgeResultBody>) {
|
||||
body.rid = new ObjectId(body.rid); // TODO remove this
|
||||
const { $set, $push } = processPayload(body);
|
||||
const $unset: any = { progress: '' };
|
||||
$set.judgeAt = new Date();
|
||||
$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
|
||||
await postJudge(rdoc);
|
||||
return rdoc;
|
||||
|
||||
@@ -115,7 +115,7 @@ class SystemScriptHandler extends SystemHandler {
|
||||
const time = new Date().getTime() - start;
|
||||
judge.end({
|
||||
domainId,
|
||||
rid,
|
||||
rid: rid.toHexString(),
|
||||
status: STATUS.STATUS_ACCEPTED,
|
||||
message: inspect(ret, false, 10, true),
|
||||
judger: 1,
|
||||
@@ -128,7 +128,7 @@ class SystemScriptHandler extends SystemHandler {
|
||||
logger.error(err);
|
||||
judge.end({
|
||||
domainId,
|
||||
rid,
|
||||
rid: rid.toHexString(),
|
||||
status: STATUS.STATUS_SYSTEM_ERROR,
|
||||
message: `${err.message} \n${(err as any).params || []} \n${err.stack} `,
|
||||
judger: 1,
|
||||
|
||||
@@ -4,11 +4,16 @@ import { CredentialDeviceType } from '@simplewebauthn/types';
|
||||
import type fs from 'fs';
|
||||
import type { Dictionary, NumericDictionary } from 'lodash';
|
||||
import type { Binary, FindCursor, ObjectId } from 'mongodb';
|
||||
import type {
|
||||
FileInfo, RecordPayload,
|
||||
} from '@hydrooj/common/types';
|
||||
import type { Context } from './context';
|
||||
import type { DocStatusType } from './model/document';
|
||||
import type { ProblemDoc } from './model/problem';
|
||||
import type { Handler } from './service/server';
|
||||
|
||||
export * from '@hydrooj/common/types';
|
||||
|
||||
type document = typeof import('./model/document');
|
||||
|
||||
export interface System {
|
||||
@@ -142,76 +147,6 @@ export interface 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 {
|
||||
redirect?: [string, string];
|
||||
count: number;
|
||||
@@ -284,46 +219,11 @@ export interface ProblemStatusDoc extends StatusDocBase {
|
||||
star?: boolean;
|
||||
}
|
||||
|
||||
export interface TestCase {
|
||||
id?: number;
|
||||
subtaskId?: number;
|
||||
score?: number;
|
||||
time: number;
|
||||
memory: number;
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface RecordDoc {
|
||||
export type RecordDoc = {
|
||||
[K in keyof RecordPayload]: K extends 'hackTarget' | 'contest' ? ObjectId : RecordPayload[K];
|
||||
} & {
|
||||
_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 {
|
||||
_id: ObjectId;
|
||||
@@ -335,24 +235,6 @@ export interface RecordStatDoc {
|
||||
length: number;
|
||||
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 {
|
||||
type: 'string' | 'rank' | 'user' | 'email' | 'record' | 'records' | 'problem' | 'solved' | 'time' | 'total_score';
|
||||
@@ -569,39 +451,6 @@ export interface Script {
|
||||
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 {
|
||||
_id: ObjectId;
|
||||
type: string;
|
||||
@@ -683,6 +532,13 @@ export interface ContestBalloonDoc {
|
||||
sentAt?: Date;
|
||||
}
|
||||
|
||||
export interface LockDoc {
|
||||
_id: ObjectId;
|
||||
key: string;
|
||||
lockAt: Date;
|
||||
daemonId: string;
|
||||
}
|
||||
|
||||
declare module './service/db' {
|
||||
interface Collections {
|
||||
'blacklist': BlacklistDoc;
|
||||
@@ -712,6 +568,7 @@ declare module './service/db' {
|
||||
'opcount': OpCountDoc;
|
||||
'schedule': Schedule;
|
||||
'contest.balloon': ContestBalloonDoc;
|
||||
'lock': LockDoc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { load } from 'js-yaml';
|
||||
import readYamlCases from '@hydrooj/utils/lib/cases';
|
||||
import type { ProblemConfig, ProblemConfigFile } from '../interface';
|
||||
import { normalizeSubtasks, parseMemoryMB, parseTimeMS } from '../utils';
|
||||
import {
|
||||
normalizeSubtasks, ProblemConfigFile, readYamlCases,
|
||||
} from '@hydrooj/common';
|
||||
import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils';
|
||||
import type { ProblemConfig } from '../interface';
|
||||
|
||||
export async function parseConfig(config: string | ProblemConfigFile = {}) {
|
||||
const cfg: ProblemConfigFile = typeof config === 'string'
|
||||
|
||||
@@ -2,9 +2,9 @@ import {
|
||||
getScoreColor, STATUS, STATUS_CODES, STATUS_SHORT_TEXTS,
|
||||
STATUS_TEXTS, USER_GENDER_FEMALE, USER_GENDER_ICONS, USER_GENDER_MALE,
|
||||
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 = {
|
||||
PERM_NONE: 0n,
|
||||
|
||||
@@ -8,11 +8,12 @@ import yaml from 'js-yaml';
|
||||
import { pick } from 'lodash';
|
||||
import { Filter, ObjectId } from 'mongodb';
|
||||
import type { Readable } from 'stream';
|
||||
import { ProblemConfigFile } from '@hydrooj/common';
|
||||
import { Logger, size, streamToBuffer } from '@hydrooj/utils/lib/utils';
|
||||
import { Context } from '../context';
|
||||
import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error';
|
||||
import type {
|
||||
Document, ProblemConfigFile, ProblemDict, ProblemStatusDoc, User,
|
||||
Document, ProblemDict, ProblemStatusDoc, User,
|
||||
} from '../interface';
|
||||
import { parseConfig } from '../lib/testdataConfig';
|
||||
import * as bus from '../service/bus';
|
||||
|
||||
@@ -5,11 +5,10 @@ import {
|
||||
Filter, FindOptions, MatchKeysAndValues,
|
||||
ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter,
|
||||
} from 'mongodb';
|
||||
import { ProblemConfigFile } from '@hydrooj/common';
|
||||
import { Context } from '../context';
|
||||
import { ProblemNotFoundError } from '../error';
|
||||
import {
|
||||
JudgeMeta, ProblemConfigFile, RecordDoc,
|
||||
} from '../interface';
|
||||
import { JudgeMeta, RecordDoc } from '../interface';
|
||||
import db from '../service/db';
|
||||
import { MaybeArray, NumberKeys } from '../typeutils';
|
||||
import { ArgMethod, buildProjection, Time } from '../utils';
|
||||
|
||||
@@ -4,8 +4,8 @@ import * as cordis from 'cordis';
|
||||
import yaml from 'js-yaml';
|
||||
import { Dictionary } from 'lodash';
|
||||
import moment from 'moment-timezone';
|
||||
import { LangConfig, parseLang } from '@hydrooj/utils/lib/lang';
|
||||
import { retry } from '@hydrooj/utils/lib/utils';
|
||||
import { LangConfig, parseLang } from '@hydrooj/common';
|
||||
import { retry } from '@hydrooj/utils';
|
||||
import { Context, Service } from '../context';
|
||||
import { Setting as _Setting } from '../interface';
|
||||
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 {
|
||||
avatar, Context, fs, PERM, PRIV, STATUS, yaml,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Button, Classes, Icon, Popover, TreeNode,
|
||||
} from '@blueprintjs/core';
|
||||
import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common';
|
||||
import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/common';
|
||||
import { TestCaseConfig } from 'hydrooj';
|
||||
import { isEqual } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Button, Classes, Dialog, DialogBody, DialogFooter, Icon,
|
||||
} from '@blueprintjs/core';
|
||||
import { readSubtasksFromFiles } from '@hydrooj/utils/lib/common';
|
||||
import { readSubtasksFromFiles } from '@hydrooj/common';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useSelector, useStore } from 'react-redux';
|
||||
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 = {
|
||||
input: '',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common';
|
||||
import type { SubtaskType } from 'hydrooj/src/interface';
|
||||
import { normalizeSubtasks, readSubtasksFromFiles, SubtaskType } from '@hydrooj/common';
|
||||
import $ from 'jquery';
|
||||
import yaml from 'js-yaml';
|
||||
import React from 'react';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
import $ from 'jquery';
|
||||
import { map } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getScoreColor } from '@hydrooj/utils/lib/status';
|
||||
import { getScoreColor } from '@hydrooj/common';
|
||||
import $ from 'jquery';
|
||||
import { NamedPage } from 'vj/misc/Page';
|
||||
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)));
|
||||
}
|
||||
|
||||
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 */
|
||||
import os from 'os';
|
||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { LangConfig, STATUS } from '@hydrooj/common';
|
||||
import {
|
||||
Context, db, DomainModel, JudgeHandler, Logger, ProblemModel, RecordModel, Service, SettingModel,
|
||||
sleep, STATUS, SystemModel, TaskModel, Time, yaml,
|
||||
Context, db, DomainModel, JudgeHandler, Logger,
|
||||
ProblemModel, RecordModel, Service, SettingModel,
|
||||
sleep, SystemModel, TaskModel, Time, yaml,
|
||||
} from 'hydrooj';
|
||||
import { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
|
||||
import providers from './providers/index';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { JudgeResultBody } from 'hydrooj';
|
||||
import { JudgeResultBody, LangConfig } from '@hydrooj/common';
|
||||
|
||||
export interface RemoteAccount {
|
||||
_id: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { STATUS } from '@hydrooj/common';
|
||||
|
||||
export function normalize(key: string) {
|
||||
return key.toUpperCase().replace(/ /g, '_');
|
||||
|
||||
Reference in new issue
Block a user