mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
测试:judger封装为模块
This commit is contained in:
1 parent
5d7f57334f
commit
6134a16f8c
8 files changed
+305
-30
No files matched your search
+12
-2
@@ -30,7 +30,9 @@ const build = async (type) => {
|
|||||||
module: {},
|
module: {},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.ProgressPlugin(),
|
new webpack.ProgressPlugin(),
|
||||||
new FriendlyErrorsPlugin(),
|
new FriendlyErrorsPlugin({
|
||||||
|
clearConsole: false,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
for (const i of modules) {
|
for (const i of modules) {
|
||||||
@@ -90,11 +92,19 @@ const build = async (type) => {
|
|||||||
if (exist(`module/${i}/template`)) {
|
if (exist(`module/${i}/template`)) {
|
||||||
current.template = template(`module/${i}/template`);
|
current.template = template(`module/${i}/template`);
|
||||||
}
|
}
|
||||||
|
if (exist(`module/${i}/file`)) {
|
||||||
|
const files = fs.readdirSync(root(`module/${i}/file`));
|
||||||
|
current.file = {};
|
||||||
|
for (const file of files) {
|
||||||
|
current.file[file] = fs.readFileSync(root(`module/${i}/file/${file}`)).toString('base64');
|
||||||
|
}
|
||||||
|
}
|
||||||
const m = require(root(`module/${i}/hydro.json`));
|
const m = require(root(`module/${i}/hydro.json`));
|
||||||
current.description = m.description || '';
|
current.description = m.description || '';
|
||||||
current.requirements = m.requirements || [];
|
current.requirements = m.requirements || [];
|
||||||
current.version = m.version || 'unknown';
|
current.version = m.version || 'unknown';
|
||||||
const data = zlib.gzipSync(Buffer.from(yaml.safeDump(current)), { level: 3 });
|
if (m.os) current.os = m.os;
|
||||||
|
const data = zlib.gzipSync(Buffer.from(yaml.safeDump(current)), { level: -1 });
|
||||||
fs.writeFileSync(root(`.build/module/${i}.hydro`), data);
|
fs.writeFileSync(root(`.build/module/${i}.hydro`), data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -34,7 +34,9 @@ const build = async (type) => {
|
|||||||
module: {},
|
module: {},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.ProgressPlugin(),
|
new webpack.ProgressPlugin(),
|
||||||
new FriendlyErrorsPlugin(),
|
new FriendlyErrorsPlugin({
|
||||||
|
clearConsole: false,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const compiler = webpack(config);
|
const compiler = webpack(config);
|
||||||
|
|||||||
+89
-23
@@ -2,6 +2,7 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
/* eslint-disable no-eval */
|
/* eslint-disable no-eval */
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
@@ -9,6 +10,14 @@ const yaml = require('js-yaml');
|
|||||||
function root(name) {
|
function root(name) {
|
||||||
return path.resolve(process.cwd(), name);
|
return path.resolve(process.cwd(), name);
|
||||||
}
|
}
|
||||||
|
function ensureDir(dir) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
} else if (!fs.statSync(dir).isDirectory()) {
|
||||||
|
fs.unlinkSync(dir);
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const active = [];
|
const active = [];
|
||||||
|
|
||||||
@@ -16,8 +25,25 @@ async function preload() {
|
|||||||
const files = fs.readdirSync(root('.build/module'));
|
const files = fs.readdirSync(root('.build/module'));
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.endsWith('.hydro')) {
|
if (file.endsWith('.hydro')) {
|
||||||
const f = fs.readFileSync(root(`.build/module/${file}`));
|
try {
|
||||||
active.push({ ...yaml.safeLoad(zlib.gunzipSync(f)), id: file.split('.')[0] });
|
const f = fs.readFileSync(root(`.build/module/${file}`));
|
||||||
|
const m = { ...yaml.safeLoad(zlib.gunzipSync(f)), id: file.split('.')[0] };
|
||||||
|
if (m.os) {
|
||||||
|
if (!m.os.includes(os.platform())) throw new Error('Unsupported OS');
|
||||||
|
}
|
||||||
|
if (m.file) {
|
||||||
|
m.files = {};
|
||||||
|
ensureDir(path.resolve(os.tmpdir(), 'hydro', file));
|
||||||
|
for (const n in m.file) {
|
||||||
|
const e = path.resolve(os.tmpdir(), 'hydro', file, n);
|
||||||
|
fs.writeFileSync(e, Buffer.from(m.file[n], 'base64'), { mode: 755 });
|
||||||
|
m.files[n] = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
active.push(m);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Module Load Fail: ${file}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,11 +51,16 @@ async function preload() {
|
|||||||
async function handler() {
|
async function handler() {
|
||||||
for (const i of active) {
|
for (const i of active) {
|
||||||
if (i.handler) {
|
if (i.handler) {
|
||||||
console.time(`Handler init: ${i.id}`);
|
try {
|
||||||
const exports = {};
|
console.time(`Handler init: ${i.id}`);
|
||||||
const module = { exports }; // eslint-disable-line no-unused-vars
|
const exports = {};
|
||||||
eval(i.handler);
|
// eslint-disable-next-line no-unused-vars
|
||||||
console.timeEnd(`Handler init: ${i.id}`);
|
const module = { exports, file: i.files || {} };
|
||||||
|
eval(i.handler);
|
||||||
|
console.timeEnd(`Handler init: ${i.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Handler Load Fail: ${i.id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,8 +68,12 @@ async function handler() {
|
|||||||
async function locale() {
|
async function locale() {
|
||||||
for (const i of active) {
|
for (const i of active) {
|
||||||
if (i.locale) {
|
if (i.locale) {
|
||||||
global.Hydro.lib.i18n(i.locale);
|
try {
|
||||||
console.log(`Locale init: ${i.id}`);
|
global.Hydro.lib.i18n(i.locale);
|
||||||
|
console.log(`Locale init: ${i.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Locale Load Fail: ${i.id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,8 +81,12 @@ async function locale() {
|
|||||||
async function template() {
|
async function template() {
|
||||||
for (const i of active) {
|
for (const i of active) {
|
||||||
if (i.template) {
|
if (i.template) {
|
||||||
Object.assign(global.Hydro.template, i.template);
|
try {
|
||||||
console.log(`Template init: ${i.id}`);
|
Object.assign(global.Hydro.template, i.template);
|
||||||
|
console.log(`Template init: ${i.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Template Load Fail: ${i.id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,12 +94,33 @@ async function template() {
|
|||||||
async function model() {
|
async function model() {
|
||||||
for (const i of active) {
|
for (const i of active) {
|
||||||
if (i.model) {
|
if (i.model) {
|
||||||
console.time(`Model init: ${i.id}`);
|
try {
|
||||||
const exports = {}; // eslint-disable-line no-unused-vars
|
console.time(`Model init: ${i.id}`);
|
||||||
const module = { exports };
|
const exports = {};
|
||||||
eval(i.model);
|
const module = { exports, file: i.files || {} };
|
||||||
if ((module.exports || {}).index) await module.exports.index();
|
eval(i.model);
|
||||||
console.timeEnd(`Model init: ${i.id}`);
|
if ((module.exports || {}).index) await module.exports.index();
|
||||||
|
console.timeEnd(`Model init: ${i.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Model Load Fail: ${i.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function lib() {
|
||||||
|
for (const i of active) {
|
||||||
|
if (i.lib) {
|
||||||
|
try {
|
||||||
|
console.time(`Lib init: ${i.id}`);
|
||||||
|
const exports = {};
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const module = { exports, file: i.files || {} };
|
||||||
|
eval(i.lib);
|
||||||
|
console.timeEnd(`Lib init: ${i.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Lib Load Fail: ${i.id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,17 +128,22 @@ async function model() {
|
|||||||
async function service() {
|
async function service() {
|
||||||
for (const i of active) {
|
for (const i of active) {
|
||||||
if (i.service) {
|
if (i.service) {
|
||||||
console.time(`Service init: ${i.id}`);
|
try {
|
||||||
const exports = {}; // eslint-disable-line no-unused-vars
|
console.time(`Service init: ${i.id}`);
|
||||||
const module = { exports };
|
const exports = {}; // eslint-disable-line no-unused-vars
|
||||||
eval(i.service);
|
const module = { exports };
|
||||||
if ((module.exports || {}).init) await module.exports.init();
|
eval(i.service);
|
||||||
console.timeEnd(`Service init: ${i.id}`);
|
if ((module.exports || {}).init) await module.exports.init();
|
||||||
|
console.timeEnd(`Service init: ${i.id}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Service Load Fail: ${i.id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
|
ensureDir(path.resolve(os.tmpdir(), 'hydro'));
|
||||||
global.Hydro = {
|
global.Hydro = {
|
||||||
handler: {},
|
handler: {},
|
||||||
service: {},
|
service: {},
|
||||||
@@ -110,6 +175,7 @@ async function load() {
|
|||||||
'template', 'validator', 'nav',
|
'template', 'validator', 'nav',
|
||||||
];
|
];
|
||||||
for (const i of builtinLib) require(`./lib/${i}`);
|
for (const i of builtinLib) require(`./lib/${i}`);
|
||||||
|
await lib();
|
||||||
await locale();
|
await locale();
|
||||||
require('./service/gridfs');
|
require('./service/gridfs');
|
||||||
const server = require('./service/server');
|
const server = require('./service/server');
|
||||||
|
|||||||
@@ -81,9 +81,8 @@ async function judge(rid) {
|
|||||||
const rdoc = await get(rid);
|
const rdoc = await get(rid);
|
||||||
const pdoc = await problem.getById(rdoc.pid);
|
const pdoc = await problem.getById(rdoc.pid);
|
||||||
await task.add({
|
await task.add({
|
||||||
event: 'judge',
|
type: 'judge',
|
||||||
rid,
|
rid,
|
||||||
type: 0,
|
|
||||||
pid: rdoc.pid,
|
pid: rdoc.pid,
|
||||||
data: pdoc.data,
|
data: pdoc.data,
|
||||||
lang: rdoc.lang,
|
lang: rdoc.lang,
|
||||||
@@ -96,9 +95,8 @@ async function rejudge(rid) {
|
|||||||
const rdoc = await get(rid);
|
const rdoc = await get(rid);
|
||||||
const pdoc = await problem.getById(rdoc.pid);
|
const pdoc = await problem.getById(rdoc.pid);
|
||||||
await task.add({
|
await task.add({
|
||||||
event: 'judge',
|
type: 'judge',
|
||||||
rid,
|
rid,
|
||||||
type: 0,
|
|
||||||
pid: rdoc.pid,
|
pid: rdoc.pid,
|
||||||
data: pdoc.data,
|
data: pdoc.data,
|
||||||
lang: rdoc.lang,
|
lang: rdoc.lang,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "judger",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "HydroJudger"
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
// Webpack Required
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const child = require('child_process');
|
||||||
|
|
||||||
|
async function postInit() {
|
||||||
|
const { mkdirp, rmdir, compilerText } = require('./HydroJudger/judger/utils');
|
||||||
|
const log = require('./HydroJudger/judger/log');
|
||||||
|
const { CACHE_DIR, TEMP_DIR } = require('./HydroJudger/judger/config');
|
||||||
|
const tmpfs = require('./HydroJudger/judger/tmpfs');
|
||||||
|
const { FormatError, CompileError, SystemError } = require('./HydroJudger/judger/error');
|
||||||
|
const { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } = require('./HydroJudger/judger/status');
|
||||||
|
const readCases = require('./HydroJudger/judger/cases');
|
||||||
|
const judger = require('./HydroJudger/judger/judger');
|
||||||
|
|
||||||
|
const fsp = fs.promises;
|
||||||
|
const { problem, task } = global.Hydro.model;
|
||||||
|
const { judge } = global.Hydro.handler;
|
||||||
|
|
||||||
|
async function problemData(pid, savePath) {
|
||||||
|
const tmpFilePath = path.resolve(CACHE_DIR, `download_${pid}`);
|
||||||
|
const res = await problem.getData(pid);
|
||||||
|
const w = await fs.createWriteStream(tmpFilePath);
|
||||||
|
res.data.pipe(w);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
w.on('finish', resolve);
|
||||||
|
w.on('error', reject);
|
||||||
|
});
|
||||||
|
mkdirp(path.dirname(savePath));
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
child.exec(`unzip ${tmpFilePath} -d ${savePath}`, (e) => {
|
||||||
|
if (e) reject(e);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await fsp.unlink(tmpFilePath);
|
||||||
|
await this.processData(savePath).catch();
|
||||||
|
return savePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cacheOpen(pid, version) {
|
||||||
|
const filePath = path.join(CACHE_DIR, pid);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
let ver;
|
||||||
|
try {
|
||||||
|
ver = fs.readFileSync(path.join(filePath, 'version')).toString();
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
if (version === ver) return filePath;
|
||||||
|
rmdir(filePath);
|
||||||
|
}
|
||||||
|
mkdirp(filePath);
|
||||||
|
await problemData(pid, filePath);
|
||||||
|
fs.writeFileSync(path.join(filePath, 'version'), version);
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNext(that) {
|
||||||
|
that.nextId = 1;
|
||||||
|
that.nextWaiting = [];
|
||||||
|
return (data, id) => {
|
||||||
|
data.key = 'next';
|
||||||
|
data.rid = that.rid;
|
||||||
|
if (id) {
|
||||||
|
if (id === that.nextId) {
|
||||||
|
judge.next(data);
|
||||||
|
that.nextId++;
|
||||||
|
let t = true;
|
||||||
|
while (t) {
|
||||||
|
t = false;
|
||||||
|
for (const i in that.nextWaiting) {
|
||||||
|
if (that.nextId === that.nextWaiting[i].id) {
|
||||||
|
judge.next(that.nextWaiting[i].data);
|
||||||
|
that.nextId++;
|
||||||
|
that.nextWaiting.splice(i, 1);
|
||||||
|
t = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else that.nextWaiting.push({ data, id });
|
||||||
|
} else judge.next(data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnd(rid) {
|
||||||
|
return (data) => {
|
||||||
|
data.key = 'end';
|
||||||
|
data.rid = rid;
|
||||||
|
log.log({
|
||||||
|
status: data.status,
|
||||||
|
score: data.score,
|
||||||
|
time_ms: data.time_ms,
|
||||||
|
memory_kb: data.memory_kb,
|
||||||
|
});
|
||||||
|
judge.end(data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class JudgeTask {
|
||||||
|
constructor(request) {
|
||||||
|
this.stat = {};
|
||||||
|
this.stat.receive = new Date();
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle() {
|
||||||
|
try {
|
||||||
|
this.stat.handle = new Date();
|
||||||
|
this.event = this.request.event;
|
||||||
|
this.pid = this.request.pid;
|
||||||
|
this.rid = this.request.rid;
|
||||||
|
this.lang = this.request.lang;
|
||||||
|
this.code = this.request.code;
|
||||||
|
this.data = this.request.data;
|
||||||
|
this.next = getNext(this);
|
||||||
|
this.end = getEnd(this.rid);
|
||||||
|
this.tmpdir = path.resolve(TEMP_DIR, 'tmp', this.rid);
|
||||||
|
this.clean = [];
|
||||||
|
mkdirp(this.tmpdir);
|
||||||
|
tmpfs.mount(this.tmpdir, '64m');
|
||||||
|
log.submission(`${this.rid}`, { pid: this.pid });
|
||||||
|
if (!this.event) await this.submission();
|
||||||
|
else throw new SystemError(`Unsupported type: ${this.type}`);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof CompileError) {
|
||||||
|
this.next({ compiler_text: compilerText(e.stdout, e.stderr) });
|
||||||
|
this.end({
|
||||||
|
status: STATUS_COMPILE_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
||||||
|
});
|
||||||
|
} else if (e instanceof FormatError) {
|
||||||
|
this.next({ judge_text: `${e.message}\n${JSON.stringify(e.params)}` });
|
||||||
|
this.end({
|
||||||
|
status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.error(e);
|
||||||
|
this.next({ judge_text: `${e.message}\n${e.stack}\n${JSON.stringify(e.params)}` });
|
||||||
|
this.end({
|
||||||
|
status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const clean of this.clean) await clean().catch();
|
||||||
|
tmpfs.umount(this.tmpdir);
|
||||||
|
await rmdir(this.tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
async submission() {
|
||||||
|
this.stat.cache_start = new Date();
|
||||||
|
this.folder = await cacheOpen(this.pid, this.data);
|
||||||
|
this.stat.read_cases = new Date();
|
||||||
|
this.config = await readCases(
|
||||||
|
this.folder,
|
||||||
|
{ detail: true },
|
||||||
|
{ next: this.next },
|
||||||
|
);
|
||||||
|
this.stat.judge = new Date();
|
||||||
|
await judger[this.config.type || 'default'].judge(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.consume((t) => {
|
||||||
|
(new JudgeTask(t)).handle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { postInit };
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "sandbox-linux",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"os": "Linux",
|
||||||
|
"description": "Sandbox service for linux"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const os = require('os');
|
||||||
|
const child = require('child_process');
|
||||||
|
|
||||||
|
const p = child.spawn(path.resolve(os.tmpdir(), 'hydro', 'sandbox-linux', 'executorserver', ['-silent']);
|
||||||
|
if (!p.stdout) throw new Error('Cannot start executorserver');
|
||||||
|
else {
|
||||||
|
p.stdout.on('data', (data) => {
|
||||||
|
const s = data.toString();
|
||||||
|
console.log(s.substr(0, s.length - 1));
|
||||||
|
});
|
||||||
|
p.stderr.on('data', (data) => {
|
||||||
|
const s = data.toString();
|
||||||
|
console.log(s.substr(0, s.length - 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
p.on('error', (error) => console.error(error));
|
||||||
|
|
||||||
|
async function stop() {
|
||||||
|
p.emit('exit');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { stop };
|
||||||
Reference in new issue
Block a user