@exercode/problem-utils 4.3.4 → 4.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/checkAllProblems.cjs +1 -1
- package/dist/cli/checkAllProblems.cjs.map +1 -1
- package/dist/cli/checkAllProblems.js +1 -1
- package/dist/cli/checkAllProblems.js.map +1 -1
- package/dist/cli/exercodeProblem.cjs.map +1 -1
- package/dist/cli/exercodeProblem.js.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.cjs.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.js.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.js.map +1 -1
- package/dist/helpers/findModelAnswerDirs.cjs.map +1 -1
- package/dist/helpers/findModelAnswerDirs.js.map +1 -1
- package/dist/helpers/javaJudge.cjs.map +1 -1
- package/dist/helpers/javaJudge.js.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.cjs.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.js.map +1 -1
- package/dist/helpers/readOutputFiles.cjs.map +1 -1
- package/dist/helpers/readOutputFiles.js.map +1 -1
- package/dist/helpers/resolveCwds.cjs +1 -1
- package/dist/helpers/resolveCwds.cjs.map +1 -1
- package/dist/helpers/resolveCwds.js +1 -1
- package/dist/helpers/resolveCwds.js.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
- package/dist/helpers/sourceCodeGrammars.cjs.map +1 -1
- package/dist/helpers/sourceCodeGrammars.js.map +1 -1
- package/dist/helpers/spawnWithLimits.cjs.map +1 -1
- package/dist/helpers/spawnWithLimits.js.map +1 -1
- package/dist/learningMaterial/validateContest.cjs.map +1 -1
- package/dist/learningMaterial/validateContest.js.map +1 -1
- package/dist/learningMaterial/validateCourse.cjs.map +1 -1
- package/dist/learningMaterial/validateCourse.js.map +1 -1
- package/dist/learningMaterial/validateMaterial.cjs.map +1 -1
- package/dist/learningMaterial/validateMaterial.js.map +1 -1
- package/dist/learningMaterial/validateProblem.cjs.map +1 -1
- package/dist/learningMaterial/validateProblem.js.map +1 -1
- package/dist/presets/command.cjs +1 -1
- package/dist/presets/command.cjs.map +1 -1
- package/dist/presets/command.js +1 -1
- package/dist/presets/command.js.map +1 -1
- package/dist/presets/evaluation.cjs +1 -1
- package/dist/presets/evaluation.cjs.map +1 -1
- package/dist/presets/evaluation.js +1 -1
- package/dist/presets/evaluation.js.map +1 -1
- package/dist/presets/guiCommand.cjs.map +1 -1
- package/dist/presets/guiCommand.js.map +1 -1
- package/dist/presets/springBoot.cjs.map +1 -1
- package/dist/presets/springBoot.js.map +1 -1
- package/dist/presets/stdio.cjs.map +1 -1
- package/dist/presets/stdio.js.map +1 -1
- package/dist/presets/tomcat.cjs.map +1 -1
- package/dist/presets/tomcat.js.map +1 -1
- package/package.json +25 -28
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"springBoot.cjs","names":["getErrorMessage","parseArgs","fs","path","tmpdir","DecisionCode","listSubmissionFiles","resolveJavaRelativePath","spawnSync","MAX_OUTPUT_LENGTH","truncateOutput","hasErrorCode","spawn","sleep","readOutputFiles","require_runtime","require_printTestCaseResult","require_parseArgs","require_decisionCode","require_javaJudge","node_fs","node_path","node_os","_willbooster_shared_lib","node_child_process","node_timers_promises","APPLICATION_NAME","SERVER_PORT","SERVER_BASE_URL","STARTUP_TIMEOUT_MS","springBootJudgePreset","options","error","submissionDir","testCaseId","runRootDir","buildDir","springBootProcess","prepareBuildDirectory","buildResult","buildWithMaven","startResult","startSpringBootApplication","judgeResult","runJudgeScript","stopSpringBootApplication","problemDirectoryPath","createApplicationProperties","relativePath","sourcePath","targetPath","resolveTargetPath","normalizedRelativePath","startedAt","result","timeSeconds","stdout","stderr","extractMavenErrors","plainOutput","output","errorMessages","match","message","childProcess","jarPath","applicationStdout","applicationStderr","chunk","hasExited","isServerResponding","feedbackMarkdown","response","outputFiles","buildSpringBootUrl","urlPath"],"sources":["../../src/presets/springBoot.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { getErrorMessage, hasErrorCode } from '@willbooster/shared-lib';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nexport interface SpringBootJudgePresetOptions {\n problemDirectoryPath: string;\n evaluate: () => Promise<void>;\n}\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\nconst APPLICATION_NAME = 'judge';\n\nconst SERVER_PORT = 59_000;\n\nconst SERVER_BASE_URL = `http://localhost:${SERVER_PORT}`;\n\nconst BUILD_TIMEOUT_MS = 90_000;\n\nconst STARTUP_TIMEOUT_MS = 60_000;\n\nconst EVALUATE_TIMEOUT_MS = 60_000;\n\nexport async function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(getErrorMessage(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'spring_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let springBootProcess: ChildProcess | undefined;\n\n try {\n await prepareBuildDirectory(buildDir, submissionDir, options.problemDirectoryPath);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n const jarPath = path.join(buildDir, 'target', `${APPLICATION_NAME}.jar`);\n const startResult = await startSpringBootApplication(buildDir, jarPath);\n springBootProcess = startResult.childProcess;\n if (startResult.failure) {\n printTestCaseResult({ testCaseId, ...startResult.failure });\n return;\n }\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: getErrorMessage(error),\n });\n } finally {\n await stopSpringBootApplication(springBootProcess);\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n problemDirectoryPath: string\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/resources'), { recursive: true });\n await fs.promises.copyFile(path.join(problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n await fs.promises.writeFile(\n path.join(buildDir, 'src/main/resources/application.properties'),\n createApplicationProperties()\n );\n\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nfunction createApplicationProperties(): string {\n return `spring.application.name=${APPLICATION_NAME}\nserver.port=${SERVER_PORT}\nspring.main.banner-mode=off\nlogging.level.org.springframework=ERROR\nlogging.level.org.apache=ERROR\nspring.main.log-startup-info=false\n`;\n}\n\nasync function resolveTargetPath(buildDir: string, relativePath: string, sourcePath: string): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (path.extname(relativePath).toLowerCase() === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n\n return path.join(buildDir, 'src/main/resources', normalizedRelativePath);\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const startedAt = Date.now();\n const result = spawnSync('mvn', ['clean', 'package', '--quiet', '--batch-mode', '--offline', '-DskipTests'], {\n cwd: buildDir,\n encoding: 'utf8',\n env: { ...process.env, CI: '', FORCE_COLOR: '0' },\n timeout: BUILD_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds };\n }\n if ((result.status ?? 0) === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus: result.status ?? undefined,\n stderr: truncateOutput(extractMavenErrors(stdout) || stderr),\n stdout,\n timeSeconds,\n };\n}\n\nfunction extractMavenErrors(output: string): string {\n const plainOutput = output.replaceAll(/\u001b\\[\\d*(?:;\\d+)*m/gu, '');\n const errorMessages = new Set<string>();\n for (const match of plainOutput.matchAll(/\\].*?\\[\\d+,\\d+\\]\\s+(.*?)$/gmu)) {\n const message = match[1]!.trim();\n if (message) {\n errorMessages.add(message);\n }\n }\n return errorMessages.size > 0 ? `ビルドエラー:\\n${[...errorMessages].join('\\n')}` : '';\n}\n\nasync function startSpringBootApplication(\n buildDir: string,\n jarPath: string\n): Promise<{ childProcess: ChildProcess; failure?: JudgeResult }> {\n const startedAt = Date.now();\n const childProcess = spawn('java', ['-jar', jarPath], {\n cwd: buildDir,\n env: { ...process.env, SPRING_MAIN_WEB_APPLICATION_TYPE: 'servlet' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let applicationStdout = '';\n let applicationStderr = '';\n childProcess.stdout?.on('data', (chunk: Buffer) => {\n applicationStdout = truncateOutput(applicationStdout + chunk.toString('utf8'));\n });\n childProcess.stderr?.on('data', (chunk: Buffer) => {\n applicationStderr = truncateOutput(applicationStderr + chunk.toString('utf8'));\n });\n let hasExited = false;\n childProcess.on('exit', () => {\n hasExited = true;\n });\n\n while (Date.now() - startedAt < STARTUP_TIMEOUT_MS) {\n if (await isServerResponding()) {\n return { childProcess };\n }\n if (hasExited) {\n break;\n }\n await sleep(200);\n }\n\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const feedbackMarkdown = hasExited\n ? 'アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。'\n : `アプリケーションが ${STARTUP_TIMEOUT_MS / 1000} 秒以内に起動しませんでした。`;\n return {\n childProcess,\n failure: {\n decisionCode: hasExited ? DecisionCode.RUNTIME_ERROR : DecisionCode.TIME_LIMIT_EXCEEDED,\n exitStatus: childProcess.exitCode ?? undefined,\n feedbackMarkdown,\n stderr: applicationStderr || undefined,\n stdout: applicationStdout || undefined,\n timeSeconds,\n },\n };\n}\n\nasync function isServerResponding(): Promise<boolean> {\n try {\n // Accept 404 so applications without a root endpoint can finish startup.\n const response = await fetch(SERVER_BASE_URL, { signal: AbortSignal.timeout(1000) });\n await response.body?.cancel();\n return response.ok || response.status === 404;\n } catch {\n return false;\n }\n}\n\nasync function stopSpringBootApplication(childProcess: ChildProcess | undefined): Promise<void> {\n if (!childProcess || childProcess.exitCode !== null) {\n return;\n }\n\n childProcess.kill('SIGTERM');\n const startedAt = Date.now();\n while (Date.now() - startedAt < 5000 && childProcess.exitCode === null) {\n await sleep(100);\n }\n if (childProcess.exitCode === null) {\n childProcess.kill('SIGKILL');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const startedAt = Date.now();\n const result = spawnSync('bun', ['run', './judge.ts', '--evaluate'], {\n cwd: problemDirectoryPath,\n encoding: 'utf8',\n env: { ...process.env, WORKING_DIRECTORY_PATH: buildDir },\n timeout: EVALUATE_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds, outputFiles };\n }\n if ((result.status ?? 0) !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n exitStatus: result.status ?? undefined,\n stderr,\n stdout,\n timeSeconds,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n exitStatus: result.status ?? undefined,\n stderr: stderr || undefined,\n stdout: stdout || undefined,\n timeSeconds,\n outputFiles,\n };\n}\n\nexport function buildSpringBootUrl(urlPath: string): string {\n return `${SERVER_BASE_URL}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"springBoot.cjs","names":["getErrorMessage","parseArgs","fs","path","tmpdir","DecisionCode","listSubmissionFiles","resolveJavaRelativePath","spawnSync","MAX_OUTPUT_LENGTH","truncateOutput","hasErrorCode","spawn","sleep","readOutputFiles","require_runtime","require_printTestCaseResult","require_parseArgs","require_decisionCode","require_javaJudge","node_fs","node_path","node_os","_willbooster_shared_lib","node_child_process","node_timers_promises","APPLICATION_NAME","SERVER_PORT","SERVER_BASE_URL","STARTUP_TIMEOUT_MS","springBootJudgePreset","options","error","submissionDir","testCaseId","runRootDir","buildDir","springBootProcess","prepareBuildDirectory","buildResult","buildWithMaven","startResult","startSpringBootApplication","judgeResult","runJudgeScript","stopSpringBootApplication","problemDirectoryPath","createApplicationProperties","relativePath","sourcePath","targetPath","resolveTargetPath","normalizedRelativePath","startedAt","result","timeSeconds","stdout","stderr","extractMavenErrors","plainOutput","output","errorMessages","match","message","childProcess","jarPath","applicationStdout","applicationStderr","chunk","hasExited","isServerResponding","feedbackMarkdown","response","outputFiles","buildSpringBootUrl","urlPath"],"sources":["../../src/presets/springBoot.ts"],"sourcesContent":["import { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { getErrorMessage, hasErrorCode } from '@willbooster/shared-lib';\n\nimport {\n listSubmissionFiles,\n MAX_OUTPUT_LENGTH,\n readOutputFiles,\n resolveJavaRelativePath,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nexport interface SpringBootJudgePresetOptions {\n problemDirectoryPath: string;\n evaluate: () => Promise<void>;\n}\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\nconst APPLICATION_NAME = 'judge';\n\nconst SERVER_PORT = 59_000;\n\nconst SERVER_BASE_URL = `http://localhost:${SERVER_PORT}`;\n\nconst BUILD_TIMEOUT_MS = 90_000;\n\nconst STARTUP_TIMEOUT_MS = 60_000;\n\nconst EVALUATE_TIMEOUT_MS = 60_000;\n\nexport async function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(getErrorMessage(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'spring_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let springBootProcess: ChildProcess | undefined;\n\n try {\n await prepareBuildDirectory(buildDir, submissionDir, options.problemDirectoryPath);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n const jarPath = path.join(buildDir, 'target', `${APPLICATION_NAME}.jar`);\n const startResult = await startSpringBootApplication(buildDir, jarPath);\n springBootProcess = startResult.childProcess;\n if (startResult.failure) {\n printTestCaseResult({ testCaseId, ...startResult.failure });\n return;\n }\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: getErrorMessage(error),\n });\n } finally {\n await stopSpringBootApplication(springBootProcess);\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n problemDirectoryPath: string\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/resources'), { recursive: true });\n await fs.promises.copyFile(path.join(problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n await fs.promises.writeFile(\n path.join(buildDir, 'src/main/resources/application.properties'),\n createApplicationProperties()\n );\n\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nfunction createApplicationProperties(): string {\n return `spring.application.name=${APPLICATION_NAME}\nserver.port=${SERVER_PORT}\nspring.main.banner-mode=off\nlogging.level.org.springframework=ERROR\nlogging.level.org.apache=ERROR\nspring.main.log-startup-info=false\n`;\n}\n\nasync function resolveTargetPath(buildDir: string, relativePath: string, sourcePath: string): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (path.extname(relativePath).toLowerCase() === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n\n return path.join(buildDir, 'src/main/resources', normalizedRelativePath);\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const startedAt = Date.now();\n const result = spawnSync('mvn', ['clean', 'package', '--quiet', '--batch-mode', '--offline', '-DskipTests'], {\n cwd: buildDir,\n encoding: 'utf8',\n env: { ...process.env, CI: '', FORCE_COLOR: '0' },\n timeout: BUILD_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds };\n }\n if ((result.status ?? 0) === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus: result.status ?? undefined,\n stderr: truncateOutput(extractMavenErrors(stdout) || stderr),\n stdout,\n timeSeconds,\n };\n}\n\nfunction extractMavenErrors(output: string): string {\n const plainOutput = output.replaceAll(/\u001b\\[\\d*(?:;\\d+)*m/gu, '');\n const errorMessages = new Set<string>();\n for (const match of plainOutput.matchAll(/\\].*?\\[\\d+,\\d+\\]\\s+(.*?)$/gmu)) {\n const message = match[1]!.trim();\n if (message) {\n errorMessages.add(message);\n }\n }\n return errorMessages.size > 0 ? `ビルドエラー:\\n${[...errorMessages].join('\\n')}` : '';\n}\n\nasync function startSpringBootApplication(\n buildDir: string,\n jarPath: string\n): Promise<{ childProcess: ChildProcess; failure?: JudgeResult }> {\n const startedAt = Date.now();\n const childProcess = spawn('java', ['-jar', jarPath], {\n cwd: buildDir,\n env: { ...process.env, SPRING_MAIN_WEB_APPLICATION_TYPE: 'servlet' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let applicationStdout = '';\n let applicationStderr = '';\n childProcess.stdout?.on('data', (chunk: Buffer) => {\n applicationStdout = truncateOutput(applicationStdout + chunk.toString('utf8'));\n });\n childProcess.stderr?.on('data', (chunk: Buffer) => {\n applicationStderr = truncateOutput(applicationStderr + chunk.toString('utf8'));\n });\n let hasExited = false;\n childProcess.on('exit', () => {\n hasExited = true;\n });\n\n while (Date.now() - startedAt < STARTUP_TIMEOUT_MS) {\n if (await isServerResponding()) {\n return { childProcess };\n }\n if (hasExited) {\n break;\n }\n await sleep(200);\n }\n\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const feedbackMarkdown = hasExited\n ? 'アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。'\n : `アプリケーションが ${STARTUP_TIMEOUT_MS / 1000} 秒以内に起動しませんでした。`;\n return {\n childProcess,\n failure: {\n decisionCode: hasExited ? DecisionCode.RUNTIME_ERROR : DecisionCode.TIME_LIMIT_EXCEEDED,\n exitStatus: childProcess.exitCode ?? undefined,\n feedbackMarkdown,\n stderr: applicationStderr || undefined,\n stdout: applicationStdout || undefined,\n timeSeconds,\n },\n };\n}\n\nasync function isServerResponding(): Promise<boolean> {\n try {\n // Accept 404 so applications without a root endpoint can finish startup.\n const response = await fetch(SERVER_BASE_URL, { signal: AbortSignal.timeout(1000) });\n await response.body?.cancel();\n return response.ok || response.status === 404;\n } catch {\n return false;\n }\n}\n\nasync function stopSpringBootApplication(childProcess: ChildProcess | undefined): Promise<void> {\n if (!childProcess || childProcess.exitCode !== null) {\n return;\n }\n\n childProcess.kill('SIGTERM');\n const startedAt = Date.now();\n while (Date.now() - startedAt < 5000 && childProcess.exitCode === null) {\n await sleep(100);\n }\n if (childProcess.exitCode === null) {\n childProcess.kill('SIGKILL');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const startedAt = Date.now();\n const result = spawnSync('bun', ['run', './judge.ts', '--evaluate'], {\n cwd: problemDirectoryPath,\n encoding: 'utf8',\n env: { ...process.env, WORKING_DIRECTORY_PATH: buildDir },\n timeout: EVALUATE_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds, outputFiles };\n }\n if ((result.status ?? 0) !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n exitStatus: result.status ?? undefined,\n stderr,\n stdout,\n timeSeconds,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n exitStatus: result.status ?? undefined,\n stderr: stderr || undefined,\n stdout: stdout || undefined,\n timeSeconds,\n outputFiles,\n };\n}\n\nexport function buildSpringBootUrl(urlPath: string): string {\n return `${SERVER_BASE_URL}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"springBoot.js","names":["sleep","fs","path","APPLICATION_NAME","SERVER_PORT","SERVER_BASE_URL","STARTUP_TIMEOUT_MS","springBootJudgePreset","options","error","getErrorMessage","submissionDir","parseArgs","testCaseId","runRootDir","tmpdir","buildDir","springBootProcess","prepareBuildDirectory","buildResult","buildWithMaven","printTestCaseResult","startResult","startSpringBootApplication","judgeResult","runJudgeScript","DecisionCode","stopSpringBootApplication","problemDirectoryPath","createApplicationProperties","relativePath","listSubmissionFiles","sourcePath","targetPath","resolveTargetPath","normalizedRelativePath","resolveJavaRelativePath","startedAt","result","spawnSync","MAX_OUTPUT_LENGTH","timeSeconds","stdout","truncateOutput","stderr","hasErrorCode","extractMavenErrors","plainOutput","output","errorMessages","match","message","childProcess","spawn","jarPath","applicationStdout","applicationStderr","chunk","hasExited","isServerResponding","setTimeout","feedbackMarkdown","response","outputFiles","readOutputFiles","buildSpringBootUrl","urlPath"],"sources":["../../src/presets/springBoot.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { getErrorMessage, hasErrorCode } from '@willbooster/shared-lib';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nexport interface SpringBootJudgePresetOptions {\n problemDirectoryPath: string;\n evaluate: () => Promise<void>;\n}\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\nconst APPLICATION_NAME = 'judge';\n\nconst SERVER_PORT = 59_000;\n\nconst SERVER_BASE_URL = `http://localhost:${SERVER_PORT}`;\n\nconst BUILD_TIMEOUT_MS = 90_000;\n\nconst STARTUP_TIMEOUT_MS = 60_000;\n\nconst EVALUATE_TIMEOUT_MS = 60_000;\n\nexport async function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(getErrorMessage(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'spring_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let springBootProcess: ChildProcess | undefined;\n\n try {\n await prepareBuildDirectory(buildDir, submissionDir, options.problemDirectoryPath);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n const jarPath = path.join(buildDir, 'target', `${APPLICATION_NAME}.jar`);\n const startResult = await startSpringBootApplication(buildDir, jarPath);\n springBootProcess = startResult.childProcess;\n if (startResult.failure) {\n printTestCaseResult({ testCaseId, ...startResult.failure });\n return;\n }\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: getErrorMessage(error),\n });\n } finally {\n await stopSpringBootApplication(springBootProcess);\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n problemDirectoryPath: string\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/resources'), { recursive: true });\n await fs.promises.copyFile(path.join(problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n await fs.promises.writeFile(\n path.join(buildDir, 'src/main/resources/application.properties'),\n createApplicationProperties()\n );\n\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nfunction createApplicationProperties(): string {\n return `spring.application.name=${APPLICATION_NAME}\nserver.port=${SERVER_PORT}\nspring.main.banner-mode=off\nlogging.level.org.springframework=ERROR\nlogging.level.org.apache=ERROR\nspring.main.log-startup-info=false\n`;\n}\n\nasync function resolveTargetPath(buildDir: string, relativePath: string, sourcePath: string): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (path.extname(relativePath).toLowerCase() === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n\n return path.join(buildDir, 'src/main/resources', normalizedRelativePath);\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const startedAt = Date.now();\n const result = spawnSync('mvn', ['clean', 'package', '--quiet', '--batch-mode', '--offline', '-DskipTests'], {\n cwd: buildDir,\n encoding: 'utf8',\n env: { ...process.env, CI: '', FORCE_COLOR: '0' },\n timeout: BUILD_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds };\n }\n if ((result.status ?? 0) === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus: result.status ?? undefined,\n stderr: truncateOutput(extractMavenErrors(stdout) || stderr),\n stdout,\n timeSeconds,\n };\n}\n\nfunction extractMavenErrors(output: string): string {\n const plainOutput = output.replaceAll(/\u001b\\[\\d*(?:;\\d+)*m/gu, '');\n const errorMessages = new Set<string>();\n for (const match of plainOutput.matchAll(/\\].*?\\[\\d+,\\d+\\]\\s+(.*?)$/gmu)) {\n const message = match[1]!.trim();\n if (message) {\n errorMessages.add(message);\n }\n }\n return errorMessages.size > 0 ? `ビルドエラー:\\n${[...errorMessages].join('\\n')}` : '';\n}\n\nasync function startSpringBootApplication(\n buildDir: string,\n jarPath: string\n): Promise<{ childProcess: ChildProcess; failure?: JudgeResult }> {\n const startedAt = Date.now();\n const childProcess = spawn('java', ['-jar', jarPath], {\n cwd: buildDir,\n env: { ...process.env, SPRING_MAIN_WEB_APPLICATION_TYPE: 'servlet' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let applicationStdout = '';\n let applicationStderr = '';\n childProcess.stdout?.on('data', (chunk: Buffer) => {\n applicationStdout = truncateOutput(applicationStdout + chunk.toString('utf8'));\n });\n childProcess.stderr?.on('data', (chunk: Buffer) => {\n applicationStderr = truncateOutput(applicationStderr + chunk.toString('utf8'));\n });\n let hasExited = false;\n childProcess.on('exit', () => {\n hasExited = true;\n });\n\n while (Date.now() - startedAt < STARTUP_TIMEOUT_MS) {\n if (await isServerResponding()) {\n return { childProcess };\n }\n if (hasExited) {\n break;\n }\n await sleep(200);\n }\n\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const feedbackMarkdown = hasExited\n ? 'アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。'\n : `アプリケーションが ${STARTUP_TIMEOUT_MS / 1000} 秒以内に起動しませんでした。`;\n return {\n childProcess,\n failure: {\n decisionCode: hasExited ? DecisionCode.RUNTIME_ERROR : DecisionCode.TIME_LIMIT_EXCEEDED,\n exitStatus: childProcess.exitCode ?? undefined,\n feedbackMarkdown,\n stderr: applicationStderr || undefined,\n stdout: applicationStdout || undefined,\n timeSeconds,\n },\n };\n}\n\nasync function isServerResponding(): Promise<boolean> {\n try {\n // Accept 404 so applications without a root endpoint can finish startup.\n const response = await fetch(SERVER_BASE_URL, { signal: AbortSignal.timeout(1000) });\n await response.body?.cancel();\n return response.ok || response.status === 404;\n } catch {\n return false;\n }\n}\n\nasync function stopSpringBootApplication(childProcess: ChildProcess | undefined): Promise<void> {\n if (!childProcess || childProcess.exitCode !== null) {\n return;\n }\n\n childProcess.kill('SIGTERM');\n const startedAt = Date.now();\n while (Date.now() - startedAt < 5000 && childProcess.exitCode === null) {\n await sleep(100);\n }\n if (childProcess.exitCode === null) {\n childProcess.kill('SIGKILL');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const startedAt = Date.now();\n const result = spawnSync('bun', ['run', './judge.ts', '--evaluate'], {\n cwd: problemDirectoryPath,\n encoding: 'utf8',\n env: { ...process.env, WORKING_DIRECTORY_PATH: buildDir },\n timeout: EVALUATE_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds, outputFiles };\n }\n if ((result.status ?? 0) !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n exitStatus: result.status ?? undefined,\n stderr,\n stdout,\n timeSeconds,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n exitStatus: result.status ?? undefined,\n stderr: stderr || undefined,\n stdout: stdout || undefined,\n timeSeconds,\n outputFiles,\n };\n}\n\nexport function buildSpringBootUrl(urlPath: string): string {\n return `${SERVER_BASE_URL}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"springBoot.js","names":["sleep","fs","path","APPLICATION_NAME","SERVER_PORT","SERVER_BASE_URL","STARTUP_TIMEOUT_MS","springBootJudgePreset","options","error","getErrorMessage","submissionDir","parseArgs","testCaseId","runRootDir","tmpdir","buildDir","springBootProcess","prepareBuildDirectory","buildResult","buildWithMaven","printTestCaseResult","startResult","startSpringBootApplication","judgeResult","runJudgeScript","DecisionCode","stopSpringBootApplication","problemDirectoryPath","createApplicationProperties","relativePath","listSubmissionFiles","sourcePath","targetPath","resolveTargetPath","normalizedRelativePath","resolveJavaRelativePath","startedAt","result","spawnSync","MAX_OUTPUT_LENGTH","timeSeconds","stdout","truncateOutput","stderr","hasErrorCode","extractMavenErrors","plainOutput","output","errorMessages","match","message","childProcess","spawn","jarPath","applicationStdout","applicationStderr","chunk","hasExited","isServerResponding","setTimeout","feedbackMarkdown","response","outputFiles","readOutputFiles","buildSpringBootUrl","urlPath"],"sources":["../../src/presets/springBoot.ts"],"sourcesContent":["import { type ChildProcess, spawn, spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { getErrorMessage, hasErrorCode } from '@willbooster/shared-lib';\n\nimport {\n listSubmissionFiles,\n MAX_OUTPUT_LENGTH,\n readOutputFiles,\n resolveJavaRelativePath,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\nexport interface SpringBootJudgePresetOptions {\n problemDirectoryPath: string;\n evaluate: () => Promise<void>;\n}\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\nconst APPLICATION_NAME = 'judge';\n\nconst SERVER_PORT = 59_000;\n\nconst SERVER_BASE_URL = `http://localhost:${SERVER_PORT}`;\n\nconst BUILD_TIMEOUT_MS = 90_000;\n\nconst STARTUP_TIMEOUT_MS = 60_000;\n\nconst EVALUATE_TIMEOUT_MS = 60_000;\n\nexport async function springBootJudgePreset(options: SpringBootJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(getErrorMessage(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'spring_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let springBootProcess: ChildProcess | undefined;\n\n try {\n await prepareBuildDirectory(buildDir, submissionDir, options.problemDirectoryPath);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n const jarPath = path.join(buildDir, 'target', `${APPLICATION_NAME}.jar`);\n const startResult = await startSpringBootApplication(buildDir, jarPath);\n springBootProcess = startResult.childProcess;\n if (startResult.failure) {\n printTestCaseResult({ testCaseId, ...startResult.failure });\n return;\n }\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: getErrorMessage(error),\n });\n } finally {\n await stopSpringBootApplication(springBootProcess);\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n problemDirectoryPath: string\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/resources'), { recursive: true });\n await fs.promises.copyFile(path.join(problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n await fs.promises.writeFile(\n path.join(buildDir, 'src/main/resources/application.properties'),\n createApplicationProperties()\n );\n\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nfunction createApplicationProperties(): string {\n return `spring.application.name=${APPLICATION_NAME}\nserver.port=${SERVER_PORT}\nspring.main.banner-mode=off\nlogging.level.org.springframework=ERROR\nlogging.level.org.apache=ERROR\nspring.main.log-startup-info=false\n`;\n}\n\nasync function resolveTargetPath(buildDir: string, relativePath: string, sourcePath: string): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (path.extname(relativePath).toLowerCase() === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n\n return path.join(buildDir, 'src/main/resources', normalizedRelativePath);\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const startedAt = Date.now();\n const result = spawnSync('mvn', ['clean', 'package', '--quiet', '--batch-mode', '--offline', '-DskipTests'], {\n cwd: buildDir,\n encoding: 'utf8',\n env: { ...process.env, CI: '', FORCE_COLOR: '0' },\n timeout: BUILD_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds };\n }\n if ((result.status ?? 0) === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus: result.status ?? undefined,\n stderr: truncateOutput(extractMavenErrors(stdout) || stderr),\n stdout,\n timeSeconds,\n };\n}\n\nfunction extractMavenErrors(output: string): string {\n const plainOutput = output.replaceAll(/\u001b\\[\\d*(?:;\\d+)*m/gu, '');\n const errorMessages = new Set<string>();\n for (const match of plainOutput.matchAll(/\\].*?\\[\\d+,\\d+\\]\\s+(.*?)$/gmu)) {\n const message = match[1]!.trim();\n if (message) {\n errorMessages.add(message);\n }\n }\n return errorMessages.size > 0 ? `ビルドエラー:\\n${[...errorMessages].join('\\n')}` : '';\n}\n\nasync function startSpringBootApplication(\n buildDir: string,\n jarPath: string\n): Promise<{ childProcess: ChildProcess; failure?: JudgeResult }> {\n const startedAt = Date.now();\n const childProcess = spawn('java', ['-jar', jarPath], {\n cwd: buildDir,\n env: { ...process.env, SPRING_MAIN_WEB_APPLICATION_TYPE: 'servlet' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let applicationStdout = '';\n let applicationStderr = '';\n childProcess.stdout?.on('data', (chunk: Buffer) => {\n applicationStdout = truncateOutput(applicationStdout + chunk.toString('utf8'));\n });\n childProcess.stderr?.on('data', (chunk: Buffer) => {\n applicationStderr = truncateOutput(applicationStderr + chunk.toString('utf8'));\n });\n let hasExited = false;\n childProcess.on('exit', () => {\n hasExited = true;\n });\n\n while (Date.now() - startedAt < STARTUP_TIMEOUT_MS) {\n if (await isServerResponding()) {\n return { childProcess };\n }\n if (hasExited) {\n break;\n }\n await sleep(200);\n }\n\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const feedbackMarkdown = hasExited\n ? 'アプリケーションの起動に失敗しました。コンソールのエラーメッセージを確認してください。'\n : `アプリケーションが ${STARTUP_TIMEOUT_MS / 1000} 秒以内に起動しませんでした。`;\n return {\n childProcess,\n failure: {\n decisionCode: hasExited ? DecisionCode.RUNTIME_ERROR : DecisionCode.TIME_LIMIT_EXCEEDED,\n exitStatus: childProcess.exitCode ?? undefined,\n feedbackMarkdown,\n stderr: applicationStderr || undefined,\n stdout: applicationStdout || undefined,\n timeSeconds,\n },\n };\n}\n\nasync function isServerResponding(): Promise<boolean> {\n try {\n // Accept 404 so applications without a root endpoint can finish startup.\n const response = await fetch(SERVER_BASE_URL, { signal: AbortSignal.timeout(1000) });\n await response.body?.cancel();\n return response.ok || response.status === 404;\n } catch {\n return false;\n }\n}\n\nasync function stopSpringBootApplication(childProcess: ChildProcess | undefined): Promise<void> {\n if (!childProcess || childProcess.exitCode !== null) {\n return;\n }\n\n childProcess.kill('SIGTERM');\n const startedAt = Date.now();\n while (Date.now() - startedAt < 5000 && childProcess.exitCode === null) {\n await sleep(100);\n }\n if (childProcess.exitCode === null) {\n childProcess.kill('SIGKILL');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const startedAt = Date.now();\n const result = spawnSync('bun', ['run', './judge.ts', '--evaluate'], {\n cwd: problemDirectoryPath,\n encoding: 'utf8',\n env: { ...process.env, WORKING_DIRECTORY_PATH: buildDir },\n timeout: EVALUATE_TIMEOUT_MS,\n maxBuffer: MAX_OUTPUT_LENGTH * 4,\n });\n const timeSeconds = (Date.now() - startedAt) / 1000;\n const stdout = truncateOutput(result.stdout ?? '');\n const stderr = truncateOutput(result.stderr ?? '');\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n\n if (hasErrorCode(result.error, 'ETIMEDOUT')) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr, stdout, timeSeconds, outputFiles };\n }\n if ((result.status ?? 0) !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n exitStatus: result.status ?? undefined,\n stderr,\n stdout,\n timeSeconds,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n exitStatus: result.status ?? undefined,\n stderr: stderr || undefined,\n stdout: stdout || undefined,\n timeSeconds,\n outputFiles,\n };\n}\n\nexport function buildSpringBootUrl(urlPath: string): string {\n return `${SERVER_BASE_URL}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.cjs","names":["z","parseArgs","readProblemMarkdownFrontMatter","readTestCases","path","judgesTestCasesWithoutExpectations","judgeByStaticAnalysis","findEntryPointFile","DecisionCode","findLanguageDefinitionByPath","getErrorMessage","spawnWithTimeout","snapshotWorkingDirectory","copyTestCaseFileInput","readOutputFiles","judgeAgainstExpectations","cleanWorkingDirectory","fs","copyProblemDirToTemporaryRoot","forciblyRemoveDirectory","EXAMPLE_TEST_CASE_ID_PATTERN","require_runtime","require_printTestCaseResult","require_compareExpectedOutputFiles","require_parseArgs","require_readTestCases","require_temporaryProblemDirCopy","require_findLanguageDefinitionByPath","require_readProblemMarkdownFrontMatter","require_stdioJudgeRules","require_decisionCode","require_cleanWorkingDirectory","require_copyTestCaseFileInput","require_findEntryPointFile","require_judgeByStaticAnalysis","require_readOutputFiles","require_spawnWithTimeout","node_fs","node_path","zod","_willbooster_shared_lib","judgeParamsSchema","debugParamsSchema","stdioJudgePreset","args","params","problemMarkdownFrontMatter","problemDir","testCases","testCase","hasExpectedFiles","staticAnalysisTestCaseResult","originalMainFilePath","languageDefinition","env","prebuiltMainFilePath","error","mainFilePath","buildCommand","buildSpawnResult","decisionCode","cwdSnapshot","timeoutSeconds","command","spawnResult","outputFiles","judgementError","judgement","fileOutputPath","dirent","stdioDebugPreset","options","debugInWorkingDirectory","tempRoot","removeOnSignal","root","cwd","exampleFileInputPath"],"sources":["../../src/presets/stdio.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { getErrorMessage } from '@willbooster/shared-lib';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.js';\nimport { judgeAgainstExpectations } from '../helpers/compareExpectedOutputFiles.js';\nimport { copyTestCaseFileInput } from '../helpers/copyTestCaseFileInput.js';\nimport { findEntryPointFile } from '../helpers/findEntryPointFile.js';\nimport { findLanguageDefinitionByPath } from '../helpers/findLanguageDefinitionByPath.js';\nimport { judgeByStaticAnalysis } from '../helpers/judgeByStaticAnalysis.js';\nimport { parseArgs } from '../helpers/parseArgs.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport {\n judgesTestCasesWithoutExpectations,\n readProblemMarkdownFrontMatter,\n} from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { spawnWithTimeout } from '../helpers/spawnWithTimeout.js';\nimport { EXAMPLE_TEST_CASE_ID_PATTERN, MAX_STDOUT_LENGTH } from '../helpers/stdioJudgeRules.js';\nimport {\n copyProblemDirToTemporaryRoot,\n forciblyRemoveDirectory,\n forciblyRemoveDirectorySync,\n} from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 2;\nconst DEBUG_DEFAULT_TIMEOUT_SECONDS = 10;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\nconst debugParamsSchema = judgeParamsSchema.extend({\n stdin: z.string().optional(),\n});\n\ntype DebugParams = z.infer<typeof debugParamsSchema>;\n\n/**\n * A preset judge function using stdin and stdout as test cases.\n *\n * A standard stdio problem must NOT commit a `judge.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `judge.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `judge.ts`:\n * ```bash\n * bun x exercode-problem judge model_answers/java\n * ```\n *\n * Run with the optional parameters:\n * ```bash\n * bun x exercode-problem judge model_answers/java '{ \"language\": \"javascript\" }'\n * ```\n */\nexport async function stdioJudgePreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = judgeParamsSchema.parse(args.params);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n\n // Without an expectation, a case would accept any run; only custom harnesses may judge input-only\n // cases. A problem judged by manual scoring or the presence of required output files has an\n // expectation of its own. Checked before the static analysis so that an authoring error surfaces\n // regardless of the submission.\n if (!judgesTestCasesWithoutExpectations(problemMarkdownFrontMatter)) {\n for (const testCase of testCases) {\n if (testCase.output === undefined && !(await hasExpectedFiles(testCase.fileOutputPath))) {\n throw new Error(\n `test case ${testCase.id} needs an expected output (${testCase.id}.out or a non-empty ${testCase.id}.fout/)`\n );\n }\n }\n }\n\n const staticAnalysisTestCaseResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisTestCaseResult) {\n printTestCaseResult({ testCaseId: testCases[0]?.id ?? 'prebuild', ...staticAnalysisTestCaseResult });\n return;\n }\n\n const originalMainFilePath = await findEntryPointFile(args.cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n prebuiltMainFilePath = await findEntryPointFile(args.cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: args.cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n }\n\n for (const testCase of testCases) {\n // prepare test case\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n // run\n const timeoutSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd: args.cwd, stdin: testCase.input, env },\n timeoutSeconds\n );\n\n let outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n // calculate decision\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n let judgementError: string | undefined;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n } else {\n try {\n const judgement = await judgeAgainstExpectations({\n stdout: spawnResult.stdout,\n expectedStdout: testCase.output,\n fileOutputPath: testCase.fileOutputPath,\n cwd: args.cwd,\n outputFiles,\n });\n if (!judgement.matches) decisionCode = DecisionCode.WRONG_ANSWER;\n outputFiles = judgement.outputFiles;\n } catch (error) {\n // An authoring error (e.g. an oversized expected file) is reported per case, like the command preset does.\n decisionCode = DecisionCode.RUNTIME_ERROR;\n judgementError = getErrorMessage(error);\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: testCase.input,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: (judgementError ?? spawnResult.stderr).slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n // clean up\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n}\n\nasync function hasExpectedFiles(fileOutputPath: string | undefined): Promise<boolean> {\n if (fileOutputPath === undefined) return false;\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n return dirents.some((dirent) => dirent.isFile());\n}\n\n/**\n * A preset debug function using stdin and stdout as test cases. The answer directory is copied to a\n * temporary directory where it is built and run together with `_shared.fin/` and the first example\n * case's `.fin/`; files the program writes are reported only through `requiredOutputFilePaths`.\n * Callers that own and remove the working directory can set `disposableWorkingDirectory` to run\n * directly there; they are responsible for cleanup even when the harness is terminated.\n *\n * A standard stdio problem must NOT commit a `debug.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `debug.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `debug.ts`:\n * ```bash\n * bun x exercode-problem debug model_answers/java '{ \"stdin\": \"1 2\" }'\n * ```\n */\nexport async function stdioDebugPreset(\n problemDir: string,\n options: { disposableWorkingDirectory?: boolean } = {}\n): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = debugParamsSchema.parse(args.params);\n\n // The judge server already owns and removes its per-request answer directory.\n if (options.disposableWorkingDirectory) {\n await debugInWorkingDirectory(problemDir, args.cwd, params);\n return;\n }\n\n // Everything (build, input files, run) happens in a disposable copy of the answer directory, so\n // the developer's files are never touched and whatever the submission leaves behind goes with it.\n // The copy keeps the directory hierarchy and links every ancestor `node_modules`, so packages the\n // answer resolves from its parents still resolve.\n // A terminated debug run must not leave the copy behind; `finally` does not run on a signal, so\n // the handlers learn the root as soon as it is created and stay until it is removed.\n let tempRoot: string | undefined;\n const removeOnSignal = (): void => {\n if (tempRoot !== undefined) forciblyRemoveDirectorySync(tempRoot);\n process.exit(1);\n };\n process.once('SIGINT', removeOnSignal);\n process.once('SIGTERM', removeOnSignal);\n try {\n const copy = await copyProblemDirToTemporaryRoot(args.cwd, { onTempRootCreated: (root) => (tempRoot = root) });\n await debugInWorkingDirectory(problemDir, copy.copiedProblemDir, params);\n } finally {\n if (tempRoot !== undefined) await forciblyRemoveDirectory(tempRoot);\n process.off('SIGINT', removeOnSignal);\n process.off('SIGTERM', removeOnSignal);\n }\n}\n\nasync function debugInWorkingDirectory(problemDir: string, cwd: string, params: DebugParams): Promise<void> {\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n // A debug run has no test case of its own: it gets the shared input files and the `.fin/` of the\n // first example case (the cases a learner may see), placed the way the judge does. Hidden cases'\n // input files are never handed to learner code.\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, cwd);\n const exampleFileInputPath = testCases.find(\n (testCase) => EXAMPLE_TEST_CASE_ID_PATTERN.test(testCase.id) && testCase.fileInputPath\n )?.fileInputPath;\n if (exampleFileInputPath) {\n await copyTestCaseFileInput(exampleFileInputPath, cwd);\n } else if (testCases.some((testCase) => testCase.fileInputPath)) {\n console.error('debug: no example test case has input files, so the program runs without them');\n }\n\n const timeoutSeconds = Math.max(DEBUG_DEFAULT_TIMEOUT_SECONDS, (problemMarkdownFrontMatter.timeLimitMs ?? 0) / 1000);\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd, stdin: params.stdin, env },\n timeoutSeconds\n );\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n }\n\n printTestCaseResult({\n testCaseId: 'debug',\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: params.stdin,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: spawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stdio.cjs","names":["z","parseArgs","readProblemMarkdownFrontMatter","readTestCases","path","judgesTestCasesWithoutExpectations","judgeByStaticAnalysis","findEntryPointFile","DecisionCode","findLanguageDefinitionByPath","getErrorMessage","spawnWithTimeout","snapshotWorkingDirectory","copyTestCaseFileInput","readOutputFiles","judgeAgainstExpectations","cleanWorkingDirectory","fs","copyProblemDirToTemporaryRoot","forciblyRemoveDirectory","EXAMPLE_TEST_CASE_ID_PATTERN","require_runtime","require_printTestCaseResult","require_compareExpectedOutputFiles","require_parseArgs","require_readTestCases","require_temporaryProblemDirCopy","require_findLanguageDefinitionByPath","require_readProblemMarkdownFrontMatter","require_stdioJudgeRules","require_decisionCode","require_cleanWorkingDirectory","require_copyTestCaseFileInput","require_findEntryPointFile","require_judgeByStaticAnalysis","require_readOutputFiles","require_spawnWithTimeout","node_fs","node_path","zod","_willbooster_shared_lib","judgeParamsSchema","debugParamsSchema","stdioJudgePreset","args","params","problemMarkdownFrontMatter","problemDir","testCases","testCase","hasExpectedFiles","staticAnalysisTestCaseResult","originalMainFilePath","languageDefinition","env","prebuiltMainFilePath","error","mainFilePath","buildCommand","buildSpawnResult","decisionCode","cwdSnapshot","timeoutSeconds","command","spawnResult","outputFiles","judgementError","judgement","fileOutputPath","dirent","stdioDebugPreset","options","debugInWorkingDirectory","tempRoot","removeOnSignal","root","cwd","exampleFileInputPath"],"sources":["../../src/presets/stdio.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { getErrorMessage } from '@willbooster/shared-lib';\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.js';\nimport { judgeAgainstExpectations } from '../helpers/compareExpectedOutputFiles.js';\nimport { copyTestCaseFileInput } from '../helpers/copyTestCaseFileInput.js';\nimport { findEntryPointFile } from '../helpers/findEntryPointFile.js';\nimport { findLanguageDefinitionByPath } from '../helpers/findLanguageDefinitionByPath.js';\nimport { judgeByStaticAnalysis } from '../helpers/judgeByStaticAnalysis.js';\nimport { parseArgs } from '../helpers/parseArgs.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport {\n judgesTestCasesWithoutExpectations,\n readProblemMarkdownFrontMatter,\n} from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { spawnWithTimeout } from '../helpers/spawnWithTimeout.js';\nimport { EXAMPLE_TEST_CASE_ID_PATTERN, MAX_STDOUT_LENGTH } from '../helpers/stdioJudgeRules.js';\nimport {\n copyProblemDirToTemporaryRoot,\n forciblyRemoveDirectory,\n forciblyRemoveDirectorySync,\n} from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 2;\nconst DEBUG_DEFAULT_TIMEOUT_SECONDS = 10;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\nconst debugParamsSchema = judgeParamsSchema.extend({\n stdin: z.string().optional(),\n});\n\ntype DebugParams = z.infer<typeof debugParamsSchema>;\n\n/**\n * A preset judge function using stdin and stdout as test cases.\n *\n * A standard stdio problem must NOT commit a `judge.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `judge.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `judge.ts`:\n * ```bash\n * bun x exercode-problem judge model_answers/java\n * ```\n *\n * Run with the optional parameters:\n * ```bash\n * bun x exercode-problem judge model_answers/java '{ \"language\": \"javascript\" }'\n * ```\n */\nexport async function stdioJudgePreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = judgeParamsSchema.parse(args.params);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n\n // Without an expectation, a case would accept any run; only custom harnesses may judge input-only\n // cases. A problem judged by manual scoring or the presence of required output files has an\n // expectation of its own. Checked before the static analysis so that an authoring error surfaces\n // regardless of the submission.\n if (!judgesTestCasesWithoutExpectations(problemMarkdownFrontMatter)) {\n for (const testCase of testCases) {\n if (testCase.output === undefined && !(await hasExpectedFiles(testCase.fileOutputPath))) {\n throw new Error(\n `test case ${testCase.id} needs an expected output (${testCase.id}.out or a non-empty ${testCase.id}.fout/)`\n );\n }\n }\n }\n\n const staticAnalysisTestCaseResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisTestCaseResult) {\n printTestCaseResult({ testCaseId: testCases[0]?.id ?? 'prebuild', ...staticAnalysisTestCaseResult });\n return;\n }\n\n const originalMainFilePath = await findEntryPointFile(args.cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n prebuiltMainFilePath = await findEntryPointFile(args.cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: args.cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n }\n\n for (const testCase of testCases) {\n // prepare test case\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n // run\n const timeoutSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd: args.cwd, stdin: testCase.input, env },\n timeoutSeconds\n );\n\n let outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n // calculate decision\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n let judgementError: string | undefined;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n } else {\n try {\n const judgement = await judgeAgainstExpectations({\n stdout: spawnResult.stdout,\n expectedStdout: testCase.output,\n fileOutputPath: testCase.fileOutputPath,\n cwd: args.cwd,\n outputFiles,\n });\n if (!judgement.matches) decisionCode = DecisionCode.WRONG_ANSWER;\n outputFiles = judgement.outputFiles;\n } catch (error) {\n // An authoring error (e.g. an oversized expected file) is reported per case, like the command preset does.\n decisionCode = DecisionCode.RUNTIME_ERROR;\n judgementError = getErrorMessage(error);\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: testCase.input,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: (judgementError ?? spawnResult.stderr).slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n // clean up\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n}\n\nasync function hasExpectedFiles(fileOutputPath: string | undefined): Promise<boolean> {\n if (fileOutputPath === undefined) return false;\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n return dirents.some((dirent) => dirent.isFile());\n}\n\n/**\n * A preset debug function using stdin and stdout as test cases. The answer directory is copied to a\n * temporary directory where it is built and run together with `_shared.fin/` and the first example\n * case's `.fin/`; files the program writes are reported only through `requiredOutputFilePaths`.\n * Callers that own and remove the working directory can set `disposableWorkingDirectory` to run\n * directly there; they are responsible for cleanup even when the harness is terminated.\n *\n * A standard stdio problem must NOT commit a `debug.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `debug.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `debug.ts`:\n * ```bash\n * bun x exercode-problem debug model_answers/java '{ \"stdin\": \"1 2\" }'\n * ```\n */\nexport async function stdioDebugPreset(\n problemDir: string,\n options: { disposableWorkingDirectory?: boolean } = {}\n): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = debugParamsSchema.parse(args.params);\n\n // The judge server already owns and removes its per-request answer directory.\n if (options.disposableWorkingDirectory) {\n await debugInWorkingDirectory(problemDir, args.cwd, params);\n return;\n }\n\n // Everything (build, input files, run) happens in a disposable copy of the answer directory, so\n // the developer's files are never touched and whatever the submission leaves behind goes with it.\n // The copy keeps the directory hierarchy and links every ancestor `node_modules`, so packages the\n // answer resolves from its parents still resolve.\n // A terminated debug run must not leave the copy behind; `finally` does not run on a signal, so\n // the handlers learn the root as soon as it is created and stay until it is removed.\n let tempRoot: string | undefined;\n const removeOnSignal = (): void => {\n if (tempRoot !== undefined) forciblyRemoveDirectorySync(tempRoot);\n process.exit(1);\n };\n process.once('SIGINT', removeOnSignal);\n process.once('SIGTERM', removeOnSignal);\n try {\n const copy = await copyProblemDirToTemporaryRoot(args.cwd, { onTempRootCreated: (root) => (tempRoot = root) });\n await debugInWorkingDirectory(problemDir, copy.copiedProblemDir, params);\n } finally {\n if (tempRoot !== undefined) await forciblyRemoveDirectory(tempRoot);\n process.off('SIGINT', removeOnSignal);\n process.off('SIGTERM', removeOnSignal);\n }\n}\n\nasync function debugInWorkingDirectory(problemDir: string, cwd: string, params: DebugParams): Promise<void> {\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n // A debug run has no test case of its own: it gets the shared input files and the `.fin/` of the\n // first example case (the cases a learner may see), placed the way the judge does. Hidden cases'\n // input files are never handed to learner code.\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, cwd);\n const exampleFileInputPath = testCases.find(\n (testCase) => EXAMPLE_TEST_CASE_ID_PATTERN.test(testCase.id) && testCase.fileInputPath\n )?.fileInputPath;\n if (exampleFileInputPath) {\n await copyTestCaseFileInput(exampleFileInputPath, cwd);\n } else if (testCases.some((testCase) => testCase.fileInputPath)) {\n console.error('debug: no example test case has input files, so the program runs without them');\n }\n\n const timeoutSeconds = Math.max(DEBUG_DEFAULT_TIMEOUT_SECONDS, (problemMarkdownFrontMatter.timeLimitMs ?? 0) / 1000);\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd, stdin: params.stdin, env },\n timeoutSeconds\n );\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n }\n\n printTestCaseResult({\n testCaseId: 'debug',\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: params.stdin,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: spawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n}\n"],"mappings":"q+BA6BA,MAIMyC,EAAoBzC,EAAAA,EAAE,OAAO,CACjC,SAAUA,EAAAA,EAAE,MAAM,CAACA,EAAAA,EAAE,OAAO,EAAGA,EAAAA,EAAE,MAAMA,EAAAA,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAChE,CAAC,EAEK0C,EAAoBD,EAAkB,OAAO,CACjD,MAAOzC,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,CAC7B,CAAC,EAsBD,eAAsB2C,EAAiB,EAAmC,CACxE,IAAMC,EAAO3C,EAAAA,UAAU,QAAQ,IAAI,EACnC,GAAI,CAAC2C,EAAK,IAAK,MAAU,MAAM,uBAAuB,EACtD,IAAMC,EAASJ,EAAkB,MAAMG,EAAK,MAAM,EAE5CE,EAA6B,MAAM5C,EAAAA,+BAA+B6C,CAAU,EAC5EC,EAAY,MAAM7C,EAAAA,cAAcC,EAAAA,QAAK,KAAK2C,EAAY,YAAY,CAAC,EAMzE,GAAI,CAAC1C,EAAAA,mCAAmCyC,CAA0B,EAC3D,KAAA,IAAMG,KAAYD,EACrB,GAAIC,EAAS,SAAW,IAAA,IAAa,CAAE,MAAMC,EAAiBD,EAAS,cAAc,EACnF,MAAU,MACR,aAAaA,EAAS,GAAG,6BAA6BA,EAAS,GAAG,sBAAsBA,EAAS,GAAG,QACtG,CAAA,CAKN,IAAME,EAA+B,MAAM7C,EAAAA,sBAAsBsC,EAAK,IAAKE,CAA0B,EACrG,GAAIK,EAA8B,CAChC7B,EAAA,oBAAoB,CAAE,WAAY0B,EAAU,EAAE,EAAE,IAAM,WAAY,GAAGG,CAA6B,CAAC,EACnG,MACF,CAEA,IAAMC,EAAuB,MAAM7C,EAAAA,mBAAmBqC,EAAK,IAAKC,EAAO,QAAQ,EAC/E,GAAI,CAACO,EAAsB,CACzB9B,EAAA,oBAAoB,CAClB,WAAY0B,EAAU,EAAE,EAAE,IAAM,WAChC,aAAcxC,EAAAA,aAAa,uCAC3B,OAAQ,sBAAsBqC,EAAO,SAAW,eAAeA,EAAO,WAAa,IACrF,CAAC,EACD,MACF,CAEA,IAAMQ,EAAqB5C,EAAAA,6BAA6B2C,CAAoB,EAC5E,GAAI,CAACC,EAAoB,CACvB/B,EAAA,oBAAoB,CAClB,WAAY0B,EAAU,EAAE,EAAE,IAAM,WAChC,aAAcxC,EAAAA,aAAa,aAC3B,OAAQ,sBACV,CAAC,EACD,MACF,CAGA,IAAM8C,EAAM,CAAE,GAAG,QAAQ,IAAK,GAAI,GAAI,YAAa,GAAI,EAEnDC,EAEJ,GAAIF,EAAmB,SACrB,GAAI,CACF,MAAMA,EAAmB,SAAST,EAAK,GAAG,EAC1C,EAAuB,MAAMrC,EAAAA,mBAAmBqC,EAAK,IAAKC,EAAO,QAAQ,CAC3E,OAASW,EAAO,CACd,QAAQ,MAAM,iBAAkBA,CAAK,EAErClC,EAAA,oBAAoB,CAClB,WAAY0B,EAAU,EAAE,EAAE,IAAM,WAChC,aAAcxC,EAAAA,aAAa,YAC3B,QAAA,EAAQE,EAAAA,gBAAAA,CAAgB8C,CAAK,CAC/B,CAAC,EACD,MACF,CAGF,IAAMC,EAAeF,GAAwBH,EAE7C,GAAIC,EAAmB,aAAc,CACnC,IAAMK,EAAeL,EAAmB,aAAaI,CAAY,EAI3DE,EAAmB,MAAMhD,EAAAA,iBAC7B+C,EAAa,GACbA,EAAa,MAAM,CAAC,EACpB,CAAE,IAAKd,EAAK,IAAK,KAAI,EACrB,EACF,EAEIgB,EAA6BpD,EAAAA,aAAa,SAa9C,GAXImD,EAAiB,SAAW,EAErBA,EAAiB,YAAc,GACxC,EAAenD,EAAAA,aAAa,2BAE5BmD,EAAiB,OAAO,OAAA,KACxBA,EAAiB,OAAO,OAAA,OAExB,EAAenD,EAAAA,aAAa,kCAP5B,EAAeA,EAAAA,aAAa,YAU1BoD,IAAiBpD,EAAAA,aAAa,SAAU,CAC1Cc,EAAA,oBAAoB,CAClB,WAAY0B,EAAU,EAAE,EAAE,IAAM,QAChC,eACA,WAAYW,EAAiB,QAAU,IAAA,GACvC,OAAQA,EAAiB,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC/D,OAAQA,EAAiB,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC/D,YAAaA,EAAiB,YAC9B,eAAgBA,EAAiB,eACjC,YAAaA,EAAiB,WAChC,CAAC,EACD,MACF,CACF,CAEA,IAAME,EAAc,MAAMjD,EAAAA,yBAAyBgC,EAAK,GAAG,EAEvDI,EAAU,SAAW,GACvB1B,EAAA,oBAAoB,CAAE,WAAY,UAAW,aAAcd,EAAAA,aAAa,QAAS,CAAC,EAGpF,IAAK,IAAMyC,KAAYD,EAAW,CAE5BA,EAAU,QAAQ,eAAe,MAAMnC,EAAAA,sBAAsBmC,EAAU,OAAO,cAAeJ,EAAK,GAAG,EACrGK,EAAS,eAAe,MAAMpC,EAAAA,sBAAsBoC,EAAS,cAAeL,EAAK,GAAG,EAGxF,IAAMkB,EACJ,OAAOhB,EAA2B,aAAgB,SAC9CA,EAA2B,YAAc,IACzC,EAEAiB,EAAUV,EAAmB,QAAQI,CAAY,EAEjDO,EAAc,MAAMrD,EAAAA,iBACxBoD,EAAQ,GACRA,EAAQ,MAAM,CAAC,EACf,CAAE,IAAKnB,EAAK,IAAK,MAAOK,EAAS,MAAO,KAAI,EAC5Ca,CACF,EAEIG,EAAc,MAAMnD,EAAAA,gBAAgB8B,EAAK,IAAKE,EAA2B,yBAA2B,CAAC,CAAC,EAGtGc,EAA6BpD,EAAAA,aAAa,SAC1C0D,EAEJ,GAAIF,EAAY,SAAW,EACzB,EAAexD,EAAAA,aAAa,mBACvB,GAAIwD,EAAY,YAAcF,EACnC,EAAetD,EAAAA,aAAa,yBACvB,GAAIwD,EAAY,aAAelB,EAA2B,iBAAmB,KAClF,EAAetC,EAAAA,aAAa,2BACvB,GAAIwD,EAAY,OAAO,OAAA,KAA8BA,EAAY,OAAO,OAAA,IAC7E,EAAexD,EAAAA,aAAa,gCACvB,GAAIyD,EAAY,QAAUnB,EAA2B,yBAAyB,QAAU,GAC7F,EAAetC,EAAAA,aAAa,wCAE5B,GAAI,CACF,IAAM2D,EAAY,MAAMpD,EAAAA,yBAAyB,CAC/C,OAAQiD,EAAY,OACpB,eAAgBf,EAAS,OACzB,eAAgBA,EAAS,eACzB,IAAKL,EAAK,IACV,aACF,CAAC,EACIuB,EAAU,UAAS,EAAe3D,EAAAA,aAAa,cACpD,EAAc2D,EAAU,WAC1B,OAASX,EAAO,CAEd,EAAehD,EAAAA,aAAa,cAC5B,GAAA,EAAiBE,EAAAA,gBAAAA,CAAgB8C,CAAK,CACxC,CAmBF,GAhBAlC,EAAA,oBAAoB,CAClB,WAAY2B,EAAS,GACrB,eACA,WAAYe,EAAY,QAAU,IAAA,GAClC,MAAOf,EAAS,MAChB,OAAQe,EAAY,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC1D,QAASE,GAAkBF,EAAY,OAAA,CAAQ,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC9E,YAAaA,EAAY,YACzB,eAAgBA,EAAY,eAC5B,YAAaA,EAAY,YACzB,YAAaC,EAAY,OAAS,EAAIA,EAAc,IAAA,EACtD,CAAC,EAGD,MAAMjD,EAAAA,sBAAsB4B,EAAK,IAAKiB,CAAW,EAE7CD,IAAiBpD,EAAAA,aAAa,SAAU,KAC9C,CACF,CAEA,eAAe0C,EAAiB,EAAsD,CAGpF,OAFIkB,IAAmB,IAAA,KAEhB,MADenD,EAAAA,QAAG,SAAS,QAAQmD,EAAgB,CAAE,cAAe,GAAM,UAAW,EAAK,CAAC,EAAA,CACnF,KAAM,GAAWC,EAAO,OAAO,CAAC,CACjD,CAmBA,eAAsBC,EACpB,EACA,EAAoD,CAAC,EACtC,CACf,IAAM1B,EAAO3C,EAAAA,UAAU,QAAQ,IAAI,EACnC,GAAI,CAAC2C,EAAK,IAAK,MAAU,MAAM,uBAAuB,EACtD,IAAMC,EAASH,EAAkB,MAAME,EAAK,MAAM,EAGlD,GAAI2B,EAAQ,2BAA4B,CACtC,MAAMC,EAAwBzB,EAAYH,EAAK,IAAKC,CAAM,EAC1D,MACF,CAQA,IAAI4B,EACEC,MAA6B,CAC7BD,IAAa,IAAA,IAAW/C,EAAA,4BAA4B+C,CAAQ,EAChE,QAAQ,KAAK,CAAC,CAChB,EACA,QAAQ,KAAK,SAAUC,CAAc,EACrC,QAAQ,KAAK,UAAWA,CAAc,EACtC,GAAI,CAEF,MAAMF,EAAwBzB,GAAY,MADvB7B,EAAAA,8BAA8B0B,EAAK,IAAK,CAAE,kBAAoB,GAAU,EAAW+B,CAAM,CAAC,EAAA,CAC9D,iBAAkB9B,CAAM,CACzE,QAAU,CACJ4B,IAAa,IAAA,IAAW,MAAMtD,EAAAA,wBAAwBsD,CAAQ,EAClE,QAAQ,IAAI,SAAUC,CAAc,EACpC,QAAQ,IAAI,UAAWA,CAAc,CACvC,CACF,CAEA,eAAeF,EAAwB,EAAoB,EAAa,EAAoC,CAC1G,IAAM1B,EAA6B,MAAM5C,EAAAA,+BAA+B6C,CAAU,EAE5EK,EAAuB,MAAM7C,EAAAA,mBAAmBqE,EAAK/B,EAAO,QAAQ,EAC1E,GAAI,CAACO,EAAsB,CACzB9B,EAAA,oBAAoB,CAClB,WAAY,WACZ,aAAcd,EAAAA,aAAa,uCAC3B,OAAQ,sBAAsBqC,EAAO,SAAW,eAAeA,EAAO,WAAa,IACrF,CAAC,EACD,MACF,CAEA,IAAMQ,EAAqB5C,EAAAA,6BAA6B2C,CAAoB,EAC5E,GAAI,CAACC,EAAoB,CACvB/B,EAAA,oBAAoB,CAClB,WAAY,WACZ,aAAcd,EAAAA,aAAa,aAC3B,OAAQ,sBACV,CAAC,EACD,MACF,CAGA,IAAM8C,EAAM,CAAE,GAAG,QAAQ,IAAK,GAAI,GAAI,YAAa,GAAI,EAEnDC,EAEJ,GAAIF,EAAmB,SACrB,GAAI,CACF,MAAMA,EAAmB,SAASuB,CAAG,EACrC,EAAuB,MAAMrE,EAAAA,mBAAmBqE,EAAK/B,EAAO,QAAQ,CACtE,OAASW,EAAO,CACd,QAAQ,MAAM,iBAAkBA,CAAK,EAErClC,EAAA,oBAAoB,CAClB,WAAY,WACZ,aAAcd,EAAAA,aAAa,YAC3B,QAAA,EAAQE,EAAAA,gBAAAA,CAAgB8C,CAAK,CAC/B,CAAC,EACD,MACF,CAGF,IAAMC,EAAeF,GAAwBH,EAE7C,GAAIC,EAAmB,aAAc,CACnC,IAAMK,EAAeL,EAAmB,aAAaI,CAAY,EAI3DE,EAAmB,MAAMhD,EAAAA,iBAC7B+C,EAAa,GACbA,EAAa,MAAM,CAAC,EACpB,CAAE,MAAK,KAAI,EACX,EACF,EAEIE,EAA6BpD,EAAAA,aAAa,SAa9C,GAXImD,EAAiB,SAAW,EAErBA,EAAiB,YAAc,GACxC,EAAenD,EAAAA,aAAa,2BAE5BmD,EAAiB,OAAO,OAAA,KACxBA,EAAiB,OAAO,OAAA,OAExB,EAAenD,EAAAA,aAAa,kCAP5B,EAAeA,EAAAA,aAAa,YAU1BoD,IAAiBpD,EAAAA,aAAa,SAAU,CAC1Cc,EAAA,oBAAoB,CAClB,WAAY,QACZ,eACA,WAAYqC,EAAiB,QAAU,IAAA,GACvC,OAAQA,EAAiB,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC/D,OAAQA,EAAiB,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC/D,YAAaA,EAAiB,YAC9B,eAAgBA,EAAiB,eACjC,YAAaA,EAAiB,WAChC,CAAC,EACD,MACF,CACF,CAKA,IAAMX,EAAY,MAAM7C,EAAAA,cAAcC,EAAAA,QAAK,KAAK2C,EAAY,YAAY,CAAC,EACrEC,EAAU,QAAQ,eAAe,MAAMnC,EAAAA,sBAAsBmC,EAAU,OAAO,cAAe4B,CAAG,EACpG,IAAMC,EAAuB7B,EAAU,KACpC,GAAa5B,EAAAA,6BAA6B,KAAK6B,EAAS,EAAE,GAAKA,EAAS,aAC3E,CAAC,EAAE,cACC4B,EACF,MAAMhE,EAAAA,sBAAsBgE,EAAsBD,CAAG,EAC5C5B,EAAU,KAAM,GAAaC,EAAS,aAAa,GAC5D,QAAQ,MAAM,+EAA+E,EAG/F,IAAMa,EAAiB,KAAK,IAAI,IAAgChB,EAA2B,aAAe,GAAK,GAAI,EAE7GiB,EAAUV,EAAmB,QAAQI,CAAY,EAEjDO,EAAc,MAAMrD,EAAAA,iBACxBoD,EAAQ,GACRA,EAAQ,MAAM,CAAC,EACf,CAAE,MAAK,MAAOlB,EAAO,MAAO,KAAI,EAChCiB,CACF,EAEMG,EAAc,MAAMnD,EAAAA,gBAAgB8D,EAAK9B,EAA2B,yBAA2B,CAAC,CAAC,EAEnGc,EAA6BpD,EAAAA,aAAa,SAE1CwD,EAAY,SAAW,EAEhBA,EAAY,YAAcF,EACnC,EAAetD,EAAAA,aAAa,oBACnBwD,EAAY,aAAelB,EAA2B,iBAAmB,KAClF,EAAetC,EAAAA,aAAa,sBACnBwD,EAAY,OAAO,OAAA,KAA8BA,EAAY,OAAO,OAAA,IAC7E,EAAexD,EAAAA,aAAa,2BACnByD,EAAY,QAAUnB,EAA2B,yBAAyB,QAAU,KAC7F,EAAetC,EAAAA,aAAa,oCAR5B,EAAeA,EAAAA,aAAa,cAW9Bc,EAAA,oBAAoB,CAClB,WAAY,QACZ,eACA,WAAY0C,EAAY,QAAU,IAAA,GAClC,MAAOnB,EAAO,MACd,OAAQmB,EAAY,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC1D,OAAQA,EAAY,OAAO,MAAM,EAAA,GAAoB,GAAK,IAAA,GAC1D,YAAaA,EAAY,YACzB,eAAgBA,EAAY,eAC5B,YAAaA,EAAY,YACzB,YAAaC,EAAY,OAAS,EAAIA,EAAc,IAAA,EACtD,CAAC,CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","names":["fs","path","judgeParamsSchema","z","debugParamsSchema","stdioJudgePreset","args","parseArgs","params","problemMarkdownFrontMatter","readProblemMarkdownFrontMatter","problemDir","testCases","readTestCases","judgesTestCasesWithoutExpectations","testCase","hasExpectedFiles","staticAnalysisTestCaseResult","judgeByStaticAnalysis","printTestCaseResult","originalMainFilePath","findEntryPointFile","DecisionCode","languageDefinition","findLanguageDefinitionByPath","env","prebuiltMainFilePath","error","getErrorMessage","mainFilePath","buildCommand","buildSpawnResult","spawnWithTimeout","decisionCode","cwdSnapshot","snapshotWorkingDirectory","copyTestCaseFileInput","timeoutSeconds","command","spawnResult","outputFiles","readOutputFiles","judgementError","judgement","judgeAgainstExpectations","cleanWorkingDirectory","fileOutputPath","dirent","stdioDebugPreset","options","debugInWorkingDirectory","tempRoot","removeOnSignal","forciblyRemoveDirectorySync","copyProblemDirToTemporaryRoot","root","forciblyRemoveDirectory","cwd","exampleFileInputPath","EXAMPLE_TEST_CASE_ID_PATTERN"],"sources":["../../src/presets/stdio.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { getErrorMessage } from '@willbooster/shared-lib';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.js';\nimport { judgeAgainstExpectations } from '../helpers/compareExpectedOutputFiles.js';\nimport { copyTestCaseFileInput } from '../helpers/copyTestCaseFileInput.js';\nimport { findEntryPointFile } from '../helpers/findEntryPointFile.js';\nimport { findLanguageDefinitionByPath } from '../helpers/findLanguageDefinitionByPath.js';\nimport { judgeByStaticAnalysis } from '../helpers/judgeByStaticAnalysis.js';\nimport { parseArgs } from '../helpers/parseArgs.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport {\n judgesTestCasesWithoutExpectations,\n readProblemMarkdownFrontMatter,\n} from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { spawnWithTimeout } from '../helpers/spawnWithTimeout.js';\nimport { EXAMPLE_TEST_CASE_ID_PATTERN, MAX_STDOUT_LENGTH } from '../helpers/stdioJudgeRules.js';\nimport {\n copyProblemDirToTemporaryRoot,\n forciblyRemoveDirectory,\n forciblyRemoveDirectorySync,\n} from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 2;\nconst DEBUG_DEFAULT_TIMEOUT_SECONDS = 10;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\nconst debugParamsSchema = judgeParamsSchema.extend({\n stdin: z.string().optional(),\n});\n\ntype DebugParams = z.infer<typeof debugParamsSchema>;\n\n/**\n * A preset judge function using stdin and stdout as test cases.\n *\n * A standard stdio problem must NOT commit a `judge.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `judge.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `judge.ts`:\n * ```bash\n * bun x exercode-problem judge model_answers/java\n * ```\n *\n * Run with the optional parameters:\n * ```bash\n * bun x exercode-problem judge model_answers/java '{ \"language\": \"javascript\" }'\n * ```\n */\nexport async function stdioJudgePreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = judgeParamsSchema.parse(args.params);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n\n // Without an expectation, a case would accept any run; only custom harnesses may judge input-only\n // cases. A problem judged by manual scoring or the presence of required output files has an\n // expectation of its own. Checked before the static analysis so that an authoring error surfaces\n // regardless of the submission.\n if (!judgesTestCasesWithoutExpectations(problemMarkdownFrontMatter)) {\n for (const testCase of testCases) {\n if (testCase.output === undefined && !(await hasExpectedFiles(testCase.fileOutputPath))) {\n throw new Error(\n `test case ${testCase.id} needs an expected output (${testCase.id}.out or a non-empty ${testCase.id}.fout/)`\n );\n }\n }\n }\n\n const staticAnalysisTestCaseResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisTestCaseResult) {\n printTestCaseResult({ testCaseId: testCases[0]?.id ?? 'prebuild', ...staticAnalysisTestCaseResult });\n return;\n }\n\n const originalMainFilePath = await findEntryPointFile(args.cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n prebuiltMainFilePath = await findEntryPointFile(args.cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: args.cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n }\n\n for (const testCase of testCases) {\n // prepare test case\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n // run\n const timeoutSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd: args.cwd, stdin: testCase.input, env },\n timeoutSeconds\n );\n\n let outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n // calculate decision\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n let judgementError: string | undefined;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n } else {\n try {\n const judgement = await judgeAgainstExpectations({\n stdout: spawnResult.stdout,\n expectedStdout: testCase.output,\n fileOutputPath: testCase.fileOutputPath,\n cwd: args.cwd,\n outputFiles,\n });\n if (!judgement.matches) decisionCode = DecisionCode.WRONG_ANSWER;\n outputFiles = judgement.outputFiles;\n } catch (error) {\n // An authoring error (e.g. an oversized expected file) is reported per case, like the command preset does.\n decisionCode = DecisionCode.RUNTIME_ERROR;\n judgementError = getErrorMessage(error);\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: testCase.input,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: (judgementError ?? spawnResult.stderr).slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n // clean up\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n}\n\nasync function hasExpectedFiles(fileOutputPath: string | undefined): Promise<boolean> {\n if (fileOutputPath === undefined) return false;\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n return dirents.some((dirent) => dirent.isFile());\n}\n\n/**\n * A preset debug function using stdin and stdout as test cases. The answer directory is copied to a\n * temporary directory where it is built and run together with `_shared.fin/` and the first example\n * case's `.fin/`; files the program writes are reported only through `requiredOutputFilePaths`.\n * Callers that own and remove the working directory can set `disposableWorkingDirectory` to run\n * directly there; they are responsible for cleanup even when the harness is terminated.\n *\n * A standard stdio problem must NOT commit a `debug.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `debug.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `debug.ts`:\n * ```bash\n * bun x exercode-problem debug model_answers/java '{ \"stdin\": \"1 2\" }'\n * ```\n */\nexport async function stdioDebugPreset(\n problemDir: string,\n options: { disposableWorkingDirectory?: boolean } = {}\n): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = debugParamsSchema.parse(args.params);\n\n // The judge server already owns and removes its per-request answer directory.\n if (options.disposableWorkingDirectory) {\n await debugInWorkingDirectory(problemDir, args.cwd, params);\n return;\n }\n\n // Everything (build, input files, run) happens in a disposable copy of the answer directory, so\n // the developer's files are never touched and whatever the submission leaves behind goes with it.\n // The copy keeps the directory hierarchy and links every ancestor `node_modules`, so packages the\n // answer resolves from its parents still resolve.\n // A terminated debug run must not leave the copy behind; `finally` does not run on a signal, so\n // the handlers learn the root as soon as it is created and stay until it is removed.\n let tempRoot: string | undefined;\n const removeOnSignal = (): void => {\n if (tempRoot !== undefined) forciblyRemoveDirectorySync(tempRoot);\n process.exit(1);\n };\n process.once('SIGINT', removeOnSignal);\n process.once('SIGTERM', removeOnSignal);\n try {\n const copy = await copyProblemDirToTemporaryRoot(args.cwd, { onTempRootCreated: (root) => (tempRoot = root) });\n await debugInWorkingDirectory(problemDir, copy.copiedProblemDir, params);\n } finally {\n if (tempRoot !== undefined) await forciblyRemoveDirectory(tempRoot);\n process.off('SIGINT', removeOnSignal);\n process.off('SIGTERM', removeOnSignal);\n }\n}\n\nasync function debugInWorkingDirectory(problemDir: string, cwd: string, params: DebugParams): Promise<void> {\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n // A debug run has no test case of its own: it gets the shared input files and the `.fin/` of the\n // first example case (the cases a learner may see), placed the way the judge does. Hidden cases'\n // input files are never handed to learner code.\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, cwd);\n const exampleFileInputPath = testCases.find(\n (testCase) => EXAMPLE_TEST_CASE_ID_PATTERN.test(testCase.id) && testCase.fileInputPath\n )?.fileInputPath;\n if (exampleFileInputPath) {\n await copyTestCaseFileInput(exampleFileInputPath, cwd);\n } else if (testCases.some((testCase) => testCase.fileInputPath)) {\n console.error('debug: no example test case has input files, so the program runs without them');\n }\n\n const timeoutSeconds = Math.max(DEBUG_DEFAULT_TIMEOUT_SECONDS, (problemMarkdownFrontMatter.timeLimitMs ?? 0) / 1000);\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd, stdin: params.stdin, env },\n timeoutSeconds\n );\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n }\n\n printTestCaseResult({\n testCaseId: 'debug',\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: params.stdin,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: spawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stdio.js","names":["fs","path","judgeParamsSchema","z","debugParamsSchema","stdioJudgePreset","args","parseArgs","params","problemMarkdownFrontMatter","readProblemMarkdownFrontMatter","problemDir","testCases","readTestCases","judgesTestCasesWithoutExpectations","testCase","hasExpectedFiles","staticAnalysisTestCaseResult","judgeByStaticAnalysis","printTestCaseResult","originalMainFilePath","findEntryPointFile","DecisionCode","languageDefinition","findLanguageDefinitionByPath","env","prebuiltMainFilePath","error","getErrorMessage","mainFilePath","buildCommand","buildSpawnResult","spawnWithTimeout","decisionCode","cwdSnapshot","snapshotWorkingDirectory","copyTestCaseFileInput","timeoutSeconds","command","spawnResult","outputFiles","readOutputFiles","judgementError","judgement","judgeAgainstExpectations","cleanWorkingDirectory","fileOutputPath","dirent","stdioDebugPreset","options","debugInWorkingDirectory","tempRoot","removeOnSignal","forciblyRemoveDirectorySync","copyProblemDirToTemporaryRoot","root","forciblyRemoveDirectory","cwd","exampleFileInputPath","EXAMPLE_TEST_CASE_ID_PATTERN"],"sources":["../../src/presets/stdio.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { getErrorMessage } from '@willbooster/shared-lib';\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.js';\nimport { judgeAgainstExpectations } from '../helpers/compareExpectedOutputFiles.js';\nimport { copyTestCaseFileInput } from '../helpers/copyTestCaseFileInput.js';\nimport { findEntryPointFile } from '../helpers/findEntryPointFile.js';\nimport { findLanguageDefinitionByPath } from '../helpers/findLanguageDefinitionByPath.js';\nimport { judgeByStaticAnalysis } from '../helpers/judgeByStaticAnalysis.js';\nimport { parseArgs } from '../helpers/parseArgs.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport {\n judgesTestCasesWithoutExpectations,\n readProblemMarkdownFrontMatter,\n} from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { spawnWithTimeout } from '../helpers/spawnWithTimeout.js';\nimport { EXAMPLE_TEST_CASE_ID_PATTERN, MAX_STDOUT_LENGTH } from '../helpers/stdioJudgeRules.js';\nimport {\n copyProblemDirToTemporaryRoot,\n forciblyRemoveDirectory,\n forciblyRemoveDirectorySync,\n} from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 2;\nconst DEBUG_DEFAULT_TIMEOUT_SECONDS = 10;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\nconst debugParamsSchema = judgeParamsSchema.extend({\n stdin: z.string().optional(),\n});\n\ntype DebugParams = z.infer<typeof debugParamsSchema>;\n\n/**\n * A preset judge function using stdin and stdout as test cases.\n *\n * A standard stdio problem must NOT commit a `judge.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `judge.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `judge.ts`:\n * ```bash\n * bun x exercode-problem judge model_answers/java\n * ```\n *\n * Run with the optional parameters:\n * ```bash\n * bun x exercode-problem judge model_answers/java '{ \"language\": \"javascript\" }'\n * ```\n */\nexport async function stdioJudgePreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = judgeParamsSchema.parse(args.params);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n\n // Without an expectation, a case would accept any run; only custom harnesses may judge input-only\n // cases. A problem judged by manual scoring or the presence of required output files has an\n // expectation of its own. Checked before the static analysis so that an authoring error surfaces\n // regardless of the submission.\n if (!judgesTestCasesWithoutExpectations(problemMarkdownFrontMatter)) {\n for (const testCase of testCases) {\n if (testCase.output === undefined && !(await hasExpectedFiles(testCase.fileOutputPath))) {\n throw new Error(\n `test case ${testCase.id} needs an expected output (${testCase.id}.out or a non-empty ${testCase.id}.fout/)`\n );\n }\n }\n }\n\n const staticAnalysisTestCaseResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisTestCaseResult) {\n printTestCaseResult({ testCaseId: testCases[0]?.id ?? 'prebuild', ...staticAnalysisTestCaseResult });\n return;\n }\n\n const originalMainFilePath = await findEntryPointFile(args.cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n prebuiltMainFilePath = await findEntryPointFile(args.cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: args.cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n }\n\n for (const testCase of testCases) {\n // prepare test case\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n // run\n const timeoutSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd: args.cwd, stdin: testCase.input, env },\n timeoutSeconds\n );\n\n let outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n // calculate decision\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n let judgementError: string | undefined;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n } else {\n try {\n const judgement = await judgeAgainstExpectations({\n stdout: spawnResult.stdout,\n expectedStdout: testCase.output,\n fileOutputPath: testCase.fileOutputPath,\n cwd: args.cwd,\n outputFiles,\n });\n if (!judgement.matches) decisionCode = DecisionCode.WRONG_ANSWER;\n outputFiles = judgement.outputFiles;\n } catch (error) {\n // An authoring error (e.g. an oversized expected file) is reported per case, like the command preset does.\n decisionCode = DecisionCode.RUNTIME_ERROR;\n judgementError = getErrorMessage(error);\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: testCase.input,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: (judgementError ?? spawnResult.stderr).slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n // clean up\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n}\n\nasync function hasExpectedFiles(fileOutputPath: string | undefined): Promise<boolean> {\n if (fileOutputPath === undefined) return false;\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n return dirents.some((dirent) => dirent.isFile());\n}\n\n/**\n * A preset debug function using stdin and stdout as test cases. The answer directory is copied to a\n * temporary directory where it is built and run together with `_shared.fin/` and the first example\n * case's `.fin/`; files the program writes are reported only through `requiredOutputFilePaths`.\n * Callers that own and remove the working directory can set `disposableWorkingDirectory` to run\n * directly there; they are responsible for cleanup even when the harness is terminated.\n *\n * A standard stdio problem must NOT commit a `debug.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `debug.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `debug.ts`:\n * ```bash\n * bun x exercode-problem debug model_answers/java '{ \"stdin\": \"1 2\" }'\n * ```\n */\nexport async function stdioDebugPreset(\n problemDir: string,\n options: { disposableWorkingDirectory?: boolean } = {}\n): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = debugParamsSchema.parse(args.params);\n\n // The judge server already owns and removes its per-request answer directory.\n if (options.disposableWorkingDirectory) {\n await debugInWorkingDirectory(problemDir, args.cwd, params);\n return;\n }\n\n // Everything (build, input files, run) happens in a disposable copy of the answer directory, so\n // the developer's files are never touched and whatever the submission leaves behind goes with it.\n // The copy keeps the directory hierarchy and links every ancestor `node_modules`, so packages the\n // answer resolves from its parents still resolve.\n // A terminated debug run must not leave the copy behind; `finally` does not run on a signal, so\n // the handlers learn the root as soon as it is created and stay until it is removed.\n let tempRoot: string | undefined;\n const removeOnSignal = (): void => {\n if (tempRoot !== undefined) forciblyRemoveDirectorySync(tempRoot);\n process.exit(1);\n };\n process.once('SIGINT', removeOnSignal);\n process.once('SIGTERM', removeOnSignal);\n try {\n const copy = await copyProblemDirToTemporaryRoot(args.cwd, { onTempRootCreated: (root) => (tempRoot = root) });\n await debugInWorkingDirectory(problemDir, copy.copiedProblemDir, params);\n } finally {\n if (tempRoot !== undefined) await forciblyRemoveDirectory(tempRoot);\n process.off('SIGINT', removeOnSignal);\n process.off('SIGTERM', removeOnSignal);\n }\n}\n\nasync function debugInWorkingDirectory(problemDir: string, cwd: string, params: DebugParams): Promise<void> {\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: getErrorMessage(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n // The build reports the submission's failures in its result; what `spawnWithTimeout` throws is\n // the judge's own failure and ends the run without a verdict.\n const buildSpawnResult = await spawnWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd, env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n cpuTimeSeconds: buildSpawnResult.cpuTimeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n }\n\n // A debug run has no test case of its own: it gets the shared input files and the `.fin/` of the\n // first example case (the cases a learner may see), placed the way the judge does. Hidden cases'\n // input files are never handed to learner code.\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, cwd);\n const exampleFileInputPath = testCases.find(\n (testCase) => EXAMPLE_TEST_CASE_ID_PATTERN.test(testCase.id) && testCase.fileInputPath\n )?.fileInputPath;\n if (exampleFileInputPath) {\n await copyTestCaseFileInput(exampleFileInputPath, cwd);\n } else if (testCases.some((testCase) => testCase.fileInputPath)) {\n console.error('debug: no example test case has input files, so the program runs without them');\n }\n\n const timeoutSeconds = Math.max(DEBUG_DEFAULT_TIMEOUT_SECONDS, (problemMarkdownFrontMatter.timeLimitMs ?? 0) / 1000);\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = await spawnWithTimeout(\n command[0],\n command.slice(1),\n { cwd, stdin: params.stdin, env },\n timeoutSeconds\n );\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n }\n\n printTestCaseResult({\n testCaseId: 'debug',\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: params.stdin,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: spawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n cpuTimeSeconds: spawnResult.cpuTimeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomcat.cjs","names":["getErrorMessage","parseArgs","fs","path","tmpdir","DecisionCode","listSubmissionFiles","resolveJavaRelativePath","truncateOutput","readOutputFiles","spawnSync","sleep","http","require_runtime","require_printTestCaseResult","require_parseArgs","require_decisionCode","require_javaJudge","node_fs","node_path","node_os","_willbooster_shared_lib","node_child_process","node_http","node_timers_promises","APPLICATION_NAME","TOMCAT_BASE_URL","SOURCE_FILE_EXTENSIONS","tomcatJudgePreset","options","error","submissionDir","testCaseId","staticAnalysisResult","judgeByStaticAnalysis","runRootDir","buildDir","catalinaBaseDir","catalinaHome","prepareBuildDirectory","buildResult","buildWithMaven","getCatalinaHome","createCatalinaBaseDir","ensureTomcatStopped","deployWarFile","startTomcat","judgeResult","runJudgeScript","submissionFiles","relativePath","sourcePath","targetPath","resolveTargetPath","normalizedRelativePath","extension","jspDirectory","forbiddenMatches","sourceCode","forbiddenText","forbiddenTexts","match","commandResult","runCommand","formatMavenBuildError","stdout","stderr","directoryName","serverXmlPath","serverXml","warFilePath","webappsDir","deployedDirectoryPath","deployedWarPath","startupResult","waitForTomcatState","problemDirectoryPath","outputFiles","startAt","timeCommand","timedResult","timeoutSeconds","command","elapsedSeconds","stderrWithError","memoryMatch","timeSeconds","memoryBytes","isTomcatRunning","startedAt","timeoutMs","expectedRunning","request","response","status","resolve","validateCatalinaHome","buildTomcatUrl","urlPath"],"sources":["../../src/presets/tomcat.ts"],"sourcesContent":["import {\n MAX_OUTPUT_LENGTH,\n resolveJavaRelativePath,\n listSubmissionFiles,\n readOutputFiles,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\nimport { getErrorMessage } from '@willbooster/shared-lib';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\n\nexport interface TomcatJudgePresetOptions {\n problemDirectoryPath: string;\n jspDirectory: '' | 'WEB-INF/jsp';\n forbiddenTexts?: readonly string[];\n evaluate: () => Promise<void>;\n}\n\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\ninterface CommandResult {\n status: number | undefined;\n stdout: string;\n stderr: string;\n timeSeconds: number;\n memoryBytes: number;\n}\nconst APPLICATION_NAME = 'judge';\nconst BUILD_TIMEOUT_SECONDS = 60;\nconst EVALUATE_TIMEOUT_SECONDS = 30;\nconst TOMCAT_BASE_URL = 'http://localhost:59000';\nconst SOURCE_FILE_EXTENSIONS = new Set(['.java', '.jsp', '.html', '.htm', '.xml', '.js', '.css']);\n\nexport async function tomcatJudgePreset(options: TomcatJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(getErrorMessage(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const staticAnalysisResult = await judgeByStaticAnalysis(submissionDir, options.forbiddenTexts ?? []);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId, ...staticAnalysisResult });\n return;\n }\n\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'tomcat_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let catalinaBaseDir: string | undefined;\n let catalinaHome: string | undefined;\n\n try {\n await fs.promises.mkdir(buildDir, { recursive: true });\n await prepareBuildDirectory(buildDir, submissionDir, options);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n catalinaHome = getCatalinaHome();\n catalinaBaseDir = await createCatalinaBaseDir(catalinaHome, runRootDir);\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n await deployWarFile(buildDir, catalinaBaseDir);\n await startTomcat(catalinaHome, catalinaBaseDir);\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: getErrorMessage(error),\n });\n } finally {\n try {\n if (catalinaHome && catalinaBaseDir) {\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n }\n } catch {\n // 判定結果を優先し、後始末の失敗は握りつぶす。\n }\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n options: TomcatJudgePresetOptions\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/webapp/WEB-INF/jsp'), { recursive: true });\n await fs.promises.copyFile(path.join(options.problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n\n const submissionFiles = await listSubmissionFiles(submissionDir);\n for (const relativePath of submissionFiles) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath, options.jspDirectory);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nasync function resolveTargetPath(\n buildDir: string,\n relativePath: string,\n sourcePath: string,\n jspDirectory: string\n): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (normalizedRelativePath.startsWith('WEB-INF/') || normalizedRelativePath.startsWith('META-INF/')) {\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n }\n\n const extension = path.extname(relativePath).toLowerCase();\n if (extension === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n if (extension === '.jsp') {\n return path.join(buildDir, 'src/main/webapp', jspDirectory, path.basename(relativePath));\n }\n\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n}\n\nasync function judgeByStaticAnalysis(\n submissionDir: string,\n forbiddenTexts: readonly string[]\n): Promise<Pick<JudgeResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const forbiddenMatches: { path: string; pattern: string }[] = [];\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n if (!SOURCE_FILE_EXTENSIONS.has(path.extname(relativePath).toLowerCase())) {\n continue;\n }\n\n const sourceCode = await fs.promises.readFile(path.join(submissionDir, relativePath), 'utf8');\n for (const forbiddenText of forbiddenTexts) {\n if (sourceCode.includes(forbiddenText)) {\n forbiddenMatches.push({ path: relativePath, pattern: forbiddenText });\n }\n }\n }\n\n if (forbiddenMatches.length === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown:\n 'ソースコード中に禁止された文字列が含まれています。\\nソースコードを修正してから再度提出してください。\\n\\n| ファイル | 禁止パターン |\\n| -------- | ------------ |\\n' +\n forbiddenMatches.map((match) => `| \\`${match.path}\\` | \\`${match.pattern}\\` |`).join('\\n'),\n };\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const commandResult = runCommand(\n ['mvn', 'clean', 'package', '--quiet', '--batch-mode', '--offline'],\n buildDir,\n BUILD_TIMEOUT_SECONDS\n );\n if (commandResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n }\n if (commandResult.status === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: formatMavenBuildError(commandResult.stdout, commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n}\n\nfunction formatMavenBuildError(stdout: string, stderr: string): string {\n return truncateOutput(\n `${stdout}\n${stderr}`.trim()\n );\n}\n\nasync function createCatalinaBaseDir(catalinaHome: string, runRootDir: string): Promise<string> {\n const catalinaBaseDir = path.join(runRootDir, 'catalina-base');\n await fs.promises.cp(path.join(catalinaHome, 'conf'), path.join(catalinaBaseDir, 'conf'), {\n recursive: true,\n dereference: true,\n });\n for (const directoryName of ['logs', 'temp', 'webapps', 'work']) {\n await fs.promises.mkdir(path.join(catalinaBaseDir, directoryName), { recursive: true });\n }\n\n const serverXmlPath = path.join(catalinaBaseDir, 'conf', 'server.xml');\n const serverXml = await fs.promises.readFile(serverXmlPath, 'utf8');\n await fs.promises.writeFile(serverXmlPath, serverXml.replaceAll('port=\"8080\"', 'port=\"59000\"'), 'utf8');\n return catalinaBaseDir;\n}\n\nasync function deployWarFile(buildDir: string, catalinaBaseDir: string): Promise<void> {\n const warFilePath = path.join(buildDir, 'target', `${APPLICATION_NAME}.war`);\n const webappsDir = path.join(catalinaBaseDir, 'webapps');\n const deployedDirectoryPath = path.join(webappsDir, APPLICATION_NAME);\n const deployedWarPath = path.join(webappsDir, `${APPLICATION_NAME}.war`);\n await fs.promises.mkdir(webappsDir, { recursive: true });\n await fs.promises.rm(deployedDirectoryPath, { recursive: true, force: true });\n await fs.promises.rm(deployedWarPath, { force: true });\n await fs.promises.copyFile(warFilePath, deployedWarPath);\n}\n\nasync function startTomcat(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n const startupScriptPath = path.join(catalinaHome, 'bin', 'startup.sh');\n const startupResult = runCommand([startupScriptPath], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n if (startupResult.status !== 0) {\n throw new Error(`Failed to start Tomcat: ${startupResult.stderr.trim() || startupResult.stdout.trim()}`);\n }\n\n const started = await waitForTomcatState(true, 10_000);\n if (!started) {\n throw new Error('Tomcat failed to start within 10 seconds');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const commandResult = runCommand(\n ['bun', 'run', './judge.ts', '--evaluate'],\n problemDirectoryPath,\n EVALUATE_TIMEOUT_SECONDS,\n {\n ...process.env,\n WORKING_DIRECTORY_PATH: buildDir,\n }\n );\n\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n if (commandResult.timeSeconds > EVALUATE_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.stdout.length > MAX_OUTPUT_LENGTH || commandResult.stderr.length > MAX_OUTPUT_LENGTH) {\n return {\n decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.status !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n stderr: truncateOutput(commandResult.stderr) || undefined,\n stdout: truncateOutput(commandResult.stdout) || undefined,\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n cwd: string | undefined,\n timeoutSeconds: number,\n env: NodeJS.ProcessEnv = process.env\n): CommandResult {\n const startAt = Date.now();\n const timeCommand = process.platform === 'darwin' ? 'gtime' : '/usr/bin/time';\n // Hosts provide GNU timeout on PATH; Homebrew coreutils also installs the unprefixed command.\n const timedResult = spawnSync('timeout', [timeoutSeconds.toFixed(3), timeCommand, '--format', '%e %M', ...command], {\n cwd,\n env,\n encoding: 'utf8',\n });\n const elapsedSeconds = (Date.now() - startAt) / 1000;\n const stderrWithError = [timedResult.stderr, timedResult.error?.message].filter(Boolean).join('\\n');\n const memoryMatch = /(?:^|\\n)(\\d+(?:\\.\\d+)?) (\\d+)\\s*$/u.exec(stderrWithError);\n const stderr = memoryMatch ? stderrWithError.slice(0, memoryMatch.index) : stderrWithError;\n const timeSeconds = Number(memoryMatch?.[1]) || elapsedSeconds;\n const memoryBytes = 1024 * Number(memoryMatch?.[2] ?? 0);\n\n return {\n status: timedResult.status === 124 ? 0 : (timedResult.status ?? undefined),\n stdout: timedResult.stdout,\n stderr,\n timeSeconds: timedResult.status === 124 ? timeoutSeconds + 0.001 : timeSeconds,\n memoryBytes,\n };\n}\n\nasync function ensureTomcatStopped(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n if (!(await isTomcatRunning())) {\n return;\n }\n\n runCommand([path.join(catalinaHome, 'bin', 'catalina.sh'), 'stop'], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n await waitForTomcatState(false, 10_000);\n}\n\nasync function waitForTomcatState(expectedRunning: boolean, timeoutMs: number): Promise<boolean> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if ((await isTomcatRunning()) === expectedRunning) {\n return true;\n }\n await sleep(200);\n }\n return (await isTomcatRunning()) === expectedRunning;\n}\n\nasync function isTomcatRunning(): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const request = http.get(TOMCAT_BASE_URL, (response) => {\n response.resume();\n const status = response.statusCode ?? 0;\n resolve((status >= 200 && status < 300) || status === 404);\n });\n request.on('error', () => resolve(false));\n request.setTimeout(1000, () => request.destroy());\n });\n}\n\nfunction getCatalinaHome(): string {\n const catalinaHome = process.env.CATALINA_HOME;\n if (!catalinaHome) {\n throw new Error('CATALINA_HOME is not set');\n }\n\n validateCatalinaHome(catalinaHome);\n return path.resolve(catalinaHome);\n}\n\nfunction validateCatalinaHome(catalinaHome: string): void {\n if (\n !fs.existsSync(path.join(catalinaHome, 'bin', 'catalina.sh')) ||\n !fs.existsSync(path.join(catalinaHome, 'conf', 'server.xml'))\n ) {\n throw new Error('Invalid CATALINA_HOME path');\n }\n}\n\nexport function buildTomcatUrl(urlPath: string): string {\n return `${TOMCAT_BASE_URL}/${APPLICATION_NAME}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"0iBA+BA,MAAMyB,EAAmB,QAGnBC,EAAkB,yBAClBC,kBAAyB,IAAI,IAAI,CAAC,QAAS,OAAQ,QAAS,OAAQ,OAAQ,MAAO,MAAM,CAAC,EAEhG,eAAsBC,EAAkB,EAAkD,CACxF,GAAI,QAAQ,KAAK,SAAS,YAAY,EAAG,CACvC,GAAI,CACF,MAAMC,EAAQ,SAAS,CACzB,OAASC,EAAO,CACd,QAAQ,OAAA,EAAM9B,EAAAA,gBAAAA,CAAgB8B,CAAK,CAAC,EACpC,QAAQ,SAAW,CACrB,CACA,MACF,CAEA,GAAM,CAAE,IAAKC,GAAkB9B,EAAAA,UAAU,QAAQ,IAAI,EACrD,GAAI,CAAC8B,EAAe,MAAU,MAAM,uBAAuB,EAC3D,IAAMC,EAAa,QACbC,EAAuB,MAAMC,EAAsBH,EAAeF,EAAQ,gBAAkB,CAAC,CAAC,EACpG,GAAII,EAAsB,CACxBnB,EAAA,oBAAoB,CAAE,aAAY,GAAGmB,CAAqB,CAAC,EAC3D,MACF,CAEA,IAAME,EAAa,MAAMjC,EAAAA,QAAG,SAAS,QAAQC,EAAAA,QAAK,MAAA,EAAKC,EAAAA,OAAAA,CAAO,EAAG,eAAe,CAAC,EAC3EgC,EAAWjC,EAAAA,QAAK,KAAKgC,EAAY,OAAO,EAC1CE,EACAC,EAEJ,GAAI,CACF,MAAMpC,EAAAA,QAAG,SAAS,MAAMkC,EAAU,CAAE,UAAW,EAAK,CAAC,EACrD,MAAMG,EAAsBH,EAAUL,EAAeF,CAAO,EAE5D,IAAMW,EAAcC,EAAeL,CAAQ,EAC3C,GAAII,EAAa,CACf1B,EAAA,oBAAoB,CAAE,aAAY,GAAG0B,CAAY,CAAC,EAClD,MACF,CAEA,EAAeE,EAAgB,EAC/B,EAAkB,MAAMC,EAAsBL,EAAcH,CAAU,EACtE,MAAMS,EAAoBN,EAAcD,CAAe,EACvD,MAAMQ,EAAcT,EAAUC,CAAe,EAC7C,MAAMS,EAAYR,EAAcD,CAAe,EAE/C,IAAMU,EAAcC,EAAeZ,EAAUP,EAAQ,oBAAoB,EACzEf,EAAA,oBAAoB,CAAE,aAAY,GAAGiC,CAAY,CAAC,CACpD,OAASjB,EAAO,CACdhB,EAAA,oBAAoB,CAClB,aACA,aAAcT,EAAAA,aAAa,oBAC3B,QAAA,EAAQL,EAAAA,gBAAAA,CAAgB8B,CAAK,CAC/B,CAAC,CACH,QAAU,CACR,GAAI,CACEQ,GAAgBD,GAClB,MAAMO,EAAoBN,EAAcD,CAAe,CAE3D,MAAQ,CAER,CACA,MAAMnC,EAAAA,QAAG,SAAS,GAAGiC,EAAY,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACnE,CACF,CAEA,eAAeI,EACb,EACA,EACA,EACe,CACf,MAAMrC,EAAAA,QAAG,SAAS,MAAMC,EAAAA,QAAK,KAAKiC,EAAU,eAAe,EAAG,CAAE,UAAW,EAAK,CAAC,EACjF,MAAMlC,EAAAA,QAAG,SAAS,MAAMC,EAAAA,QAAK,KAAKiC,EAAU,6BAA6B,EAAG,CAAE,UAAW,EAAK,CAAC,EAC/F,MAAMlC,EAAAA,QAAG,SAAS,SAASC,EAAAA,QAAK,KAAK0B,EAAQ,qBAAsB,SAAS,EAAG1B,EAAAA,QAAK,KAAKiC,EAAU,SAAS,CAAC,EAE7G,IAAMa,EAAkB,MAAM3C,EAAAA,oBAAoByB,CAAa,EAC/D,IAAK,IAAMmB,KAAgBD,EAAiB,CAC1C,IAAME,EAAahD,EAAAA,QAAK,KAAK4B,EAAemB,CAAY,EAClDE,EAAa,MAAMC,EAAkBjB,EAAUc,EAAcC,EAAYtB,EAAQ,YAAY,EACnG,MAAM3B,EAAAA,QAAG,SAAS,MAAMC,EAAAA,QAAK,QAAQiD,CAAU,EAAG,CAAE,UAAW,EAAK,CAAC,EACrE,MAAMlD,EAAAA,QAAG,SAAS,SAASiD,EAAYC,CAAU,CACnD,CACF,CAEA,eAAeC,EACb,EACA,EACA,EACA,EACiB,CACjB,IAAMC,EAAyBJ,EAAa,WAAW,KAAM,GAAG,EAChE,GAAII,EAAuB,WAAW,WAAW,EAC/C,OAAOnD,EAAAA,QAAK,KAAKiC,EAAUkB,CAAsB,EAEnD,GAAIA,EAAuB,WAAW,UAAU,GAAKA,EAAuB,WAAW,WAAW,EAChG,OAAOnD,EAAAA,QAAK,KAAKiC,EAAU,kBAAmBkB,CAAsB,EAGtE,IAAMC,EAAYpD,EAAAA,QAAK,QAAQ+C,CAAY,CAAC,CAAC,YAAY,EAQzD,OAPIK,IAAc,QACTpD,EAAAA,QAAK,KAAKiC,EAAU,gBAAiB,MAAM7B,EAAAA,wBAAwB4C,EAAYD,CAAY,CAAC,EAEjGK,IAAc,OACTpD,EAAAA,QAAK,KAAKiC,EAAU,kBAAmBoB,EAAcrD,EAAAA,QAAK,SAAS+C,CAAY,CAAC,EAGlF/C,EAAAA,QAAK,KAAKiC,EAAU,kBAAmBkB,CAAsB,CACtE,CAEA,eAAepB,EACb,EACA,EAC6E,CAC7E,IAAMuB,EAAwD,CAAC,EAC/D,IAAK,IAAMP,KAAgB,MAAM5C,EAAAA,oBAAoByB,CAAa,EAAG,CACnE,GAAI,CAACJ,EAAuB,IAAIxB,EAAAA,QAAK,QAAQ+C,CAAY,CAAC,CAAC,YAAY,CAAC,EACtE,SAGF,IAAMQ,EAAa,MAAMxD,EAAAA,QAAG,SAAS,SAASC,EAAAA,QAAK,KAAK4B,EAAemB,CAAY,EAAG,MAAM,EAC5F,IAAK,IAAMS,KAAiBC,EACtBF,EAAW,SAASC,CAAa,GACnCF,EAAiB,KAAK,CAAE,KAAMP,EAAc,QAASS,CAAc,CAAC,CAG1E,CAEA,GAAIF,EAAiB,SAAW,EAIhC,MAAO,CACL,aAAcpD,EAAAA,aAAa,iCAC3B,iBACE;;;;;EACAoD,EAAiB,IAAK,GAAU,OAAOI,EAAM,KAAK,SAASA,EAAM,QAAQ,KAAK,CAAC,CAAC,KAAK;CAAI,CAC7F,CACF,CAEA,SAASpB,EAAe,EAA2C,CACjE,IAAMqB,EAAgBC,EACpB,CAAC,MAAO,QAAS,UAAW,UAAW,eAAgB,WAAW,EAClE3B,EACA,EACF,EACA,GAAI0B,EAAc,YAAc,GAC9B,MAAO,CACL,aAAczD,EAAAA,aAAa,0BAC3B,OAAQG,EAAAA,eAAesD,EAAc,MAAM,EAC3C,OAAQtD,EAAAA,eAAesD,EAAc,MAAM,EAC3C,WAAYA,EAAc,OAC1B,YAAaA,EAAc,YAC3B,YAAaA,EAAc,WAC7B,EAEF,GAAIA,EAAc,SAAW,EAI7B,MAAO,CACL,aAAczD,EAAAA,aAAa,YAC3B,OAAQ2D,EAAsBF,EAAc,OAAQA,EAAc,MAAM,EACxE,OAAQtD,EAAAA,eAAesD,EAAc,MAAM,EAC3C,WAAYA,EAAc,OAC1B,YAAaA,EAAc,YAC3B,YAAaA,EAAc,WAC7B,CACF,CAEA,SAASE,EAAsB,EAAgB,EAAwB,CACrE,OAAOxD,EAAAA,eACL,GAAGyD,EAAO;EACZC,IAAS,KAAK,CACd,CACF,CAEA,eAAevB,EAAsB,EAAsB,EAAqC,CAC9F,IAAMN,EAAkBlC,EAAAA,QAAK,KAAKgC,EAAY,eAAe,EAC7D,MAAMjC,EAAAA,QAAG,SAAS,GAAGC,EAAAA,QAAK,KAAKmC,EAAc,MAAM,EAAGnC,EAAAA,QAAK,KAAKkC,EAAiB,MAAM,EAAG,CACxF,UAAW,GACX,YAAa,EACf,CAAC,EACD,IAAK,IAAM8B,IAAiB,CAAC,OAAQ,OAAQ,UAAW,MAAM,EAC5D,MAAMjE,EAAAA,QAAG,SAAS,MAAMC,EAAAA,QAAK,KAAKkC,EAAiB8B,CAAa,EAAG,CAAE,UAAW,EAAK,CAAC,EAGxF,IAAMC,EAAgBjE,EAAAA,QAAK,KAAKkC,EAAiB,OAAQ,YAAY,EAC/DgC,EAAY,MAAMnE,EAAAA,QAAG,SAAS,SAASkE,EAAe,MAAM,EAElE,OADA,MAAMlE,EAAAA,QAAG,SAAS,UAAUkE,EAAeC,EAAU,WAAW,cAAe,cAAc,EAAG,MAAM,EAC/FhC,CACT,CAEA,eAAeQ,EAAc,EAAkB,EAAwC,CACrF,IAAMyB,EAAcnE,EAAAA,QAAK,KAAKiC,EAAU,SAAU,GAAGX,EAAiB,KAAK,EACrE8C,EAAapE,EAAAA,QAAK,KAAKkC,EAAiB,SAAS,EACjDmC,EAAwBrE,EAAAA,QAAK,KAAKoE,EAAY9C,CAAgB,EAC9DgD,EAAkBtE,EAAAA,QAAK,KAAKoE,EAAY,GAAG9C,EAAiB,KAAK,EACvE,MAAMvB,EAAAA,QAAG,SAAS,MAAMqE,EAAY,CAAE,UAAW,EAAK,CAAC,EACvD,MAAMrE,EAAAA,QAAG,SAAS,GAAGsE,EAAuB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EAC5E,MAAMtE,EAAAA,QAAG,SAAS,GAAGuE,EAAiB,CAAE,MAAO,EAAK,CAAC,EACrD,MAAMvE,EAAAA,QAAG,SAAS,SAASoE,EAAaG,CAAe,CACzD,CAEA,eAAe3B,EAAY,EAAsB,EAAwC,CAEvF,IAAM4B,EAAgBX,EAAW,CADP5D,EAAAA,QAAK,KAAKmC,EAAc,MAAO,YACP,CAAC,EAAG,IAAA,GAAW,GAAI,CACnE,GAAG,QAAQ,IACX,cAAeD,EACf,cAAeC,CACjB,CAAC,EACD,GAAIoC,EAAc,SAAW,EAC3B,MAAU,MAAM,2BAA2BA,EAAc,OAAO,KAAK,GAAKA,EAAc,OAAO,KAAK,GAAG,EAIzG,GAAI,CAAC,MADiBC,EAAmB,GAAM,GAAM,EAEnD,MAAU,MAAM,0CAA0C,CAE9D,CAEA,SAAS3B,EAAe,EAAkB,EAA2C,CACnF,IAAMc,EAAgBC,EACpB,CAAC,MAAO,MAAO,aAAc,YAAY,EACzCa,EACA,GACA,CACE,GAAG,QAAQ,IACX,uBAAwBxC,CAC1B,CACF,EAEMyC,EAAcpE,EAAAA,gBAAgBN,EAAAA,QAAK,KAAKiC,EAAU,oBAAoB,CAAC,EAmC7E,OAlCI0B,EAAc,YAAc,GACvB,CACL,aAAczD,EAAAA,aAAa,oBAC3B,OAAQG,EAAAA,eAAesD,EAAc,MAAM,EAC3C,OAAQtD,EAAAA,eAAesD,EAAc,MAAM,EAC3C,WAAYA,EAAc,OAC1B,YAAaA,EAAc,YAC3B,YAAaA,EAAc,YAC3B,aACF,EAEEA,EAAc,OAAO,OAAA,KAA8BA,EAAc,OAAO,OAAA,IACnE,CACL,aAAczD,EAAAA,aAAa,2BAC3B,OAAQG,EAAAA,eAAesD,EAAc,MAAM,EAC3C,OAAQtD,EAAAA,eAAesD,EAAc,MAAM,EAC3C,WAAYA,EAAc,OAC1B,YAAaA,EAAc,YAC3B,YAAaA,EAAc,YAC3B,aACF,EAEEA,EAAc,SAAW,EAYtB,CACL,aAAczD,EAAAA,aAAa,SAC3B,OAAQG,EAAAA,eAAesD,EAAc,MAAM,GAAK,IAAA,GAChD,OAAQtD,EAAAA,eAAesD,EAAc,MAAM,GAAK,IAAA,GAChD,WAAYA,EAAc,OAC1B,YAAaA,EAAc,YAC3B,YAAaA,EAAc,YAC3B,aACF,EAnBS,CACL,aAAczD,EAAAA,aAAa,aAC3B,OAAQG,EAAAA,eAAesD,EAAc,MAAM,EAC3C,OAAQtD,EAAAA,eAAesD,EAAc,MAAM,EAC3C,WAAYA,EAAc,OAC1B,YAAaA,EAAc,YAC3B,YAAaA,EAAc,YAC3B,aACF,CAYJ,CAEA,SAASC,EACP,EACA,EACA,EACA,EAAyB,QAAQ,IAClB,CACf,IAAMe,EAAU,KAAK,IAAI,EACnBC,EAAc,QAAQ,WAAa,SAAW,QAAU,gBAExDC,GAAA,EAActE,EAAAA,UAAAA,CAAU,UAAW,CAACuE,EAAe,QAAQ,CAAC,EAAGF,EAAa,WAAY,QAAS,GAAGG,CAAO,EAAG,CAClH,MACA,MACA,SAAU,MACZ,CAAC,EACKC,GAAkB,KAAK,IAAI,EAAIL,GAAW,IAC1CM,EAAkB,CAACJ,EAAY,OAAQA,EAAY,OAAO,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK;CAAI,EAC5FK,EAAc,qCAAqC,KAAKD,CAAe,EACvElB,EAASmB,EAAcD,EAAgB,MAAM,EAAGC,EAAY,KAAK,EAAID,EACrEE,EAAc,OAAOD,IAAc,EAAE,GAAKF,EAC1CI,EAAc,KAAO,OAAOF,IAAc,IAAM,CAAC,EAEvD,MAAO,CACL,OAAQL,EAAY,SAAW,IAAM,EAAKA,EAAY,QAAU,IAAA,GAChE,OAAQA,EAAY,OACpB,SACA,YAAaA,EAAY,SAAW,IAAMC,EAAiB,KAAQK,EACnE,aACF,CACF,CAEA,eAAe1C,EAAoB,EAAsB,EAAwC,CACzF,MAAM4C,EAAgB,IAI5BzB,EAAW,CAAC5D,EAAAA,QAAK,KAAKmC,EAAc,MAAO,aAAa,EAAG,MAAM,EAAG,IAAA,GAAW,GAAI,CACjF,GAAG,QAAQ,IACX,cAAeD,EACf,cAAeC,CACjB,CAAC,EACD,MAAMqC,EAAmB,GAAO,GAAM,EACxC,CAEA,eAAeA,EAAmB,EAA0B,EAAqC,CAC/F,IAAMc,EAAY,KAAK,IAAI,EAC3B,KAAO,KAAK,IAAI,EAAIA,EAAYC,GAAW,CACzC,GAAK,MAAMF,EAAgB,IAAOG,EAChC,MAAO,GAET,MAAA,EAAMhF,EAAAA,WAAAA,CAAM,GAAG,CACjB,CACA,OAAQ,MAAM6E,EAAgB,IAAOG,CACvC,CAEA,eAAeH,GAAoC,CACjD,OAAO,MAAM,IAAI,QAAkB,GAAY,CAC7C,IAAMI,EAAUhF,EAAAA,QAAK,IAAIc,EAAkB,GAAa,CACtDmE,EAAS,OAAO,EAChB,IAAMC,EAASD,EAAS,YAAc,EACtCE,EAASD,GAAU,KAAOA,EAAS,KAAQA,IAAW,GAAG,CAC3D,CAAC,EACDF,EAAQ,GAAG,YAAeG,EAAQ,EAAK,CAAC,EACxCH,EAAQ,WAAW,QAAYA,EAAQ,QAAQ,CAAC,CAClD,CAAC,CACH,CAEA,SAASlD,GAA0B,CACjC,IAAMJ,EAAe,QAAQ,IAAI,cACjC,GAAI,CAACA,EACH,MAAU,MAAM,0BAA0B,EAI5C,OADA0D,EAAqB1D,CAAY,EAC1BnC,EAAAA,QAAK,QAAQmC,CAAY,CAClC,CAEA,SAAS0D,EAAqB,EAA4B,CACxD,GACE,CAAC9F,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKmC,EAAc,MAAO,aAAa,CAAC,GAC5D,CAACpC,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKmC,EAAc,OAAQ,YAAY,CAAC,EAE5D,MAAU,MAAM,4BAA4B,CAEhD,CAEA,SAAgB2D,EAAe,EAAyB,CACtD,MAAO,GAAGvE,EAAgB,GAAGD,IAAmByE,EAAQ,WAAW,GAAG,EAAIA,EAAU,IAAIA,KAC1F"}
|
|
1
|
+
{"version":3,"file":"tomcat.cjs","names":["getErrorMessage","parseArgs","fs","path","tmpdir","DecisionCode","listSubmissionFiles","resolveJavaRelativePath","truncateOutput","readOutputFiles","spawnSync","sleep","http","require_runtime","require_printTestCaseResult","require_parseArgs","require_decisionCode","require_javaJudge","node_fs","node_path","node_os","_willbooster_shared_lib","node_child_process","node_http","node_timers_promises","APPLICATION_NAME","TOMCAT_BASE_URL","SOURCE_FILE_EXTENSIONS","tomcatJudgePreset","options","error","submissionDir","testCaseId","staticAnalysisResult","judgeByStaticAnalysis","runRootDir","buildDir","catalinaBaseDir","catalinaHome","prepareBuildDirectory","buildResult","buildWithMaven","getCatalinaHome","createCatalinaBaseDir","ensureTomcatStopped","deployWarFile","startTomcat","judgeResult","runJudgeScript","submissionFiles","relativePath","sourcePath","targetPath","resolveTargetPath","normalizedRelativePath","extension","jspDirectory","forbiddenMatches","sourceCode","forbiddenText","forbiddenTexts","match","commandResult","runCommand","formatMavenBuildError","stdout","stderr","directoryName","serverXmlPath","serverXml","warFilePath","webappsDir","deployedDirectoryPath","deployedWarPath","startupResult","waitForTomcatState","problemDirectoryPath","outputFiles","startAt","timeCommand","timedResult","timeoutSeconds","command","elapsedSeconds","stderrWithError","memoryMatch","timeSeconds","memoryBytes","isTomcatRunning","startedAt","timeoutMs","expectedRunning","request","response","status","resolve","validateCatalinaHome","buildTomcatUrl","urlPath"],"sources":["../../src/presets/tomcat.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport http from 'node:http';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { getErrorMessage } from '@willbooster/shared-lib';\n\nimport {\n listSubmissionFiles,\n MAX_OUTPUT_LENGTH,\n readOutputFiles,\n resolveJavaRelativePath,\n truncateOutput,\n} from '../helpers/javaJudge.js';\nimport { DecisionCode, parseArgs, printTestCaseResult, type TestCaseResult } from '../index.js';\n\nexport interface TomcatJudgePresetOptions {\n problemDirectoryPath: string;\n jspDirectory: '' | 'WEB-INF/jsp';\n forbiddenTexts?: readonly string[];\n evaluate: () => Promise<void>;\n}\n\ntype JudgeResult = Omit<TestCaseResult, 'testCaseId'>;\ninterface CommandResult {\n status: number | undefined;\n stdout: string;\n stderr: string;\n timeSeconds: number;\n memoryBytes: number;\n}\nconst APPLICATION_NAME = 'judge';\nconst BUILD_TIMEOUT_SECONDS = 60;\nconst EVALUATE_TIMEOUT_SECONDS = 30;\nconst TOMCAT_BASE_URL = 'http://localhost:59000';\nconst SOURCE_FILE_EXTENSIONS = new Set(['.java', '.jsp', '.html', '.htm', '.xml', '.js', '.css']);\n\nexport async function tomcatJudgePreset(options: TomcatJudgePresetOptions): Promise<void> {\n if (process.argv.includes('--evaluate')) {\n try {\n await options.evaluate();\n } catch (error) {\n console.error(getErrorMessage(error));\n process.exitCode = 1;\n }\n return;\n }\n\n const { cwd: submissionDir } = parseArgs(process.argv);\n if (!submissionDir) throw new Error('cwd argument required');\n const testCaseId = 'test1';\n const staticAnalysisResult = await judgeByStaticAnalysis(submissionDir, options.forbiddenTexts ?? []);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId, ...staticAnalysisResult });\n return;\n }\n\n const runRootDir = await fs.promises.mkdtemp(path.join(tmpdir(), 'tomcat_judge_'));\n const buildDir = path.join(runRootDir, 'build');\n let catalinaBaseDir: string | undefined;\n let catalinaHome: string | undefined;\n\n try {\n await fs.promises.mkdir(buildDir, { recursive: true });\n await prepareBuildDirectory(buildDir, submissionDir, options);\n\n const buildResult = buildWithMaven(buildDir);\n if (buildResult) {\n printTestCaseResult({ testCaseId, ...buildResult });\n return;\n }\n\n catalinaHome = getCatalinaHome();\n catalinaBaseDir = await createCatalinaBaseDir(catalinaHome, runRootDir);\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n await deployWarFile(buildDir, catalinaBaseDir);\n await startTomcat(catalinaHome, catalinaBaseDir);\n\n const judgeResult = runJudgeScript(buildDir, options.problemDirectoryPath);\n printTestCaseResult({ testCaseId, ...judgeResult });\n } catch (error) {\n printTestCaseResult({\n testCaseId,\n decisionCode: DecisionCode.JUDGE_NOT_AVAILABLE,\n stderr: getErrorMessage(error),\n });\n } finally {\n try {\n if (catalinaHome && catalinaBaseDir) {\n await ensureTomcatStopped(catalinaHome, catalinaBaseDir);\n }\n } catch {\n // 判定結果を優先し、後始末の失敗は握りつぶす。\n }\n await fs.promises.rm(runRootDir, { recursive: true, force: true });\n }\n}\n\nasync function prepareBuildDirectory(\n buildDir: string,\n submissionDir: string,\n options: TomcatJudgePresetOptions\n): Promise<void> {\n await fs.promises.mkdir(path.join(buildDir, 'src/main/java'), { recursive: true });\n await fs.promises.mkdir(path.join(buildDir, 'src/main/webapp/WEB-INF/jsp'), { recursive: true });\n await fs.promises.copyFile(path.join(options.problemDirectoryPath, 'pom.xml'), path.join(buildDir, 'pom.xml'));\n\n const submissionFiles = await listSubmissionFiles(submissionDir);\n for (const relativePath of submissionFiles) {\n const sourcePath = path.join(submissionDir, relativePath);\n const targetPath = await resolveTargetPath(buildDir, relativePath, sourcePath, options.jspDirectory);\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.copyFile(sourcePath, targetPath);\n }\n}\n\nasync function resolveTargetPath(\n buildDir: string,\n relativePath: string,\n sourcePath: string,\n jspDirectory: string\n): Promise<string> {\n const normalizedRelativePath = relativePath.replaceAll('\\\\', '/');\n if (normalizedRelativePath.startsWith('src/main/')) {\n return path.join(buildDir, normalizedRelativePath);\n }\n if (normalizedRelativePath.startsWith('WEB-INF/') || normalizedRelativePath.startsWith('META-INF/')) {\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n }\n\n const extension = path.extname(relativePath).toLowerCase();\n if (extension === '.java') {\n return path.join(buildDir, 'src/main/java', await resolveJavaRelativePath(sourcePath, relativePath));\n }\n if (extension === '.jsp') {\n return path.join(buildDir, 'src/main/webapp', jspDirectory, path.basename(relativePath));\n }\n\n return path.join(buildDir, 'src/main/webapp', normalizedRelativePath);\n}\n\nasync function judgeByStaticAnalysis(\n submissionDir: string,\n forbiddenTexts: readonly string[]\n): Promise<Pick<JudgeResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const forbiddenMatches: { path: string; pattern: string }[] = [];\n for (const relativePath of await listSubmissionFiles(submissionDir)) {\n if (!SOURCE_FILE_EXTENSIONS.has(path.extname(relativePath).toLowerCase())) {\n continue;\n }\n\n const sourceCode = await fs.promises.readFile(path.join(submissionDir, relativePath), 'utf8');\n for (const forbiddenText of forbiddenTexts) {\n if (sourceCode.includes(forbiddenText)) {\n forbiddenMatches.push({ path: relativePath, pattern: forbiddenText });\n }\n }\n }\n\n if (forbiddenMatches.length === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown:\n 'ソースコード中に禁止された文字列が含まれています。\\nソースコードを修正してから再度提出してください。\\n\\n| ファイル | 禁止パターン |\\n| -------- | ------------ |\\n' +\n forbiddenMatches.map((match) => `| \\`${match.path}\\` | \\`${match.pattern}\\` |`).join('\\n'),\n };\n}\n\nfunction buildWithMaven(buildDir: string): JudgeResult | undefined {\n const commandResult = runCommand(\n ['mvn', 'clean', 'package', '--quiet', '--batch-mode', '--offline'],\n buildDir,\n BUILD_TIMEOUT_SECONDS\n );\n if (commandResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n }\n if (commandResult.status === 0) {\n return undefined;\n }\n\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: formatMavenBuildError(commandResult.stdout, commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n };\n}\n\nfunction formatMavenBuildError(stdout: string, stderr: string): string {\n return truncateOutput(\n `${stdout}\n${stderr}`.trim()\n );\n}\n\nasync function createCatalinaBaseDir(catalinaHome: string, runRootDir: string): Promise<string> {\n const catalinaBaseDir = path.join(runRootDir, 'catalina-base');\n await fs.promises.cp(path.join(catalinaHome, 'conf'), path.join(catalinaBaseDir, 'conf'), {\n recursive: true,\n dereference: true,\n });\n for (const directoryName of ['logs', 'temp', 'webapps', 'work']) {\n await fs.promises.mkdir(path.join(catalinaBaseDir, directoryName), { recursive: true });\n }\n\n const serverXmlPath = path.join(catalinaBaseDir, 'conf', 'server.xml');\n const serverXml = await fs.promises.readFile(serverXmlPath, 'utf8');\n await fs.promises.writeFile(serverXmlPath, serverXml.replaceAll('port=\"8080\"', 'port=\"59000\"'), 'utf8');\n return catalinaBaseDir;\n}\n\nasync function deployWarFile(buildDir: string, catalinaBaseDir: string): Promise<void> {\n const warFilePath = path.join(buildDir, 'target', `${APPLICATION_NAME}.war`);\n const webappsDir = path.join(catalinaBaseDir, 'webapps');\n const deployedDirectoryPath = path.join(webappsDir, APPLICATION_NAME);\n const deployedWarPath = path.join(webappsDir, `${APPLICATION_NAME}.war`);\n await fs.promises.mkdir(webappsDir, { recursive: true });\n await fs.promises.rm(deployedDirectoryPath, { recursive: true, force: true });\n await fs.promises.rm(deployedWarPath, { force: true });\n await fs.promises.copyFile(warFilePath, deployedWarPath);\n}\n\nasync function startTomcat(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n const startupScriptPath = path.join(catalinaHome, 'bin', 'startup.sh');\n const startupResult = runCommand([startupScriptPath], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n if (startupResult.status !== 0) {\n throw new Error(`Failed to start Tomcat: ${startupResult.stderr.trim() || startupResult.stdout.trim()}`);\n }\n\n const started = await waitForTomcatState(true, 10_000);\n if (!started) {\n throw new Error('Tomcat failed to start within 10 seconds');\n }\n}\n\nfunction runJudgeScript(buildDir: string, problemDirectoryPath: string): JudgeResult {\n const commandResult = runCommand(\n ['bun', 'run', './judge.ts', '--evaluate'],\n problemDirectoryPath,\n EVALUATE_TIMEOUT_SECONDS,\n {\n ...process.env,\n WORKING_DIRECTORY_PATH: buildDir,\n }\n );\n\n const outputFiles = readOutputFiles(path.join(buildDir, '__SCREENSHOTS.json'));\n if (commandResult.timeSeconds > EVALUATE_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.stdout.length > MAX_OUTPUT_LENGTH || commandResult.stderr.length > MAX_OUTPUT_LENGTH) {\n return {\n decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n if (commandResult.status !== 0) {\n return {\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: truncateOutput(commandResult.stderr),\n stdout: truncateOutput(commandResult.stdout),\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n }\n\n return {\n decisionCode: DecisionCode.ACCEPTED,\n stderr: truncateOutput(commandResult.stderr) || undefined,\n stdout: truncateOutput(commandResult.stdout) || undefined,\n exitStatus: commandResult.status,\n timeSeconds: commandResult.timeSeconds,\n memoryBytes: commandResult.memoryBytes,\n outputFiles,\n };\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n cwd: string | undefined,\n timeoutSeconds: number,\n env: NodeJS.ProcessEnv = process.env\n): CommandResult {\n const startAt = Date.now();\n const timeCommand = process.platform === 'darwin' ? 'gtime' : '/usr/bin/time';\n // Hosts provide GNU timeout on PATH; Homebrew coreutils also installs the unprefixed command.\n const timedResult = spawnSync('timeout', [timeoutSeconds.toFixed(3), timeCommand, '--format', '%e %M', ...command], {\n cwd,\n env,\n encoding: 'utf8',\n });\n const elapsedSeconds = (Date.now() - startAt) / 1000;\n const stderrWithError = [timedResult.stderr, timedResult.error?.message].filter(Boolean).join('\\n');\n const memoryMatch = /(?:^|\\n)(\\d+(?:\\.\\d+)?) (\\d+)\\s*$/u.exec(stderrWithError);\n const stderr = memoryMatch ? stderrWithError.slice(0, memoryMatch.index) : stderrWithError;\n const timeSeconds = Number(memoryMatch?.[1]) || elapsedSeconds;\n const memoryBytes = 1024 * Number(memoryMatch?.[2] ?? 0);\n\n return {\n status: timedResult.status === 124 ? 0 : (timedResult.status ?? undefined),\n stdout: timedResult.stdout,\n stderr,\n timeSeconds: timedResult.status === 124 ? timeoutSeconds + 0.001 : timeSeconds,\n memoryBytes,\n };\n}\n\nasync function ensureTomcatStopped(catalinaHome: string, catalinaBaseDir: string): Promise<void> {\n if (!(await isTomcatRunning())) {\n return;\n }\n\n runCommand([path.join(catalinaHome, 'bin', 'catalina.sh'), 'stop'], undefined, 10, {\n ...process.env,\n CATALINA_BASE: catalinaBaseDir,\n CATALINA_HOME: catalinaHome,\n });\n await waitForTomcatState(false, 10_000);\n}\n\nasync function waitForTomcatState(expectedRunning: boolean, timeoutMs: number): Promise<boolean> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if ((await isTomcatRunning()) === expectedRunning) {\n return true;\n }\n await sleep(200);\n }\n return (await isTomcatRunning()) === expectedRunning;\n}\n\nasync function isTomcatRunning(): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const request = http.get(TOMCAT_BASE_URL, (response) => {\n response.resume();\n const status = response.statusCode ?? 0;\n resolve((status >= 200 && status < 300) || status === 404);\n });\n request.on('error', () => resolve(false));\n request.setTimeout(1000, () => request.destroy());\n });\n}\n\nfunction getCatalinaHome(): string {\n const catalinaHome = process.env.CATALINA_HOME;\n if (!catalinaHome) {\n throw new Error('CATALINA_HOME is not set');\n }\n\n validateCatalinaHome(catalinaHome);\n return path.resolve(catalinaHome);\n}\n\nfunction validateCatalinaHome(catalinaHome: string): void {\n if (\n !fs.existsSync(path.join(catalinaHome, 'bin', 'catalina.sh')) ||\n !fs.existsSync(path.join(catalinaHome, 'conf', 'server.xml'))\n ) {\n throw new Error('Invalid CATALINA_HOME path');\n }\n}\n\nexport function buildTomcatUrl(urlPath: string): string {\n return `${TOMCAT_BASE_URL}/${APPLICATION_NAME}${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n}\n"],"mappings":"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"}
|