mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 23:12:23 +00:00
feat(judge): implement full testcase support and result recording
- Add testcase handling with proper input/output validation - Modify run function to process multiple testcases sequentially - Implement testcase result recording in database - Improve error handling and status updates - Add related type definitions for testcases
This commit is contained in:
parent
4da3723195
commit
a81be5c0f9
@ -8,7 +8,8 @@ import { auth } from "@/lib/auth";
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Readable, Writable } from "stream";
|
import { Readable, Writable } from "stream";
|
||||||
import { Status } from "@/generated/client";
|
import { Status } from "@/generated/client";
|
||||||
import type { EditorLanguage, Submission } from "@/generated/client";
|
import type { ProblemWithTestcases, TestcaseWithDetails } from "@/types/prisma";
|
||||||
|
import type { EditorLanguage, Submission, TestcaseResult } from "@/generated/client";
|
||||||
|
|
||||||
const isRemote = process.env.DOCKER_HOST_MODE === "remote";
|
const isRemote = process.env.DOCKER_HOST_MODE === "remote";
|
||||||
|
|
||||||
@ -82,7 +83,14 @@ export async function judge(
|
|||||||
try {
|
try {
|
||||||
const problem = await prisma.problem.findUnique({
|
const problem = await prisma.problem.findUnique({
|
||||||
where: { id: problemId },
|
where: { id: problemId },
|
||||||
});
|
include: {
|
||||||
|
testcases: {
|
||||||
|
include: {
|
||||||
|
data: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as ProblemWithTestcases | null;
|
||||||
|
|
||||||
if (!problem) {
|
if (!problem) {
|
||||||
submission = await prisma.submission.create({
|
submission = await prisma.submission.create({
|
||||||
@ -119,6 +127,22 @@ export async function judge(
|
|||||||
return submission;
|
return submission;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testcases = problem.testcases;
|
||||||
|
|
||||||
|
if (!testcases || testcases.length === 0) {
|
||||||
|
submission = await prisma.submission.create({
|
||||||
|
data: {
|
||||||
|
language,
|
||||||
|
code,
|
||||||
|
status: Status.SE,
|
||||||
|
userId,
|
||||||
|
problemId,
|
||||||
|
message: "Testcases not found",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
image,
|
image,
|
||||||
tag,
|
tag,
|
||||||
@ -133,6 +157,7 @@ export async function judge(
|
|||||||
if (await prepareEnvironment(image, tag)) {
|
if (await prepareEnvironment(image, tag)) {
|
||||||
container = await createContainer(image, tag, workingDir, problem.memoryLimit);
|
container = await createContainer(image, tag, workingDir, problem.memoryLimit);
|
||||||
} else {
|
} else {
|
||||||
|
console.error("Docker image not found:", image, ":", tag);
|
||||||
submission = await prisma.submission.create({
|
submission = await prisma.submission.create({
|
||||||
data: {
|
data: {
|
||||||
language,
|
language,
|
||||||
@ -168,7 +193,7 @@ export async function judge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the code
|
// Run the code
|
||||||
const runResult = await run(container, fileName, problem.timeLimit, runOutputLimit, submission.id);
|
const runResult = await run(container, fileName, problem.timeLimit, runOutputLimit, submission.id, testcases);
|
||||||
return runResult;
|
return runResult;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -305,7 +330,14 @@ async function run(
|
|||||||
timeLimit: number = 1000,
|
timeLimit: number = 1000,
|
||||||
maxOutput: number = 1 * 1024 * 1024,
|
maxOutput: number = 1 * 1024 * 1024,
|
||||||
submissionId: string,
|
submissionId: string,
|
||||||
|
testcases: TestcaseWithDetails,
|
||||||
): Promise<Submission> {
|
): Promise<Submission> {
|
||||||
|
let finalSubmission: Submission | null = null;
|
||||||
|
|
||||||
|
for (const testcase of testcases) {
|
||||||
|
const sortedData = testcase.data.sort((a, b) => a.index - b.index);
|
||||||
|
const inputData = sortedData.map(d => d.value).join("\n");
|
||||||
|
|
||||||
const runExec = await container.exec({
|
const runExec = await container.exec({
|
||||||
Cmd: [`./${fileName}`],
|
Cmd: [`./${fileName}`],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
@ -313,56 +345,63 @@ async function run(
|
|||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<Submission>((resolve, reject) => {
|
const result = await new Promise<Submission | TestcaseResult>((resolve, reject) => {
|
||||||
const stdoutChunks: string[] = [];
|
// Start the exec stream
|
||||||
let stdoutLength = 0;
|
runExec.start({ hijack: true }, async (error, stream) => {
|
||||||
const stdoutStream = new Writable({
|
if (error || !stream) {
|
||||||
write(chunk, _encoding, callback) {
|
const submission = await prisma.submission.update({
|
||||||
let text = chunk.toString();
|
where: { id: submissionId },
|
||||||
if (stdoutLength + text.length > maxOutput) {
|
data: {
|
||||||
text = text.substring(0, maxOutput - stdoutLength);
|
status: Status.SE,
|
||||||
stdoutChunks.push(text);
|
message: "System Error",
|
||||||
stdoutLength = maxOutput;
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return resolve(submission);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.write(inputData);
|
||||||
|
stream.end();
|
||||||
|
|
||||||
|
const stdoutChunks: string[] = [];
|
||||||
|
const stderrChunks: string[] = [];
|
||||||
|
let stdoutLength = 0;
|
||||||
|
let stderrLength = 0;
|
||||||
|
|
||||||
|
const stdoutStream = new Writable({
|
||||||
|
write: (chunk, _, callback) => {
|
||||||
|
const text = chunk.toString();
|
||||||
|
if (stdoutLength + text.length > maxOutput) {
|
||||||
|
stdoutChunks.push(text.substring(0, maxOutput - stdoutLength));
|
||||||
|
stdoutLength = maxOutput;
|
||||||
|
} else {
|
||||||
stdoutChunks.push(text);
|
stdoutChunks.push(text);
|
||||||
stdoutLength += text.length;
|
stdoutLength += text.length;
|
||||||
|
}
|
||||||
callback();
|
callback();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const stderrChunks: string[] = [];
|
|
||||||
let stderrLength = 0;
|
|
||||||
const stderrStream = new Writable({
|
const stderrStream = new Writable({
|
||||||
write(chunk, _encoding, callback) {
|
write: (chunk, _, callback) => {
|
||||||
let text = chunk.toString();
|
const text = chunk.toString();
|
||||||
if (stderrLength + text.length > maxOutput) {
|
if (stderrLength + text.length > maxOutput) {
|
||||||
text = text.substring(0, maxOutput - stderrLength);
|
stderrChunks.push(text.substring(0, maxOutput - stderrLength));
|
||||||
stderrChunks.push(text);
|
|
||||||
stderrLength = maxOutput;
|
stderrLength = maxOutput;
|
||||||
callback();
|
} else {
|
||||||
return;
|
|
||||||
}
|
|
||||||
stderrChunks.push(text);
|
stderrChunks.push(text);
|
||||||
stderrLength += text.length;
|
stderrLength += text.length;
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the exec stream
|
|
||||||
runExec.start({ hijack: true }, (error, stream) => {
|
|
||||||
if (error || !stream) {
|
|
||||||
return reject({ message: "System Error", status: Status.SE });
|
|
||||||
}
|
}
|
||||||
|
callback();
|
||||||
stream.write("[2,7,11,15]\n9\n[3,2,4]\n6\n[3,3]\n6");
|
}
|
||||||
stream.end();
|
});
|
||||||
|
|
||||||
docker.modem.demuxStream(stream, stdoutStream, stderrStream);
|
docker.modem.demuxStream(stream, stdoutStream, stderrStream);
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
// Timeout mechanism
|
// Timeout mechanism
|
||||||
const timeoutId = setTimeout(async () => {
|
const timeoutId = setTimeout(async () => {
|
||||||
|
stream.destroy(); // Destroy the stream to stop execution
|
||||||
const updatedSubmission = await prisma.submission.update({
|
const updatedSubmission = await prisma.submission.update({
|
||||||
where: { id: submissionId },
|
where: { id: submissionId },
|
||||||
data: {
|
data: {
|
||||||
@ -378,36 +417,58 @@ async function run(
|
|||||||
const stdout = stdoutChunks.join("");
|
const stdout = stdoutChunks.join("");
|
||||||
const stderr = stderrChunks.join("");
|
const stderr = stderrChunks.join("");
|
||||||
const exitCode = (await runExec.inspect()).ExitCode;
|
const exitCode = (await runExec.inspect()).ExitCode;
|
||||||
|
const executionTime = Date.now() - startTime;
|
||||||
let updatedSubmission: Submission;
|
|
||||||
|
|
||||||
// Exit code 0 means successful execution
|
// Exit code 0 means successful execution
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
updatedSubmission = await prisma.submission.update({
|
const expectedOutput = testcase.expectedOutput;
|
||||||
where: { id: submissionId },
|
const testcaseResult = await prisma.testcaseResult.create({
|
||||||
data: {
|
data: {
|
||||||
status: Status.AC,
|
isCorrect: stdout.trim() === expectedOutput.trim(),
|
||||||
message: stdout,
|
output: stdout,
|
||||||
|
executionTime,
|
||||||
|
submissionId,
|
||||||
|
testcaseId: testcase.id,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
resolve(testcaseResult);
|
||||||
} else if (exitCode === 137) {
|
} else if (exitCode === 137) {
|
||||||
updatedSubmission = await prisma.submission.update({
|
await prisma.testcaseResult.create({
|
||||||
|
data: {
|
||||||
|
isCorrect: false,
|
||||||
|
output: stdout,
|
||||||
|
executionTime,
|
||||||
|
submissionId,
|
||||||
|
testcaseId: testcase.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const updatedSubmission = await prisma.submission.update({
|
||||||
where: { id: submissionId },
|
where: { id: submissionId },
|
||||||
data: {
|
data: {
|
||||||
status: Status.MLE,
|
status: Status.MLE,
|
||||||
message: stderr || "Memory Limit Exceeded",
|
message: stderr || "Memory Limit Exceeded",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
resolve(updatedSubmission);
|
||||||
} else {
|
} else {
|
||||||
updatedSubmission = await prisma.submission.update({
|
await prisma.testcaseResult.create({
|
||||||
|
data: {
|
||||||
|
isCorrect: false,
|
||||||
|
output: stdout,
|
||||||
|
executionTime,
|
||||||
|
submissionId,
|
||||||
|
testcaseId: testcase.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const updatedSubmission = await prisma.submission.update({
|
||||||
where: { id: submissionId },
|
where: { id: submissionId },
|
||||||
data: {
|
data: {
|
||||||
status: Status.RE,
|
status: Status.RE,
|
||||||
message: stderr || "Runtime Error",
|
message: stderr || "Runtime Error",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
resolve(updatedSubmission);
|
resolve(updatedSubmission);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on("error", () => {
|
stream.on("error", () => {
|
||||||
@ -416,4 +477,34 @@ async function run(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ('status' in result) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
if (!result.isCorrect) {
|
||||||
|
finalSubmission = await prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: {
|
||||||
|
status: Status.WA,
|
||||||
|
message: "Wrong Answer",
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
TestcaseResult: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return finalSubmission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalSubmission = await prisma.submission.update({
|
||||||
|
where: { id: submissionId },
|
||||||
|
data: {
|
||||||
|
status: Status.AC,
|
||||||
|
message: "All testcases passed",
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
TestcaseResult: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return finalSubmission;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user