@exercode/problem-utils 2.3.0 → 2.4.0

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.
Files changed (95) hide show
  1. package/README.md +28 -0
  2. package/dist/cli/checkAllProblems.cjs +1 -1
  3. package/dist/cli/checkAllProblems.cjs.map +1 -1
  4. package/dist/cli/checkAllProblems.js +1 -1
  5. package/dist/cli/checkAllProblems.js.map +1 -1
  6. package/dist/cli/runSingleHarness.cjs +1 -1
  7. package/dist/cli/runSingleHarness.js +1 -1
  8. package/dist/helpers/checkProblemDirIsolation.cjs +1 -1
  9. package/dist/helpers/checkProblemDirIsolation.cjs.map +1 -1
  10. package/dist/helpers/checkProblemDirIsolation.d.ts +9 -16
  11. package/dist/helpers/checkProblemDirIsolation.js +1 -1
  12. package/dist/helpers/checkProblemDirIsolation.js.map +1 -1
  13. package/dist/helpers/cleanWorkingDirectory.cjs +1 -1
  14. package/dist/helpers/cleanWorkingDirectory.js +1 -1
  15. package/dist/helpers/compareExpectedOutputFiles.cjs +2 -0
  16. package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -0
  17. package/dist/helpers/compareExpectedOutputFiles.d.ts +45 -0
  18. package/dist/helpers/compareExpectedOutputFiles.js +2 -0
  19. package/dist/helpers/compareExpectedOutputFiles.js.map +1 -0
  20. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.cjs +1 -1
  21. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.cjs.map +1 -1
  22. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.js +1 -1
  23. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.js.map +1 -1
  24. package/dist/helpers/defaultStdioHarness.cjs +1 -1
  25. package/dist/helpers/defaultStdioHarness.js +1 -1
  26. package/dist/helpers/findModelAnswerDirs.cjs +1 -1
  27. package/dist/helpers/findModelAnswerDirs.js +1 -1
  28. package/dist/helpers/judgeByStaticAnalysis.cjs +1 -1
  29. package/dist/helpers/judgeByStaticAnalysis.js +1 -1
  30. package/dist/helpers/readOutputFiles.cjs +1 -1
  31. package/dist/helpers/readOutputFiles.js +1 -1
  32. package/dist/helpers/readProblemMarkdownFrontMatter.cjs +1 -1
  33. package/dist/helpers/readProblemMarkdownFrontMatter.js +1 -1
  34. package/dist/helpers/readTestCases.cjs +1 -1
  35. package/dist/helpers/readTestCases.cjs.map +1 -1
  36. package/dist/helpers/readTestCases.d.ts +6 -0
  37. package/dist/helpers/readTestCases.js +1 -1
  38. package/dist/helpers/readTestCases.js.map +1 -1
  39. package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
  40. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
  41. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
  42. package/dist/helpers/runHarnessProcess.cjs +2 -0
  43. package/dist/helpers/runHarnessProcess.cjs.map +1 -0
  44. package/dist/helpers/runHarnessProcess.d.ts +23 -0
  45. package/dist/helpers/runHarnessProcess.js +2 -0
  46. package/dist/helpers/runHarnessProcess.js.map +1 -0
  47. package/dist/helpers/runIsolationCheckInDebugMode.cjs +2 -0
  48. package/dist/helpers/runIsolationCheckInDebugMode.cjs.map +1 -0
  49. package/dist/helpers/runIsolationCheckInDebugMode.d.ts +7 -0
  50. package/dist/helpers/runIsolationCheckInDebugMode.js +2 -0
  51. package/dist/helpers/runIsolationCheckInDebugMode.js.map +1 -0
  52. package/dist/helpers/safeFs.cjs +1 -1
  53. package/dist/helpers/safeFs.js +1 -1
  54. package/dist/helpers/sandboxUser.cjs +2 -2
  55. package/dist/helpers/sandboxUser.cjs.map +1 -1
  56. package/dist/helpers/sandboxUser.d.ts +5 -0
  57. package/dist/helpers/sandboxUser.js +2 -2
  58. package/dist/helpers/sandboxUser.js.map +1 -1
  59. package/dist/helpers/startHttpServer.cjs +1 -1
  60. package/dist/helpers/startHttpServer.js +1 -1
  61. package/dist/helpers/temporaryProblemDirCopy.cjs +2 -0
  62. package/dist/helpers/temporaryProblemDirCopy.cjs.map +1 -0
  63. package/dist/helpers/temporaryProblemDirCopy.d.ts +18 -0
  64. package/dist/helpers/temporaryProblemDirCopy.js +2 -0
  65. package/dist/helpers/temporaryProblemDirCopy.js.map +1 -0
  66. package/dist/index.cjs +1 -1
  67. package/dist/index.d.ts +3 -0
  68. package/dist/index.js +1 -1
  69. package/dist/presets/command.cjs +1 -1
  70. package/dist/presets/command.cjs.map +1 -1
  71. package/dist/presets/command.d.ts +35 -3
  72. package/dist/presets/command.js +1 -1
  73. package/dist/presets/command.js.map +1 -1
  74. package/dist/presets/evaluation.cjs +4 -4
  75. package/dist/presets/evaluation.cjs.map +1 -1
  76. package/dist/presets/evaluation.js +4 -4
  77. package/dist/presets/evaluation.js.map +1 -1
  78. package/dist/presets/guiCommand.cjs +3 -3
  79. package/dist/presets/guiCommand.cjs.map +1 -1
  80. package/dist/presets/guiCommand.d.ts +14 -2
  81. package/dist/presets/guiCommand.js +3 -3
  82. package/dist/presets/guiCommand.js.map +1 -1
  83. package/dist/presets/llm.cjs +1 -1
  84. package/dist/presets/llm.cjs.map +1 -1
  85. package/dist/presets/llm.d.ts +12 -10
  86. package/dist/presets/llm.js +1 -1
  87. package/dist/presets/llm.js.map +1 -1
  88. package/dist/presets/stdio.cjs +1 -1
  89. package/dist/presets/stdio.cjs.map +1 -1
  90. package/dist/presets/stdio.d.ts +3 -1
  91. package/dist/presets/stdio.js +1 -1
  92. package/dist/presets/stdio.js.map +1 -1
  93. package/dist/types/language.cjs +2 -2
  94. package/dist/types/language.js +2 -2
  95. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"guiCommand.cjs","names":["os","z","parseArgs","readProblemMarkdownFrontMatter","judgeByStaticAnalysis","findEntryPointFile","DecisionCode","findLanguageDefinitionByPath","runCustomRunner","getSandboxUserEnvOverrides","snapshotWorkingDirectory","copyTestCaseFileInput","wrapCommandWithSandboxUser","cleanWorkingDirectory","readOutputFiles","sandboxUserName","path","languageIdToDefinition","spawnSyncWithTimeout","readFileTestCases","TIMEOUT_COMMAND","startSandboxTimeoutWatchdog","childProcess","wait","getTrustedHelperEnv","fs"],"sources":["../../src/presets/guiCommand.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as wait } from 'node:timers/promises';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.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 { readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases as readFileTestCases } from '../helpers/readTestCases.js';\nimport { runCustomRunner } from '../helpers/runCustomRunner.js';\nimport {\n getSandboxUserEnvOverrides,\n getTrustedHelperEnv,\n killSandboxUserProcesses,\n makeAccessibleToSandboxUser,\n sandboxUserName,\n startSandboxTimeoutWatchdog,\n TIMEOUT_COMMAND,\n wrapCommandWithSandboxUser,\n} from '../helpers/sandboxUser.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { languageIdToDefinition } from '../types/language.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 5;\nconst SCREENSHOT_WAIT_SECONDS = 0.3;\nconst XVFB_STARTUP_WAIT_SECONDS = 0.3;\nconst XVFB_SHUTDOWN_WAIT_SECONDS = 0.1;\nconst PROCESS_SHUTDOWN_WAIT_SECONDS = 0.2;\nconst STOP_DETECTION_THRESHOLD = 5;\nconst TIME_COMMAND = [os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time', '--format', '%e %M'] as const;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\ninterface BaseGuiTestCase {\n id: string;\n input?: string;\n fileInputPath?: string;\n}\n\nexport interface GuiScreenshotFile {\n path: string;\n data: string;\n encoding: 'base64';\n}\n\nexport interface GuiCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n screenshots: GuiScreenshotFile[];\n stopReason: 'process_exit' | 'stable_screenshot' | 'timeout';\n}\n\ninterface GuiJudgeContext {\n timeLimitSeconds: number;\n problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;\n}\n\ntype GuiJudgeCaseResult = Pick<\n TestCaseResult,\n 'decisionCode' | 'feedbackMarkdown' | 'stderr' | 'stdout' | 'outputFiles'\n>;\n\nexport interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase = BaseGuiTestCase> {\n mainFilePath?: string;\n runTimeoutSeconds?: number;\n screenshotWaitSeconds?: number;\n stopDetectionThreshold?: number;\n readTestCases?: (problemDir: string) => Promise<readonly TTestCase[]>;\n prepare?: (context: {\n cwd: string;\n /**\n * Build the submission with it. Under `EXERCODE_SANDBOX_USER` delegation it already carries the\n * sandbox user's overrides, but the handler must still wrap whatever it spawns with\n * `wrapCommandWithSandboxUser` (both exported): a build runs the submission's own scripts\n * (`package.json` lifecycle scripts, `build.gradle`, `build.rs`), which as the trusted harness\n * user could read the problem's test cases and rewrite the harness. The preset terminates\n * leftover sandbox processes after the handler returns.\n */\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n problemMarkdownFrontMatter: ProblemMarkdownFrontMatter;\n }) => Promise<Partial<GuiJudgeCaseResult> | undefined> | Partial<GuiJudgeCaseResult> | undefined;\n /**\n * Runs as the trusted harness user with the submission's `cwd`. Fixture files it creates there\n * must be written with `createDirectoryWithoutFollowingSymlinks`/`writeFileWithoutFollowingSymlinks`\n * (both exported): a submission can plant a symlink at a fixture path, and a plain `fs.writeFile`\n * would follow it into a file only the harness can write.\n */\n resolveInput?: (context: { testCase: TTestCase; cwd: string; env: NodeJS.ProcessEnv }) => Promise<string> | string;\n command?: (context: {\n testCase: TTestCase;\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n }) => Promise<readonly [string, ...string[]]> | readonly [string, ...string[]];\n runCommand?: (context: {\n testCase: TTestCase;\n /**\n * The command to run. Under `EXERCODE_SANDBOX_USER` delegation it is already wrapped so it\n * executes as the sandbox user; spawn it as given (with the supplied `env`) instead of\n * reconstructing it, or the submission runs as the trusted harness user.\n *\n * Its direct child is then a root-owned `sudo` whose descendants belong to the sandbox user, so\n * the handler cannot signal them: enforce `timeLimitSeconds` with `startSandboxTimeoutWatchdog`\n * and `killSandboxUserProcesses` (both exported) rather than `child.kill()` or an outer\n * `timeout`. The preset terminates leftover sandbox processes after the handler returns.\n */\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n }) => Promise<GuiCommandRunResult> | GuiCommandRunResult;\n test: (context: {\n testCase: TTestCase;\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: GuiJudgeContext;\n }) => Promise<Partial<GuiJudgeCaseResult>> | Partial<GuiJudgeCaseResult>;\n}\n\n/**\n * A preset function for judging GUI programs by collecting screenshots while the program runs.\n *\n * Keep problem-specific logic in `prepare`, `command`, and `test`.\n *\n * @example\n * Create `judge.ts`:\n * ```ts\n * import { DecisionCode } from '@exercode/problem-utils';\n * import { guiCommandJudgePreset } from '@exercode/problem-utils/presets/guiCommand';\n *\n * await guiCommandJudgePreset(import.meta.dirname, {\n * mainFilePath: 'Main.java',\n * readTestCases: async () => [{ id: 'default' }],\n * test: ({ runResult }) => {\n * return runResult.screenshots.length > 0\n * ? { decisionCode: DecisionCode.ACCEPTED }\n * : { decisionCode: DecisionCode.WRONG_ANSWER };\n * },\n * });\n * ```\n */\nexport async function guiCommandJudgePreset<TTestCase extends BaseGuiTestCase = BaseGuiTestCase>(\n problemDir: string,\n options: GuiCommandJudgePresetOptions<TTestCase>\n): 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 const submissionDir = args.cwd;\n\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(submissionDir);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const configuredTestCases = await (options.readTestCases ?? readGuiTestCases<TTestCase>)(problemDir);\n const testCases =\n configuredTestCases.length > 0 ? configuredTestCases : ([{ id: 'default' }] as unknown as readonly TTestCase[]);\n const prebuildTestCaseId = testCases[0]?.id ?? 'prebuild';\n\n const staticAnalysisResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId: prebuildTestCaseId, ...staticAnalysisResult });\n return;\n }\n\n const initialMainFilePath = options.mainFilePath ?? (await findEntryPointFile(args.cwd, params.language));\n if (!initialMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(initialMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let resolvedMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language,\n configuredMainFilePath: options.mainFilePath,\n });\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n const prebuiltMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language ?? inferLanguageIdsByPath(initialMainFilePath),\n configuredMainFilePath: options.mainFilePath,\n allowConfiguredPathFallback: true,\n });\n if (prebuiltMainFilePath) resolvedMainFilePath = prebuiltMainFilePath;\n } catch (error) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n if (!resolvedMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n let customPrepareResult: Partial<GuiJudgeCaseResult> | undefined;\n if (options.prepare) {\n try {\n customPrepareResult = await runCustomRunner(() =>\n options.prepare?.({\n cwd: submissionDir,\n env: { ...env, ...getSandboxUserEnvOverrides(env) },\n mainFilePath: resolvedMainFilePath,\n problemMarkdownFrontMatter,\n })\n );\n } catch (error) {\n // Like the `prebuild` step above: report the failed build rather than letting the throw (from\n // the handler itself, or from the fail-closed sandbox sweep around it) end the run resultless.\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n const prepareResult =\n customPrepareResult ??\n runDefaultPrepare({\n cwd: args.cwd,\n env,\n mainFilePath: resolvedMainFilePath,\n languageDefinition,\n });\n if (prepareResult) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: prepareResult.decisionCode ?? DecisionCode.BUILD_ERROR,\n feedbackMarkdown: prepareResult.feedbackMarkdown,\n stderr: prepareResult.stderr,\n stdout: prepareResult.stdout,\n outputFiles: prepareResult.outputFiles,\n });\n return;\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n let displayServer: Awaited<ReturnType<typeof ensureDisplayServer>> | undefined;\n let currentTestCaseId = prebuildTestCaseId;\n let currentStdin: string | undefined;\n try {\n displayServer = options.runCommand ? undefined : await ensureDisplayServer();\n const sharedFileInputPath = (configuredTestCases as { shared?: { fileInputPath?: string } }).shared?.fileInputPath;\n for (const testCase of testCases) {\n currentTestCaseId = testCase.id;\n if (sharedFileInputPath) await copyTestCaseFileInput(sharedFileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n const timeLimitSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : (options.runTimeoutSeconds ?? JUDGE_DEFAULT_TIMEOUT_SECONDS);\n\n const runEnv = displayServer ? { ...env, DISPLAY: displayServer.display } : env;\n const stdin = (await options.resolveInput?.({ testCase, cwd: args.cwd, env: runEnv })) ?? testCase.input ?? '';\n currentStdin = stdin;\n const command =\n (await options.command?.({ testCase, cwd: args.cwd, env: runEnv, mainFilePath: resolvedMainFilePath })) ??\n languageDefinition.command(resolvedMainFilePath);\n\n let runResult: GuiCommandRunResult;\n try {\n runResult = options.runCommand\n ? await runCustomRunner(\n () =>\n // Hand custom runners a command that is already sandbox-wrapped, and the matching\n // environment: they spawn it themselves, so an unwrapped command would run the\n // submission as the trusted harness user and defeat the delegation boundary.\n options.runCommand?.({\n testCase,\n command: wrapCommandWithSandboxUser(command),\n stdin,\n cwd: submissionDir,\n env: { ...runEnv, ...getSandboxUserEnvOverrides(runEnv) },\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n }) as Promise<GuiCommandRunResult>\n )\n : await spawnGuiProgram({\n command,\n stdin,\n cwd: args.cwd,\n env: runEnv,\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n });\n } catch (error) {\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n return;\n }\n\n const outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n const judgeContext: GuiJudgeContext = {\n timeLimitSeconds,\n problemMarkdownFrontMatter: {\n memoryLimitByte: problemMarkdownFrontMatter.memoryLimitByte,\n requiredOutputFilePaths: problemMarkdownFrontMatter.requiredOutputFilePaths,\n },\n };\n const baseJudgeResult = evaluateGuiRunResult({ runResult, outputFiles, context: judgeContext });\n let judgeResult = baseJudgeResult;\n if (baseJudgeResult.decisionCode === DecisionCode.ACCEPTED) {\n try {\n const extendedJudgeResult = await options.test({\n testCase,\n runResult,\n outputFiles,\n context: judgeContext,\n });\n judgeResult = {\n decisionCode: extendedJudgeResult.decisionCode ?? baseJudgeResult.decisionCode,\n feedbackMarkdown: extendedJudgeResult.feedbackMarkdown,\n stderr: extendedJudgeResult.stderr,\n stdout: extendedJudgeResult.stdout,\n outputFiles: extendedJudgeResult.outputFiles,\n };\n } catch (error) {\n judgeResult = {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: errorToMessage(error),\n };\n }\n }\n\n const decisionCode = judgeResult.decisionCode ?? DecisionCode.ACCEPTED;\n const stdout = judgeResult.stdout ?? runResult.stdout;\n const stderr = judgeResult.stderr ?? runResult.stderr;\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: runResult.status,\n stdin: runResult.stdin || undefined,\n stdout: stdout || undefined,\n stderr: stderr || undefined,\n timeSeconds: runResult.timeSeconds,\n memoryBytes: runResult.memoryBytes,\n feedbackMarkdown: judgeResult.feedbackMarkdown,\n outputFiles: judgeResult.outputFiles ?? (outputFiles.length > 0 ? outputFiles : undefined),\n });\n\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n } catch (error) {\n printTestCaseResult({\n testCaseId: currentTestCaseId,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin: currentStdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n } finally {\n try {\n // Sweep any sandbox processes the run left behind (e.g. a SIGTERM-ignoring forked child) so\n // they cannot race the harness's output reads or survive into the next test case/request.\n if (sandboxUserName) killSandboxUserProcesses();\n } finally {\n // Must run even when the sweep fails closed, or Xvfb keeps holding its display number and\n // later GUI requests exhaust the `:90`–`:99` range.\n await displayServer?.dispose();\n }\n }\n}\n\nasync function resolveMainFilePath(context: {\n cwd: string;\n language?: string | string[];\n configuredMainFilePath?: string;\n allowConfiguredPathFallback?: boolean;\n}): Promise<string | undefined> {\n if (context.configuredMainFilePath) {\n const resolvedPath = path.join(context.cwd, context.configuredMainFilePath);\n if (await pathExists(resolvedPath)) return context.configuredMainFilePath;\n if (!context.allowConfiguredPathFallback) return undefined;\n }\n\n return await findEntryPointFile(context.cwd, context.language);\n}\n\nfunction inferLanguageIdsByPath(filePath: string): string[] | undefined {\n const languageIds = Object.entries(languageIdToDefinition)\n .filter(([, definition]) => definition.fileExtensions.some((ext) => filePath.endsWith(ext)))\n .map(([languageId]) => languageId);\n return languageIds.length > 0 ? languageIds : undefined;\n}\n\nfunction runDefaultPrepare(context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n languageDefinition: NonNullable<ReturnType<typeof findLanguageDefinitionByPath>>;\n}): Partial<GuiJudgeCaseResult> | undefined {\n const buildCommand = context.languageDefinition.buildCommand?.(context.mainFilePath);\n if (!buildCommand) return undefined;\n\n const buildResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: context.cwd, encoding: 'utf8', env: context.env },\n BUILD_TIMEOUT_SECONDS\n );\n\n if (buildResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: buildResult.stderr || undefined,\n };\n }\n\n if (buildResult.status !== 0) {\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: buildResult.stderr || buildResult.stdout || undefined,\n };\n }\n\n return undefined;\n}\n\nfunction evaluateGuiRunResult(context: {\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: GuiJudgeContext;\n}): Partial<GuiJudgeCaseResult> {\n if (context.runResult.stopReason === 'timeout') {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n if (context.runResult.status !== 0) {\n return {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: context.runResult.stderr,\n };\n }\n\n if (\n context.runResult.memoryBytes >\n (context.context.problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)\n ) {\n return {\n decisionCode: DecisionCode.MEMORY_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n const requiredOutputFilesCount = context.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0;\n if (context.outputFiles.length < requiredOutputFilesCount) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR,\n };\n }\n\n return { decisionCode: DecisionCode.ACCEPTED };\n}\n\nasync function readGuiTestCases<TTestCase extends BaseGuiTestCase>(problemDir: string): Promise<readonly TTestCase[]> {\n return (await readFileTestCases(path.join(problemDir, 'test_cases'))) as unknown as readonly TTestCase[];\n}\n\nasync function spawnGuiProgram(context: {\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n}): Promise<GuiCommandRunResult> {\n const wrappedCommand = wrapCommandWithSandboxUser([\n TIMEOUT_COMMAND,\n context.timeLimitSeconds.toFixed(3),\n ...TIME_COMMAND,\n ...context.command,\n ]);\n // A submission can signal its own sandboxed `timeout` and then wedge the event loop (e.g.\n // XGrabServer makes the synchronous `xwininfo`/`maim` block), so a harness-owned watchdog\n // force-kills the sandbox user at the deadline; killing the client also releases its X grab.\n // Armed before the submission starts: a submission that exhausts the PID cgroup would otherwise\n // prevent the watchdog from spawning at all.\n const watchdog = startSandboxTimeoutWatchdog(context.timeLimitSeconds);\n let child: childProcess.ChildProcessWithoutNullStreams;\n try {\n child = childProcess.spawn(wrappedCommand[0], wrappedCommand.slice(1), {\n cwd: context.cwd,\n env: { ...context.env, ...getSandboxUserEnvOverrides(context.env) },\n detached: process.platform !== 'win32',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n watchdog.cancel();\n throw error;\n }\n\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n\n let stdout = '';\n let stderr = '';\n let exitCode: number | undefined;\n let spawnError: Error | undefined;\n let stopReason: GuiCommandRunResult['stopReason'] = 'process_exit';\n let submissionStopped = false;\n const screenshotSignaturesHistory: string[][] = [];\n let screenshots: GuiScreenshotFile[] = [];\n const startTimeSeconds = Date.now() / 1000;\n let sampledMemoryBytes = 0;\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n });\n child.on('error', (error) => {\n spawnError = error;\n exitCode = 1;\n });\n // A child that exits before the input is fully written makes the write fail; without a listener\n // that EPIPE would be an unhandled stream error terminating the harness mid-run.\n child.stdin.on('error', () => {\n // The submission simply stopped reading its input; the exit handling below reports the result.\n });\n child.on('close', (code, signal) => {\n if (code === 124) {\n stopReason = 'timeout';\n exitCode = 0;\n return;\n }\n if (signal) {\n exitCode = 1;\n stderr = stderr\n ? `${stderr}\\nprocess terminated by signal: ${signal}`\n : `process terminated by signal: ${signal}`;\n return;\n }\n exitCode = code ?? 1;\n });\n\n // Everything below must run under the `finally` that decides the watchdog's fate: it is cancelled\n // only once the submission is demonstrably stopped. When a helper throws first — `takeScreenshots`\n // rethrows spawn failures, which a submission can provoke by exhausting PIDs — the watchdog stays\n // armed on purpose, since nothing else would end the submission. The cost is that a detached\n // watchdog can outlive this harness and SIGKILL sandbox processes of a request that starts within\n // its remaining deadline, which is the same trade-off the package-manager runner accepts.\n try {\n if (context.stdin) child.stdin.write(context.stdin);\n child.stdin.end();\n\n while (exitCode === undefined) {\n await wait(context.screenshotWaitSeconds * 1000);\n sampledMemoryBytes = Math.max(sampledMemoryBytes, readProcessGroupMemoryBytes(child.pid));\n const currentScreenshots = takeScreenshots(context.env.DISPLAY);\n screenshots = currentScreenshots.toSorted((a, b) => a.data.length - b.data.length);\n\n if (screenshots.length > 0) {\n const screenshotSignatures = screenshots.map((file) => file.data).toSorted();\n screenshotSignaturesHistory.unshift(screenshotSignatures);\n screenshotSignaturesHistory.length = Math.min(\n screenshotSignaturesHistory.length,\n context.stopDetectionThreshold\n );\n if (\n screenshotSignaturesHistory.length === context.stopDetectionThreshold &&\n screenshotSignaturesHistory.every(\n (files) =>\n files.length === screenshotSignatures.length &&\n files.every((file, index) => file === screenshotSignatures[index])\n )\n ) {\n stopReason = 'stable_screenshot';\n exitCode = 0;\n break;\n }\n }\n\n if (Date.now() / 1000 - startTimeSeconds > context.timeLimitSeconds) {\n stopReason = 'timeout';\n exitCode = 0;\n break;\n }\n }\n\n if (stopReason !== 'process_exit' && child.exitCode === null) {\n child.removeAllListeners('close');\n child.removeAllListeners('error');\n }\n // A submission can kill its own same-UID `timeout` and be stopped by the watchdog instead,\n // which surfaces as a SIGKILL rather than a deadline. Report that as the timeout it is, not as\n // the runtime error the signal would otherwise imply — but only when the child's fate is what\n // decided the verdict. A `stable_screenshot` (or the loop's own `timeout`) was already decided\n // from the screenshots, so the watchdog must not overwrite it.\n const watchdogFired = stopReason === 'process_exit' && watchdog.fired();\n await stopProcess(child);\n submissionStopped = true;\n if (spawnError) throw spawnError;\n const {\n memoryBytes,\n stderr: normalizedStderr,\n timeSeconds,\n } = parseTimedStderr(stderr, startTimeSeconds, sampledMemoryBytes);\n\n return {\n stdin: context.stdin,\n stdout: stdout.trimEnd(),\n stderr: normalizedStderr,\n status: watchdogFired ? 0 : exitCode,\n timeSeconds,\n memoryBytes,\n screenshots,\n stopReason: watchdogFired ? 'timeout' : stopReason,\n };\n } finally {\n // Only once the submission is demonstrably stopped: if termination itself failed (or never ran\n // because a helper threw), the watchdog is the only thing left able to end it, and cancelling\n // would leave it running unchecked.\n if (submissionStopped) watchdog.cancel();\n }\n}\n\nfunction takeScreenshots(display: string | undefined): GuiScreenshotFile[] {\n // These helpers run as the trusted harness user, so they must not be resolved through a\n // submission-influenced `PATH` (see `getTrustedHelperEnv`).\n const env = getTrustedHelperEnv(display ? { DISPLAY: display } : { DISPLAY: process.env.DISPLAY });\n const xwininfo = childProcess.spawnSync('xwininfo', ['-root', '-tree'], { encoding: 'utf8', env });\n if (xwininfo.error) throw xwininfo.error;\n if (xwininfo.status !== 0 || !xwininfo.stdout) return [];\n\n const screenshots: GuiScreenshotFile[] = [];\n for (const windowId of extractTopLevelWindowIds(xwininfo.stdout)) {\n const screenshot = childProcess.spawnSync('maim', ['-i', windowId], { env });\n if (screenshot.error) throw screenshot.error;\n if (screenshot.status !== 0 || screenshot.stdout.length === 0) continue;\n\n const windowNameResult = childProcess.spawnSync('xdotool', ['getwindowname', windowId], { encoding: 'utf8', env });\n if (windowNameResult.error) throw windowNameResult.error;\n const windowName = windowNameResult.stdout.trim().replaceAll(/[\\s/]/g, '_');\n\n screenshots.push({\n path: `${windowName || 'window'}_${windowId}.png`,\n data: screenshot.stdout.toString('base64'),\n encoding: 'base64',\n });\n }\n\n return screenshots;\n}\n\nfunction extractTopLevelWindowIds(stdout: string): string[] {\n const windowIds: string[] = [];\n const lines = stdout.split('\\n');\n for (const line of lines) {\n if (line.includes('Root window id:') || line.includes('Parent window id:') || line.includes('()')) continue;\n\n const match = /^\\s{5}(0x[\\da-f]+) /.exec(line);\n if (!match?.[1]) continue;\n windowIds.push(Number.parseInt(match[1], 16).toString());\n }\n return windowIds;\n}\n\nfunction parseTimedStderr(\n stderr: string,\n startTimeSeconds: number,\n sampledMemoryBytes: number\n): Pick<GuiCommandRunResult, 'stderr' | 'timeSeconds' | 'memoryBytes'> {\n const match = /(?:^|\\n)(\\d+\\.\\d+) (\\d+)\\s*$/.exec(stderr);\n const normalizedStderr = match ? stderr.slice(0, match.index).trimEnd() : stderr.trimEnd();\n const parsedMemoryBytes = Number(match?.[2]) * 1024 || 0;\n return {\n stderr: normalizedStderr,\n timeSeconds: Number(match?.[1]) || Date.now() / 1000 - startTimeSeconds,\n memoryBytes: Math.max(parsedMemoryBytes, sampledMemoryBytes),\n };\n}\n\nasync function ensureDisplayServer(): Promise<{ display: string; dispose: () => Promise<void> }> {\n if (process.platform !== 'linux') {\n throw new Error('GUI screenshot capture is supported only on Linux.');\n }\n\n for (let displayNumber = 90; displayNumber < 100; displayNumber++) {\n const display = `:${displayNumber}`;\n let spawnError: Error | undefined;\n const xvfb = childProcess.spawn('Xvfb', [display, '-screen', '0', '1280x1024x24', '-ac'], {\n stdio: 'ignore',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n xvfb.on('error', (error) => {\n spawnError = error;\n });\n\n await wait(XVFB_STARTUP_WAIT_SECONDS * 1000);\n if (spawnError) throw spawnError;\n if (xvfb.exitCode !== null) continue;\n\n return {\n display,\n dispose: async () => {\n if (!xvfb.killed) {\n xvfb.kill('SIGTERM');\n await wait(XVFB_SHUTDOWN_WAIT_SECONDS * 1000);\n if (xvfb.exitCode === null) xvfb.kill('SIGKILL');\n }\n },\n };\n }\n\n throw new Error('Xvfb could not be started.');\n}\n\nasync function stopProcess(child: childProcess.ChildProcess): Promise<void> {\n if (!child.pid) return;\n // The current user cannot signal the root-owned sudo wrapper nor the sandbox user's processes, so\n // go through sudo. SIGKILL is unconditional after the grace period: `child.exitCode` reflects only\n // the sudo wrapper, so a daemonized child that ignored SIGTERM would survive if we gated on it.\n if (sandboxUserName) {\n killSandboxUserProcesses(['TERM']);\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n killSandboxUserProcesses(['KILL']);\n return;\n }\n killProcessGroup(child.pid, 'SIGTERM');\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n if (child.exitCode === null) killProcessGroup(child.pid, 'SIGKILL');\n}\n\nfunction readProcessGroupMemoryBytes(processGroupId: number | undefined): number {\n if (!processGroupId || process.platform !== 'linux') return 0;\n\n // The delegation contract requires sudoers `!use_pty`, so sudo execs the command in place and it\n // stays in the detached child's process group; `--pgroup` therefore still captures it there.\n const result = childProcess.spawnSync('ps', ['-o', 'rss=', '--no-headers', '--pgroup', String(processGroupId)], {\n encoding: 'utf8',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n if (result.error || result.status !== 0 || !result.stdout) return 0;\n\n return result.stdout\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter((value) => Number.isFinite(value) && value > 0)\n .reduce((sum, value) => sum + value * 1024, 0);\n}\n\nfunction killProcessGroup(processGroupId: number, signal: NodeJS.Signals): void {\n try {\n if (process.platform !== 'win32') {\n process.kill(-processGroupId, signal);\n return;\n }\n } catch {\n // The process group may already be gone. Fall back to the direct PID below.\n }\n\n try {\n process.kill(processGroupId, signal);\n } catch {\n // The direct child may also already be gone.\n }\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction errorToMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"8iCAmCA,MAEM,EAA0B,GAG1B,EAAgC,GAEhC,EAAe,CAACA,EAAAA,QAAG,SAAS,IAAM,SAAW,QAAU,gBAAiB,WAAY,OAAO,EAE3F,EAAoBC,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,EAsHD,eAAsB,EACpB,EACA,EACe,CACf,IAAM,EAAOC,EAAAA,UAAU,QAAQ,IAAI,EACnC,GAAI,CAAC,EAAK,IAAK,MAAU,MAAM,uBAAuB,EACtD,IAAM,EAAS,EAAkB,MAAM,EAAK,MAAM,EAC5C,EAAgB,EAAK,IAG3B,EAAA,4BAA4B,CAAa,EAEzC,IAAM,EAA6B,MAAMC,EAAAA,+BAA+B,CAAU,EAC5E,EAAsB,MAAO,EAAQ,eAAiB,EAAA,CAA6B,CAAU,EAC7F,EACJ,EAAoB,OAAS,EAAI,EAAuB,CAAC,CAAE,GAAI,SAAU,CAAC,EACtE,EAAqB,EAAU,EAAE,EAAE,IAAM,WAEzC,EAAuB,MAAMC,EAAAA,sBAAsB,EAAK,IAAK,CAA0B,EAC7F,GAAI,EAAsB,CACxB,EAAA,oBAAoB,CAAE,WAAY,EAAoB,GAAG,CAAqB,CAAC,EAC/E,MACF,CAEA,IAAM,EAAsB,EAAQ,cAAiB,MAAMC,EAAAA,mBAAmB,EAAK,IAAK,EAAO,QAAQ,EACvG,GAAI,CAAC,EAAqB,CACxB,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAcC,EAAAA,aAAa,uCAC3B,OAAQ,EAAQ,aACZ,iCAAiC,EAAQ,eACzC,sBAAsB,EAAO,SAAW,eAAe,EAAO,WAAa,IACjF,CAAC,EACD,MACF,CAEA,IAAM,EAAqBC,EAAAA,6BAA6B,CAAmB,EAC3E,GAAI,CAAC,EAAoB,CACvB,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAcD,EAAAA,aAAa,aAC3B,OAAQ,sBACV,CAAC,EACD,MACF,CAEA,IAAM,EAAM,CAAE,GAAG,QAAQ,IAAK,GAAI,GAAI,YAAa,GAAI,EAEnD,EAAuB,MAAM,EAAoB,CACnD,IAAK,EAAK,IACV,SAAU,EAAO,SACjB,uBAAwB,EAAQ,YAClC,CAAC,EACD,GAAI,EAAmB,SACrB,GAAI,CACF,MAAM,EAAmB,SAAS,EAAK,GAAG,EAC1C,IAAM,EAAuB,MAAM,EAAoB,CACrD,IAAK,EAAK,IACV,SAAU,EAAO,UAAY,EAAuB,CAAmB,EACvE,uBAAwB,EAAQ,aAChC,4BAA6B,EAC/B,CAAC,EACG,IAAsB,EAAuB,EACnD,OAAS,EAAO,CACd,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAcA,EAAAA,aAAa,YAC3B,OAAQ,EAAe,CAAK,CAC9B,CAAC,EACD,MACF,CAEF,GAAI,CAAC,EAAsB,CACzB,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAcA,EAAAA,aAAa,uCAC3B,OAAQ,EAAQ,aACZ,iCAAiC,EAAQ,eACzC,sBAAsB,EAAO,SAAW,eAAe,EAAO,WAAa,IACjF,CAAC,EACD,MACF,CAEA,IAAI,EACJ,GAAI,EAAQ,QACV,GAAI,CACF,EAAsB,MAAME,EAAAA,oBAC1B,EAAQ,UAAU,CAChB,IAAK,EACL,IAAK,CAAE,GAAG,EAAK,GAAGC,EAAAA,2BAA2B,CAAG,CAAE,EAClD,aAAc,EACd,4BACF,CAAC,CACH,CACF,OAAS,EAAO,CAGd,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAcH,EAAAA,aAAa,YAC3B,OAAQ,EAAe,CAAK,CAC9B,CAAC,EACD,MACF,CAEF,IAAM,EACJ,GACA,EAAkB,CAChB,IAAK,EAAK,IACV,MACA,aAAc,EACd,oBACF,CAAC,EACH,GAAI,EAAe,CACjB,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAc,EAAc,cAAgBA,EAAAA,aAAa,YACzD,iBAAkB,EAAc,iBAChC,OAAQ,EAAc,OACtB,OAAQ,EAAc,OACtB,YAAa,EAAc,WAC7B,CAAC,EACD,MACF,CAEA,IAAM,EAAc,MAAMI,EAAAA,yBAAyB,EAAK,GAAG,EACvD,EACA,EAAoB,EACpB,EACJ,GAAI,CACF,EAAgB,EAAQ,WAAa,IAAA,GAAY,MAAM,EAAoB,EAC3E,IAAM,EAAuB,EAAgE,QAAQ,cACrG,IAAK,IAAM,KAAY,EAAW,CAChC,EAAoB,EAAS,GACzB,GAAqB,MAAMC,EAAAA,sBAAsB,EAAqB,EAAK,GAAG,EAC9E,EAAS,eAAe,MAAMA,EAAAA,sBAAsB,EAAS,cAAe,EAAK,GAAG,EAExF,IAAM,EACJ,OAAO,EAA2B,aAAgB,SAC9C,EAA2B,YAAc,IACxC,EAAQ,mBAAqB,EAE9B,EAAS,EAAgB,CAAE,GAAG,EAAK,QAAS,EAAc,OAAQ,EAAI,EACtE,EAAS,MAAM,EAAQ,eAAe,CAAE,WAAU,IAAK,EAAK,IAAK,IAAK,CAAO,CAAC,GAAM,EAAS,OAAS,GAC5G,EAAe,EACf,IAAM,EACH,MAAM,EAAQ,UAAU,CAAE,WAAU,IAAK,EAAK,IAAK,IAAK,EAAQ,aAAc,CAAqB,CAAC,GACrG,EAAmB,QAAQ,CAAoB,EAE7C,EACJ,GAAI,CACF,EAAY,EAAQ,WAChB,MAAMH,EAAAA,oBAKF,EAAQ,aAAa,CACnB,WACA,QAASI,EAAAA,2BAA2B,CAAO,EAC3C,QACA,IAAK,EACL,IAAK,CAAE,GAAG,EAAQ,GAAGH,EAAAA,2BAA2B,CAAM,CAAE,EACxD,mBACA,sBAAuB,EAAQ,uBAAyB,EACxD,uBAAwB,EAAQ,wBAA0B,CAC5D,CAAC,CACL,EACA,MAAM,EAAgB,CACpB,UACA,QACA,IAAK,EAAK,IACV,IAAK,EACL,mBACA,sBAAuB,EAAQ,uBAAyB,EACxD,uBAAwB,EAAQ,wBAA0B,CAC5D,CAAC,CACP,OAAS,EAAO,CACd,EAAA,oBAAoB,CAClB,WAAY,EAAS,GACrB,aAAcH,EAAAA,aAAa,cAC3B,QACA,OAAQ,EAAe,CAAK,CAC9B,CAAC,EACD,MAAMO,EAAAA,sBAAsB,EAAK,IAAK,CAAW,EACjD,MACF,CAEA,IAAM,EAAc,MAAMC,EAAAA,gBAAgB,EAAK,IAAK,EAA2B,yBAA2B,CAAC,CAAC,EACtG,EAAgC,CACpC,mBACA,2BAA4B,CAC1B,gBAAiB,EAA2B,gBAC5C,wBAAyB,EAA2B,uBACtD,CACF,EACM,EAAkB,EAAqB,CAAE,YAAW,cAAa,QAAS,CAAa,CAAC,EAC1F,EAAc,EAClB,GAAI,EAAgB,eAAiBR,EAAAA,aAAa,SAChD,GAAI,CACF,IAAM,EAAsB,MAAM,EAAQ,KAAK,CAC7C,WACA,YACA,cACA,QAAS,CACX,CAAC,EACD,EAAc,CACZ,aAAc,EAAoB,cAAgB,EAAgB,aAClE,iBAAkB,EAAoB,iBACtC,OAAQ,EAAoB,OAC5B,OAAQ,EAAoB,OAC5B,YAAa,EAAoB,WACnC,CACF,OAAS,EAAO,CACd,EAAc,CACZ,aAAcA,EAAAA,aAAa,cAC3B,OAAQ,EAAe,CAAK,CAC9B,CACF,CAGF,IAAM,EAAe,EAAY,cAAgBA,EAAAA,aAAa,SACxD,EAAS,EAAY,QAAU,EAAU,OACzC,EAAS,EAAY,QAAU,EAAU,OAe/C,GAdA,EAAA,oBAAoB,CAClB,WAAY,EAAS,GACrB,eACA,WAAY,EAAU,OACtB,MAAO,EAAU,OAAS,IAAA,GAC1B,OAAQ,GAAU,IAAA,GAClB,OAAQ,GAAU,IAAA,GAClB,YAAa,EAAU,YACvB,YAAa,EAAU,YACvB,iBAAkB,EAAY,iBAC9B,YAAa,EAAY,cAAgB,EAAY,OAAS,EAAI,EAAc,IAAA,GAClF,CAAC,EAED,MAAMO,EAAAA,sBAAsB,EAAK,IAAK,CAAW,EAC7C,IAAiBP,EAAAA,aAAa,SAAU,KAC9C,CACF,OAAS,EAAO,CACd,EAAA,oBAAoB,CAClB,WAAY,EACZ,aAAcA,EAAAA,aAAa,cAC3B,MAAO,EACP,OAAQ,EAAe,CAAK,CAC9B,CAAC,EACD,MAAMO,EAAAA,sBAAsB,EAAK,IAAK,CAAW,CACnD,QAAU,CACR,GAAI,CAGEE,EAAAA,iBAAiB,EAAA,yBAAyB,CAChD,QAAU,CAGR,MAAM,GAAe,QAAQ,CAC/B,CACF,CACF,CAEA,eAAe,EAAoB,EAKH,CAC9B,GAAI,EAAQ,uBAAwB,CAElC,GAAI,MAAM,EADWC,EAAAA,QAAK,KAAK,EAAQ,IAAK,EAAQ,sBACpB,CAAC,EAAG,OAAO,EAAQ,uBACnD,GAAI,CAAC,EAAQ,4BAA6B,MAC5C,CAEA,OAAO,MAAMX,EAAAA,mBAAmB,EAAQ,IAAK,EAAQ,QAAQ,CAC/D,CAEA,SAAS,EAAuB,EAAwC,CACtE,IAAM,EAAc,OAAO,QAAQY,EAAAA,sBAAsB,CAAC,CACvD,QAAQ,EAAG,KAAgB,EAAW,eAAe,KAAM,GAAQ,EAAS,SAAS,CAAG,CAAC,CAAC,CAAC,CAC3F,KAAK,CAAC,KAAgB,CAAU,EACnC,OAAO,EAAY,OAAS,EAAI,EAAc,IAAA,EAChD,CAEA,SAAS,EAAkB,EAKiB,CAC1C,IAAM,EAAe,EAAQ,mBAAmB,eAAe,EAAQ,YAAY,EACnF,GAAI,CAAC,EAAc,OAEnB,IAAM,EAAcC,EAAAA,qBAClB,EAAa,GACb,EAAa,MAAM,CAAC,EACpB,CAAE,IAAK,EAAQ,IAAK,SAAU,OAAQ,IAAK,EAAQ,GAAI,EACvD,EACF,EAEA,GAAI,EAAY,YAAc,GAC5B,MAAO,CACL,aAAcZ,EAAAA,aAAa,0BAC3B,OAAQ,EAAY,QAAU,IAAA,EAChC,EAGF,GAAI,EAAY,SAAW,EACzB,MAAO,CACL,aAAcA,EAAAA,aAAa,YAC3B,OAAQ,EAAY,QAAU,EAAY,QAAU,IAAA,EACtD,CAIJ,CAEA,SAAS,EAAqB,EAIE,CAC9B,GAAI,EAAQ,UAAU,aAAe,UACnC,MAAO,CACL,aAAcA,EAAAA,aAAa,oBAC3B,OAAQ,EAAQ,UAAU,MAC5B,EAGF,GAAI,EAAQ,UAAU,SAAW,EAC/B,MAAO,CACL,aAAcA,EAAAA,aAAa,cAC3B,OAAQ,EAAQ,UAAU,MAC5B,EAGF,GACE,EAAQ,UAAU,aACjB,EAAQ,QAAQ,2BAA2B,iBAAmB,KAE/D,MAAO,CACL,aAAcA,EAAAA,aAAa,sBAC3B,OAAQ,EAAQ,UAAU,MAC5B,EAGF,IAAM,EAA2B,EAAQ,QAAQ,2BAA2B,yBAAyB,QAAU,EAO/G,OANI,EAAQ,YAAY,OAAS,EACxB,CACL,aAAcA,EAAAA,aAAa,kCAC7B,EAGK,CAAE,aAAcA,EAAAA,aAAa,QAAS,CAC/C,CAEA,eAAe,EAAoD,EAAmD,CACpH,OAAQ,MAAMa,EAAAA,cAAkBH,EAAAA,QAAK,KAAK,EAAY,YAAY,CAAC,CACrE,CAEA,eAAe,EAAgB,EAQE,CAC/B,IAAM,EAAiBJ,EAAAA,2BAA2B,CAChDQ,EAAAA,gBACA,EAAQ,iBAAiB,QAAQ,CAAC,EAClC,GAAG,EACH,GAAG,EAAQ,OACb,CAAC,EAMK,EAAWC,EAAAA,4BAA4B,EAAQ,gBAAgB,EACjE,EACJ,GAAI,CACF,EAAQC,EAAAA,QAAa,MAAM,EAAe,GAAI,EAAe,MAAM,CAAC,EAAG,CACrE,IAAK,EAAQ,IACb,IAAK,CAAE,GAAG,EAAQ,IAAK,GAAGb,EAAAA,2BAA2B,EAAQ,GAAG,CAAE,EAClE,SAAU,QAAQ,WAAa,QAC/B,MAAO,CAAC,OAAQ,OAAQ,MAAM,CAChC,CAAC,CACH,OAAS,EAAO,CAEd,MADA,EAAS,OAAO,EACV,CACR,CAEA,EAAM,OAAO,YAAY,MAAM,EAC/B,EAAM,OAAO,YAAY,MAAM,EAE/B,IAAI,EAAS,GACT,EAAS,GACT,EACA,EACA,EAAgD,eAChD,EAAoB,GAClB,EAA0C,CAAC,EAC7C,EAAmC,CAAC,EAClC,EAAmB,KAAK,IAAI,EAAI,IAClC,EAAqB,EACzB,EAAM,OAAO,GAAG,OAAS,GAAkB,CACzC,GAAU,CACZ,CAAC,EACD,EAAM,OAAO,GAAG,OAAS,GAAkB,CACzC,GAAU,CACZ,CAAC,EACD,EAAM,GAAG,QAAU,GAAU,CAC3B,EAAa,EACb,EAAW,CACb,CAAC,EAGD,EAAM,MAAM,GAAG,YAAe,CAE9B,CAAC,EACD,EAAM,GAAG,SAAU,EAAM,IAAW,CAClC,GAAI,IAAS,IAAK,CAChB,EAAa,UACb,EAAW,EACX,MACF,CACA,GAAI,EAAQ,CACV,EAAW,EACX,EAAS,EACL,GAAG,EAAO,kCAAkC,IAC5C,iCAAiC,IACrC,MACF,CACA,EAAW,GAAQ,CACrB,CAAC,EAQD,GAAI,CAIF,IAHI,EAAQ,OAAO,EAAM,MAAM,MAAM,EAAQ,KAAK,EAClD,EAAM,MAAM,IAAI,EAET,IAAa,IAAA,IAAW,CAM7B,GALA,MAAA,EAAMc,EAAAA,WAAAA,CAAK,EAAQ,sBAAwB,GAAI,EAC/C,EAAqB,KAAK,IAAI,EAAoB,EAA4B,EAAM,GAAG,CAAC,EAExF,EAD2B,EAAgB,EAAQ,IAAI,OACxB,CAAC,CAAC,UAAU,EAAG,IAAM,EAAE,KAAK,OAAS,EAAE,KAAK,MAAM,EAE7E,EAAY,OAAS,EAAG,CAC1B,IAAM,EAAuB,EAAY,IAAK,GAAS,EAAK,IAAI,CAAC,CAAC,SAAS,EAM3E,GALA,EAA4B,QAAQ,CAAoB,EACxD,EAA4B,OAAS,KAAK,IACxC,EAA4B,OAC5B,EAAQ,sBACV,EAEE,EAA4B,SAAW,EAAQ,wBAC/C,EAA4B,MACzB,GACC,EAAM,SAAW,EAAqB,QACtC,EAAM,OAAO,EAAM,IAAU,IAAS,EAAqB,EAAM,CACrE,EACA,CACA,EAAa,oBACb,EAAW,EACX,KACF,CACF,CAEA,GAAI,KAAK,IAAI,EAAI,IAAO,EAAmB,EAAQ,iBAAkB,CACnE,EAAa,UACb,EAAW,EACX,KACF,CACF,CAEI,IAAe,gBAAkB,EAAM,WAAa,OACtD,EAAM,mBAAmB,OAAO,EAChC,EAAM,mBAAmB,OAAO,GAOlC,IAAM,EAAgB,IAAe,gBAAkB,EAAS,MAAM,EAGtE,GAFA,MAAM,EAAY,CAAK,EACvB,EAAoB,GAChB,EAAY,MAAM,EACtB,GAAM,CACJ,cACA,OAAQ,EACR,eACE,EAAiB,EAAQ,EAAkB,CAAkB,EAEjE,MAAO,CACL,MAAO,EAAQ,MACf,OAAQ,EAAO,QAAQ,EACvB,OAAQ,EACR,OAAQ,EAAgB,EAAI,EAC5B,cACA,cACA,cACA,WAAY,EAAgB,UAAY,CAC1C,CACF,QAAU,CAIJ,GAAmB,EAAS,OAAO,CACzC,CACF,CAEA,SAAS,EAAgB,EAAkD,CAGzE,IAAM,EAAMC,EAAAA,oBAAoB,EAAU,CAAE,QAAS,CAAQ,EAAI,CAAE,QAAS,QAAQ,IAAI,OAAQ,CAAC,EAC3F,EAAWF,EAAAA,QAAa,UAAU,WAAY,CAAC,QAAS,OAAO,EAAG,CAAE,SAAU,OAAQ,KAAI,CAAC,EACjG,GAAI,EAAS,MAAO,MAAM,EAAS,MACnC,GAAI,EAAS,SAAW,GAAK,CAAC,EAAS,OAAQ,MAAO,CAAC,EAEvD,IAAM,EAAmC,CAAC,EAC1C,IAAK,IAAM,KAAY,EAAyB,EAAS,MAAM,EAAG,CAChE,IAAM,EAAaA,EAAAA,QAAa,UAAU,OAAQ,CAAC,KAAM,CAAQ,EAAG,CAAE,KAAI,CAAC,EAC3E,GAAI,EAAW,MAAO,MAAM,EAAW,MACvC,GAAI,EAAW,SAAW,GAAK,EAAW,OAAO,SAAW,EAAG,SAE/D,IAAM,EAAmBA,EAAAA,QAAa,UAAU,UAAW,CAAC,gBAAiB,CAAQ,EAAG,CAAE,SAAU,OAAQ,KAAI,CAAC,EACjH,GAAI,EAAiB,MAAO,MAAM,EAAiB,MACnD,IAAM,EAAa,EAAiB,OAAO,KAAK,CAAC,CAAC,WAAW,SAAU,GAAG,EAE1E,EAAY,KAAK,CACf,KAAM,GAAG,GAAc,SAAS,GAAG,EAAS,MAC5C,KAAM,EAAW,OAAO,SAAS,QAAQ,EACzC,SAAU,QACZ,CAAC,CACH,CAEA,OAAO,CACT,CAEA,SAAS,EAAyB,EAA0B,CAC1D,IAAM,EAAsB,CAAC,EACvB,EAAQ,EAAO,MAAM;CAAI,EAC/B,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,EAAK,SAAS,iBAAiB,GAAK,EAAK,SAAS,mBAAmB,GAAK,EAAK,SAAS,IAAI,EAAG,SAEnG,IAAM,EAAQ,sBAAsB,KAAK,CAAI,EACxC,IAAQ,IACb,EAAU,KAAK,OAAO,SAAS,EAAM,GAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CACzD,CACA,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACqE,CACrE,IAAM,EAAQ,+BAA+B,KAAK,CAAM,EAClD,EAAmB,EAAQ,EAAO,MAAM,EAAG,EAAM,KAAK,CAAC,CAAC,QAAQ,EAAI,EAAO,QAAQ,EACnF,EAAoB,OAAO,IAAQ,EAAE,EAAI,MAAQ,EACvD,MAAO,CACL,OAAQ,EACR,YAAa,OAAO,IAAQ,EAAE,GAAK,KAAK,IAAI,EAAI,IAAO,EACvD,YAAa,KAAK,IAAI,EAAmB,CAAkB,CAC7D,CACF,CAEA,eAAe,GAAkF,CAC/F,GAAI,QAAQ,WAAa,QACvB,MAAU,MAAM,oDAAoD,EAGtE,IAAK,IAAI,EAAgB,GAAI,EAAgB,IAAK,IAAiB,CACjE,IAAM,EAAU,IAAI,IAChB,EACE,EAAOA,EAAAA,QAAa,MAAM,OAAQ,CAAC,EAAS,UAAW,IAAK,eAAgB,KAAK,EAAG,CACxF,MAAO,SAEP,IAAKE,EAAAA,oBAAoB,CAC3B,CAAC,EAMD,GALA,EAAK,GAAG,QAAU,GAAU,CAC1B,EAAa,CACf,CAAC,EAED,MAAA,EAAMD,EAAAA,WAAAA,CAAK,GAAgC,EACvC,EAAY,MAAM,EAClB,KAAK,WAAa,KAEtB,MAAO,CACL,UACA,QAAS,SAAY,CACd,EAAK,SACR,EAAK,KAAK,SAAS,EACnB,MAAA,EAAMA,EAAAA,WAAAA,CAAK,GAAiC,EACxC,EAAK,WAAa,MAAM,EAAK,KAAK,SAAS,EAEnD,CACF,CACF,CAEA,MAAU,MAAM,4BAA4B,CAC9C,CAEA,eAAe,EAAY,EAAiD,CACrE,KAAM,IAIX,IAAIR,EAAAA,gBAAiB,CACnB,EAAA,yBAAyB,CAAC,MAAM,CAAC,EACjC,MAAA,EAAMQ,EAAAA,WAAAA,CAAK,EAAgC,GAAI,EAC/C,EAAA,yBAAyB,CAAC,MAAM,CAAC,EACjC,MACF,CACA,EAAiB,EAAM,IAAK,SAAS,EACrC,MAAA,EAAMA,EAAAA,WAAAA,CAAK,EAAgC,GAAI,EAC3C,EAAM,WAAa,MAAM,EAAiB,EAAM,IAAK,SAAS,CAHlE,CAIF,CAEA,SAAS,EAA4B,EAA4C,CAC/E,GAAI,CAAC,GAAkB,QAAQ,WAAa,QAAS,MAAO,GAI5D,IAAM,EAASD,EAAAA,QAAa,UAAU,KAAM,CAAC,KAAM,OAAQ,eAAgB,WAAY,OAAO,CAAc,CAAC,EAAG,CAC9G,SAAU,OAEV,IAAKE,EAAAA,oBAAoB,CAC3B,CAAC,EAGD,OAFI,EAAO,OAAS,EAAO,SAAW,GAAK,CAAC,EAAO,OAAe,EAE3D,EAAO,OACX,MAAM;CAAI,CAAC,CACX,IAAK,GAAS,OAAO,EAAK,KAAK,CAAC,CAAC,CAAC,CAClC,OAAQ,GAAU,OAAO,SAAS,CAAK,GAAK,EAAQ,CAAC,CAAC,CACtD,QAAQ,EAAK,IAAU,EAAM,EAAQ,KAAM,CAAC,CACjD,CAEA,SAAS,EAAiB,EAAwB,EAA8B,CAC9E,GAAI,CACF,GAAI,QAAQ,WAAa,QAAS,CAChC,QAAQ,KAAK,CAAC,EAAgB,CAAM,EACpC,MACF,CACF,MAAQ,CAER,CAEA,GAAI,CACF,QAAQ,KAAK,EAAgB,CAAM,CACrC,MAAQ,CAER,CACF,CAEA,eAAe,EAAW,EAAoC,CAC5D,GAAI,CAEF,OADA,MAAMC,EAAAA,QAAG,OAAO,CAAQ,EACjB,EACT,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAAS,EAAe,EAAwB,CAC9C,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D"}
1
+ {"version":3,"file":"guiCommand.cjs","names":["os","z","parseArgs","readProblemMarkdownFrontMatter","judgeByStaticAnalysis","findEntryPointFile","DecisionCode","findLanguageDefinitionByPath","runCustomRunner","getSandboxUserEnvOverrides","snapshotWorkingDirectory","copyTestCaseFileInput","wrapCommandWithSandboxUser","cleanWorkingDirectory","readOutputFiles","sandboxUserName","path","languageIdToDefinition","spawnSyncWithTimeout","readFileTestCases","TIMEOUT_COMMAND","startSandboxTimeoutWatchdog","childProcess","wait","getTrustedHelperEnv","fs"],"sources":["../../src/presets/guiCommand.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as wait } from 'node:timers/promises';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.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 { readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases as readFileTestCases } from '../helpers/readTestCases.js';\nimport { runCustomRunner } from '../helpers/runCustomRunner.js';\nimport {\n getSandboxUserEnvOverrides,\n getTrustedHelperEnv,\n killSandboxUserProcesses,\n makeAccessibleToSandboxUser,\n sandboxUserName,\n startSandboxTimeoutWatchdog,\n TIMEOUT_COMMAND,\n wrapCommandWithSandboxUser,\n} from '../helpers/sandboxUser.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { languageIdToDefinition } from '../types/language.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 5;\nconst SCREENSHOT_WAIT_SECONDS = 0.3;\nconst XVFB_STARTUP_WAIT_SECONDS = 0.3;\nconst XVFB_SHUTDOWN_WAIT_SECONDS = 0.1;\nconst PROCESS_SHUTDOWN_WAIT_SECONDS = 0.2;\nconst STOP_DETECTION_THRESHOLD = 5;\nconst TIME_COMMAND = [os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time', '--format', '%e %M'] as const;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\n/** What every GUI test case needs; a custom `readTestCases` may add any fields of its own. */\ninterface BaseGuiTestCase {\n id: string;\n /** Standard input (`test_cases/<id>.in`). */\n input?: string;\n /** Directory copied into the working directory before the run (`test_cases/<id>.fin/`). */\n fileInputPath?: string;\n}\n\n/** A test case read from `test_cases/` by the default reader. */\nexport interface GuiTestCase extends BaseGuiTestCase {\n /** Expected standard output (`test_cases/<id>.out`), for the problem's `test` to compare. */\n output?: string;\n /** Directory of expected output files (`test_cases/<id>.fout/`), for the problem's `test` to compare. */\n fileOutputPath?: string;\n}\n\nexport interface GuiScreenshotFile {\n path: string;\n data: string;\n encoding: 'base64';\n}\n\nexport interface GuiCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n screenshots: GuiScreenshotFile[];\n stopReason: 'process_exit' | 'stable_screenshot' | 'timeout';\n}\n\ninterface GuiJudgeContext {\n timeLimitSeconds: number;\n problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;\n}\n\ntype GuiJudgeCaseResult = Pick<\n TestCaseResult,\n 'decisionCode' | 'feedbackMarkdown' | 'stderr' | 'stdout' | 'outputFiles'\n>;\n\nexport interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase = GuiTestCase> {\n mainFilePath?: string;\n runTimeoutSeconds?: number;\n screenshotWaitSeconds?: number;\n stopDetectionThreshold?: number;\n readTestCases?: (problemDir: string) => Promise<readonly TTestCase[]>;\n prepare?: (context: {\n cwd: string;\n /**\n * Build the submission with it. Under `EXERCODE_SANDBOX_USER` delegation it already carries the\n * sandbox user's overrides, but the handler must still wrap whatever it spawns with\n * `wrapCommandWithSandboxUser` (both exported): a build runs the submission's own scripts\n * (`package.json` lifecycle scripts, `build.gradle`, `build.rs`), which as the trusted harness\n * user could read the problem's test cases and rewrite the harness. The preset terminates\n * leftover sandbox processes after the handler returns.\n */\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n problemMarkdownFrontMatter: ProblemMarkdownFrontMatter;\n }) => Promise<Partial<GuiJudgeCaseResult> | undefined> | Partial<GuiJudgeCaseResult> | undefined;\n /**\n * Runs as the trusted harness user with the submission's `cwd`. Fixture files it creates there\n * must be written with `createDirectoryWithoutFollowingSymlinks`/`writeFileWithoutFollowingSymlinks`\n * (both exported): a submission can plant a symlink at a fixture path, and a plain `fs.writeFile`\n * would follow it into a file only the harness can write.\n */\n resolveInput?: (context: { testCase: TTestCase; cwd: string; env: NodeJS.ProcessEnv }) => Promise<string> | string;\n command?: (context: {\n testCase: TTestCase;\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n }) => Promise<readonly [string, ...string[]]> | readonly [string, ...string[]];\n runCommand?: (context: {\n testCase: TTestCase;\n /**\n * The command to run. Under `EXERCODE_SANDBOX_USER` delegation it is already wrapped so it\n * executes as the sandbox user; spawn it as given (with the supplied `env`) instead of\n * reconstructing it, or the submission runs as the trusted harness user.\n *\n * Its direct child is then a root-owned `sudo` whose descendants belong to the sandbox user, so\n * the handler cannot signal them: enforce `timeLimitSeconds` with `startSandboxTimeoutWatchdog`\n * and `killSandboxUserProcesses` (both exported) rather than `child.kill()` or an outer\n * `timeout`. The preset terminates leftover sandbox processes after the handler returns.\n */\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n }) => Promise<GuiCommandRunResult> | GuiCommandRunResult;\n test: (context: {\n testCase: TTestCase;\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n /** The submission's working directory, e.g. for `compareExpectedOutputFiles(cwd, testCase.fileOutputPath)`. */\n cwd: string;\n context: GuiJudgeContext;\n }) => Promise<Partial<GuiJudgeCaseResult>> | Partial<GuiJudgeCaseResult>;\n}\n\n/**\n * A preset function for judging GUI programs by collecting screenshots while the program runs.\n *\n * Keep problem-specific logic in `prepare`, `command`, and `test`.\n *\n * @example\n * Create `judge.ts`:\n * ```ts\n * import { DecisionCode } from '@exercode/problem-utils';\n * import { guiCommandJudgePreset } from '@exercode/problem-utils/presets/guiCommand';\n *\n * await guiCommandJudgePreset(import.meta.dirname, {\n * mainFilePath: 'Main.java',\n * readTestCases: async () => [{ id: 'default' }],\n * test: ({ runResult }) => {\n * return runResult.screenshots.length > 0\n * ? { decisionCode: DecisionCode.ACCEPTED }\n * : { decisionCode: DecisionCode.WRONG_ANSWER };\n * },\n * });\n * ```\n */\nexport async function guiCommandJudgePreset<TTestCase extends BaseGuiTestCase = GuiTestCase>(\n problemDir: string,\n options: GuiCommandJudgePresetOptions<TTestCase>\n): 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 const submissionDir = args.cwd;\n\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(submissionDir);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const configuredTestCases = await (options.readTestCases ?? readGuiTestCases<TTestCase>)(problemDir);\n const testCases =\n configuredTestCases.length > 0 ? configuredTestCases : ([{ id: 'default' }] as unknown as readonly TTestCase[]);\n const prebuildTestCaseId = testCases[0]?.id ?? 'prebuild';\n\n const staticAnalysisResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId: prebuildTestCaseId, ...staticAnalysisResult });\n return;\n }\n\n const initialMainFilePath = options.mainFilePath ?? (await findEntryPointFile(args.cwd, params.language));\n if (!initialMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(initialMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let resolvedMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language,\n configuredMainFilePath: options.mainFilePath,\n });\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n const prebuiltMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language ?? inferLanguageIdsByPath(initialMainFilePath),\n configuredMainFilePath: options.mainFilePath,\n allowConfiguredPathFallback: true,\n });\n if (prebuiltMainFilePath) resolvedMainFilePath = prebuiltMainFilePath;\n } catch (error) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n if (!resolvedMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n let customPrepareResult: Partial<GuiJudgeCaseResult> | undefined;\n if (options.prepare) {\n try {\n customPrepareResult = await runCustomRunner(() =>\n options.prepare?.({\n cwd: submissionDir,\n env: { ...env, ...getSandboxUserEnvOverrides(env) },\n mainFilePath: resolvedMainFilePath,\n problemMarkdownFrontMatter,\n })\n );\n } catch (error) {\n // Like the `prebuild` step above: report the failed build rather than letting the throw (from\n // the handler itself, or from the fail-closed sandbox sweep around it) end the run resultless.\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n const prepareResult =\n customPrepareResult ??\n runDefaultPrepare({\n cwd: args.cwd,\n env,\n mainFilePath: resolvedMainFilePath,\n languageDefinition,\n });\n if (prepareResult) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: prepareResult.decisionCode ?? DecisionCode.BUILD_ERROR,\n feedbackMarkdown: prepareResult.feedbackMarkdown,\n stderr: prepareResult.stderr,\n stdout: prepareResult.stdout,\n outputFiles: prepareResult.outputFiles,\n });\n return;\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n let displayServer: Awaited<ReturnType<typeof ensureDisplayServer>> | undefined;\n let currentTestCaseId = prebuildTestCaseId;\n let currentStdin: string | undefined;\n try {\n displayServer = options.runCommand ? undefined : await ensureDisplayServer();\n const sharedFileInputPath = (configuredTestCases as { shared?: { fileInputPath?: string } }).shared?.fileInputPath;\n for (const testCase of testCases) {\n currentTestCaseId = testCase.id;\n if (sharedFileInputPath) await copyTestCaseFileInput(sharedFileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n const timeLimitSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : (options.runTimeoutSeconds ?? JUDGE_DEFAULT_TIMEOUT_SECONDS);\n\n const runEnv = displayServer ? { ...env, DISPLAY: displayServer.display } : env;\n const stdin = (await options.resolveInput?.({ testCase, cwd: args.cwd, env: runEnv })) ?? testCase.input ?? '';\n currentStdin = stdin;\n const command =\n (await options.command?.({ testCase, cwd: args.cwd, env: runEnv, mainFilePath: resolvedMainFilePath })) ??\n languageDefinition.command(resolvedMainFilePath);\n\n let runResult: GuiCommandRunResult;\n try {\n runResult = options.runCommand\n ? await runCustomRunner(\n () =>\n // Hand custom runners a command that is already sandbox-wrapped, and the matching\n // environment: they spawn it themselves, so an unwrapped command would run the\n // submission as the trusted harness user and defeat the delegation boundary.\n options.runCommand?.({\n testCase,\n command: wrapCommandWithSandboxUser(command),\n stdin,\n cwd: submissionDir,\n env: { ...runEnv, ...getSandboxUserEnvOverrides(runEnv) },\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n }) as Promise<GuiCommandRunResult>\n )\n : await spawnGuiProgram({\n command,\n stdin,\n cwd: args.cwd,\n env: runEnv,\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n });\n } catch (error) {\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n return;\n }\n\n const outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n const judgeContext: GuiJudgeContext = {\n timeLimitSeconds,\n problemMarkdownFrontMatter: {\n memoryLimitByte: problemMarkdownFrontMatter.memoryLimitByte,\n requiredOutputFilePaths: problemMarkdownFrontMatter.requiredOutputFilePaths,\n },\n };\n const baseJudgeResult = evaluateGuiRunResult({ runResult, outputFiles, context: judgeContext });\n let judgeResult = baseJudgeResult;\n if (baseJudgeResult.decisionCode === DecisionCode.ACCEPTED) {\n try {\n const extendedJudgeResult = await options.test({\n testCase,\n runResult,\n outputFiles,\n cwd: args.cwd,\n context: judgeContext,\n });\n judgeResult = {\n decisionCode: extendedJudgeResult.decisionCode ?? baseJudgeResult.decisionCode,\n feedbackMarkdown: extendedJudgeResult.feedbackMarkdown,\n stderr: extendedJudgeResult.stderr,\n stdout: extendedJudgeResult.stdout,\n outputFiles: extendedJudgeResult.outputFiles,\n };\n } catch (error) {\n judgeResult = {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: errorToMessage(error),\n };\n }\n }\n\n const decisionCode = judgeResult.decisionCode ?? DecisionCode.ACCEPTED;\n const stdout = judgeResult.stdout ?? runResult.stdout;\n const stderr = judgeResult.stderr ?? runResult.stderr;\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: runResult.status,\n stdin: runResult.stdin || undefined,\n stdout: stdout || undefined,\n stderr: stderr || undefined,\n timeSeconds: runResult.timeSeconds,\n memoryBytes: runResult.memoryBytes,\n feedbackMarkdown: judgeResult.feedbackMarkdown,\n outputFiles: judgeResult.outputFiles ?? (outputFiles.length > 0 ? outputFiles : undefined),\n });\n\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n } catch (error) {\n printTestCaseResult({\n testCaseId: currentTestCaseId,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin: currentStdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n } finally {\n try {\n // Sweep any sandbox processes the run left behind (e.g. a SIGTERM-ignoring forked child) so\n // they cannot race the harness's output reads or survive into the next test case/request.\n if (sandboxUserName) killSandboxUserProcesses();\n } finally {\n // Must run even when the sweep fails closed, or Xvfb keeps holding its display number and\n // later GUI requests exhaust the `:90`–`:99` range.\n await displayServer?.dispose();\n }\n }\n}\n\nasync function resolveMainFilePath(context: {\n cwd: string;\n language?: string | string[];\n configuredMainFilePath?: string;\n allowConfiguredPathFallback?: boolean;\n}): Promise<string | undefined> {\n if (context.configuredMainFilePath) {\n const resolvedPath = path.join(context.cwd, context.configuredMainFilePath);\n if (await pathExists(resolvedPath)) return context.configuredMainFilePath;\n if (!context.allowConfiguredPathFallback) return undefined;\n }\n\n return await findEntryPointFile(context.cwd, context.language);\n}\n\nfunction inferLanguageIdsByPath(filePath: string): string[] | undefined {\n const languageIds = Object.entries(languageIdToDefinition)\n .filter(([, definition]) => definition.fileExtensions.some((ext) => filePath.endsWith(ext)))\n .map(([languageId]) => languageId);\n return languageIds.length > 0 ? languageIds : undefined;\n}\n\nfunction runDefaultPrepare(context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n languageDefinition: NonNullable<ReturnType<typeof findLanguageDefinitionByPath>>;\n}): Partial<GuiJudgeCaseResult> | undefined {\n const buildCommand = context.languageDefinition.buildCommand?.(context.mainFilePath);\n if (!buildCommand) return undefined;\n\n const buildResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: context.cwd, encoding: 'utf8', env: context.env },\n BUILD_TIMEOUT_SECONDS\n );\n\n if (buildResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: buildResult.stderr || undefined,\n };\n }\n\n if (buildResult.status !== 0) {\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: buildResult.stderr || buildResult.stdout || undefined,\n };\n }\n\n return undefined;\n}\n\nfunction evaluateGuiRunResult(context: {\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: GuiJudgeContext;\n}): Partial<GuiJudgeCaseResult> {\n if (context.runResult.stopReason === 'timeout') {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n if (context.runResult.status !== 0) {\n return {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: context.runResult.stderr,\n };\n }\n\n if (\n context.runResult.memoryBytes >\n (context.context.problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)\n ) {\n return {\n decisionCode: DecisionCode.MEMORY_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n const requiredOutputFilesCount = context.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0;\n if (context.outputFiles.length < requiredOutputFilesCount) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR,\n };\n }\n\n return { decisionCode: DecisionCode.ACCEPTED };\n}\n\nasync function readGuiTestCases<TTestCase extends BaseGuiTestCase>(problemDir: string): Promise<readonly TTestCase[]> {\n // The default reader yields the base shape; a narrower TTestCase must come from `options.readTestCases`.\n return (await readFileTestCases(path.join(problemDir, 'test_cases'))) as unknown as readonly TTestCase[];\n}\n\nasync function spawnGuiProgram(context: {\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n}): Promise<GuiCommandRunResult> {\n const wrappedCommand = wrapCommandWithSandboxUser([\n TIMEOUT_COMMAND,\n context.timeLimitSeconds.toFixed(3),\n ...TIME_COMMAND,\n ...context.command,\n ]);\n // A submission can signal its own sandboxed `timeout` and then wedge the event loop (e.g.\n // XGrabServer makes the synchronous `xwininfo`/`maim` block), so a harness-owned watchdog\n // force-kills the sandbox user at the deadline; killing the client also releases its X grab.\n // Armed before the submission starts: a submission that exhausts the PID cgroup would otherwise\n // prevent the watchdog from spawning at all.\n const watchdog = startSandboxTimeoutWatchdog(context.timeLimitSeconds);\n let child: childProcess.ChildProcessWithoutNullStreams;\n try {\n child = childProcess.spawn(wrappedCommand[0], wrappedCommand.slice(1), {\n cwd: context.cwd,\n env: { ...context.env, ...getSandboxUserEnvOverrides(context.env) },\n detached: process.platform !== 'win32',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n watchdog.cancel();\n throw error;\n }\n\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n\n let stdout = '';\n let stderr = '';\n let exitCode: number | undefined;\n let spawnError: Error | undefined;\n let stopReason: GuiCommandRunResult['stopReason'] = 'process_exit';\n let submissionStopped = false;\n const screenshotSignaturesHistory: string[][] = [];\n let screenshots: GuiScreenshotFile[] = [];\n const startTimeSeconds = Date.now() / 1000;\n let sampledMemoryBytes = 0;\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n });\n child.on('error', (error) => {\n spawnError = error;\n exitCode = 1;\n });\n // A child that exits before the input is fully written makes the write fail; without a listener\n // that EPIPE would be an unhandled stream error terminating the harness mid-run.\n child.stdin.on('error', () => {\n // The submission simply stopped reading its input; the exit handling below reports the result.\n });\n child.on('close', (code, signal) => {\n if (code === 124) {\n stopReason = 'timeout';\n exitCode = 0;\n return;\n }\n if (signal) {\n exitCode = 1;\n stderr = stderr\n ? `${stderr}\\nprocess terminated by signal: ${signal}`\n : `process terminated by signal: ${signal}`;\n return;\n }\n exitCode = code ?? 1;\n });\n\n // Everything below must run under the `finally` that decides the watchdog's fate: it is cancelled\n // only once the submission is demonstrably stopped. When a helper throws first — `takeScreenshots`\n // rethrows spawn failures, which a submission can provoke by exhausting PIDs — the watchdog stays\n // armed on purpose, since nothing else would end the submission. The cost is that a detached\n // watchdog can outlive this harness and SIGKILL sandbox processes of a request that starts within\n // its remaining deadline, which is the same trade-off the package-manager runner accepts.\n try {\n if (context.stdin) child.stdin.write(context.stdin);\n child.stdin.end();\n\n while (exitCode === undefined) {\n await wait(context.screenshotWaitSeconds * 1000);\n sampledMemoryBytes = Math.max(sampledMemoryBytes, readProcessGroupMemoryBytes(child.pid));\n const currentScreenshots = takeScreenshots(context.env.DISPLAY);\n screenshots = currentScreenshots.toSorted((a, b) => a.data.length - b.data.length);\n\n if (screenshots.length > 0) {\n const screenshotSignatures = screenshots.map((file) => file.data).toSorted();\n screenshotSignaturesHistory.unshift(screenshotSignatures);\n screenshotSignaturesHistory.length = Math.min(\n screenshotSignaturesHistory.length,\n context.stopDetectionThreshold\n );\n if (\n screenshotSignaturesHistory.length === context.stopDetectionThreshold &&\n screenshotSignaturesHistory.every(\n (files) =>\n files.length === screenshotSignatures.length &&\n files.every((file, index) => file === screenshotSignatures[index])\n )\n ) {\n stopReason = 'stable_screenshot';\n exitCode = 0;\n break;\n }\n }\n\n if (Date.now() / 1000 - startTimeSeconds > context.timeLimitSeconds) {\n stopReason = 'timeout';\n exitCode = 0;\n break;\n }\n }\n\n if (stopReason !== 'process_exit' && child.exitCode === null) {\n child.removeAllListeners('close');\n child.removeAllListeners('error');\n }\n // A submission can kill its own same-UID `timeout` and be stopped by the watchdog instead,\n // which surfaces as a SIGKILL rather than a deadline. Report that as the timeout it is, not as\n // the runtime error the signal would otherwise imply — but only when the child's fate is what\n // decided the verdict. A `stable_screenshot` (or the loop's own `timeout`) was already decided\n // from the screenshots, so the watchdog must not overwrite it.\n const watchdogFired = stopReason === 'process_exit' && watchdog.fired();\n await stopProcess(child);\n submissionStopped = true;\n if (spawnError) throw spawnError;\n const {\n memoryBytes,\n stderr: normalizedStderr,\n timeSeconds,\n } = parseTimedStderr(stderr, startTimeSeconds, sampledMemoryBytes);\n\n return {\n stdin: context.stdin,\n stdout: stdout.trimEnd(),\n stderr: normalizedStderr,\n status: watchdogFired ? 0 : exitCode,\n timeSeconds,\n memoryBytes,\n screenshots,\n stopReason: watchdogFired ? 'timeout' : stopReason,\n };\n } finally {\n // Only once the submission is demonstrably stopped: if termination itself failed (or never ran\n // because a helper threw), the watchdog is the only thing left able to end it, and cancelling\n // would leave it running unchecked.\n if (submissionStopped) watchdog.cancel();\n }\n}\n\nfunction takeScreenshots(display: string | undefined): GuiScreenshotFile[] {\n // These helpers run as the trusted harness user, so they must not be resolved through a\n // submission-influenced `PATH` (see `getTrustedHelperEnv`).\n const env = getTrustedHelperEnv(display ? { DISPLAY: display } : { DISPLAY: process.env.DISPLAY });\n const xwininfo = childProcess.spawnSync('xwininfo', ['-root', '-tree'], { encoding: 'utf8', env });\n if (xwininfo.error) throw xwininfo.error;\n if (xwininfo.status !== 0 || !xwininfo.stdout) return [];\n\n const screenshots: GuiScreenshotFile[] = [];\n for (const windowId of extractTopLevelWindowIds(xwininfo.stdout)) {\n const screenshot = childProcess.spawnSync('maim', ['-i', windowId], { env });\n if (screenshot.error) throw screenshot.error;\n if (screenshot.status !== 0 || screenshot.stdout.length === 0) continue;\n\n const windowNameResult = childProcess.spawnSync('xdotool', ['getwindowname', windowId], { encoding: 'utf8', env });\n if (windowNameResult.error) throw windowNameResult.error;\n const windowName = windowNameResult.stdout.trim().replaceAll(/[\\s/]/g, '_');\n\n screenshots.push({\n path: `${windowName || 'window'}_${windowId}.png`,\n data: screenshot.stdout.toString('base64'),\n encoding: 'base64',\n });\n }\n\n return screenshots;\n}\n\nfunction extractTopLevelWindowIds(stdout: string): string[] {\n const windowIds: string[] = [];\n const lines = stdout.split('\\n');\n for (const line of lines) {\n if (line.includes('Root window id:') || line.includes('Parent window id:') || line.includes('()')) continue;\n\n const match = /^\\s{5}(0x[\\da-f]+) /.exec(line);\n if (!match?.[1]) continue;\n windowIds.push(Number.parseInt(match[1], 16).toString());\n }\n return windowIds;\n}\n\nfunction parseTimedStderr(\n stderr: string,\n startTimeSeconds: number,\n sampledMemoryBytes: number\n): Pick<GuiCommandRunResult, 'stderr' | 'timeSeconds' | 'memoryBytes'> {\n const match = /(?:^|\\n)(\\d+\\.\\d+) (\\d+)\\s*$/.exec(stderr);\n const normalizedStderr = match ? stderr.slice(0, match.index).trimEnd() : stderr.trimEnd();\n const parsedMemoryBytes = Number(match?.[2]) * 1024 || 0;\n return {\n stderr: normalizedStderr,\n timeSeconds: Number(match?.[1]) || Date.now() / 1000 - startTimeSeconds,\n memoryBytes: Math.max(parsedMemoryBytes, sampledMemoryBytes),\n };\n}\n\nasync function ensureDisplayServer(): Promise<{ display: string; dispose: () => Promise<void> }> {\n if (process.platform !== 'linux') {\n throw new Error('GUI screenshot capture is supported only on Linux.');\n }\n\n for (let displayNumber = 90; displayNumber < 100; displayNumber++) {\n const display = `:${displayNumber}`;\n let spawnError: Error | undefined;\n const xvfb = childProcess.spawn('Xvfb', [display, '-screen', '0', '1280x1024x24', '-ac'], {\n stdio: 'ignore',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n xvfb.on('error', (error) => {\n spawnError = error;\n });\n\n await wait(XVFB_STARTUP_WAIT_SECONDS * 1000);\n if (spawnError) throw spawnError;\n if (xvfb.exitCode !== null) continue;\n\n return {\n display,\n dispose: async () => {\n if (!xvfb.killed) {\n xvfb.kill('SIGTERM');\n await wait(XVFB_SHUTDOWN_WAIT_SECONDS * 1000);\n if (xvfb.exitCode === null) xvfb.kill('SIGKILL');\n }\n },\n };\n }\n\n throw new Error('Xvfb could not be started.');\n}\n\nasync function stopProcess(child: childProcess.ChildProcess): Promise<void> {\n if (!child.pid) return;\n // The current user cannot signal the root-owned sudo wrapper nor the sandbox user's processes, so\n // go through sudo. SIGKILL is unconditional after the grace period: `child.exitCode` reflects only\n // the sudo wrapper, so a daemonized child that ignored SIGTERM would survive if we gated on it.\n if (sandboxUserName) {\n killSandboxUserProcesses(['TERM']);\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n killSandboxUserProcesses(['KILL']);\n return;\n }\n killProcessGroup(child.pid, 'SIGTERM');\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n if (child.exitCode === null) killProcessGroup(child.pid, 'SIGKILL');\n}\n\nfunction readProcessGroupMemoryBytes(processGroupId: number | undefined): number {\n if (!processGroupId || process.platform !== 'linux') return 0;\n\n // The delegation contract requires sudoers `!use_pty`, so sudo execs the command in place and it\n // stays in the detached child's process group; `--pgroup` therefore still captures it there.\n const result = childProcess.spawnSync('ps', ['-o', 'rss=', '--no-headers', '--pgroup', String(processGroupId)], {\n encoding: 'utf8',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n if (result.error || result.status !== 0 || !result.stdout) return 0;\n\n return result.stdout\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter((value) => Number.isFinite(value) && value > 0)\n .reduce((sum, value) => sum + value * 1024, 0);\n}\n\nfunction killProcessGroup(processGroupId: number, signal: NodeJS.Signals): void {\n try {\n if (process.platform !== 'win32') {\n process.kill(-processGroupId, signal);\n return;\n }\n } catch {\n // The process group may already be gone. Fall back to the direct PID below.\n }\n\n try {\n process.kill(processGroupId, signal);\n } catch {\n // The direct child may also already be gone.\n }\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction errorToMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
@@ -1,10 +1,20 @@
1
1
  import type { ProblemMarkdownFrontMatter } from '../types/problem.js';
2
2
  import type { TestCaseResult } from '../types/testCaseResult.js';
3
+ /** What every GUI test case needs; a custom `readTestCases` may add any fields of its own. */
3
4
  interface BaseGuiTestCase {
4
5
  id: string;
6
+ /** Standard input (`test_cases/<id>.in`). */
5
7
  input?: string;
8
+ /** Directory copied into the working directory before the run (`test_cases/<id>.fin/`). */
6
9
  fileInputPath?: string;
7
10
  }
11
+ /** A test case read from `test_cases/` by the default reader. */
12
+ export interface GuiTestCase extends BaseGuiTestCase {
13
+ /** Expected standard output (`test_cases/<id>.out`), for the problem's `test` to compare. */
14
+ output?: string;
15
+ /** Directory of expected output files (`test_cases/<id>.fout/`), for the problem's `test` to compare. */
16
+ fileOutputPath?: string;
17
+ }
8
18
  export interface GuiScreenshotFile {
9
19
  path: string;
10
20
  data: string;
@@ -25,7 +35,7 @@ interface GuiJudgeContext {
25
35
  problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;
26
36
  }
27
37
  type GuiJudgeCaseResult = Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown' | 'stderr' | 'stdout' | 'outputFiles'>;
28
- export interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase = BaseGuiTestCase> {
38
+ export interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase = GuiTestCase> {
29
39
  mainFilePath?: string;
30
40
  runTimeoutSeconds?: number;
31
41
  screenshotWaitSeconds?: number;
@@ -86,6 +96,8 @@ export interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase
86
96
  testCase: TTestCase;
87
97
  runResult: GuiCommandRunResult;
88
98
  outputFiles: NonNullable<TestCaseResult['outputFiles']>;
99
+ /** The submission's working directory, e.g. for `compareExpectedOutputFiles(cwd, testCase.fileOutputPath)`. */
100
+ cwd: string;
89
101
  context: GuiJudgeContext;
90
102
  }) => Promise<Partial<GuiJudgeCaseResult>> | Partial<GuiJudgeCaseResult>;
91
103
  }
@@ -111,5 +123,5 @@ export interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase
111
123
  * });
112
124
  * ```
113
125
  */
114
- export declare function guiCommandJudgePreset<TTestCase extends BaseGuiTestCase = BaseGuiTestCase>(problemDir: string, options: GuiCommandJudgePresetOptions<TTestCase>): Promise<void>;
126
+ export declare function guiCommandJudgePreset<TTestCase extends BaseGuiTestCase = GuiTestCase>(problemDir: string, options: GuiCommandJudgePresetOptions<TTestCase>): Promise<void>;
115
127
  export {};
@@ -1,4 +1,4 @@
1
- import{parseArgs as e}from"../helpers/parseArgs.js";import{printTestCaseResult as t}from"../helpers/printTestCaseResult.js";import{TIMEOUT_COMMAND as n,getSandboxUserEnvOverrides as r,getTrustedHelperEnv as i,killSandboxUserProcesses as a,makeAccessibleToSandboxUser as o,sandboxUserName as s,startSandboxTimeoutWatchdog as c,wrapCommandWithSandboxUser as l}from"../helpers/sandboxUser.js";import{languageIdToDefinition as u}from"../types/language.js";import{DecisionCode as d}from"../types/decisionCode.js";import{cleanWorkingDirectory as f,snapshotWorkingDirectory as p}from"../helpers/cleanWorkingDirectory.js";import{copyTestCaseFileInput as m}from"../helpers/copyTestCaseFileInput.js";import{findEntryPointFile as h}from"../helpers/findEntryPointFile.js";import{findLanguageDefinitionByPath as g}from"../helpers/findLanguageDefinitionByPath.js";import{judgeByStaticAnalysis as _}from"../helpers/judgeByStaticAnalysis.js";import{readOutputFiles as v}from"../helpers/readOutputFiles.js";import{runCustomRunner as y}from"../helpers/runCustomRunner.js";import{readProblemMarkdownFrontMatter as b}from"../helpers/readProblemMarkdownFrontMatter.js";import{readTestCases as x}from"../helpers/readTestCases.js";import{spawnSyncWithTimeout as S}from"../helpers/spawnSyncWithTimeout.js";import C from"node:fs/promises";import w from"node:path";import{z as T}from"zod";import E from"node:child_process";import D from"node:os";import{setTimeout as O}from"node:timers/promises";const k=.3,A=.2,j=[D.platform()===`darwin`?`gtime`:`/usr/bin/time`,`--format`,`%e %M`],M=T.object({language:T.union([T.string(),T.array(T.string())]).optional()});async function N(n,i){let c=e(process.argv);if(!c.cwd)throw Error(`cwd argument required`);let u=M.parse(c.params),x=c.cwd;o(x);let S=await b(n),C=await(i.readTestCases??R)(n),w=C.length>0?C:[{id:`default`}],T=w[0]?.id??`prebuild`,E=await _(c.cwd,S);if(E){t({testCaseId:T,...E});return}let D=i.mainFilePath??await h(c.cwd,u.language);if(!D){t({testCaseId:T,decisionCode:d.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,stderr:i.mainFilePath?`required main file not found: ${i.mainFilePath}`:`main file not found${u.language?`: language: ${u.language}`:``}`});return}let O=g(D);if(!O){t({testCaseId:T,decisionCode:d.WRONG_ANSWER,stderr:`unsupported language`});return}let A={...process.env,CI:``,FORCE_COLOR:`0`},j=await P({cwd:c.cwd,language:u.language,configuredMainFilePath:i.mainFilePath});if(O.prebuild)try{await O.prebuild(c.cwd);let e=await P({cwd:c.cwd,language:u.language??F(D),configuredMainFilePath:i.mainFilePath,allowConfiguredPathFallback:!0});e&&(j=e)}catch(e){t({testCaseId:T,decisionCode:d.BUILD_ERROR,stderr:J(e)});return}if(!j){t({testCaseId:T,decisionCode:d.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,stderr:i.mainFilePath?`required main file not found: ${i.mainFilePath}`:`main file not found${u.language?`: language: ${u.language}`:``}`});return}let N;if(i.prepare)try{N=await y(()=>i.prepare?.({cwd:x,env:{...A,...r(A)},mainFilePath:j,problemMarkdownFrontMatter:S}))}catch(e){t({testCaseId:T,decisionCode:d.BUILD_ERROR,stderr:J(e)});return}let B=N??I({cwd:c.cwd,env:A,mainFilePath:j,languageDefinition:O});if(B){t({testCaseId:T,decisionCode:B.decisionCode??d.BUILD_ERROR,feedbackMarkdown:B.feedbackMarkdown,stderr:B.stderr,stdout:B.stdout,outputFiles:B.outputFiles});return}let V=await p(c.cwd),H,W=T,G;try{H=i.runCommand?void 0:await U();let e=C.shared?.fileInputPath;for(let n of w){W=n.id,e&&await m(e,c.cwd),n.fileInputPath&&await m(n.fileInputPath,c.cwd);let a=typeof S.timeLimitMs==`number`?S.timeLimitMs/1e3:i.runTimeoutSeconds??5,o=H?{...A,DISPLAY:H.display}:A,s=await i.resolveInput?.({testCase:n,cwd:c.cwd,env:o})??n.input??``;G=s;let u=await i.command?.({testCase:n,cwd:c.cwd,env:o,mainFilePath:j})??O.command(j),p;try{p=i.runCommand?await y(()=>i.runCommand?.({testCase:n,command:l(u),stdin:s,cwd:x,env:{...o,...r(o)},timeLimitSeconds:a,screenshotWaitSeconds:i.screenshotWaitSeconds??k,stopDetectionThreshold:i.stopDetectionThreshold??5})):await z({command:u,stdin:s,cwd:c.cwd,env:o,timeLimitSeconds:a,screenshotWaitSeconds:i.screenshotWaitSeconds??k,stopDetectionThreshold:i.stopDetectionThreshold??5})}catch(e){t({testCaseId:n.id,decisionCode:d.RUNTIME_ERROR,stdin:s,stderr:J(e)}),await f(c.cwd,V);return}let h=await v(c.cwd,S.requiredOutputFilePaths??[]),g={timeLimitSeconds:a,problemMarkdownFrontMatter:{memoryLimitByte:S.memoryLimitByte,requiredOutputFilePaths:S.requiredOutputFilePaths}},_=L({runResult:p,outputFiles:h,context:g}),b=_;if(_.decisionCode===d.ACCEPTED)try{let e=await i.test({testCase:n,runResult:p,outputFiles:h,context:g});b={decisionCode:e.decisionCode??_.decisionCode,feedbackMarkdown:e.feedbackMarkdown,stderr:e.stderr,stdout:e.stdout,outputFiles:e.outputFiles}}catch(e){b={decisionCode:d.RUNTIME_ERROR,stderr:J(e)}}let C=b.decisionCode??d.ACCEPTED,w=b.stdout??p.stdout,T=b.stderr??p.stderr;if(t({testCaseId:n.id,decisionCode:C,exitStatus:p.status,stdin:p.stdin||void 0,stdout:w||void 0,stderr:T||void 0,timeSeconds:p.timeSeconds,memoryBytes:p.memoryBytes,feedbackMarkdown:b.feedbackMarkdown,outputFiles:b.outputFiles??(h.length>0?h:void 0)}),await f(c.cwd,V),C!==d.ACCEPTED)break}}catch(e){t({testCaseId:W,decisionCode:d.RUNTIME_ERROR,stdin:G,stderr:J(e)}),await f(c.cwd,V)}finally{try{s&&a()}finally{await H?.dispose()}}}async function P(e){if(e.configuredMainFilePath){if(await q(w.join(e.cwd,e.configuredMainFilePath)))return e.configuredMainFilePath;if(!e.allowConfiguredPathFallback)return}return await h(e.cwd,e.language)}function F(e){let t=Object.entries(u).filter(([,t])=>t.fileExtensions.some(t=>e.endsWith(t))).map(([e])=>e);return t.length>0?t:void 0}function I(e){let t=e.languageDefinition.buildCommand?.(e.mainFilePath);if(!t)return;let n=S(t[0],t.slice(1),{cwd:e.cwd,encoding:`utf8`,env:e.env},10);if(n.timeSeconds>10)return{decisionCode:d.BUILD_TIME_LIMIT_EXCEEDED,stderr:n.stderr||void 0};if(n.status!==0)return{decisionCode:d.BUILD_ERROR,stderr:n.stderr||n.stdout||void 0}}function L(e){if(e.runResult.stopReason===`timeout`)return{decisionCode:d.TIME_LIMIT_EXCEEDED,stderr:e.runResult.stderr};if(e.runResult.status!==0)return{decisionCode:d.RUNTIME_ERROR,stderr:e.runResult.stderr};if(e.runResult.memoryBytes>(e.context.problemMarkdownFrontMatter.memoryLimitByte??1/0))return{decisionCode:d.MEMORY_LIMIT_EXCEEDED,stderr:e.runResult.stderr};let t=e.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length??0;return e.outputFiles.length<t?{decisionCode:d.MISSING_REQUIRED_OUTPUT_FILE_ERROR}:{decisionCode:d.ACCEPTED}}async function R(e){return await x(w.join(e,`test_cases`))}async function z(e){let t=l([n,e.timeLimitSeconds.toFixed(3),...j,...e.command]),i=c(e.timeLimitSeconds),a;try{a=E.spawn(t[0],t.slice(1),{cwd:e.cwd,env:{...e.env,...r(e.env)},detached:process.platform!==`win32`,stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw i.cancel(),e}a.stdout.setEncoding(`utf8`),a.stderr.setEncoding(`utf8`);let o=``,s=``,u,d,f=`process_exit`,p=!1,m=[],h=[],g=Date.now()/1e3,_=0;a.stdout.on(`data`,e=>{o+=e}),a.stderr.on(`data`,e=>{s+=e}),a.on(`error`,e=>{d=e,u=1}),a.stdin.on(`error`,()=>{}),a.on(`close`,(e,t)=>{if(e===124){f=`timeout`,u=0;return}if(t){u=1,s=s?`${s}\nprocess terminated by signal: ${t}`:`process terminated by signal: ${t}`;return}u=e??1});try{for(e.stdin&&a.stdin.write(e.stdin),a.stdin.end();u===void 0;){if(await O(e.screenshotWaitSeconds*1e3),_=Math.max(_,G(a.pid)),h=B(e.env.DISPLAY).toSorted((e,t)=>e.data.length-t.data.length),h.length>0){let t=h.map(e=>e.data).toSorted();if(m.unshift(t),m.length=Math.min(m.length,e.stopDetectionThreshold),m.length===e.stopDetectionThreshold&&m.every(e=>e.length===t.length&&e.every((e,n)=>e===t[n]))){f=`stable_screenshot`,u=0;break}}if(Date.now()/1e3-g>e.timeLimitSeconds){f=`timeout`,u=0;break}}f!==`process_exit`&&a.exitCode===null&&(a.removeAllListeners(`close`),a.removeAllListeners(`error`));let t=f===`process_exit`&&i.fired();if(await W(a),p=!0,d)throw d;let{memoryBytes:n,stderr:r,timeSeconds:c}=H(s,g,_);return{stdin:e.stdin,stdout:o.trimEnd(),stderr:r,status:t?0:u,timeSeconds:c,memoryBytes:n,screenshots:h,stopReason:t?`timeout`:f}}finally{p&&i.cancel()}}function B(e){let t=i(e?{DISPLAY:e}:{DISPLAY:process.env.DISPLAY}),n=E.spawnSync(`xwininfo`,[`-root`,`-tree`],{encoding:`utf8`,env:t});if(n.error)throw n.error;if(n.status!==0||!n.stdout)return[];let r=[];for(let e of V(n.stdout)){let n=E.spawnSync(`maim`,[`-i`,e],{env:t});if(n.error)throw n.error;if(n.status!==0||n.stdout.length===0)continue;let i=E.spawnSync(`xdotool`,[`getwindowname`,e],{encoding:`utf8`,env:t});if(i.error)throw i.error;let a=i.stdout.trim().replaceAll(/[\s/]/g,`_`);r.push({path:`${a||`window`}_${e}.png`,data:n.stdout.toString(`base64`),encoding:`base64`})}return r}function V(e){let t=[],n=e.split(`
2
- `);for(let e of n){if(e.includes(`Root window id:`)||e.includes(`Parent window id:`)||e.includes(`()`))continue;let n=/^\s{5}(0x[\da-f]+) /.exec(e);n?.[1]&&t.push(Number.parseInt(n[1],16).toString())}return t}function H(e,t,n){let r=/(?:^|\n)(\d+\.\d+) (\d+)\s*$/.exec(e),i=r?e.slice(0,r.index).trimEnd():e.trimEnd(),a=Number(r?.[2])*1024||0;return{stderr:i,timeSeconds:Number(r?.[1])||Date.now()/1e3-t,memoryBytes:Math.max(a,n)}}async function U(){if(process.platform!==`linux`)throw Error(`GUI screenshot capture is supported only on Linux.`);for(let e=90;e<100;e++){let t=`:${e}`,n,r=E.spawn(`Xvfb`,[t,`-screen`,`0`,`1280x1024x24`,`-ac`],{stdio:`ignore`,env:i()});if(r.on(`error`,e=>{n=e}),await O(300),n)throw n;if(r.exitCode===null)return{display:t,dispose:async()=>{r.killed||(r.kill(`SIGTERM`),await O(100),r.exitCode===null&&r.kill(`SIGKILL`))}}}throw Error(`Xvfb could not be started.`)}async function W(e){if(e.pid){if(s){a([`TERM`]),await O(A*1e3),a([`KILL`]);return}K(e.pid,`SIGTERM`),await O(A*1e3),e.exitCode===null&&K(e.pid,`SIGKILL`)}}function G(e){if(!e||process.platform!==`linux`)return 0;let t=E.spawnSync(`ps`,[`-o`,`rss=`,`--no-headers`,`--pgroup`,String(e)],{encoding:`utf8`,env:i()});return t.error||t.status!==0||!t.stdout?0:t.stdout.split(`
3
- `).map(e=>Number(e.trim())).filter(e=>Number.isFinite(e)&&e>0).reduce((e,t)=>e+t*1024,0)}function K(e,t){try{if(process.platform!==`win32`){process.kill(-e,t);return}}catch{}try{process.kill(e,t)}catch{}}async function q(e){try{return await C.access(e),!0}catch{return!1}}function J(e){return e instanceof Error?e.message:String(e)}export{N as guiCommandJudgePreset};
1
+ import{printTestCaseResult as e}from"../helpers/printTestCaseResult.js";import{TIMEOUT_COMMAND as t,getSandboxUserEnvOverrides as n,getTrustedHelperEnv as r,killSandboxUserProcesses as i,makeAccessibleToSandboxUser as a,sandboxUserName as o,startSandboxTimeoutWatchdog as s,wrapCommandWithSandboxUser as c}from"../helpers/sandboxUser.js";import{readOutputFiles as l}from"../helpers/readOutputFiles.js";import{parseArgs as u}from"../helpers/parseArgs.js";import{readTestCases as d}from"../helpers/readTestCases.js";import{languageIdToDefinition as f}from"../types/language.js";import{DecisionCode as p}from"../types/decisionCode.js";import{cleanWorkingDirectory as m,snapshotWorkingDirectory as h}from"../helpers/cleanWorkingDirectory.js";import{copyTestCaseFileInput as g}from"../helpers/copyTestCaseFileInput.js";import{findEntryPointFile as _}from"../helpers/findEntryPointFile.js";import{findLanguageDefinitionByPath as v}from"../helpers/findLanguageDefinitionByPath.js";import{judgeByStaticAnalysis as y}from"../helpers/judgeByStaticAnalysis.js";import{runCustomRunner as b}from"../helpers/runCustomRunner.js";import{readProblemMarkdownFrontMatter as x}from"../helpers/readProblemMarkdownFrontMatter.js";import{spawnSyncWithTimeout as S}from"../helpers/spawnSyncWithTimeout.js";import C from"node:path";import{z as w}from"zod";import T from"node:child_process";import E from"node:os";import D from"node:fs/promises";import{setTimeout as O}from"node:timers/promises";const k=.3,A=.2,j=[E.platform()===`darwin`?`gtime`:`/usr/bin/time`,`--format`,`%e %M`],M=w.object({language:w.union([w.string(),w.array(w.string())]).optional()});async function N(t,r){let s=u(process.argv);if(!s.cwd)throw Error(`cwd argument required`);let d=M.parse(s.params),f=s.cwd;a(f);let S=await x(t),C=await(r.readTestCases??R)(t),w=C.length>0?C:[{id:`default`}],T=w[0]?.id??`prebuild`,E=await y(s.cwd,S);if(E){e({testCaseId:T,...E});return}let D=r.mainFilePath??await _(s.cwd,d.language);if(!D){e({testCaseId:T,decisionCode:p.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,stderr:r.mainFilePath?`required main file not found: ${r.mainFilePath}`:`main file not found${d.language?`: language: ${d.language}`:``}`});return}let O=v(D);if(!O){e({testCaseId:T,decisionCode:p.WRONG_ANSWER,stderr:`unsupported language`});return}let A={...process.env,CI:``,FORCE_COLOR:`0`},j=await P({cwd:s.cwd,language:d.language,configuredMainFilePath:r.mainFilePath});if(O.prebuild)try{await O.prebuild(s.cwd);let e=await P({cwd:s.cwd,language:d.language??F(D),configuredMainFilePath:r.mainFilePath,allowConfiguredPathFallback:!0});e&&(j=e)}catch(t){e({testCaseId:T,decisionCode:p.BUILD_ERROR,stderr:J(t)});return}if(!j){e({testCaseId:T,decisionCode:p.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,stderr:r.mainFilePath?`required main file not found: ${r.mainFilePath}`:`main file not found${d.language?`: language: ${d.language}`:``}`});return}let N;if(r.prepare)try{N=await b(()=>r.prepare?.({cwd:f,env:{...A,...n(A)},mainFilePath:j,problemMarkdownFrontMatter:S}))}catch(t){e({testCaseId:T,decisionCode:p.BUILD_ERROR,stderr:J(t)});return}let B=N??I({cwd:s.cwd,env:A,mainFilePath:j,languageDefinition:O});if(B){e({testCaseId:T,decisionCode:B.decisionCode??p.BUILD_ERROR,feedbackMarkdown:B.feedbackMarkdown,stderr:B.stderr,stdout:B.stdout,outputFiles:B.outputFiles});return}let V=await h(s.cwd),H,W=T,G;try{H=r.runCommand?void 0:await U();let t=C.shared?.fileInputPath;for(let i of w){W=i.id,t&&await g(t,s.cwd),i.fileInputPath&&await g(i.fileInputPath,s.cwd);let a=typeof S.timeLimitMs==`number`?S.timeLimitMs/1e3:r.runTimeoutSeconds??5,o=H?{...A,DISPLAY:H.display}:A,u=await r.resolveInput?.({testCase:i,cwd:s.cwd,env:o})??i.input??``;G=u;let d=await r.command?.({testCase:i,cwd:s.cwd,env:o,mainFilePath:j})??O.command(j),h;try{h=r.runCommand?await b(()=>r.runCommand?.({testCase:i,command:c(d),stdin:u,cwd:f,env:{...o,...n(o)},timeLimitSeconds:a,screenshotWaitSeconds:r.screenshotWaitSeconds??k,stopDetectionThreshold:r.stopDetectionThreshold??5})):await z({command:d,stdin:u,cwd:s.cwd,env:o,timeLimitSeconds:a,screenshotWaitSeconds:r.screenshotWaitSeconds??k,stopDetectionThreshold:r.stopDetectionThreshold??5})}catch(t){e({testCaseId:i.id,decisionCode:p.RUNTIME_ERROR,stdin:u,stderr:J(t)}),await m(s.cwd,V);return}let _=await l(s.cwd,S.requiredOutputFilePaths??[]),v={timeLimitSeconds:a,problemMarkdownFrontMatter:{memoryLimitByte:S.memoryLimitByte,requiredOutputFilePaths:S.requiredOutputFilePaths}},y=L({runResult:h,outputFiles:_,context:v}),x=y;if(y.decisionCode===p.ACCEPTED)try{let e=await r.test({testCase:i,runResult:h,outputFiles:_,cwd:s.cwd,context:v});x={decisionCode:e.decisionCode??y.decisionCode,feedbackMarkdown:e.feedbackMarkdown,stderr:e.stderr,stdout:e.stdout,outputFiles:e.outputFiles}}catch(e){x={decisionCode:p.RUNTIME_ERROR,stderr:J(e)}}let C=x.decisionCode??p.ACCEPTED,w=x.stdout??h.stdout,T=x.stderr??h.stderr;if(e({testCaseId:i.id,decisionCode:C,exitStatus:h.status,stdin:h.stdin||void 0,stdout:w||void 0,stderr:T||void 0,timeSeconds:h.timeSeconds,memoryBytes:h.memoryBytes,feedbackMarkdown:x.feedbackMarkdown,outputFiles:x.outputFiles??(_.length>0?_:void 0)}),await m(s.cwd,V),C!==p.ACCEPTED)break}}catch(t){e({testCaseId:W,decisionCode:p.RUNTIME_ERROR,stdin:G,stderr:J(t)}),await m(s.cwd,V)}finally{try{o&&i()}finally{await H?.dispose()}}}async function P(e){if(e.configuredMainFilePath){if(await q(C.join(e.cwd,e.configuredMainFilePath)))return e.configuredMainFilePath;if(!e.allowConfiguredPathFallback)return}return await _(e.cwd,e.language)}function F(e){let t=Object.entries(f).filter(([,t])=>t.fileExtensions.some(t=>e.endsWith(t))).map(([e])=>e);return t.length>0?t:void 0}function I(e){let t=e.languageDefinition.buildCommand?.(e.mainFilePath);if(!t)return;let n=S(t[0],t.slice(1),{cwd:e.cwd,encoding:`utf8`,env:e.env},10);if(n.timeSeconds>10)return{decisionCode:p.BUILD_TIME_LIMIT_EXCEEDED,stderr:n.stderr||void 0};if(n.status!==0)return{decisionCode:p.BUILD_ERROR,stderr:n.stderr||n.stdout||void 0}}function L(e){if(e.runResult.stopReason===`timeout`)return{decisionCode:p.TIME_LIMIT_EXCEEDED,stderr:e.runResult.stderr};if(e.runResult.status!==0)return{decisionCode:p.RUNTIME_ERROR,stderr:e.runResult.stderr};if(e.runResult.memoryBytes>(e.context.problemMarkdownFrontMatter.memoryLimitByte??1/0))return{decisionCode:p.MEMORY_LIMIT_EXCEEDED,stderr:e.runResult.stderr};let t=e.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length??0;return e.outputFiles.length<t?{decisionCode:p.MISSING_REQUIRED_OUTPUT_FILE_ERROR}:{decisionCode:p.ACCEPTED}}async function R(e){return await d(C.join(e,`test_cases`))}async function z(e){let r=c([t,e.timeLimitSeconds.toFixed(3),...j,...e.command]),i=s(e.timeLimitSeconds),a;try{a=T.spawn(r[0],r.slice(1),{cwd:e.cwd,env:{...e.env,...n(e.env)},detached:process.platform!==`win32`,stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw i.cancel(),e}a.stdout.setEncoding(`utf8`),a.stderr.setEncoding(`utf8`);let o=``,l=``,u,d,f=`process_exit`,p=!1,m=[],h=[],g=Date.now()/1e3,_=0;a.stdout.on(`data`,e=>{o+=e}),a.stderr.on(`data`,e=>{l+=e}),a.on(`error`,e=>{d=e,u=1}),a.stdin.on(`error`,()=>{}),a.on(`close`,(e,t)=>{if(e===124){f=`timeout`,u=0;return}if(t){u=1,l=l?`${l}\nprocess terminated by signal: ${t}`:`process terminated by signal: ${t}`;return}u=e??1});try{for(e.stdin&&a.stdin.write(e.stdin),a.stdin.end();u===void 0;){if(await O(e.screenshotWaitSeconds*1e3),_=Math.max(_,G(a.pid)),h=B(e.env.DISPLAY).toSorted((e,t)=>e.data.length-t.data.length),h.length>0){let t=h.map(e=>e.data).toSorted();if(m.unshift(t),m.length=Math.min(m.length,e.stopDetectionThreshold),m.length===e.stopDetectionThreshold&&m.every(e=>e.length===t.length&&e.every((e,n)=>e===t[n]))){f=`stable_screenshot`,u=0;break}}if(Date.now()/1e3-g>e.timeLimitSeconds){f=`timeout`,u=0;break}}f!==`process_exit`&&a.exitCode===null&&(a.removeAllListeners(`close`),a.removeAllListeners(`error`));let t=f===`process_exit`&&i.fired();if(await W(a),p=!0,d)throw d;let{memoryBytes:n,stderr:r,timeSeconds:s}=H(l,g,_);return{stdin:e.stdin,stdout:o.trimEnd(),stderr:r,status:t?0:u,timeSeconds:s,memoryBytes:n,screenshots:h,stopReason:t?`timeout`:f}}finally{p&&i.cancel()}}function B(e){let t=r(e?{DISPLAY:e}:{DISPLAY:process.env.DISPLAY}),n=T.spawnSync(`xwininfo`,[`-root`,`-tree`],{encoding:`utf8`,env:t});if(n.error)throw n.error;if(n.status!==0||!n.stdout)return[];let i=[];for(let e of V(n.stdout)){let n=T.spawnSync(`maim`,[`-i`,e],{env:t});if(n.error)throw n.error;if(n.status!==0||n.stdout.length===0)continue;let r=T.spawnSync(`xdotool`,[`getwindowname`,e],{encoding:`utf8`,env:t});if(r.error)throw r.error;let a=r.stdout.trim().replaceAll(/[\s/]/g,`_`);i.push({path:`${a||`window`}_${e}.png`,data:n.stdout.toString(`base64`),encoding:`base64`})}return i}function V(e){let t=[],n=e.split(`
2
+ `);for(let e of n){if(e.includes(`Root window id:`)||e.includes(`Parent window id:`)||e.includes(`()`))continue;let n=/^\s{5}(0x[\da-f]+) /.exec(e);n?.[1]&&t.push(Number.parseInt(n[1],16).toString())}return t}function H(e,t,n){let r=/(?:^|\n)(\d+\.\d+) (\d+)\s*$/.exec(e),i=r?e.slice(0,r.index).trimEnd():e.trimEnd(),a=Number(r?.[2])*1024||0;return{stderr:i,timeSeconds:Number(r?.[1])||Date.now()/1e3-t,memoryBytes:Math.max(a,n)}}async function U(){if(process.platform!==`linux`)throw Error(`GUI screenshot capture is supported only on Linux.`);for(let e=90;e<100;e++){let t=`:${e}`,n,i=T.spawn(`Xvfb`,[t,`-screen`,`0`,`1280x1024x24`,`-ac`],{stdio:`ignore`,env:r()});if(i.on(`error`,e=>{n=e}),await O(300),n)throw n;if(i.exitCode===null)return{display:t,dispose:async()=>{i.killed||(i.kill(`SIGTERM`),await O(100),i.exitCode===null&&i.kill(`SIGKILL`))}}}throw Error(`Xvfb could not be started.`)}async function W(e){if(e.pid){if(o){i([`TERM`]),await O(A*1e3),i([`KILL`]);return}K(e.pid,`SIGTERM`),await O(A*1e3),e.exitCode===null&&K(e.pid,`SIGKILL`)}}function G(e){if(!e||process.platform!==`linux`)return 0;let t=T.spawnSync(`ps`,[`-o`,`rss=`,`--no-headers`,`--pgroup`,String(e)],{encoding:`utf8`,env:r()});return t.error||t.status!==0||!t.stdout?0:t.stdout.split(`
3
+ `).map(e=>Number(e.trim())).filter(e=>Number.isFinite(e)&&e>0).reduce((e,t)=>e+t*1024,0)}function K(e,t){try{if(process.platform!==`win32`){process.kill(-e,t);return}}catch{}try{process.kill(e,t)}catch{}}async function q(e){try{return await D.access(e),!0}catch{return!1}}function J(e){return e instanceof Error?e.message:String(e)}export{N as guiCommandJudgePreset};
4
4
  //# sourceMappingURL=guiCommand.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guiCommand.js","names":["readFileTestCases","childProcess","wait"],"sources":["../../src/presets/guiCommand.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as wait } from 'node:timers/promises';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.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 { readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases as readFileTestCases } from '../helpers/readTestCases.js';\nimport { runCustomRunner } from '../helpers/runCustomRunner.js';\nimport {\n getSandboxUserEnvOverrides,\n getTrustedHelperEnv,\n killSandboxUserProcesses,\n makeAccessibleToSandboxUser,\n sandboxUserName,\n startSandboxTimeoutWatchdog,\n TIMEOUT_COMMAND,\n wrapCommandWithSandboxUser,\n} from '../helpers/sandboxUser.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { languageIdToDefinition } from '../types/language.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 5;\nconst SCREENSHOT_WAIT_SECONDS = 0.3;\nconst XVFB_STARTUP_WAIT_SECONDS = 0.3;\nconst XVFB_SHUTDOWN_WAIT_SECONDS = 0.1;\nconst PROCESS_SHUTDOWN_WAIT_SECONDS = 0.2;\nconst STOP_DETECTION_THRESHOLD = 5;\nconst TIME_COMMAND = [os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time', '--format', '%e %M'] as const;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\ninterface BaseGuiTestCase {\n id: string;\n input?: string;\n fileInputPath?: string;\n}\n\nexport interface GuiScreenshotFile {\n path: string;\n data: string;\n encoding: 'base64';\n}\n\nexport interface GuiCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n screenshots: GuiScreenshotFile[];\n stopReason: 'process_exit' | 'stable_screenshot' | 'timeout';\n}\n\ninterface GuiJudgeContext {\n timeLimitSeconds: number;\n problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;\n}\n\ntype GuiJudgeCaseResult = Pick<\n TestCaseResult,\n 'decisionCode' | 'feedbackMarkdown' | 'stderr' | 'stdout' | 'outputFiles'\n>;\n\nexport interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase = BaseGuiTestCase> {\n mainFilePath?: string;\n runTimeoutSeconds?: number;\n screenshotWaitSeconds?: number;\n stopDetectionThreshold?: number;\n readTestCases?: (problemDir: string) => Promise<readonly TTestCase[]>;\n prepare?: (context: {\n cwd: string;\n /**\n * Build the submission with it. Under `EXERCODE_SANDBOX_USER` delegation it already carries the\n * sandbox user's overrides, but the handler must still wrap whatever it spawns with\n * `wrapCommandWithSandboxUser` (both exported): a build runs the submission's own scripts\n * (`package.json` lifecycle scripts, `build.gradle`, `build.rs`), which as the trusted harness\n * user could read the problem's test cases and rewrite the harness. The preset terminates\n * leftover sandbox processes after the handler returns.\n */\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n problemMarkdownFrontMatter: ProblemMarkdownFrontMatter;\n }) => Promise<Partial<GuiJudgeCaseResult> | undefined> | Partial<GuiJudgeCaseResult> | undefined;\n /**\n * Runs as the trusted harness user with the submission's `cwd`. Fixture files it creates there\n * must be written with `createDirectoryWithoutFollowingSymlinks`/`writeFileWithoutFollowingSymlinks`\n * (both exported): a submission can plant a symlink at a fixture path, and a plain `fs.writeFile`\n * would follow it into a file only the harness can write.\n */\n resolveInput?: (context: { testCase: TTestCase; cwd: string; env: NodeJS.ProcessEnv }) => Promise<string> | string;\n command?: (context: {\n testCase: TTestCase;\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n }) => Promise<readonly [string, ...string[]]> | readonly [string, ...string[]];\n runCommand?: (context: {\n testCase: TTestCase;\n /**\n * The command to run. Under `EXERCODE_SANDBOX_USER` delegation it is already wrapped so it\n * executes as the sandbox user; spawn it as given (with the supplied `env`) instead of\n * reconstructing it, or the submission runs as the trusted harness user.\n *\n * Its direct child is then a root-owned `sudo` whose descendants belong to the sandbox user, so\n * the handler cannot signal them: enforce `timeLimitSeconds` with `startSandboxTimeoutWatchdog`\n * and `killSandboxUserProcesses` (both exported) rather than `child.kill()` or an outer\n * `timeout`. The preset terminates leftover sandbox processes after the handler returns.\n */\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n }) => Promise<GuiCommandRunResult> | GuiCommandRunResult;\n test: (context: {\n testCase: TTestCase;\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: GuiJudgeContext;\n }) => Promise<Partial<GuiJudgeCaseResult>> | Partial<GuiJudgeCaseResult>;\n}\n\n/**\n * A preset function for judging GUI programs by collecting screenshots while the program runs.\n *\n * Keep problem-specific logic in `prepare`, `command`, and `test`.\n *\n * @example\n * Create `judge.ts`:\n * ```ts\n * import { DecisionCode } from '@exercode/problem-utils';\n * import { guiCommandJudgePreset } from '@exercode/problem-utils/presets/guiCommand';\n *\n * await guiCommandJudgePreset(import.meta.dirname, {\n * mainFilePath: 'Main.java',\n * readTestCases: async () => [{ id: 'default' }],\n * test: ({ runResult }) => {\n * return runResult.screenshots.length > 0\n * ? { decisionCode: DecisionCode.ACCEPTED }\n * : { decisionCode: DecisionCode.WRONG_ANSWER };\n * },\n * });\n * ```\n */\nexport async function guiCommandJudgePreset<TTestCase extends BaseGuiTestCase = BaseGuiTestCase>(\n problemDir: string,\n options: GuiCommandJudgePresetOptions<TTestCase>\n): 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 const submissionDir = args.cwd;\n\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(submissionDir);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const configuredTestCases = await (options.readTestCases ?? readGuiTestCases<TTestCase>)(problemDir);\n const testCases =\n configuredTestCases.length > 0 ? configuredTestCases : ([{ id: 'default' }] as unknown as readonly TTestCase[]);\n const prebuildTestCaseId = testCases[0]?.id ?? 'prebuild';\n\n const staticAnalysisResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId: prebuildTestCaseId, ...staticAnalysisResult });\n return;\n }\n\n const initialMainFilePath = options.mainFilePath ?? (await findEntryPointFile(args.cwd, params.language));\n if (!initialMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(initialMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let resolvedMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language,\n configuredMainFilePath: options.mainFilePath,\n });\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n const prebuiltMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language ?? inferLanguageIdsByPath(initialMainFilePath),\n configuredMainFilePath: options.mainFilePath,\n allowConfiguredPathFallback: true,\n });\n if (prebuiltMainFilePath) resolvedMainFilePath = prebuiltMainFilePath;\n } catch (error) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n if (!resolvedMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n let customPrepareResult: Partial<GuiJudgeCaseResult> | undefined;\n if (options.prepare) {\n try {\n customPrepareResult = await runCustomRunner(() =>\n options.prepare?.({\n cwd: submissionDir,\n env: { ...env, ...getSandboxUserEnvOverrides(env) },\n mainFilePath: resolvedMainFilePath,\n problemMarkdownFrontMatter,\n })\n );\n } catch (error) {\n // Like the `prebuild` step above: report the failed build rather than letting the throw (from\n // the handler itself, or from the fail-closed sandbox sweep around it) end the run resultless.\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n const prepareResult =\n customPrepareResult ??\n runDefaultPrepare({\n cwd: args.cwd,\n env,\n mainFilePath: resolvedMainFilePath,\n languageDefinition,\n });\n if (prepareResult) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: prepareResult.decisionCode ?? DecisionCode.BUILD_ERROR,\n feedbackMarkdown: prepareResult.feedbackMarkdown,\n stderr: prepareResult.stderr,\n stdout: prepareResult.stdout,\n outputFiles: prepareResult.outputFiles,\n });\n return;\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n let displayServer: Awaited<ReturnType<typeof ensureDisplayServer>> | undefined;\n let currentTestCaseId = prebuildTestCaseId;\n let currentStdin: string | undefined;\n try {\n displayServer = options.runCommand ? undefined : await ensureDisplayServer();\n const sharedFileInputPath = (configuredTestCases as { shared?: { fileInputPath?: string } }).shared?.fileInputPath;\n for (const testCase of testCases) {\n currentTestCaseId = testCase.id;\n if (sharedFileInputPath) await copyTestCaseFileInput(sharedFileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n const timeLimitSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : (options.runTimeoutSeconds ?? JUDGE_DEFAULT_TIMEOUT_SECONDS);\n\n const runEnv = displayServer ? { ...env, DISPLAY: displayServer.display } : env;\n const stdin = (await options.resolveInput?.({ testCase, cwd: args.cwd, env: runEnv })) ?? testCase.input ?? '';\n currentStdin = stdin;\n const command =\n (await options.command?.({ testCase, cwd: args.cwd, env: runEnv, mainFilePath: resolvedMainFilePath })) ??\n languageDefinition.command(resolvedMainFilePath);\n\n let runResult: GuiCommandRunResult;\n try {\n runResult = options.runCommand\n ? await runCustomRunner(\n () =>\n // Hand custom runners a command that is already sandbox-wrapped, and the matching\n // environment: they spawn it themselves, so an unwrapped command would run the\n // submission as the trusted harness user and defeat the delegation boundary.\n options.runCommand?.({\n testCase,\n command: wrapCommandWithSandboxUser(command),\n stdin,\n cwd: submissionDir,\n env: { ...runEnv, ...getSandboxUserEnvOverrides(runEnv) },\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n }) as Promise<GuiCommandRunResult>\n )\n : await spawnGuiProgram({\n command,\n stdin,\n cwd: args.cwd,\n env: runEnv,\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n });\n } catch (error) {\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n return;\n }\n\n const outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n const judgeContext: GuiJudgeContext = {\n timeLimitSeconds,\n problemMarkdownFrontMatter: {\n memoryLimitByte: problemMarkdownFrontMatter.memoryLimitByte,\n requiredOutputFilePaths: problemMarkdownFrontMatter.requiredOutputFilePaths,\n },\n };\n const baseJudgeResult = evaluateGuiRunResult({ runResult, outputFiles, context: judgeContext });\n let judgeResult = baseJudgeResult;\n if (baseJudgeResult.decisionCode === DecisionCode.ACCEPTED) {\n try {\n const extendedJudgeResult = await options.test({\n testCase,\n runResult,\n outputFiles,\n context: judgeContext,\n });\n judgeResult = {\n decisionCode: extendedJudgeResult.decisionCode ?? baseJudgeResult.decisionCode,\n feedbackMarkdown: extendedJudgeResult.feedbackMarkdown,\n stderr: extendedJudgeResult.stderr,\n stdout: extendedJudgeResult.stdout,\n outputFiles: extendedJudgeResult.outputFiles,\n };\n } catch (error) {\n judgeResult = {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: errorToMessage(error),\n };\n }\n }\n\n const decisionCode = judgeResult.decisionCode ?? DecisionCode.ACCEPTED;\n const stdout = judgeResult.stdout ?? runResult.stdout;\n const stderr = judgeResult.stderr ?? runResult.stderr;\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: runResult.status,\n stdin: runResult.stdin || undefined,\n stdout: stdout || undefined,\n stderr: stderr || undefined,\n timeSeconds: runResult.timeSeconds,\n memoryBytes: runResult.memoryBytes,\n feedbackMarkdown: judgeResult.feedbackMarkdown,\n outputFiles: judgeResult.outputFiles ?? (outputFiles.length > 0 ? outputFiles : undefined),\n });\n\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n } catch (error) {\n printTestCaseResult({\n testCaseId: currentTestCaseId,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin: currentStdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n } finally {\n try {\n // Sweep any sandbox processes the run left behind (e.g. a SIGTERM-ignoring forked child) so\n // they cannot race the harness's output reads or survive into the next test case/request.\n if (sandboxUserName) killSandboxUserProcesses();\n } finally {\n // Must run even when the sweep fails closed, or Xvfb keeps holding its display number and\n // later GUI requests exhaust the `:90`–`:99` range.\n await displayServer?.dispose();\n }\n }\n}\n\nasync function resolveMainFilePath(context: {\n cwd: string;\n language?: string | string[];\n configuredMainFilePath?: string;\n allowConfiguredPathFallback?: boolean;\n}): Promise<string | undefined> {\n if (context.configuredMainFilePath) {\n const resolvedPath = path.join(context.cwd, context.configuredMainFilePath);\n if (await pathExists(resolvedPath)) return context.configuredMainFilePath;\n if (!context.allowConfiguredPathFallback) return undefined;\n }\n\n return await findEntryPointFile(context.cwd, context.language);\n}\n\nfunction inferLanguageIdsByPath(filePath: string): string[] | undefined {\n const languageIds = Object.entries(languageIdToDefinition)\n .filter(([, definition]) => definition.fileExtensions.some((ext) => filePath.endsWith(ext)))\n .map(([languageId]) => languageId);\n return languageIds.length > 0 ? languageIds : undefined;\n}\n\nfunction runDefaultPrepare(context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n languageDefinition: NonNullable<ReturnType<typeof findLanguageDefinitionByPath>>;\n}): Partial<GuiJudgeCaseResult> | undefined {\n const buildCommand = context.languageDefinition.buildCommand?.(context.mainFilePath);\n if (!buildCommand) return undefined;\n\n const buildResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: context.cwd, encoding: 'utf8', env: context.env },\n BUILD_TIMEOUT_SECONDS\n );\n\n if (buildResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: buildResult.stderr || undefined,\n };\n }\n\n if (buildResult.status !== 0) {\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: buildResult.stderr || buildResult.stdout || undefined,\n };\n }\n\n return undefined;\n}\n\nfunction evaluateGuiRunResult(context: {\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: GuiJudgeContext;\n}): Partial<GuiJudgeCaseResult> {\n if (context.runResult.stopReason === 'timeout') {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n if (context.runResult.status !== 0) {\n return {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: context.runResult.stderr,\n };\n }\n\n if (\n context.runResult.memoryBytes >\n (context.context.problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)\n ) {\n return {\n decisionCode: DecisionCode.MEMORY_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n const requiredOutputFilesCount = context.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0;\n if (context.outputFiles.length < requiredOutputFilesCount) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR,\n };\n }\n\n return { decisionCode: DecisionCode.ACCEPTED };\n}\n\nasync function readGuiTestCases<TTestCase extends BaseGuiTestCase>(problemDir: string): Promise<readonly TTestCase[]> {\n return (await readFileTestCases(path.join(problemDir, 'test_cases'))) as unknown as readonly TTestCase[];\n}\n\nasync function spawnGuiProgram(context: {\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n}): Promise<GuiCommandRunResult> {\n const wrappedCommand = wrapCommandWithSandboxUser([\n TIMEOUT_COMMAND,\n context.timeLimitSeconds.toFixed(3),\n ...TIME_COMMAND,\n ...context.command,\n ]);\n // A submission can signal its own sandboxed `timeout` and then wedge the event loop (e.g.\n // XGrabServer makes the synchronous `xwininfo`/`maim` block), so a harness-owned watchdog\n // force-kills the sandbox user at the deadline; killing the client also releases its X grab.\n // Armed before the submission starts: a submission that exhausts the PID cgroup would otherwise\n // prevent the watchdog from spawning at all.\n const watchdog = startSandboxTimeoutWatchdog(context.timeLimitSeconds);\n let child: childProcess.ChildProcessWithoutNullStreams;\n try {\n child = childProcess.spawn(wrappedCommand[0], wrappedCommand.slice(1), {\n cwd: context.cwd,\n env: { ...context.env, ...getSandboxUserEnvOverrides(context.env) },\n detached: process.platform !== 'win32',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n watchdog.cancel();\n throw error;\n }\n\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n\n let stdout = '';\n let stderr = '';\n let exitCode: number | undefined;\n let spawnError: Error | undefined;\n let stopReason: GuiCommandRunResult['stopReason'] = 'process_exit';\n let submissionStopped = false;\n const screenshotSignaturesHistory: string[][] = [];\n let screenshots: GuiScreenshotFile[] = [];\n const startTimeSeconds = Date.now() / 1000;\n let sampledMemoryBytes = 0;\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n });\n child.on('error', (error) => {\n spawnError = error;\n exitCode = 1;\n });\n // A child that exits before the input is fully written makes the write fail; without a listener\n // that EPIPE would be an unhandled stream error terminating the harness mid-run.\n child.stdin.on('error', () => {\n // The submission simply stopped reading its input; the exit handling below reports the result.\n });\n child.on('close', (code, signal) => {\n if (code === 124) {\n stopReason = 'timeout';\n exitCode = 0;\n return;\n }\n if (signal) {\n exitCode = 1;\n stderr = stderr\n ? `${stderr}\\nprocess terminated by signal: ${signal}`\n : `process terminated by signal: ${signal}`;\n return;\n }\n exitCode = code ?? 1;\n });\n\n // Everything below must run under the `finally` that decides the watchdog's fate: it is cancelled\n // only once the submission is demonstrably stopped. When a helper throws first — `takeScreenshots`\n // rethrows spawn failures, which a submission can provoke by exhausting PIDs — the watchdog stays\n // armed on purpose, since nothing else would end the submission. The cost is that a detached\n // watchdog can outlive this harness and SIGKILL sandbox processes of a request that starts within\n // its remaining deadline, which is the same trade-off the package-manager runner accepts.\n try {\n if (context.stdin) child.stdin.write(context.stdin);\n child.stdin.end();\n\n while (exitCode === undefined) {\n await wait(context.screenshotWaitSeconds * 1000);\n sampledMemoryBytes = Math.max(sampledMemoryBytes, readProcessGroupMemoryBytes(child.pid));\n const currentScreenshots = takeScreenshots(context.env.DISPLAY);\n screenshots = currentScreenshots.toSorted((a, b) => a.data.length - b.data.length);\n\n if (screenshots.length > 0) {\n const screenshotSignatures = screenshots.map((file) => file.data).toSorted();\n screenshotSignaturesHistory.unshift(screenshotSignatures);\n screenshotSignaturesHistory.length = Math.min(\n screenshotSignaturesHistory.length,\n context.stopDetectionThreshold\n );\n if (\n screenshotSignaturesHistory.length === context.stopDetectionThreshold &&\n screenshotSignaturesHistory.every(\n (files) =>\n files.length === screenshotSignatures.length &&\n files.every((file, index) => file === screenshotSignatures[index])\n )\n ) {\n stopReason = 'stable_screenshot';\n exitCode = 0;\n break;\n }\n }\n\n if (Date.now() / 1000 - startTimeSeconds > context.timeLimitSeconds) {\n stopReason = 'timeout';\n exitCode = 0;\n break;\n }\n }\n\n if (stopReason !== 'process_exit' && child.exitCode === null) {\n child.removeAllListeners('close');\n child.removeAllListeners('error');\n }\n // A submission can kill its own same-UID `timeout` and be stopped by the watchdog instead,\n // which surfaces as a SIGKILL rather than a deadline. Report that as the timeout it is, not as\n // the runtime error the signal would otherwise imply — but only when the child's fate is what\n // decided the verdict. A `stable_screenshot` (or the loop's own `timeout`) was already decided\n // from the screenshots, so the watchdog must not overwrite it.\n const watchdogFired = stopReason === 'process_exit' && watchdog.fired();\n await stopProcess(child);\n submissionStopped = true;\n if (spawnError) throw spawnError;\n const {\n memoryBytes,\n stderr: normalizedStderr,\n timeSeconds,\n } = parseTimedStderr(stderr, startTimeSeconds, sampledMemoryBytes);\n\n return {\n stdin: context.stdin,\n stdout: stdout.trimEnd(),\n stderr: normalizedStderr,\n status: watchdogFired ? 0 : exitCode,\n timeSeconds,\n memoryBytes,\n screenshots,\n stopReason: watchdogFired ? 'timeout' : stopReason,\n };\n } finally {\n // Only once the submission is demonstrably stopped: if termination itself failed (or never ran\n // because a helper threw), the watchdog is the only thing left able to end it, and cancelling\n // would leave it running unchecked.\n if (submissionStopped) watchdog.cancel();\n }\n}\n\nfunction takeScreenshots(display: string | undefined): GuiScreenshotFile[] {\n // These helpers run as the trusted harness user, so they must not be resolved through a\n // submission-influenced `PATH` (see `getTrustedHelperEnv`).\n const env = getTrustedHelperEnv(display ? { DISPLAY: display } : { DISPLAY: process.env.DISPLAY });\n const xwininfo = childProcess.spawnSync('xwininfo', ['-root', '-tree'], { encoding: 'utf8', env });\n if (xwininfo.error) throw xwininfo.error;\n if (xwininfo.status !== 0 || !xwininfo.stdout) return [];\n\n const screenshots: GuiScreenshotFile[] = [];\n for (const windowId of extractTopLevelWindowIds(xwininfo.stdout)) {\n const screenshot = childProcess.spawnSync('maim', ['-i', windowId], { env });\n if (screenshot.error) throw screenshot.error;\n if (screenshot.status !== 0 || screenshot.stdout.length === 0) continue;\n\n const windowNameResult = childProcess.spawnSync('xdotool', ['getwindowname', windowId], { encoding: 'utf8', env });\n if (windowNameResult.error) throw windowNameResult.error;\n const windowName = windowNameResult.stdout.trim().replaceAll(/[\\s/]/g, '_');\n\n screenshots.push({\n path: `${windowName || 'window'}_${windowId}.png`,\n data: screenshot.stdout.toString('base64'),\n encoding: 'base64',\n });\n }\n\n return screenshots;\n}\n\nfunction extractTopLevelWindowIds(stdout: string): string[] {\n const windowIds: string[] = [];\n const lines = stdout.split('\\n');\n for (const line of lines) {\n if (line.includes('Root window id:') || line.includes('Parent window id:') || line.includes('()')) continue;\n\n const match = /^\\s{5}(0x[\\da-f]+) /.exec(line);\n if (!match?.[1]) continue;\n windowIds.push(Number.parseInt(match[1], 16).toString());\n }\n return windowIds;\n}\n\nfunction parseTimedStderr(\n stderr: string,\n startTimeSeconds: number,\n sampledMemoryBytes: number\n): Pick<GuiCommandRunResult, 'stderr' | 'timeSeconds' | 'memoryBytes'> {\n const match = /(?:^|\\n)(\\d+\\.\\d+) (\\d+)\\s*$/.exec(stderr);\n const normalizedStderr = match ? stderr.slice(0, match.index).trimEnd() : stderr.trimEnd();\n const parsedMemoryBytes = Number(match?.[2]) * 1024 || 0;\n return {\n stderr: normalizedStderr,\n timeSeconds: Number(match?.[1]) || Date.now() / 1000 - startTimeSeconds,\n memoryBytes: Math.max(parsedMemoryBytes, sampledMemoryBytes),\n };\n}\n\nasync function ensureDisplayServer(): Promise<{ display: string; dispose: () => Promise<void> }> {\n if (process.platform !== 'linux') {\n throw new Error('GUI screenshot capture is supported only on Linux.');\n }\n\n for (let displayNumber = 90; displayNumber < 100; displayNumber++) {\n const display = `:${displayNumber}`;\n let spawnError: Error | undefined;\n const xvfb = childProcess.spawn('Xvfb', [display, '-screen', '0', '1280x1024x24', '-ac'], {\n stdio: 'ignore',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n xvfb.on('error', (error) => {\n spawnError = error;\n });\n\n await wait(XVFB_STARTUP_WAIT_SECONDS * 1000);\n if (spawnError) throw spawnError;\n if (xvfb.exitCode !== null) continue;\n\n return {\n display,\n dispose: async () => {\n if (!xvfb.killed) {\n xvfb.kill('SIGTERM');\n await wait(XVFB_SHUTDOWN_WAIT_SECONDS * 1000);\n if (xvfb.exitCode === null) xvfb.kill('SIGKILL');\n }\n },\n };\n }\n\n throw new Error('Xvfb could not be started.');\n}\n\nasync function stopProcess(child: childProcess.ChildProcess): Promise<void> {\n if (!child.pid) return;\n // The current user cannot signal the root-owned sudo wrapper nor the sandbox user's processes, so\n // go through sudo. SIGKILL is unconditional after the grace period: `child.exitCode` reflects only\n // the sudo wrapper, so a daemonized child that ignored SIGTERM would survive if we gated on it.\n if (sandboxUserName) {\n killSandboxUserProcesses(['TERM']);\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n killSandboxUserProcesses(['KILL']);\n return;\n }\n killProcessGroup(child.pid, 'SIGTERM');\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n if (child.exitCode === null) killProcessGroup(child.pid, 'SIGKILL');\n}\n\nfunction readProcessGroupMemoryBytes(processGroupId: number | undefined): number {\n if (!processGroupId || process.platform !== 'linux') return 0;\n\n // The delegation contract requires sudoers `!use_pty`, so sudo execs the command in place and it\n // stays in the detached child's process group; `--pgroup` therefore still captures it there.\n const result = childProcess.spawnSync('ps', ['-o', 'rss=', '--no-headers', '--pgroup', String(processGroupId)], {\n encoding: 'utf8',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n if (result.error || result.status !== 0 || !result.stdout) return 0;\n\n return result.stdout\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter((value) => Number.isFinite(value) && value > 0)\n .reduce((sum, value) => sum + value * 1024, 0);\n}\n\nfunction killProcessGroup(processGroupId: number, signal: NodeJS.Signals): void {\n try {\n if (process.platform !== 'win32') {\n process.kill(-processGroupId, signal);\n return;\n }\n } catch {\n // The process group may already be gone. Fall back to the direct PID below.\n }\n\n try {\n process.kill(processGroupId, signal);\n } catch {\n // The direct child may also already be gone.\n }\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction errorToMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"guiCommand.js","names":["readFileTestCases","childProcess","wait"],"sources":["../../src/presets/guiCommand.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { setTimeout as wait } from 'node:timers/promises';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.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 { readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases as readFileTestCases } from '../helpers/readTestCases.js';\nimport { runCustomRunner } from '../helpers/runCustomRunner.js';\nimport {\n getSandboxUserEnvOverrides,\n getTrustedHelperEnv,\n killSandboxUserProcesses,\n makeAccessibleToSandboxUser,\n sandboxUserName,\n startSandboxTimeoutWatchdog,\n TIMEOUT_COMMAND,\n wrapCommandWithSandboxUser,\n} from '../helpers/sandboxUser.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { languageIdToDefinition } from '../types/language.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 5;\nconst SCREENSHOT_WAIT_SECONDS = 0.3;\nconst XVFB_STARTUP_WAIT_SECONDS = 0.3;\nconst XVFB_SHUTDOWN_WAIT_SECONDS = 0.1;\nconst PROCESS_SHUTDOWN_WAIT_SECONDS = 0.2;\nconst STOP_DETECTION_THRESHOLD = 5;\nconst TIME_COMMAND = [os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time', '--format', '%e %M'] as const;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\n/** What every GUI test case needs; a custom `readTestCases` may add any fields of its own. */\ninterface BaseGuiTestCase {\n id: string;\n /** Standard input (`test_cases/<id>.in`). */\n input?: string;\n /** Directory copied into the working directory before the run (`test_cases/<id>.fin/`). */\n fileInputPath?: string;\n}\n\n/** A test case read from `test_cases/` by the default reader. */\nexport interface GuiTestCase extends BaseGuiTestCase {\n /** Expected standard output (`test_cases/<id>.out`), for the problem's `test` to compare. */\n output?: string;\n /** Directory of expected output files (`test_cases/<id>.fout/`), for the problem's `test` to compare. */\n fileOutputPath?: string;\n}\n\nexport interface GuiScreenshotFile {\n path: string;\n data: string;\n encoding: 'base64';\n}\n\nexport interface GuiCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n screenshots: GuiScreenshotFile[];\n stopReason: 'process_exit' | 'stable_screenshot' | 'timeout';\n}\n\ninterface GuiJudgeContext {\n timeLimitSeconds: number;\n problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;\n}\n\ntype GuiJudgeCaseResult = Pick<\n TestCaseResult,\n 'decisionCode' | 'feedbackMarkdown' | 'stderr' | 'stdout' | 'outputFiles'\n>;\n\nexport interface GuiCommandJudgePresetOptions<TTestCase extends BaseGuiTestCase = GuiTestCase> {\n mainFilePath?: string;\n runTimeoutSeconds?: number;\n screenshotWaitSeconds?: number;\n stopDetectionThreshold?: number;\n readTestCases?: (problemDir: string) => Promise<readonly TTestCase[]>;\n prepare?: (context: {\n cwd: string;\n /**\n * Build the submission with it. Under `EXERCODE_SANDBOX_USER` delegation it already carries the\n * sandbox user's overrides, but the handler must still wrap whatever it spawns with\n * `wrapCommandWithSandboxUser` (both exported): a build runs the submission's own scripts\n * (`package.json` lifecycle scripts, `build.gradle`, `build.rs`), which as the trusted harness\n * user could read the problem's test cases and rewrite the harness. The preset terminates\n * leftover sandbox processes after the handler returns.\n */\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n problemMarkdownFrontMatter: ProblemMarkdownFrontMatter;\n }) => Promise<Partial<GuiJudgeCaseResult> | undefined> | Partial<GuiJudgeCaseResult> | undefined;\n /**\n * Runs as the trusted harness user with the submission's `cwd`. Fixture files it creates there\n * must be written with `createDirectoryWithoutFollowingSymlinks`/`writeFileWithoutFollowingSymlinks`\n * (both exported): a submission can plant a symlink at a fixture path, and a plain `fs.writeFile`\n * would follow it into a file only the harness can write.\n */\n resolveInput?: (context: { testCase: TTestCase; cwd: string; env: NodeJS.ProcessEnv }) => Promise<string> | string;\n command?: (context: {\n testCase: TTestCase;\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n }) => Promise<readonly [string, ...string[]]> | readonly [string, ...string[]];\n runCommand?: (context: {\n testCase: TTestCase;\n /**\n * The command to run. Under `EXERCODE_SANDBOX_USER` delegation it is already wrapped so it\n * executes as the sandbox user; spawn it as given (with the supplied `env`) instead of\n * reconstructing it, or the submission runs as the trusted harness user.\n *\n * Its direct child is then a root-owned `sudo` whose descendants belong to the sandbox user, so\n * the handler cannot signal them: enforce `timeLimitSeconds` with `startSandboxTimeoutWatchdog`\n * and `killSandboxUserProcesses` (both exported) rather than `child.kill()` or an outer\n * `timeout`. The preset terminates leftover sandbox processes after the handler returns.\n */\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n }) => Promise<GuiCommandRunResult> | GuiCommandRunResult;\n test: (context: {\n testCase: TTestCase;\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n /** The submission's working directory, e.g. for `compareExpectedOutputFiles(cwd, testCase.fileOutputPath)`. */\n cwd: string;\n context: GuiJudgeContext;\n }) => Promise<Partial<GuiJudgeCaseResult>> | Partial<GuiJudgeCaseResult>;\n}\n\n/**\n * A preset function for judging GUI programs by collecting screenshots while the program runs.\n *\n * Keep problem-specific logic in `prepare`, `command`, and `test`.\n *\n * @example\n * Create `judge.ts`:\n * ```ts\n * import { DecisionCode } from '@exercode/problem-utils';\n * import { guiCommandJudgePreset } from '@exercode/problem-utils/presets/guiCommand';\n *\n * await guiCommandJudgePreset(import.meta.dirname, {\n * mainFilePath: 'Main.java',\n * readTestCases: async () => [{ id: 'default' }],\n * test: ({ runResult }) => {\n * return runResult.screenshots.length > 0\n * ? { decisionCode: DecisionCode.ACCEPTED }\n * : { decisionCode: DecisionCode.WRONG_ANSWER };\n * },\n * });\n * ```\n */\nexport async function guiCommandJudgePreset<TTestCase extends BaseGuiTestCase = GuiTestCase>(\n problemDir: string,\n options: GuiCommandJudgePresetOptions<TTestCase>\n): 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 const submissionDir = args.cwd;\n\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(submissionDir);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const configuredTestCases = await (options.readTestCases ?? readGuiTestCases<TTestCase>)(problemDir);\n const testCases =\n configuredTestCases.length > 0 ? configuredTestCases : ([{ id: 'default' }] as unknown as readonly TTestCase[]);\n const prebuildTestCaseId = testCases[0]?.id ?? 'prebuild';\n\n const staticAnalysisResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId: prebuildTestCaseId, ...staticAnalysisResult });\n return;\n }\n\n const initialMainFilePath = options.mainFilePath ?? (await findEntryPointFile(args.cwd, params.language));\n if (!initialMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(initialMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let resolvedMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language,\n configuredMainFilePath: options.mainFilePath,\n });\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n const prebuiltMainFilePath = await resolveMainFilePath({\n cwd: args.cwd,\n language: params.language ?? inferLanguageIdsByPath(initialMainFilePath),\n configuredMainFilePath: options.mainFilePath,\n allowConfiguredPathFallback: true,\n });\n if (prebuiltMainFilePath) resolvedMainFilePath = prebuiltMainFilePath;\n } catch (error) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n if (!resolvedMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: options.mainFilePath\n ? `required main file not found: ${options.mainFilePath}`\n : `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n let customPrepareResult: Partial<GuiJudgeCaseResult> | undefined;\n if (options.prepare) {\n try {\n customPrepareResult = await runCustomRunner(() =>\n options.prepare?.({\n cwd: submissionDir,\n env: { ...env, ...getSandboxUserEnvOverrides(env) },\n mainFilePath: resolvedMainFilePath,\n problemMarkdownFrontMatter,\n })\n );\n } catch (error) {\n // Like the `prebuild` step above: report the failed build rather than letting the throw (from\n // the handler itself, or from the fail-closed sandbox sweep around it) end the run resultless.\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: errorToMessage(error),\n });\n return;\n }\n }\n const prepareResult =\n customPrepareResult ??\n runDefaultPrepare({\n cwd: args.cwd,\n env,\n mainFilePath: resolvedMainFilePath,\n languageDefinition,\n });\n if (prepareResult) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: prepareResult.decisionCode ?? DecisionCode.BUILD_ERROR,\n feedbackMarkdown: prepareResult.feedbackMarkdown,\n stderr: prepareResult.stderr,\n stdout: prepareResult.stdout,\n outputFiles: prepareResult.outputFiles,\n });\n return;\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n let displayServer: Awaited<ReturnType<typeof ensureDisplayServer>> | undefined;\n let currentTestCaseId = prebuildTestCaseId;\n let currentStdin: string | undefined;\n try {\n displayServer = options.runCommand ? undefined : await ensureDisplayServer();\n const sharedFileInputPath = (configuredTestCases as { shared?: { fileInputPath?: string } }).shared?.fileInputPath;\n for (const testCase of testCases) {\n currentTestCaseId = testCase.id;\n if (sharedFileInputPath) await copyTestCaseFileInput(sharedFileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n const timeLimitSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : (options.runTimeoutSeconds ?? JUDGE_DEFAULT_TIMEOUT_SECONDS);\n\n const runEnv = displayServer ? { ...env, DISPLAY: displayServer.display } : env;\n const stdin = (await options.resolveInput?.({ testCase, cwd: args.cwd, env: runEnv })) ?? testCase.input ?? '';\n currentStdin = stdin;\n const command =\n (await options.command?.({ testCase, cwd: args.cwd, env: runEnv, mainFilePath: resolvedMainFilePath })) ??\n languageDefinition.command(resolvedMainFilePath);\n\n let runResult: GuiCommandRunResult;\n try {\n runResult = options.runCommand\n ? await runCustomRunner(\n () =>\n // Hand custom runners a command that is already sandbox-wrapped, and the matching\n // environment: they spawn it themselves, so an unwrapped command would run the\n // submission as the trusted harness user and defeat the delegation boundary.\n options.runCommand?.({\n testCase,\n command: wrapCommandWithSandboxUser(command),\n stdin,\n cwd: submissionDir,\n env: { ...runEnv, ...getSandboxUserEnvOverrides(runEnv) },\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n }) as Promise<GuiCommandRunResult>\n )\n : await spawnGuiProgram({\n command,\n stdin,\n cwd: args.cwd,\n env: runEnv,\n timeLimitSeconds,\n screenshotWaitSeconds: options.screenshotWaitSeconds ?? SCREENSHOT_WAIT_SECONDS,\n stopDetectionThreshold: options.stopDetectionThreshold ?? STOP_DETECTION_THRESHOLD,\n });\n } catch (error) {\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n return;\n }\n\n const outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n const judgeContext: GuiJudgeContext = {\n timeLimitSeconds,\n problemMarkdownFrontMatter: {\n memoryLimitByte: problemMarkdownFrontMatter.memoryLimitByte,\n requiredOutputFilePaths: problemMarkdownFrontMatter.requiredOutputFilePaths,\n },\n };\n const baseJudgeResult = evaluateGuiRunResult({ runResult, outputFiles, context: judgeContext });\n let judgeResult = baseJudgeResult;\n if (baseJudgeResult.decisionCode === DecisionCode.ACCEPTED) {\n try {\n const extendedJudgeResult = await options.test({\n testCase,\n runResult,\n outputFiles,\n cwd: args.cwd,\n context: judgeContext,\n });\n judgeResult = {\n decisionCode: extendedJudgeResult.decisionCode ?? baseJudgeResult.decisionCode,\n feedbackMarkdown: extendedJudgeResult.feedbackMarkdown,\n stderr: extendedJudgeResult.stderr,\n stdout: extendedJudgeResult.stdout,\n outputFiles: extendedJudgeResult.outputFiles,\n };\n } catch (error) {\n judgeResult = {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: errorToMessage(error),\n };\n }\n }\n\n const decisionCode = judgeResult.decisionCode ?? DecisionCode.ACCEPTED;\n const stdout = judgeResult.stdout ?? runResult.stdout;\n const stderr = judgeResult.stderr ?? runResult.stderr;\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: runResult.status,\n stdin: runResult.stdin || undefined,\n stdout: stdout || undefined,\n stderr: stderr || undefined,\n timeSeconds: runResult.timeSeconds,\n memoryBytes: runResult.memoryBytes,\n feedbackMarkdown: judgeResult.feedbackMarkdown,\n outputFiles: judgeResult.outputFiles ?? (outputFiles.length > 0 ? outputFiles : undefined),\n });\n\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n } catch (error) {\n printTestCaseResult({\n testCaseId: currentTestCaseId,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin: currentStdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n } finally {\n try {\n // Sweep any sandbox processes the run left behind (e.g. a SIGTERM-ignoring forked child) so\n // they cannot race the harness's output reads or survive into the next test case/request.\n if (sandboxUserName) killSandboxUserProcesses();\n } finally {\n // Must run even when the sweep fails closed, or Xvfb keeps holding its display number and\n // later GUI requests exhaust the `:90`–`:99` range.\n await displayServer?.dispose();\n }\n }\n}\n\nasync function resolveMainFilePath(context: {\n cwd: string;\n language?: string | string[];\n configuredMainFilePath?: string;\n allowConfiguredPathFallback?: boolean;\n}): Promise<string | undefined> {\n if (context.configuredMainFilePath) {\n const resolvedPath = path.join(context.cwd, context.configuredMainFilePath);\n if (await pathExists(resolvedPath)) return context.configuredMainFilePath;\n if (!context.allowConfiguredPathFallback) return undefined;\n }\n\n return await findEntryPointFile(context.cwd, context.language);\n}\n\nfunction inferLanguageIdsByPath(filePath: string): string[] | undefined {\n const languageIds = Object.entries(languageIdToDefinition)\n .filter(([, definition]) => definition.fileExtensions.some((ext) => filePath.endsWith(ext)))\n .map(([languageId]) => languageId);\n return languageIds.length > 0 ? languageIds : undefined;\n}\n\nfunction runDefaultPrepare(context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n mainFilePath: string;\n languageDefinition: NonNullable<ReturnType<typeof findLanguageDefinitionByPath>>;\n}): Partial<GuiJudgeCaseResult> | undefined {\n const buildCommand = context.languageDefinition.buildCommand?.(context.mainFilePath);\n if (!buildCommand) return undefined;\n\n const buildResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: context.cwd, encoding: 'utf8', env: context.env },\n BUILD_TIMEOUT_SECONDS\n );\n\n if (buildResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n return {\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n stderr: buildResult.stderr || undefined,\n };\n }\n\n if (buildResult.status !== 0) {\n return {\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: buildResult.stderr || buildResult.stdout || undefined,\n };\n }\n\n return undefined;\n}\n\nfunction evaluateGuiRunResult(context: {\n runResult: GuiCommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: GuiJudgeContext;\n}): Partial<GuiJudgeCaseResult> {\n if (context.runResult.stopReason === 'timeout') {\n return {\n decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n if (context.runResult.status !== 0) {\n return {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: context.runResult.stderr,\n };\n }\n\n if (\n context.runResult.memoryBytes >\n (context.context.problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)\n ) {\n return {\n decisionCode: DecisionCode.MEMORY_LIMIT_EXCEEDED,\n stderr: context.runResult.stderr,\n };\n }\n\n const requiredOutputFilesCount = context.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0;\n if (context.outputFiles.length < requiredOutputFilesCount) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR,\n };\n }\n\n return { decisionCode: DecisionCode.ACCEPTED };\n}\n\nasync function readGuiTestCases<TTestCase extends BaseGuiTestCase>(problemDir: string): Promise<readonly TTestCase[]> {\n // The default reader yields the base shape; a narrower TTestCase must come from `options.readTestCases`.\n return (await readFileTestCases(path.join(problemDir, 'test_cases'))) as unknown as readonly TTestCase[];\n}\n\nasync function spawnGuiProgram(context: {\n command: readonly [string, ...string[]];\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n screenshotWaitSeconds: number;\n stopDetectionThreshold: number;\n}): Promise<GuiCommandRunResult> {\n const wrappedCommand = wrapCommandWithSandboxUser([\n TIMEOUT_COMMAND,\n context.timeLimitSeconds.toFixed(3),\n ...TIME_COMMAND,\n ...context.command,\n ]);\n // A submission can signal its own sandboxed `timeout` and then wedge the event loop (e.g.\n // XGrabServer makes the synchronous `xwininfo`/`maim` block), so a harness-owned watchdog\n // force-kills the sandbox user at the deadline; killing the client also releases its X grab.\n // Armed before the submission starts: a submission that exhausts the PID cgroup would otherwise\n // prevent the watchdog from spawning at all.\n const watchdog = startSandboxTimeoutWatchdog(context.timeLimitSeconds);\n let child: childProcess.ChildProcessWithoutNullStreams;\n try {\n child = childProcess.spawn(wrappedCommand[0], wrappedCommand.slice(1), {\n cwd: context.cwd,\n env: { ...context.env, ...getSandboxUserEnvOverrides(context.env) },\n detached: process.platform !== 'win32',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n watchdog.cancel();\n throw error;\n }\n\n child.stdout.setEncoding('utf8');\n child.stderr.setEncoding('utf8');\n\n let stdout = '';\n let stderr = '';\n let exitCode: number | undefined;\n let spawnError: Error | undefined;\n let stopReason: GuiCommandRunResult['stopReason'] = 'process_exit';\n let submissionStopped = false;\n const screenshotSignaturesHistory: string[][] = [];\n let screenshots: GuiScreenshotFile[] = [];\n const startTimeSeconds = Date.now() / 1000;\n let sampledMemoryBytes = 0;\n child.stdout.on('data', (chunk: string) => {\n stdout += chunk;\n });\n child.stderr.on('data', (chunk: string) => {\n stderr += chunk;\n });\n child.on('error', (error) => {\n spawnError = error;\n exitCode = 1;\n });\n // A child that exits before the input is fully written makes the write fail; without a listener\n // that EPIPE would be an unhandled stream error terminating the harness mid-run.\n child.stdin.on('error', () => {\n // The submission simply stopped reading its input; the exit handling below reports the result.\n });\n child.on('close', (code, signal) => {\n if (code === 124) {\n stopReason = 'timeout';\n exitCode = 0;\n return;\n }\n if (signal) {\n exitCode = 1;\n stderr = stderr\n ? `${stderr}\\nprocess terminated by signal: ${signal}`\n : `process terminated by signal: ${signal}`;\n return;\n }\n exitCode = code ?? 1;\n });\n\n // Everything below must run under the `finally` that decides the watchdog's fate: it is cancelled\n // only once the submission is demonstrably stopped. When a helper throws first — `takeScreenshots`\n // rethrows spawn failures, which a submission can provoke by exhausting PIDs — the watchdog stays\n // armed on purpose, since nothing else would end the submission. The cost is that a detached\n // watchdog can outlive this harness and SIGKILL sandbox processes of a request that starts within\n // its remaining deadline, which is the same trade-off the package-manager runner accepts.\n try {\n if (context.stdin) child.stdin.write(context.stdin);\n child.stdin.end();\n\n while (exitCode === undefined) {\n await wait(context.screenshotWaitSeconds * 1000);\n sampledMemoryBytes = Math.max(sampledMemoryBytes, readProcessGroupMemoryBytes(child.pid));\n const currentScreenshots = takeScreenshots(context.env.DISPLAY);\n screenshots = currentScreenshots.toSorted((a, b) => a.data.length - b.data.length);\n\n if (screenshots.length > 0) {\n const screenshotSignatures = screenshots.map((file) => file.data).toSorted();\n screenshotSignaturesHistory.unshift(screenshotSignatures);\n screenshotSignaturesHistory.length = Math.min(\n screenshotSignaturesHistory.length,\n context.stopDetectionThreshold\n );\n if (\n screenshotSignaturesHistory.length === context.stopDetectionThreshold &&\n screenshotSignaturesHistory.every(\n (files) =>\n files.length === screenshotSignatures.length &&\n files.every((file, index) => file === screenshotSignatures[index])\n )\n ) {\n stopReason = 'stable_screenshot';\n exitCode = 0;\n break;\n }\n }\n\n if (Date.now() / 1000 - startTimeSeconds > context.timeLimitSeconds) {\n stopReason = 'timeout';\n exitCode = 0;\n break;\n }\n }\n\n if (stopReason !== 'process_exit' && child.exitCode === null) {\n child.removeAllListeners('close');\n child.removeAllListeners('error');\n }\n // A submission can kill its own same-UID `timeout` and be stopped by the watchdog instead,\n // which surfaces as a SIGKILL rather than a deadline. Report that as the timeout it is, not as\n // the runtime error the signal would otherwise imply — but only when the child's fate is what\n // decided the verdict. A `stable_screenshot` (or the loop's own `timeout`) was already decided\n // from the screenshots, so the watchdog must not overwrite it.\n const watchdogFired = stopReason === 'process_exit' && watchdog.fired();\n await stopProcess(child);\n submissionStopped = true;\n if (spawnError) throw spawnError;\n const {\n memoryBytes,\n stderr: normalizedStderr,\n timeSeconds,\n } = parseTimedStderr(stderr, startTimeSeconds, sampledMemoryBytes);\n\n return {\n stdin: context.stdin,\n stdout: stdout.trimEnd(),\n stderr: normalizedStderr,\n status: watchdogFired ? 0 : exitCode,\n timeSeconds,\n memoryBytes,\n screenshots,\n stopReason: watchdogFired ? 'timeout' : stopReason,\n };\n } finally {\n // Only once the submission is demonstrably stopped: if termination itself failed (or never ran\n // because a helper threw), the watchdog is the only thing left able to end it, and cancelling\n // would leave it running unchecked.\n if (submissionStopped) watchdog.cancel();\n }\n}\n\nfunction takeScreenshots(display: string | undefined): GuiScreenshotFile[] {\n // These helpers run as the trusted harness user, so they must not be resolved through a\n // submission-influenced `PATH` (see `getTrustedHelperEnv`).\n const env = getTrustedHelperEnv(display ? { DISPLAY: display } : { DISPLAY: process.env.DISPLAY });\n const xwininfo = childProcess.spawnSync('xwininfo', ['-root', '-tree'], { encoding: 'utf8', env });\n if (xwininfo.error) throw xwininfo.error;\n if (xwininfo.status !== 0 || !xwininfo.stdout) return [];\n\n const screenshots: GuiScreenshotFile[] = [];\n for (const windowId of extractTopLevelWindowIds(xwininfo.stdout)) {\n const screenshot = childProcess.spawnSync('maim', ['-i', windowId], { env });\n if (screenshot.error) throw screenshot.error;\n if (screenshot.status !== 0 || screenshot.stdout.length === 0) continue;\n\n const windowNameResult = childProcess.spawnSync('xdotool', ['getwindowname', windowId], { encoding: 'utf8', env });\n if (windowNameResult.error) throw windowNameResult.error;\n const windowName = windowNameResult.stdout.trim().replaceAll(/[\\s/]/g, '_');\n\n screenshots.push({\n path: `${windowName || 'window'}_${windowId}.png`,\n data: screenshot.stdout.toString('base64'),\n encoding: 'base64',\n });\n }\n\n return screenshots;\n}\n\nfunction extractTopLevelWindowIds(stdout: string): string[] {\n const windowIds: string[] = [];\n const lines = stdout.split('\\n');\n for (const line of lines) {\n if (line.includes('Root window id:') || line.includes('Parent window id:') || line.includes('()')) continue;\n\n const match = /^\\s{5}(0x[\\da-f]+) /.exec(line);\n if (!match?.[1]) continue;\n windowIds.push(Number.parseInt(match[1], 16).toString());\n }\n return windowIds;\n}\n\nfunction parseTimedStderr(\n stderr: string,\n startTimeSeconds: number,\n sampledMemoryBytes: number\n): Pick<GuiCommandRunResult, 'stderr' | 'timeSeconds' | 'memoryBytes'> {\n const match = /(?:^|\\n)(\\d+\\.\\d+) (\\d+)\\s*$/.exec(stderr);\n const normalizedStderr = match ? stderr.slice(0, match.index).trimEnd() : stderr.trimEnd();\n const parsedMemoryBytes = Number(match?.[2]) * 1024 || 0;\n return {\n stderr: normalizedStderr,\n timeSeconds: Number(match?.[1]) || Date.now() / 1000 - startTimeSeconds,\n memoryBytes: Math.max(parsedMemoryBytes, sampledMemoryBytes),\n };\n}\n\nasync function ensureDisplayServer(): Promise<{ display: string; dispose: () => Promise<void> }> {\n if (process.platform !== 'linux') {\n throw new Error('GUI screenshot capture is supported only on Linux.');\n }\n\n for (let displayNumber = 90; displayNumber < 100; displayNumber++) {\n const display = `:${displayNumber}`;\n let spawnError: Error | undefined;\n const xvfb = childProcess.spawn('Xvfb', [display, '-screen', '0', '1280x1024x24', '-ac'], {\n stdio: 'ignore',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n xvfb.on('error', (error) => {\n spawnError = error;\n });\n\n await wait(XVFB_STARTUP_WAIT_SECONDS * 1000);\n if (spawnError) throw spawnError;\n if (xvfb.exitCode !== null) continue;\n\n return {\n display,\n dispose: async () => {\n if (!xvfb.killed) {\n xvfb.kill('SIGTERM');\n await wait(XVFB_SHUTDOWN_WAIT_SECONDS * 1000);\n if (xvfb.exitCode === null) xvfb.kill('SIGKILL');\n }\n },\n };\n }\n\n throw new Error('Xvfb could not be started.');\n}\n\nasync function stopProcess(child: childProcess.ChildProcess): Promise<void> {\n if (!child.pid) return;\n // The current user cannot signal the root-owned sudo wrapper nor the sandbox user's processes, so\n // go through sudo. SIGKILL is unconditional after the grace period: `child.exitCode` reflects only\n // the sudo wrapper, so a daemonized child that ignored SIGTERM would survive if we gated on it.\n if (sandboxUserName) {\n killSandboxUserProcesses(['TERM']);\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n killSandboxUserProcesses(['KILL']);\n return;\n }\n killProcessGroup(child.pid, 'SIGTERM');\n await wait(PROCESS_SHUTDOWN_WAIT_SECONDS * 1000);\n if (child.exitCode === null) killProcessGroup(child.pid, 'SIGKILL');\n}\n\nfunction readProcessGroupMemoryBytes(processGroupId: number | undefined): number {\n if (!processGroupId || process.platform !== 'linux') return 0;\n\n // The delegation contract requires sudoers `!use_pty`, so sudo execs the command in place and it\n // stays in the detached child's process group; `--pgroup` therefore still captures it there.\n const result = childProcess.spawnSync('ps', ['-o', 'rss=', '--no-headers', '--pgroup', String(processGroupId)], {\n encoding: 'utf8',\n // Runs as the trusted harness user, so pin the lookup to system paths.\n env: getTrustedHelperEnv(),\n });\n if (result.error || result.status !== 0 || !result.stdout) return 0;\n\n return result.stdout\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter((value) => Number.isFinite(value) && value > 0)\n .reduce((sum, value) => sum + value * 1024, 0);\n}\n\nfunction killProcessGroup(processGroupId: number, signal: NodeJS.Signals): void {\n try {\n if (process.platform !== 'win32') {\n process.kill(-processGroupId, signal);\n return;\n }\n } catch {\n // The process group may already be gone. Fall back to the direct PID below.\n }\n\n try {\n process.kill(processGroupId, signal);\n } catch {\n // The direct child may also already be gone.\n }\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction errorToMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/parseArgs.cjs"),n=require("../helpers/printTestCaseResult.cjs"),r=require("../types/decisionCode.cjs"),i=require("../helpers/readTestCases.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("zod"),s=require("node:fs");s=e.__toESM(s,1);let c=require("@ai-sdk/amazon-bedrock"),l=require("@ai-sdk/google"),u=require("@ai-sdk/openai"),d=require("@ai-sdk/xai"),f=require("ai");const p={bedrock:c.bedrock,google:l.google,openai:u.openai,xai:d.xai},m=o.z.object({model:o.z.string().min(1)});async function h(e,o){let c=t.parseArgs(process.argv);if(!c.cwd)throw Error(`cwd argument required`);let l=m.parse(c.params),u=await i.readTestCases(a.default.join(e,`test_cases`)),d=await s.default.promises.readFile(a.default.join(c.cwd,`prompt.txt`),`utf8`);for(let e of u){let t=Date.now();try{let{text:i}=await(0,f.generateText)({model:g(l.model),prompt:o.buildPrompt?.({prompt:d,testCase:e})??d.replaceAll(`{input}`,e.input??``)}),a=Date.now(),s={testCaseId:e.id,decisionCode:r.DecisionCode.ACCEPTED,stdin:e.input,stdout:i,timeSeconds:(a-t)/1e3,...await o.test({testCase:e,result:{output:i}})};if(n.printTestCaseResult(s),s.decisionCode!==r.DecisionCode.ACCEPTED)break}catch(i){let a=Date.now();n.printTestCaseResult({testCaseId:e.id,decisionCode:r.DecisionCode.RUNTIME_ERROR,stdin:e.input,stderr:i instanceof Error?i.message:String(i),timeSeconds:(a-t)/1e3});break}}}function g(e){let[t,n]=e.split(`/`);if(!t||!n)throw Error(`bad model: ${e}`);let r=p[t]?.(n);if(!r)throw Error(`model not found: ${e}`);return r}exports.llmJudgePreset=h;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/printTestCaseResult.cjs"),n=require("../helpers/parseArgs.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../types/decisionCode.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);let s=require("zod"),c=require("@ai-sdk/amazon-bedrock"),l=require("@ai-sdk/google"),u=require("@ai-sdk/openai"),d=require("@ai-sdk/xai"),f=require("ai");const p={bedrock:c.bedrock,google:l.google,openai:u.openai,xai:d.xai},m=s.z.object({model:s.z.string().min(1)});async function h(e,s){let c=n.parseArgs(process.argv);if(!c.cwd)throw Error(`cwd argument required`);let l=m.parse(c.params),u=await r.readTestCases(o.default.join(e,`test_cases`)),d=await a.default.promises.readFile(o.default.join(c.cwd,`prompt.txt`),`utf8`);for(let e of u){let n=Date.now();try{let{text:r}=await(0,f.generateText)({model:g(l.model),prompt:s.buildPrompt?.({prompt:d,testCase:e})??d.replaceAll(`{input}`,e.input??``)}),a=Date.now(),o={testCaseId:e.id,decisionCode:i.DecisionCode.ACCEPTED,stdin:e.input,stdout:r,timeSeconds:(a-n)/1e3,...await s.test({testCase:e,result:{output:r}})};if(t.printTestCaseResult(o),o.decisionCode!==i.DecisionCode.ACCEPTED)break}catch(r){let a=Date.now();t.printTestCaseResult({testCaseId:e.id,decisionCode:i.DecisionCode.RUNTIME_ERROR,stdin:e.input,stderr:r instanceof Error?r.message:String(r),timeSeconds:(a-n)/1e3});break}}}function g(e){let[t,n]=e.split(`/`);if(!t||!n)throw Error(`bad model: ${e}`);let r=p[t]?.(n);if(!r)throw Error(`model not found: ${e}`);return r}exports.llmJudgePreset=h;
2
2
  //# sourceMappingURL=llm.cjs.map