mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 23:42:24 +00:00
feat: add TestcaseDataConfig and prototype randomjudge.ts
This commit is contained in:
parent
2c7223a323
commit
16ec2d3730
7
bun.lock
7
bun.lock
@ -51,6 +51,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"normalize-url": "^8.0.1",
|
"normalize-url": "^8.0.1",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
|
"randexp": "^0.5.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
@ -777,6 +778,8 @@
|
|||||||
|
|
||||||
"domutils": ["domutils@3.2.2", "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
"domutils": ["domutils@3.2.2", "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||||
|
|
||||||
|
"drange": ["drange@1.1.1", "", {}, "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA=="],
|
||||||
|
|
||||||
"dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
"dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
|
|
||||||
"eastasianwidth": ["eastasianwidth@0.2.0", "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
"eastasianwidth": ["eastasianwidth@0.2.0", "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||||
@ -1441,6 +1444,8 @@
|
|||||||
|
|
||||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||||
|
|
||||||
|
"randexp": ["randexp@0.5.3", "", { "dependencies": { "drange": "^1.0.2", "ret": "^0.2.0" } }, "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w=="],
|
||||||
|
|
||||||
"react": ["react@19.0.0", "https://registry.npmmirror.com/react/-/react-19.0.0.tgz", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
|
"react": ["react@19.0.0", "https://registry.npmmirror.com/react/-/react-19.0.0.tgz", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
|
||||||
|
|
||||||
"react-dom": ["react-dom@19.0.0", "https://registry.npmmirror.com/react-dom/-/react-dom-19.0.0.tgz", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
|
"react-dom": ["react-dom@19.0.0", "https://registry.npmmirror.com/react-dom/-/react-dom-19.0.0.tgz", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
|
||||||
@ -1519,6 +1524,8 @@
|
|||||||
|
|
||||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
|
"ret": ["ret@0.2.2", "", {}, "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ=="],
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
"rimraf": ["rimraf@3.0.2", "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
"rimraf": ["rimraf@3.0.2", "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"normalize-url": "^8.0.1",
|
"normalize-url": "^8.0.1",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
|
"randexp": "^0.5.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
|
@ -163,6 +163,8 @@ model TestcaseData {
|
|||||||
value String
|
value String
|
||||||
testcaseId String
|
testcaseId String
|
||||||
testcase Testcase @relation(fields: [testcaseId], references: [id], onDelete: Cascade)
|
testcase Testcase @relation(fields: [testcaseId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
TestcaseDataConfig TestcaseDataConfig[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model TestcaseResult {
|
model TestcaseResult {
|
||||||
@ -236,3 +238,17 @@ model Authenticator {
|
|||||||
|
|
||||||
@@id([userId, credentialID])
|
@@id([userId, credentialID])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// schema.prisma
|
||||||
|
model TestcaseDataConfig {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
testcaseDataId String @unique
|
||||||
|
testcaseData TestcaseData @relation(fields: [testcaseDataId], references: [id])
|
||||||
|
type String // 存储INT/FLOAT/STRING/BOOLEAN
|
||||||
|
min Int? // 数值最小值
|
||||||
|
max Int? // 数值最大值
|
||||||
|
length Int? // 字符串长度
|
||||||
|
pattern String? // 正则表达式模式
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
411
src/actions/randomjudge.ts
Normal file
411
src/actions/randomjudge.ts
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import tar from "tar-stream";
|
||||||
|
import Docker from "dockerode";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { Readable, Writable } from "stream";
|
||||||
|
import { Status } from "@/generated/client";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import type { EditorLanguage, Submission, TestcaseResult } from "@/generated/client";
|
||||||
|
import RandExp from "randexp";
|
||||||
|
|
||||||
|
const isRemote = process.env.DOCKER_HOST_MODE === "remote";
|
||||||
|
const docker = isRemote
|
||||||
|
? new Docker({
|
||||||
|
protocol: process.env.DOCKER_REMOTE_PROTOCOL as "https" | "http" | "ssh" | undefined,
|
||||||
|
host: process.env.DOCKER_REMOTE_HOST,
|
||||||
|
port: process.env.DOCKER_REMOTE_PORT,
|
||||||
|
ca: fs.readFileSync(process.env.DOCKER_REMOTE_CA_PATH || "/certs/ca.pem"),
|
||||||
|
cert: fs.readFileSync(process.env.DOCKER_REMOTE_CERT_PATH || "/certs/cert.pem"),
|
||||||
|
key: fs.readFileSync(process.env.DOCKER_REMOTE_KEY_PATH || "/certs/key.pem"),
|
||||||
|
})
|
||||||
|
: new Docker({ socketPath: "/var/run/docker.sock" });
|
||||||
|
|
||||||
|
async function prepareEnvironment(image: string, tag: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const reference = `${image}:${tag}`;
|
||||||
|
const filters = { reference: [reference] };
|
||||||
|
const images = await docker.listImages({ filters });
|
||||||
|
return images.length > 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking Docker images:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createContainer(
|
||||||
|
image: string,
|
||||||
|
tag: string,
|
||||||
|
workingDir: string,
|
||||||
|
memoryLimit?: number
|
||||||
|
) {
|
||||||
|
const container = await docker.createContainer({
|
||||||
|
Image: `${image}:${tag}`,
|
||||||
|
Cmd: ["tail", "-f", "/dev/null"],
|
||||||
|
WorkingDir: workingDir,
|
||||||
|
HostConfig: {
|
||||||
|
Memory: memoryLimit ? memoryLimit * 1024 * 1024 : undefined,
|
||||||
|
MemorySwap: memoryLimit ? memoryLimit * 1024 * 1024 : undefined,
|
||||||
|
},
|
||||||
|
NetworkDisabled: true,
|
||||||
|
});
|
||||||
|
await container.start();
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTarStream(file: string, value: string) {
|
||||||
|
const pack = tar.pack();
|
||||||
|
pack.entry({ name: file }, value);
|
||||||
|
pack.finalize();
|
||||||
|
return Readable.from(pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random input cases based on TestcaseDataConfig
|
||||||
|
async function generateRandomCases(problemId: string, count: number) {
|
||||||
|
const configs = await prisma.testcaseDataConfig.findMany({
|
||||||
|
where: { testcaseData: { testcase: { problemId } } },
|
||||||
|
include: { testcaseData: true },
|
||||||
|
});
|
||||||
|
const grouped: Record<number, typeof configs> = {};
|
||||||
|
configs.forEach(cfg => {
|
||||||
|
const idx = cfg.testcaseData.index;
|
||||||
|
grouped[idx] = grouped[idx] || [];
|
||||||
|
grouped[idx].push(cfg);
|
||||||
|
});
|
||||||
|
const cases: { id: string; data: { index: number; value: string }[] }[] = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const inputs: { index: number; value: string }[] = [];
|
||||||
|
Object.keys(grouped)
|
||||||
|
.map(Number)
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.forEach(idx => {
|
||||||
|
grouped[idx].forEach(cfg => {
|
||||||
|
let value: string;
|
||||||
|
switch (cfg.type.toUpperCase()) {
|
||||||
|
case "INT":
|
||||||
|
value = String(
|
||||||
|
Math.floor(
|
||||||
|
Math.random() * ((cfg.max ?? 100) - (cfg.min ?? 0) + 1)
|
||||||
|
) + (cfg.min ?? 0)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "FLOAT": {
|
||||||
|
const lo = cfg.min ?? 0;
|
||||||
|
const hi = cfg.max ?? lo + 1;
|
||||||
|
value = (Math.random() * (hi - lo) + lo).toFixed(3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "BOOLEAN":
|
||||||
|
value = String(Math.random() < 0.5);
|
||||||
|
break;
|
||||||
|
case "STRING":
|
||||||
|
if (cfg.pattern) value = new RandExp(cfg.pattern).gen();
|
||||||
|
else {
|
||||||
|
const len = cfg.length ?? 10;
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
value = Array.from({ length: len })
|
||||||
|
.map(() =>
|
||||||
|
chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
inputs.push({ index: idx, value });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
cases.push({ id: String(i), data: inputs });
|
||||||
|
}
|
||||||
|
return cases;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function judge(
|
||||||
|
language: EditorLanguage,
|
||||||
|
code: string,
|
||||||
|
problemId: string,
|
||||||
|
randomCount: number = 10
|
||||||
|
): Promise<Submission> {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session?.user?.id) redirect("/sign-in");
|
||||||
|
const userId = session.user.id;
|
||||||
|
|
||||||
|
let container: Docker.Container | null = null;
|
||||||
|
let submission: Submission | null = null;
|
||||||
|
try {
|
||||||
|
const problem = await prisma.problem.findUnique({ where: { id: problemId } });
|
||||||
|
if (!problem) {
|
||||||
|
return prisma.submission.create({
|
||||||
|
data: { language, code, status: Status.SE, userId, problemId, message: "Problem not found" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const config = await prisma.editorLanguageConfig.findUnique({
|
||||||
|
where: { language },
|
||||||
|
include: { dockerConfig: true },
|
||||||
|
});
|
||||||
|
if (!config?.dockerConfig) {
|
||||||
|
return prisma.submission.create({
|
||||||
|
data: { language, code, status: Status.SE, userId, problemId, message: "Missing docker configuration" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { image, tag, workingDir, compileOutputLimit, runOutputLimit } = config.dockerConfig;
|
||||||
|
const { fileName, fileExtension } = config;
|
||||||
|
const file = `${fileName}.${fileExtension}`;
|
||||||
|
|
||||||
|
if (!(await prepareEnvironment(image, tag))) {
|
||||||
|
return prisma.submission.create({
|
||||||
|
data: { language, code, status: Status.SE, userId, problemId, message: "Docker environment not ready" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
container = await createContainer(image, tag, workingDir, problem.memoryLimit);
|
||||||
|
submission = await prisma.submission.create({
|
||||||
|
data: { language, code, status: Status.PD, userId, problemId, message: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upload code
|
||||||
|
const tarStream = createTarStream(file, code);
|
||||||
|
await container.putArchive(tarStream, { path: workingDir });
|
||||||
|
|
||||||
|
// Compile
|
||||||
|
const compileResult = await compile(
|
||||||
|
container,
|
||||||
|
file,
|
||||||
|
fileName,
|
||||||
|
compileOutputLimit,
|
||||||
|
submission.id,
|
||||||
|
language
|
||||||
|
);
|
||||||
|
if (compileResult.status === Status.CE) return compileResult;
|
||||||
|
|
||||||
|
// Generate random test cases
|
||||||
|
const testcases = await generateRandomCases(problemId, randomCount);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
const runResult = await run(
|
||||||
|
container,
|
||||||
|
fileName,
|
||||||
|
problem.timeLimit,
|
||||||
|
runOutputLimit,
|
||||||
|
submission.id,
|
||||||
|
testcases
|
||||||
|
);
|
||||||
|
return runResult;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (submission) {
|
||||||
|
return prisma.submission.update({
|
||||||
|
where: { id: submission.id },
|
||||||
|
data: { status: Status.SE, message: "System Error" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prisma.submission.create({
|
||||||
|
data: { language, code, status: Status.SE, userId, problemId, message: "System Error" },
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
revalidatePath(`/problems/${problemId}`);
|
||||||
|
if (container) {
|
||||||
|
try {
|
||||||
|
await container.kill();
|
||||||
|
await container.remove();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compile(
|
||||||
|
container: Docker.Container,
|
||||||
|
file: string,
|
||||||
|
fileName: string,
|
||||||
|
compileOutputLimit: number,
|
||||||
|
submissionId: string,
|
||||||
|
language: EditorLanguage
|
||||||
|
): Promise<Submission> {
|
||||||
|
const compileCmd =
|
||||||
|
language === "c"
|
||||||
|
? ["gcc", "-O2", file, "-o", fileName]
|
||||||
|
: language === "cpp"
|
||||||
|
? ["g++", "-O2", file, "-o", fileName]
|
||||||
|
: null;
|
||||||
|
if (!compileCmd) {
|
||||||
|
return prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.SE, message: "Unsupported language" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const exec = await container.exec({
|
||||||
|
Cmd: compileCmd,
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise<Submission>((resolve, reject) => {
|
||||||
|
exec.start({}, (err, stream) => {
|
||||||
|
if (err || !stream) return reject(err || "No stream");
|
||||||
|
|
||||||
|
const stdoutChunks: string[] = [];
|
||||||
|
const stderrChunks: string[] = [];
|
||||||
|
let stdoutLen = 0;
|
||||||
|
let stderrLen = 0;
|
||||||
|
|
||||||
|
const out = new Writable({
|
||||||
|
write(chunk, _, cb) {
|
||||||
|
let txt = chunk.toString();
|
||||||
|
if (stdoutLen + txt.length > compileOutputLimit) {
|
||||||
|
txt = txt.slice(0, compileOutputLimit - stdoutLen);
|
||||||
|
}
|
||||||
|
stdoutLen += txt.length;
|
||||||
|
stdoutChunks.push(txt);
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const errOut = new Writable({
|
||||||
|
write(chunk, _, cb) {
|
||||||
|
let txt = chunk.toString();
|
||||||
|
if (stderrLen + txt.length > compileOutputLimit) {
|
||||||
|
txt = txt.slice(0, compileOutputLimit - stderrLen);
|
||||||
|
}
|
||||||
|
stderrLen += txt.length;
|
||||||
|
stderrChunks.push(txt);
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
docker.modem.demuxStream(stream, out, errOut);
|
||||||
|
stream.on("end", async () => {
|
||||||
|
const { ExitCode } = await exec.inspect();
|
||||||
|
const stdout = stdoutChunks.join("");
|
||||||
|
const stderr = stderrChunks.join("");
|
||||||
|
if (ExitCode !== 0 || stderr) {
|
||||||
|
resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.CE, message: stderr || stdout },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.CS, message: stdout },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(
|
||||||
|
container: Docker.Container,
|
||||||
|
fileName: string,
|
||||||
|
timeLimit: number,
|
||||||
|
maxOutput: number,
|
||||||
|
submissionId: string,
|
||||||
|
testcases: { id: string; data: { index: number; value: string }[] }[]
|
||||||
|
): Promise<Submission> {
|
||||||
|
let maxTime = 0;
|
||||||
|
for (const tc of testcases) {
|
||||||
|
const input = tc.data.map(d => d.value).join("\n");
|
||||||
|
const exec = await container.exec({
|
||||||
|
Cmd: [`./${fileName}`],
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
AttachStdin: true,
|
||||||
|
});
|
||||||
|
const result = await new Promise<Submission>((resolve) => {
|
||||||
|
exec.start({ hijack: true }, (err, stream) => {
|
||||||
|
if (err || !stream) {
|
||||||
|
return resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.SE, message: "System Error" },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const stdoutChunks: string[] = [];
|
||||||
|
const stderrChunks: string[] = [];
|
||||||
|
let outLen = 0;
|
||||||
|
let errLen = 0;
|
||||||
|
const out = new Writable({ write(chunk, _, cb) {
|
||||||
|
const txt = chunk.toString();
|
||||||
|
if (outLen + txt.length > maxOutput) {
|
||||||
|
stdoutChunks.push(txt.slice(0, maxOutput - outLen));
|
||||||
|
outLen = maxOutput;
|
||||||
|
} else {
|
||||||
|
stdoutChunks.push(txt);
|
||||||
|
outLen += txt.length;
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}});
|
||||||
|
const errOut = new Writable({ write(chunk, _, cb) {
|
||||||
|
const txt = chunk.toString();
|
||||||
|
if (errLen + txt.length > maxOutput) {
|
||||||
|
stderrChunks.push(txt.slice(0, maxOutput - errLen));
|
||||||
|
errLen = maxOutput;
|
||||||
|
} else {
|
||||||
|
stderrChunks.push(txt);
|
||||||
|
errLen += txt.length;
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}});
|
||||||
|
docker.modem.demuxStream(stream, out, errOut);
|
||||||
|
|
||||||
|
stream.write(input);
|
||||||
|
stream.end();
|
||||||
|
const start = Date.now();
|
||||||
|
const timeout = setTimeout(async () => {
|
||||||
|
stream.destroy();
|
||||||
|
resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.TLE, message: "Time Limit Exceeded" },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, timeLimit);
|
||||||
|
|
||||||
|
stream.on("end", async () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
const execInfo = await exec.inspect();
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
const stdout = stdoutChunks.join("");
|
||||||
|
const stderr = stderrChunks.join("");
|
||||||
|
switch (execInfo.ExitCode) {
|
||||||
|
case 0:
|
||||||
|
maxTime = Math.max(maxTime, elapsed);
|
||||||
|
break;
|
||||||
|
case 137:
|
||||||
|
return resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.MLE, message: stderr || "Memory Limit Exceeded" },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.RE, message: stderr || stdout },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolve(
|
||||||
|
prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: {},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (result.status !== Status.CS) return result;
|
||||||
|
}
|
||||||
|
return prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: { status: Status.AC, message: "Random tests passed", executionTime: maxTime },
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user