@exercode/problem-utils 2.2.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/checkAllProblems.cjs +1 -1
- package/dist/cli/checkAllProblems.cjs.map +1 -1
- package/dist/cli/checkAllProblems.js +1 -1
- package/dist/cli/checkAllProblems.js.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.cjs +1 -1
- package/dist/helpers/checkProblemDirIsolation.cjs.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.d.ts +9 -16
- package/dist/helpers/checkProblemDirIsolation.js +1 -1
- package/dist/helpers/checkProblemDirIsolation.js.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.cjs +7 -8
- package/dist/helpers/judgeByStaticAnalysis.cjs.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.js +7 -8
- package/dist/helpers/judgeByStaticAnalysis.js.map +1 -1
- package/dist/helpers/runHarnessProcess.cjs +2 -0
- package/dist/helpers/runHarnessProcess.cjs.map +1 -0
- package/dist/helpers/runHarnessProcess.d.ts +23 -0
- package/dist/helpers/runHarnessProcess.js +2 -0
- package/dist/helpers/runHarnessProcess.js.map +1 -0
- package/dist/helpers/runIsolationCheckInDebugMode.cjs +2 -0
- package/dist/helpers/runIsolationCheckInDebugMode.cjs.map +1 -0
- package/dist/helpers/runIsolationCheckInDebugMode.d.ts +7 -0
- package/dist/helpers/runIsolationCheckInDebugMode.js +2 -0
- package/dist/helpers/runIsolationCheckInDebugMode.js.map +1 -0
- package/dist/helpers/temporaryProblemDirCopy.cjs +2 -0
- package/dist/helpers/temporaryProblemDirCopy.cjs.map +1 -0
- package/dist/helpers/temporaryProblemDirCopy.d.ts +15 -0
- package/dist/helpers/temporaryProblemDirCopy.js +2 -0
- package/dist/helpers/temporaryProblemDirCopy.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/presets/command.cjs +1 -1
- package/dist/presets/command.cjs.map +1 -1
- package/dist/presets/command.js +1 -1
- package/dist/presets/command.js.map +1 -1
- package/dist/presets/evaluation.cjs +4 -4
- package/dist/presets/evaluation.cjs.map +1 -1
- package/dist/presets/evaluation.js +4 -4
- package/dist/presets/evaluation.js.map +1 -1
- package/dist/types/problem.cjs +1 -1
- package/dist/types/problem.cjs.map +1 -1
- package/dist/types/problem.d.ts +26 -3
- package/dist/types/problem.js +1 -1
- package/dist/types/problem.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import{removeCommentsInSourceCode as e}from"./removeCommentsInSourceCode.js";import{DecisionCode as t}from"../types/decisionCode.js";import{
|
|
1
|
+
import{removeCommentsInSourceCode as e}from"./removeCommentsInSourceCode.js";import{DecisionCode as t}from"../types/decisionCode.js";import{normalizeCodeRule as n}from"../types/problem.js";import{findLanguageDefinitionByPath as r}from"./findLanguageDefinitionByPath.js";import i from"node:path";import a from"node:fs";async function o(o,c){let l=new Set,u=[];for(let t of await a.promises.readdir(o,{withFileTypes:!0,recursive:!0})){if(!t.isFile())continue;let n=i.relative(o,i.join(t.parentPath,t.name));l.add(n);let s=await a.promises.readFile(i.join(t.parentPath,t.name),`utf8`);if(s.includes(`�`))continue;let c=r(t.name);c&&u.push({path:n,data:c.grammer?e(c.grammer,s):s})}if(c.requiredSubmissionFilePaths){let e=c.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:t.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
|
|
2
2
|
次のファイルを追加してから再度提出してください。
|
|
3
3
|
|
|
4
4
|
${e.map(e=>`- \`${e}\``).join(`
|
|
5
5
|
`)}
|
|
6
|
-
`}}let
|
|
6
|
+
`}}let d=[];for(let e of c.forbiddenRegExpsInCode??[]){let{pattern:t,message:r}=n(e),i=new RegExp(t,`g`),a=u.flatMap(e=>[...e.data.matchAll(i)].map(t=>({path:e.path,match:t[0]})));a.length>0&&d.push({label:r??`禁止パターン \`${i.toString()}\``,matches:a})}for(let e of c.forbiddenTextsInCode??[]){let{pattern:t,message:r}=n(e),i=u.filter(e=>e.data.includes(t)).map(e=>({path:e.path,match:t}));i.length>0&&d.push({label:r??`禁止文字列 \`${t}\``,matches:i})}if(d.length>0)return{decisionCode:t.FORBIDDEN_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に禁止された文字列が含まれています。
|
|
7
7
|
ソースコードを修正してから再度提出してください。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
| -------- | ------------ | ------ |
|
|
11
|
-
${l.map(e=>`| \`${e.path}\` | \`${e.pattern}\` | \`${e.match}\` |`).join(`
|
|
9
|
+
${d.map(s).join(`
|
|
12
10
|
`)}
|
|
13
|
-
`};let
|
|
11
|
+
`};let f=[];for(let e of c.requiredRegExpsInCode??[]){let{pattern:t,message:r}=n(e),i=new RegExp(t);u.some(e=>i.test(e.data))||f.push(r??`\`${i.toString()}\``)}if(f.length>0)return{decisionCode:t.REQUIRED_PATTERNS_IN_CODE_ERROR,feedbackMarkdown:`ソースコード中に必要な文字列が含まれていません。
|
|
14
12
|
ソースコードを修正してから再度提出してください。
|
|
15
13
|
|
|
16
|
-
${
|
|
14
|
+
${f.map(e=>`- ${e}`).join(`
|
|
17
15
|
`)}
|
|
18
|
-
`}}
|
|
16
|
+
`}}function s(e){let t=[...new Set(e.matches.map(({path:e,match:t})=>` - \`${e}\`: \`${t}\``))];return`- ${e.label}\n${t.join(`
|
|
17
|
+
`)}`}export{o as judgeByStaticAnalysis};
|
|
19
18
|
//# sourceMappingURL=judgeByStaticAnalysis.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"judgeByStaticAnalysis.js","names":[],"sources":["../../src/helpers/judgeByStaticAnalysis.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DecisionCode } from '../types/decisionCode.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nimport { findLanguageDefinitionByPath } from './findLanguageDefinitionByPath.js';\nimport { removeCommentsInSourceCode } from './removeCommentsInSourceCode.js';\n\nexport async function judgeByStaticAnalysis(\n cwd: string,\n problemMarkdownFrontMatterLike: Pick<\n ProblemMarkdownFrontMatter,\n 'forbiddenRegExpsInCode' | 'forbiddenTextsInCode' | 'requiredRegExpsInCode' | 'requiredSubmissionFilePaths'\n >\n): Promise<Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const filePathSet = new Set<string>();\n const sourceCodeWithoutCommentFiles: { path: string; data: string }[] = [];\n\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true, recursive: true })) {\n if (!dirent.isFile()) continue;\n\n const relativePath = path.relative(cwd, path.join(dirent.parentPath, dirent.name));\n filePathSet.add(relativePath);\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n const isBinary = text.includes('\\uFFFD');\n if (isBinary) continue;\n\n const languageDefinition = findLanguageDefinitionByPath(dirent.name);\n if (!languageDefinition) continue;\n\n sourceCodeWithoutCommentFiles.push({\n path:
|
|
1
|
+
{"version":3,"file":"judgeByStaticAnalysis.js","names":[],"sources":["../../src/helpers/judgeByStaticAnalysis.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DecisionCode } from '../types/decisionCode.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport { normalizeCodeRule } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nimport { findLanguageDefinitionByPath } from './findLanguageDefinitionByPath.js';\nimport { removeCommentsInSourceCode } from './removeCommentsInSourceCode.js';\n\ninterface ForbiddenRuleViolation {\n /** Learner-facing description of the rule; the raw pattern when the problem declares no message. */\n label: string;\n matches: { path: string; match: string }[];\n}\n\nexport async function judgeByStaticAnalysis(\n cwd: string,\n problemMarkdownFrontMatterLike: Pick<\n ProblemMarkdownFrontMatter,\n 'forbiddenRegExpsInCode' | 'forbiddenTextsInCode' | 'requiredRegExpsInCode' | 'requiredSubmissionFilePaths'\n >\n): Promise<Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const filePathSet = new Set<string>();\n const sourceCodeWithoutCommentFiles: { path: string; data: string }[] = [];\n\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true, recursive: true })) {\n if (!dirent.isFile()) continue;\n\n const relativePath = path.relative(cwd, path.join(dirent.parentPath, dirent.name));\n filePathSet.add(relativePath);\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n const isBinary = text.includes('\\uFFFD');\n if (isBinary) continue;\n\n const languageDefinition = findLanguageDefinitionByPath(dirent.name);\n if (!languageDefinition) continue;\n\n sourceCodeWithoutCommentFiles.push({\n path: relativePath,\n data: languageDefinition.grammer ? removeCommentsInSourceCode(languageDefinition.grammer, text) : text,\n });\n }\n\n if (problemMarkdownFrontMatterLike.requiredSubmissionFilePaths) {\n const missingFilePaths = problemMarkdownFrontMatterLike.requiredSubmissionFilePaths\n .filter((p) => !filePathSet.has(p))\n .toSorted();\n\n if (missingFilePaths.length > 0) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n feedbackMarkdown: `ファイルが不足しています。\n次のファイルを追加してから再度提出してください。\n\n${missingFilePaths.map((p) => `- \\`${p}\\``).join('\\n')}\n`,\n };\n }\n }\n\n const violations: ForbiddenRuleViolation[] = [];\n\n for (const rule of problemMarkdownFrontMatterLike.forbiddenRegExpsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const re = new RegExp(pattern, 'g');\n const matches = sourceCodeWithoutCommentFiles.flatMap((file) =>\n [...file.data.matchAll(re)].map((match) => ({ path: file.path, match: match[0] }))\n );\n if (matches.length > 0) violations.push({ label: message ?? `禁止パターン \\`${re.toString()}\\``, matches });\n }\n for (const rule of problemMarkdownFrontMatterLike.forbiddenTextsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const matches = sourceCodeWithoutCommentFiles\n .filter((file) => file.data.includes(pattern))\n .map((file) => ({ path: file.path, match: pattern }));\n if (matches.length > 0) violations.push({ label: message ?? `禁止文字列 \\`${pattern}\\``, matches });\n }\n\n if (violations.length > 0) {\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown: `ソースコード中に禁止された文字列が含まれています。\nソースコードを修正してから再度提出してください。\n\n${violations.map(formatForbiddenRuleViolation).join('\\n')}\n`,\n };\n }\n\n const missingRequiredLabels: string[] = [];\n\n for (const rule of problemMarkdownFrontMatterLike.requiredRegExpsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const re = new RegExp(pattern);\n const isFound = sourceCodeWithoutCommentFiles.some((f) => re.test(f.data));\n if (!isFound) missingRequiredLabels.push(message ?? `\\`${re.toString()}\\``);\n }\n\n if (missingRequiredLabels.length > 0) {\n return {\n decisionCode: DecisionCode.REQUIRED_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown: `ソースコード中に必要な文字列が含まれていません。\nソースコードを修正してから再度提出してください。\n\n${missingRequiredLabels.map((label) => `- ${label}`).join('\\n')}\n`,\n };\n }\n\n return;\n}\n\nfunction formatForbiddenRuleViolation(violation: ForbiddenRuleViolation): string {\n const locations = [...new Set(violation.matches.map(({ path, match }) => ` - \\`${path}\\`: \\`${match}\\``))];\n return `- ${violation.label}\\n${locations.join('\\n')}`;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./temporaryProblemDirCopy.cjs");let n=require("node:child_process");n=e.__toESM(n,1);const r=new Set;let i=!1;function a(e,t){return o(),new Promise(i=>{let a=n.default.spawn(process.execPath,e,{cwd:t.cwd,detached:process.platform!==`win32`,env:t.env,stdio:[`ignore`,`pipe`,`pipe`]}),o=a.pid===void 0?void 0:{pid:a.pid,tempRoot:t.tempRoot};o&&r.add(o);let c=[],l=[],u=0,d,f=!1,p=!1,m=e=>{if(d!==void 0)return!1;d=e,a.stdout?.destroy(),a.stderr?.destroy();try{a.pid===void 0?a.kill(`SIGKILL`):s(a.pid)}catch{a.kill(`SIGKILL`)}return!0},h=setTimeout(()=>{f=m(`timed out after ${t.timeoutMs/1e3} seconds`)},t.timeoutMs),g=(e,n)=>{if(u+=e.byteLength,u>t.maxOutputBytes){m(`the harness printed more than ${t.maxOutputBytes/1024/1024} MB of output`);return}n.push(e)};a.stdout?.on(`data`,e=>g(e,c)),a.stderr?.on(`data`,e=>g(e,l));let _=(e,t,n)=>{p||(p=!0,clearTimeout(h),o&&r.delete(o),i({stdout:Buffer.concat(c).toString(`utf8`),stderr:Buffer.concat(l).toString(`utf8`),exitCode:e,signal:t,failureReason:d??(n?`failed to run the harness: ${n.message}`:void 0),timedOut:f}))};a.on(`error`,e=>_(void 0,void 0,e)),a.on(`close`,(e,t)=>_(e??void 0,t??void 0))})}function o(){if(!i){i=!0;for(let e of[`SIGINT`,`SIGTERM`])process.once(e,()=>{for(let e of r){try{s(e.pid)}catch{}e.tempRoot&&t.forciblyRemoveDirectorySync(e.tempRoot)}process.kill(process.pid,e)})}}function s(e){process.platform===`win32`?n.default.spawnSync(`taskkill`,[`/PID`,String(e),`/T`,`/F`],{stdio:`ignore`}):process.kill(-e,`SIGKILL`)}exports.runHarnessProcess=a;
|
|
2
|
+
//# sourceMappingURL=runHarnessProcess.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runHarnessProcess.cjs","names":["child_process"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded so grandchild submission processes cannot outlive the run.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n env: options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"kKAiCA,MAAM,EAAkB,IAAI,IAC5B,IAAI,EAA0B,GAM9B,SAAgB,EACd,EACA,EAC+B,CAE/B,OADA,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAQA,EAAAA,QAAc,MAAM,QAAQ,SAAU,EAAa,CAC/D,IAAK,EAAQ,IACb,SAAU,QAAQ,WAAa,QAC/B,IAAK,EAAQ,IACb,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACK,EACJ,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAK,EAAM,IAAK,SAAU,EAAQ,QAAS,EACjF,GAAS,EAAgB,IAAI,CAAO,EACxC,IAAM,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAC5B,EAAmB,EACnB,EACA,EAAW,GACX,EAAU,GAGR,EAAoB,GAA4B,CACpD,GAAI,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgB,EAGhB,EAAM,QAAQ,QAAQ,EACtB,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACE,EAAM,MAAQ,IAAA,GAChB,EAAM,KAAK,SAAS,EAEpB,EAAgB,EAAM,GAAG,CAE7B,MAAQ,CACN,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACM,EAAY,eAAiB,CACjC,EAAW,EAAiB,mBAAmB,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAG,EAAQ,SAAS,EAEd,GAAgB,EAAe,IAA2B,CAE9D,GADA,GAAoB,EAAM,WACtB,EAAmB,EAAQ,eAAgB,CAC7C,EAAiB,iCAAiC,EAAQ,eAAiB,KAAO,KAAK,cAAc,EACrG,MACF,CACA,EAAO,KAAK,CAAK,CACnB,EACA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAE7E,IAAM,GAAU,EAA8B,EAAoC,IAA6B,CACzG,IACJ,EAAU,GACV,aAAa,CAAS,EAClB,GAAS,EAAgB,OAAO,CAAO,EAC3C,EAAQ,CACN,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAe,IAAkB,EAAa,8BAA8B,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACA,EAAM,GAAG,QAAU,GAAU,EAAO,IAAA,GAAW,IAAA,GAAW,CAAK,CAAC,EAChE,EAAM,GAAG,SAAU,EAAU,IAAW,EAAO,GAAY,IAAA,GAAW,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAAS,GAA8B,CACjC,MACJ,GAA0B,GAC1B,IAAK,IAAM,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAK,MAAc,CACzB,IAAK,IAAM,KAAW,EAAiB,CACrC,GAAI,CACF,EAAgB,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEI,EAAQ,UAAU,EAAA,4BAA4B,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAK,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAAS,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvB,EAAA,QAAc,UAAU,WAAY,CAAC,OAAQ,OAAO,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAAC,EAAK,SAAS,CAEhC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface HarnessProcessOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
env: NodeJS.ProcessEnv;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
maxOutputBytes: number;
|
|
6
|
+
/** A temporary copy to remove when the check itself is interrupted by a signal. */
|
|
7
|
+
tempRoot?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface HarnessProcessResult {
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
exitCode: number | undefined;
|
|
13
|
+
/** The signal that terminated the harness when it did not exit on its own. */
|
|
14
|
+
signal: NodeJS.Signals | undefined;
|
|
15
|
+
/** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */
|
|
16
|
+
failureReason: string | undefined;
|
|
17
|
+
timedOut: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Run a judge harness with `process.execPath` in its own process group, killing the whole group on
|
|
21
|
+
* timeout or when the output cap is exceeded so grandchild submission processes cannot outlive the run.
|
|
22
|
+
*/
|
|
23
|
+
export declare function runHarnessProcess(commandArgs: readonly string[], options: HarnessProcessOptions): Promise<HarnessProcessResult>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{forciblyRemoveDirectorySync as e}from"./temporaryProblemDirCopy.js";import t from"node:child_process";const n=new Set;let r=!1;function i(e,r){return a(),new Promise(i=>{let a=t.spawn(process.execPath,e,{cwd:r.cwd,detached:process.platform!==`win32`,env:r.env,stdio:[`ignore`,`pipe`,`pipe`]}),s=a.pid===void 0?void 0:{pid:a.pid,tempRoot:r.tempRoot};s&&n.add(s);let c=[],l=[],u=0,d,f=!1,p=!1,m=e=>{if(d!==void 0)return!1;d=e,a.stdout?.destroy(),a.stderr?.destroy();try{a.pid===void 0?a.kill(`SIGKILL`):o(a.pid)}catch{a.kill(`SIGKILL`)}return!0},h=setTimeout(()=>{f=m(`timed out after ${r.timeoutMs/1e3} seconds`)},r.timeoutMs),g=(e,t)=>{if(u+=e.byteLength,u>r.maxOutputBytes){m(`the harness printed more than ${r.maxOutputBytes/1024/1024} MB of output`);return}t.push(e)};a.stdout?.on(`data`,e=>g(e,c)),a.stderr?.on(`data`,e=>g(e,l));let _=(e,t,r)=>{p||(p=!0,clearTimeout(h),s&&n.delete(s),i({stdout:Buffer.concat(c).toString(`utf8`),stderr:Buffer.concat(l).toString(`utf8`),exitCode:e,signal:t,failureReason:d??(r?`failed to run the harness: ${r.message}`:void 0),timedOut:f}))};a.on(`error`,e=>_(void 0,void 0,e)),a.on(`close`,(e,t)=>_(e??void 0,t??void 0))})}function a(){if(!r){r=!0;for(let t of[`SIGINT`,`SIGTERM`])process.once(t,()=>{for(let t of n){try{o(t.pid)}catch{}t.tempRoot&&e(t.tempRoot)}process.kill(process.pid,t)})}}function o(e){process.platform===`win32`?t.spawnSync(`taskkill`,[`/PID`,String(e),`/T`,`/F`],{stdio:`ignore`}):process.kill(-e,`SIGKILL`)}export{i as runHarnessProcess};
|
|
2
|
+
//# sourceMappingURL=runHarnessProcess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runHarnessProcess.js","names":[],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded so grandchild submission processes cannot outlive the run.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n env: options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"6GAiCA,MAAM,EAAkB,IAAI,IAC5B,IAAI,EAA0B,GAM9B,SAAgB,EACd,EACA,EAC+B,CAE/B,OADA,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAQ,EAAc,MAAM,QAAQ,SAAU,EAAa,CAC/D,IAAK,EAAQ,IACb,SAAU,QAAQ,WAAa,QAC/B,IAAK,EAAQ,IACb,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACK,EACJ,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAK,EAAM,IAAK,SAAU,EAAQ,QAAS,EACjF,GAAS,EAAgB,IAAI,CAAO,EACxC,IAAM,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAC5B,EAAmB,EACnB,EACA,EAAW,GACX,EAAU,GAGR,EAAoB,GAA4B,CACpD,GAAI,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgB,EAGhB,EAAM,QAAQ,QAAQ,EACtB,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACE,EAAM,MAAQ,IAAA,GAChB,EAAM,KAAK,SAAS,EAEpB,EAAgB,EAAM,GAAG,CAE7B,MAAQ,CACN,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACM,EAAY,eAAiB,CACjC,EAAW,EAAiB,mBAAmB,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAG,EAAQ,SAAS,EAEd,GAAgB,EAAe,IAA2B,CAE9D,GADA,GAAoB,EAAM,WACtB,EAAmB,EAAQ,eAAgB,CAC7C,EAAiB,iCAAiC,EAAQ,eAAiB,KAAO,KAAK,cAAc,EACrG,MACF,CACA,EAAO,KAAK,CAAK,CACnB,EACA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAE7E,IAAM,GAAU,EAA8B,EAAoC,IAA6B,CACzG,IACJ,EAAU,GACV,aAAa,CAAS,EAClB,GAAS,EAAgB,OAAO,CAAO,EAC3C,EAAQ,CACN,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAe,IAAkB,EAAa,8BAA8B,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACA,EAAM,GAAG,QAAU,GAAU,EAAO,IAAA,GAAW,IAAA,GAAW,CAAK,CAAC,EAChE,EAAM,GAAG,SAAU,EAAU,IAAW,EAAO,GAAY,IAAA,GAAW,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAAS,GAA8B,CACjC,MACJ,GAA0B,GAC1B,IAAK,IAAM,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAK,MAAc,CACzB,IAAK,IAAM,KAAW,EAAiB,CACrC,GAAI,CACF,EAAgB,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEI,EAAQ,UAAU,EAA4B,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAK,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAAS,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvB,EAAc,UAAU,WAAY,CAAC,OAAQ,OAAO,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAAC,EAAK,SAAS,CAEhC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("./printDebugBanner.cjs"),t=require("./checkProblemDirIsolation.cjs");async function n(n,r,i,a={}){let o=r.find(e=>e.expectedResult===`accepted`);return o?(await t.checkProblemDirIsolation(n,o,i,a)).passed:(e.printDebugBanner([`[DEBUG MODE] isolated problem directory check skipped`,``,`No accepted model answer is available for checking that the copied judge still accepts a valid submission.`]),!0)}exports.passesIsolationCheckInDebugMode=n;
|
|
2
|
+
//# sourceMappingURL=runIsolationCheckInDebugMode.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runIsolationCheckInDebugMode.cjs","names":["checkProblemDirIsolation"],"sources":["../../src/helpers/runIsolationCheckInDebugMode.ts"],"sourcesContent":["import { checkProblemDirIsolation, type ProblemDirIsolationCheckOptions } from './checkProblemDirIsolation.js';\nimport { printDebugBanner } from './printDebugBanner.js';\nimport type { ResolvedCwd } from './resolveCwds.js';\n\n/**\n * Run the isolated problem directory check against the first accepted model answer in debug mode.\n * Returns false when the check fails, so the caller can stop before judging anything else.\n */\nexport async function passesIsolationCheckInDebugMode(\n problemDir: string,\n cwds: readonly ResolvedCwd[],\n params: unknown,\n options: ProblemDirIsolationCheckOptions = {}\n): Promise<boolean> {\n const acceptedCwd = cwds.find((cwd) => cwd.expectedResult === 'accepted');\n if (!acceptedCwd) {\n printDebugBanner([\n '[DEBUG MODE] isolated problem directory check skipped',\n '',\n 'No accepted model answer is available for checking that the copied judge still accepts a valid submission.',\n ]);\n return true;\n }\n const result = await checkProblemDirIsolation(problemDir, acceptedCwd, params, options);\n return result.passed;\n}\n"],"mappings":"mGAQA,eAAsB,EACpB,EACA,EACA,EACA,EAA2C,CAAC,EAC1B,CAClB,IAAM,EAAc,EAAK,KAAM,GAAQ,EAAI,iBAAmB,UAAU,EAUxE,OATK,GASE,MADcA,EAAAA,yBAAyB,EAAY,EAAa,EAAQ,CAAO,EAAA,CACxE,QARZ,EAAA,iBAAiB,CACf,wDACA,GACA,4GACF,CAAC,EACM,GAIX"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ProblemDirIsolationCheckOptions } from './checkProblemDirIsolation.js';
|
|
2
|
+
import type { ResolvedCwd } from './resolveCwds.js';
|
|
3
|
+
/**
|
|
4
|
+
* Run the isolated problem directory check against the first accepted model answer in debug mode.
|
|
5
|
+
* Returns false when the check fails, so the caller can stop before judging anything else.
|
|
6
|
+
*/
|
|
7
|
+
export declare function passesIsolationCheckInDebugMode(problemDir: string, cwds: readonly ResolvedCwd[], params: unknown, options?: ProblemDirIsolationCheckOptions): Promise<boolean>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{printDebugBanner as e}from"./printDebugBanner.js";import{checkProblemDirIsolation as t}from"./checkProblemDirIsolation.js";async function n(n,r,i,a={}){let o=r.find(e=>e.expectedResult===`accepted`);return o?(await t(n,o,i,a)).passed:(e([`[DEBUG MODE] isolated problem directory check skipped`,``,`No accepted model answer is available for checking that the copied judge still accepts a valid submission.`]),!0)}export{n as passesIsolationCheckInDebugMode};
|
|
2
|
+
//# sourceMappingURL=runIsolationCheckInDebugMode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runIsolationCheckInDebugMode.js","names":[],"sources":["../../src/helpers/runIsolationCheckInDebugMode.ts"],"sourcesContent":["import { checkProblemDirIsolation, type ProblemDirIsolationCheckOptions } from './checkProblemDirIsolation.js';\nimport { printDebugBanner } from './printDebugBanner.js';\nimport type { ResolvedCwd } from './resolveCwds.js';\n\n/**\n * Run the isolated problem directory check against the first accepted model answer in debug mode.\n * Returns false when the check fails, so the caller can stop before judging anything else.\n */\nexport async function passesIsolationCheckInDebugMode(\n problemDir: string,\n cwds: readonly ResolvedCwd[],\n params: unknown,\n options: ProblemDirIsolationCheckOptions = {}\n): Promise<boolean> {\n const acceptedCwd = cwds.find((cwd) => cwd.expectedResult === 'accepted');\n if (!acceptedCwd) {\n printDebugBanner([\n '[DEBUG MODE] isolated problem directory check skipped',\n '',\n 'No accepted model answer is available for checking that the copied judge still accepts a valid submission.',\n ]);\n return true;\n }\n const result = await checkProblemDirIsolation(problemDir, acceptedCwd, params, options);\n return result.passed;\n}\n"],"mappings":"kIAQA,eAAsB,EACpB,EACA,EACA,EACA,EAA2C,CAAC,EAC1B,CAClB,IAAM,EAAc,EAAK,KAAM,GAAQ,EAAI,iBAAmB,UAAU,EAUxE,OATK,GASE,MADc,EAAyB,EAAY,EAAa,EAAQ,CAAO,EAAA,CACxE,QARZ,EAAiB,CACf,wDACA,GACA,4GACF,CAAC,EACM,GAIX"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:child_process");n=e.__toESM(n,1);let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);async function a(e){let n=await r.default.promises.mkdtemp(t.default.join(i.default.tmpdir(),`problem-utils-isolation_`));try{let i=t.default.resolve(e),a=t.default.join(n,f(i));return await r.default.promises.mkdir(t.default.dirname(a),{recursive:!0}),await r.default.promises.cp(i,a,{recursive:!0,filter:l,verbatimSymlinks:!0}),await d(n,i),{tempRoot:n,copiedProblemDir:a}}catch(e){throw await o(n),e}}async function o(e){try{return await r.default.promises.rm(e,{recursive:!0,force:!0}),!0}catch{c(e);try{return await r.default.promises.rm(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function s(e){try{return r.default.rmSync(e,{recursive:!0,force:!0}),!0}catch{c(e);try{return r.default.rmSync(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function c(e){process.platform!==`win32`&&n.default.spawnSync(`chmod`,[`-R`,`u+rwX`,e])}async function l(e){if(!u(e))return!1;if((await r.default.promises.lstat(e)).isSymbolicLink()&&t.default.isAbsolute(await r.default.promises.readlink(e)))throw Error(`${e} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);return!0}function u(e){let n=t.default.basename(e);return n!==`node_modules`&&n!==`.git`}async function d(e,n){let i=t.default.resolve(n);for(;;){let n=t.default.join(i,`node_modules`);if(r.default.existsSync(n)){let a=t.default.join(e,f(i),`node_modules`);try{await r.default.promises.symlink(n,a,process.platform===`win32`?`junction`:`dir`)}catch{}}let a=t.default.dirname(i);if(a===i)break;i=a}}function f(e){let{root:n}=t.default.parse(e);return t.default.relative(n,e)}exports.copyProblemDirToTemporaryRoot=a,exports.forciblyRemoveDirectory=o,exports.forciblyRemoveDirectorySync=s;
|
|
2
|
+
//# sourceMappingURL=temporaryProblemDirCopy.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporaryProblemDirCopy.cjs","names":["fs","path","os"],"sources":["../../src/helpers/temporaryProblemDirCopy.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/**\n * Copy a problem directory into a temporary root, symlinking every ancestor `node_modules` so the\n * copied harness still resolves its imports. Callers must remove the returned `tempRoot`.\n */\nexport async function copyProblemDirToTemporaryRoot(\n problemDir: string\n): Promise<{ tempRoot: string; copiedProblemDir: string }> {\n const tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'problem-utils-isolation_'));\n try {\n const absoluteProblemDir = path.resolve(problemDir);\n const copiedProblemDir = path.join(tempRoot, toTempRelativePath(absoluteProblemDir));\n await fs.promises.mkdir(path.dirname(copiedProblemDir), { recursive: true });\n await fs.promises.cp(absoluteProblemDir, copiedProblemDir, {\n recursive: true,\n filter: rejectAbsoluteSymlinks,\n // Keep relative symlinks relative: the default rewrites them to absolute paths into the\n // source tree, so judging the copy could write through them into the checked repository.\n verbatimSymlinks: true,\n });\n await symlinkAllAncestorNodeModules(tempRoot, absoluteProblemDir);\n return { tempRoot, copiedProblemDir };\n } catch (error) {\n await forciblyRemoveDirectory(tempRoot);\n throw error;\n }\n}\n\n/**\n * Remove a temporary directory even when judged code left permission-locked entries in it (e.g. a\n * mode-000 directory makes a plain `fs.rm` fail with EACCES). Returns whether removal succeeded.\n */\nexport async function forciblyRemoveDirectory(dir: string): Promise<boolean> {\n try {\n await fs.promises.rm(dir, { recursive: true, force: true });\n return true;\n } catch {\n unlockPermissions(dir);\n try {\n await fs.promises.rm(dir, { recursive: true, force: true });\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/** Synchronous variant of {@link forciblyRemoveDirectory} for signal handlers. */\nexport function forciblyRemoveDirectorySync(dir: string): boolean {\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n return true;\n } catch {\n unlockPermissions(dir);\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n return true;\n } catch {\n return false;\n }\n }\n}\n\n// chmod does not exist on Windows, where POSIX-mode locks cannot occur anyway. spawnSync reports\n// spawn failures via its return value instead of throwing, so no try/catch is needed.\nfunction unlockPermissions(dir: string): void {\n if (process.platform !== 'win32') child_process.spawnSync('chmod', ['-R', 'u+rwX', dir]);\n}\n\n// An absolute symlink is copied verbatim, so judging the copy could write through it into the\n// original tree; reject it instead of silently breaking the isolation guarantee.\nasync function rejectAbsoluteSymlinks(src: string): Promise<boolean> {\n if (!isCopiedProblemPath(src)) return false;\n const stats = await fs.promises.lstat(src);\n if (stats.isSymbolicLink() && path.isAbsolute(await fs.promises.readlink(src))) {\n throw new Error(`${src} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);\n }\n return true;\n}\n\nfunction isCopiedProblemPath(src: string): boolean {\n const name = path.basename(src);\n return name !== 'node_modules' && name !== '.git';\n}\n\nasync function symlinkAllAncestorNodeModules(tempRoot: string, problemDir: string): Promise<void> {\n let currentDir = path.resolve(problemDir);\n while (true) {\n const nodeModulesPath = path.join(currentDir, 'node_modules');\n if (fs.existsSync(nodeModulesPath)) {\n const targetSymlinkPath = path.join(tempRoot, toTempRelativePath(currentDir), 'node_modules');\n try {\n await fs.promises.symlink(\n nodeModulesPath,\n targetSymlinkPath,\n process.platform === 'win32' ? 'junction' : 'dir'\n );\n } catch {\n // Package resolution is best-effort; the isolation check still reports a clear spawn failure if imports break.\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) break;\n currentDir = parentDir;\n }\n}\n\nfunction toTempRelativePath(absolutePath: string): string {\n const { root } = path.parse(absolutePath);\n return path.relative(root, absolutePath);\n}\n"],"mappings":"uPASA,eAAsB,EACpB,EACyD,CACzD,IAAM,EAAW,MAAMA,EAAAA,QAAG,SAAS,QAAQC,EAAAA,QAAK,KAAKC,EAAAA,QAAG,OAAO,EAAG,0BAA0B,CAAC,EAC7F,GAAI,CACF,IAAM,EAAqBD,EAAAA,QAAK,QAAQ,CAAU,EAC5C,EAAmBA,EAAAA,QAAK,KAAK,EAAU,EAAmB,CAAkB,CAAC,EAUnF,OATA,MAAMD,EAAAA,QAAG,SAAS,MAAMC,EAAAA,QAAK,QAAQ,CAAgB,EAAG,CAAE,UAAW,EAAK,CAAC,EAC3E,MAAMD,EAAAA,QAAG,SAAS,GAAG,EAAoB,EAAkB,CACzD,UAAW,GACX,OAAQ,EAGR,iBAAkB,EACpB,CAAC,EACD,MAAM,EAA8B,EAAU,CAAkB,EACzD,CAAE,WAAU,kBAAiB,CACtC,OAAS,EAAO,CAEd,MADA,MAAM,EAAwB,CAAQ,EAChC,CACR,CACF,CAMA,eAAsB,EAAwB,EAA+B,CAC3E,GAAI,CAEF,OADA,MAAMA,EAAAA,QAAG,SAAS,GAAG,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnD,EACT,MAAQ,CACN,EAAkB,CAAG,EACrB,GAAI,CAEF,OADA,MAAMA,EAAAA,QAAG,SAAS,GAAG,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnD,EACT,MAAQ,CACN,MAAO,EACT,CACF,CACF,CAGA,SAAgB,EAA4B,EAAsB,CAChE,GAAI,CAEF,OADA,EAAA,QAAG,OAAO,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACxC,EACT,MAAQ,CACN,EAAkB,CAAG,EACrB,GAAI,CAEF,OADA,EAAA,QAAG,OAAO,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACxC,EACT,MAAQ,CACN,MAAO,EACT,CACF,CACF,CAIA,SAAS,EAAkB,EAAmB,CACxC,QAAQ,WAAa,SAAS,EAAA,QAAc,UAAU,QAAS,CAAC,KAAM,QAAS,CAAG,CAAC,CACzF,CAIA,eAAe,EAAuB,EAA+B,CACnE,GAAI,CAAC,EAAoB,CAAG,EAAG,MAAO,GAEtC,IAAI,MADgBA,EAAAA,QAAG,SAAS,MAAM,CAAG,EAAA,CAC/B,eAAe,GAAKC,EAAAA,QAAK,WAAW,MAAMD,EAAAA,QAAG,SAAS,SAAS,CAAG,CAAC,EAC3E,MAAU,MAAM,GAAG,EAAI,uFAAuF,EAEhH,MAAO,EACT,CAEA,SAAS,EAAoB,EAAsB,CACjD,IAAM,EAAOC,EAAAA,QAAK,SAAS,CAAG,EAC9B,OAAO,IAAS,gBAAkB,IAAS,MAC7C,CAEA,eAAe,EAA8B,EAAkB,EAAmC,CAChG,IAAI,EAAaA,EAAAA,QAAK,QAAQ,CAAU,EACxC,OAAa,CACX,IAAM,EAAkBA,EAAAA,QAAK,KAAK,EAAY,cAAc,EAC5D,GAAID,EAAAA,QAAG,WAAW,CAAe,EAAG,CAClC,IAAM,EAAoBC,EAAAA,QAAK,KAAK,EAAU,EAAmB,CAAU,EAAG,cAAc,EAC5F,GAAI,CACF,MAAMD,EAAAA,QAAG,SAAS,QAChB,EACA,EACA,QAAQ,WAAa,QAAU,WAAa,KAC9C,CACF,MAAQ,CAER,CACF,CAEA,IAAM,EAAYC,EAAAA,QAAK,QAAQ,CAAU,EACzC,GAAI,IAAc,EAAY,MAC9B,EAAa,CACf,CACF,CAEA,SAAS,EAAmB,EAA8B,CACxD,GAAM,CAAE,QAASA,EAAAA,QAAK,MAAM,CAAY,EACxC,OAAOA,EAAAA,QAAK,SAAS,EAAM,CAAY,CACzC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy a problem directory into a temporary root, symlinking every ancestor `node_modules` so the
|
|
3
|
+
* copied harness still resolves its imports. Callers must remove the returned `tempRoot`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function copyProblemDirToTemporaryRoot(problemDir: string): Promise<{
|
|
6
|
+
tempRoot: string;
|
|
7
|
+
copiedProblemDir: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Remove a temporary directory even when judged code left permission-locked entries in it (e.g. a
|
|
11
|
+
* mode-000 directory makes a plain `fs.rm` fail with EACCES). Returns whether removal succeeded.
|
|
12
|
+
*/
|
|
13
|
+
export declare function forciblyRemoveDirectory(dir: string): Promise<boolean>;
|
|
14
|
+
/** Synchronous variant of {@link forciblyRemoveDirectory} for signal handlers. */
|
|
15
|
+
export declare function forciblyRemoveDirectorySync(dir: string): boolean;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"node:path";import t from"node:child_process";import n from"node:fs";import r from"node:os";async function i(t){let i=await n.promises.mkdtemp(e.join(r.tmpdir(),`problem-utils-isolation_`));try{let r=e.resolve(t),a=e.join(i,d(r));return await n.promises.mkdir(e.dirname(a),{recursive:!0}),await n.promises.cp(r,a,{recursive:!0,filter:c,verbatimSymlinks:!0}),await u(i,r),{tempRoot:i,copiedProblemDir:a}}catch(e){throw await a(i),e}}async function a(e){try{return await n.promises.rm(e,{recursive:!0,force:!0}),!0}catch{s(e);try{return await n.promises.rm(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function o(e){try{return n.rmSync(e,{recursive:!0,force:!0}),!0}catch{s(e);try{return n.rmSync(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function s(e){process.platform!==`win32`&&t.spawnSync(`chmod`,[`-R`,`u+rwX`,e])}async function c(t){if(!l(t))return!1;if((await n.promises.lstat(t)).isSymbolicLink()&&e.isAbsolute(await n.promises.readlink(t)))throw Error(`${t} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);return!0}function l(t){let n=e.basename(t);return n!==`node_modules`&&n!==`.git`}async function u(t,r){let i=e.resolve(r);for(;;){let r=e.join(i,`node_modules`);if(n.existsSync(r)){let a=e.join(t,d(i),`node_modules`);try{await n.promises.symlink(r,a,process.platform===`win32`?`junction`:`dir`)}catch{}}let a=e.dirname(i);if(a===i)break;i=a}}function d(t){let{root:n}=e.parse(t);return e.relative(n,t)}export{i as copyProblemDirToTemporaryRoot,a as forciblyRemoveDirectory,o as forciblyRemoveDirectorySync};
|
|
2
|
+
//# sourceMappingURL=temporaryProblemDirCopy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporaryProblemDirCopy.js","names":[],"sources":["../../src/helpers/temporaryProblemDirCopy.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/**\n * Copy a problem directory into a temporary root, symlinking every ancestor `node_modules` so the\n * copied harness still resolves its imports. Callers must remove the returned `tempRoot`.\n */\nexport async function copyProblemDirToTemporaryRoot(\n problemDir: string\n): Promise<{ tempRoot: string; copiedProblemDir: string }> {\n const tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'problem-utils-isolation_'));\n try {\n const absoluteProblemDir = path.resolve(problemDir);\n const copiedProblemDir = path.join(tempRoot, toTempRelativePath(absoluteProblemDir));\n await fs.promises.mkdir(path.dirname(copiedProblemDir), { recursive: true });\n await fs.promises.cp(absoluteProblemDir, copiedProblemDir, {\n recursive: true,\n filter: rejectAbsoluteSymlinks,\n // Keep relative symlinks relative: the default rewrites them to absolute paths into the\n // source tree, so judging the copy could write through them into the checked repository.\n verbatimSymlinks: true,\n });\n await symlinkAllAncestorNodeModules(tempRoot, absoluteProblemDir);\n return { tempRoot, copiedProblemDir };\n } catch (error) {\n await forciblyRemoveDirectory(tempRoot);\n throw error;\n }\n}\n\n/**\n * Remove a temporary directory even when judged code left permission-locked entries in it (e.g. a\n * mode-000 directory makes a plain `fs.rm` fail with EACCES). Returns whether removal succeeded.\n */\nexport async function forciblyRemoveDirectory(dir: string): Promise<boolean> {\n try {\n await fs.promises.rm(dir, { recursive: true, force: true });\n return true;\n } catch {\n unlockPermissions(dir);\n try {\n await fs.promises.rm(dir, { recursive: true, force: true });\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/** Synchronous variant of {@link forciblyRemoveDirectory} for signal handlers. */\nexport function forciblyRemoveDirectorySync(dir: string): boolean {\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n return true;\n } catch {\n unlockPermissions(dir);\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n return true;\n } catch {\n return false;\n }\n }\n}\n\n// chmod does not exist on Windows, where POSIX-mode locks cannot occur anyway. spawnSync reports\n// spawn failures via its return value instead of throwing, so no try/catch is needed.\nfunction unlockPermissions(dir: string): void {\n if (process.platform !== 'win32') child_process.spawnSync('chmod', ['-R', 'u+rwX', dir]);\n}\n\n// An absolute symlink is copied verbatim, so judging the copy could write through it into the\n// original tree; reject it instead of silently breaking the isolation guarantee.\nasync function rejectAbsoluteSymlinks(src: string): Promise<boolean> {\n if (!isCopiedProblemPath(src)) return false;\n const stats = await fs.promises.lstat(src);\n if (stats.isSymbolicLink() && path.isAbsolute(await fs.promises.readlink(src))) {\n throw new Error(`${src} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);\n }\n return true;\n}\n\nfunction isCopiedProblemPath(src: string): boolean {\n const name = path.basename(src);\n return name !== 'node_modules' && name !== '.git';\n}\n\nasync function symlinkAllAncestorNodeModules(tempRoot: string, problemDir: string): Promise<void> {\n let currentDir = path.resolve(problemDir);\n while (true) {\n const nodeModulesPath = path.join(currentDir, 'node_modules');\n if (fs.existsSync(nodeModulesPath)) {\n const targetSymlinkPath = path.join(tempRoot, toTempRelativePath(currentDir), 'node_modules');\n try {\n await fs.promises.symlink(\n nodeModulesPath,\n targetSymlinkPath,\n process.platform === 'win32' ? 'junction' : 'dir'\n );\n } catch {\n // Package resolution is best-effort; the isolation check still reports a clear spawn failure if imports break.\n }\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) break;\n currentDir = parentDir;\n }\n}\n\nfunction toTempRelativePath(absolutePath: string): string {\n const { root } = path.parse(absolutePath);\n return path.relative(root, absolutePath);\n}\n"],"mappings":"yGASA,eAAsB,EACpB,EACyD,CACzD,IAAM,EAAW,MAAM,EAAG,SAAS,QAAQ,EAAK,KAAK,EAAG,OAAO,EAAG,0BAA0B,CAAC,EAC7F,GAAI,CACF,IAAM,EAAqB,EAAK,QAAQ,CAAU,EAC5C,EAAmB,EAAK,KAAK,EAAU,EAAmB,CAAkB,CAAC,EAUnF,OATA,MAAM,EAAG,SAAS,MAAM,EAAK,QAAQ,CAAgB,EAAG,CAAE,UAAW,EAAK,CAAC,EAC3E,MAAM,EAAG,SAAS,GAAG,EAAoB,EAAkB,CACzD,UAAW,GACX,OAAQ,EAGR,iBAAkB,EACpB,CAAC,EACD,MAAM,EAA8B,EAAU,CAAkB,EACzD,CAAE,WAAU,kBAAiB,CACtC,OAAS,EAAO,CAEd,MADA,MAAM,EAAwB,CAAQ,EAChC,CACR,CACF,CAMA,eAAsB,EAAwB,EAA+B,CAC3E,GAAI,CAEF,OADA,MAAM,EAAG,SAAS,GAAG,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnD,EACT,MAAQ,CACN,EAAkB,CAAG,EACrB,GAAI,CAEF,OADA,MAAM,EAAG,SAAS,GAAG,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnD,EACT,MAAQ,CACN,MAAO,EACT,CACF,CACF,CAGA,SAAgB,EAA4B,EAAsB,CAChE,GAAI,CAEF,OADA,EAAG,OAAO,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACxC,EACT,MAAQ,CACN,EAAkB,CAAG,EACrB,GAAI,CAEF,OADA,EAAG,OAAO,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACxC,EACT,MAAQ,CACN,MAAO,EACT,CACF,CACF,CAIA,SAAS,EAAkB,EAAmB,CACxC,QAAQ,WAAa,SAAS,EAAc,UAAU,QAAS,CAAC,KAAM,QAAS,CAAG,CAAC,CACzF,CAIA,eAAe,EAAuB,EAA+B,CACnE,GAAI,CAAC,EAAoB,CAAG,EAAG,MAAO,GAEtC,IAAI,MADgB,EAAG,SAAS,MAAM,CAAG,EAAA,CAC/B,eAAe,GAAK,EAAK,WAAW,MAAM,EAAG,SAAS,SAAS,CAAG,CAAC,EAC3E,MAAU,MAAM,GAAG,EAAI,uFAAuF,EAEhH,MAAO,EACT,CAEA,SAAS,EAAoB,EAAsB,CACjD,IAAM,EAAO,EAAK,SAAS,CAAG,EAC9B,OAAO,IAAS,gBAAkB,IAAS,MAC7C,CAEA,eAAe,EAA8B,EAAkB,EAAmC,CAChG,IAAI,EAAa,EAAK,QAAQ,CAAU,EACxC,OAAa,CACX,IAAM,EAAkB,EAAK,KAAK,EAAY,cAAc,EAC5D,GAAI,EAAG,WAAW,CAAe,EAAG,CAClC,IAAM,EAAoB,EAAK,KAAK,EAAU,EAAmB,CAAU,EAAG,cAAc,EAC5F,GAAI,CACF,MAAM,EAAG,SAAS,QAChB,EACA,EACA,QAAQ,WAAa,QAAU,WAAa,KAC9C,CACF,MAAQ,CAER,CACF,CAEA,IAAM,EAAY,EAAK,QAAQ,CAAU,EACzC,GAAI,IAAc,EAAY,MAC9B,EAAa,CACf,CACF,CAEA,SAAS,EAAmB,EAA8B,CACxD,GAAM,CAAE,QAAS,EAAK,MAAM,CAAY,EACxC,OAAO,EAAK,SAAS,EAAM,CAAY,CACzC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/defaultStdioHarness.cjs"),t=require("./helpers/parseArgs.cjs"),n=require("./helpers/parseCsv.cjs"),r=require("./types/testCaseResult.cjs"),i=require("./helpers/printTestCaseResult.cjs"),a=require("./helpers/removeCommentsInSourceCode.cjs"),o=require("./helpers/sandboxUser.cjs"),s=require("./helpers/safeFs.cjs"),c=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),l=require("./helpers/runPythonInTemporaryUvProject.cjs"),u=require("./helpers/sourceCodeGrammars.cjs"),d=require("./helpers/startHttpServer.cjs"),f=require("./types/decisionCode.cjs");exports.CsvParseError=n.CsvParseError,exports.DecisionCode=f.DecisionCode,exports.HARNESS_FILE_PRESETS=e.HARNESS_FILE_PRESETS,exports.SANDBOX_ENV_PREFIX=o.SANDBOX_ENV_PREFIX,exports.SANDBOX_USER_ENV_NAME=o.SANDBOX_USER_ENV_NAME,exports.SANDBOX_WATCHDOG_GRACE_SECONDS=o.SANDBOX_WATCHDOG_GRACE_SECONDS,exports.TEST_CASE_RESULT_PREFIX=r.TEST_CASE_RESULT_PREFIX,exports.TIMEOUT_COMMAND=o.TIMEOUT_COMMAND,exports.copyPackageManagerProjectFiles=c.copyPackageManagerProjectFiles,exports.copyWithoutFollowingSymlinks=s.copyWithoutFollowingSymlinks,exports.createDirectoryWithoutFollowingSymlinks=s.createDirectoryWithoutFollowingSymlinks,exports.encodeFileForTestCaseResult=i.encodeFileForTestCaseResult,exports.findDefaultStdioHarnessFiles=e.findDefaultStdioHarnessFiles,exports.forceRemove=o.forceRemove,exports.getSandboxUserEnvOverrides=o.getSandboxUserEnvOverrides,exports.getTrustedHelperEnv=o.getTrustedHelperEnv,exports.isContainedPath=s.isContainedPath,exports.isDefaultStdioHarnessSource=e.isDefaultStdioHarnessSource,exports.isSandboxWrappedCommand=o.isSandboxWrappedCommand,exports.killSandboxUserProcesses=o.killSandboxUserProcesses,exports.languageIdToSourceCodeGrammar=u.languageIdToSourceCodeGrammar,exports.makeAccessibleToSandboxUser=o.makeAccessibleToSandboxUser,exports.parseArgs=t.parseArgs,exports.parseCsv=n.parseCsv,exports.parseCsvRecords=n.parseCsvRecords,exports.prependInsideSandboxWrapper=o.prependInsideSandboxWrapper,exports.printTestCaseResult=i.printTestCaseResult,exports.relaxPermissionsAsSandboxUser=o.relaxPermissionsAsSandboxUser,exports.removeCommentsAndStringsInSourceCode=a.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=a.removeCommentsInSourceCode,exports.runCommandInTemporaryPackageManagerProject=c.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=l.runPythonInTemporaryUvProject,exports.sandboxUserName=o.sandboxUserName,exports.startHttpServer=d.startHttpServer,exports.startSandboxTimeoutWatchdog=o.startSandboxTimeoutWatchdog,exports.testCaseResultSchema=r.testCaseResultSchema,exports.wrapCommandWithSandboxUser=o.wrapCommandWithSandboxUser,exports.writeFileWithoutFollowingSymlinks=s.writeFileWithoutFollowingSymlinks;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/defaultStdioHarness.cjs"),t=require("./helpers/parseArgs.cjs"),n=require("./helpers/parseCsv.cjs"),r=require("./types/testCaseResult.cjs"),i=require("./helpers/printTestCaseResult.cjs"),a=require("./helpers/removeCommentsInSourceCode.cjs"),o=require("./helpers/sandboxUser.cjs"),s=require("./helpers/safeFs.cjs"),c=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),l=require("./helpers/runPythonInTemporaryUvProject.cjs"),u=require("./helpers/sourceCodeGrammars.cjs"),d=require("./helpers/startHttpServer.cjs"),f=require("./types/decisionCode.cjs"),p=require("./types/problem.cjs");exports.CsvParseError=n.CsvParseError,exports.DecisionCode=f.DecisionCode,exports.HARNESS_FILE_PRESETS=e.HARNESS_FILE_PRESETS,exports.SANDBOX_ENV_PREFIX=o.SANDBOX_ENV_PREFIX,exports.SANDBOX_USER_ENV_NAME=o.SANDBOX_USER_ENV_NAME,exports.SANDBOX_WATCHDOG_GRACE_SECONDS=o.SANDBOX_WATCHDOG_GRACE_SECONDS,exports.TEST_CASE_RESULT_PREFIX=r.TEST_CASE_RESULT_PREFIX,exports.TIMEOUT_COMMAND=o.TIMEOUT_COMMAND,exports.codeRuleSchema=p.codeRuleSchema,exports.copyPackageManagerProjectFiles=c.copyPackageManagerProjectFiles,exports.copyWithoutFollowingSymlinks=s.copyWithoutFollowingSymlinks,exports.createDirectoryWithoutFollowingSymlinks=s.createDirectoryWithoutFollowingSymlinks,exports.encodeFileForTestCaseResult=i.encodeFileForTestCaseResult,exports.findDefaultStdioHarnessFiles=e.findDefaultStdioHarnessFiles,exports.forceRemove=o.forceRemove,exports.getSandboxUserEnvOverrides=o.getSandboxUserEnvOverrides,exports.getTrustedHelperEnv=o.getTrustedHelperEnv,exports.isContainedPath=s.isContainedPath,exports.isDefaultStdioHarnessSource=e.isDefaultStdioHarnessSource,exports.isSandboxWrappedCommand=o.isSandboxWrappedCommand,exports.killSandboxUserProcesses=o.killSandboxUserProcesses,exports.languageIdToSourceCodeGrammar=u.languageIdToSourceCodeGrammar,exports.makeAccessibleToSandboxUser=o.makeAccessibleToSandboxUser,exports.normalizeCodeRule=p.normalizeCodeRule,exports.parseArgs=t.parseArgs,exports.parseCsv=n.parseCsv,exports.parseCsvRecords=n.parseCsvRecords,exports.prependInsideSandboxWrapper=o.prependInsideSandboxWrapper,exports.printTestCaseResult=i.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=p.problemMarkdownFrontMatterSchema,exports.relaxPermissionsAsSandboxUser=o.relaxPermissionsAsSandboxUser,exports.removeCommentsAndStringsInSourceCode=a.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=a.removeCommentsInSourceCode,exports.runCommandInTemporaryPackageManagerProject=c.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=l.runPythonInTemporaryUvProject,exports.sandboxUserName=o.sandboxUserName,exports.startHttpServer=d.startHttpServer,exports.startSandboxTimeoutWatchdog=o.startSandboxTimeoutWatchdog,exports.testCaseResultSchema=r.testCaseResultSchema,exports.wrapCommandWithSandboxUser=o.wrapCommandWithSandboxUser,exports.writeFileWithoutFollowingSymlinks=s.writeFileWithoutFollowingSymlinks;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ export * from './helpers/sandboxUser.js';
|
|
|
10
10
|
export * from './helpers/sourceCodeGrammars.js';
|
|
11
11
|
export * from './helpers/startHttpServer.js';
|
|
12
12
|
export * from './types/decisionCode.js';
|
|
13
|
+
export * from './types/problem.js';
|
|
13
14
|
export * from './types/testCaseResult.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{HARNESS_FILE_PRESETS as e,findDefaultStdioHarnessFiles as t,isDefaultStdioHarnessSource as n}from"./helpers/defaultStdioHarness.js";import{parseArgs as r}from"./helpers/parseArgs.js";import{CsvParseError as i,parseCsv as a,parseCsvRecords as o}from"./helpers/parseCsv.js";import{TEST_CASE_RESULT_PREFIX as s,testCaseResultSchema as c}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as l,printTestCaseResult as u}from"./helpers/printTestCaseResult.js";import{removeCommentsAndStringsInSourceCode as d,removeCommentsInSourceCode as f}from"./helpers/removeCommentsInSourceCode.js";import{SANDBOX_ENV_PREFIX as p,SANDBOX_USER_ENV_NAME as m,SANDBOX_WATCHDOG_GRACE_SECONDS as h,TIMEOUT_COMMAND as g,forceRemove as _,getSandboxUserEnvOverrides as v,getTrustedHelperEnv as y,isSandboxWrappedCommand as b,killSandboxUserProcesses as x,makeAccessibleToSandboxUser as S,prependInsideSandboxWrapper as C,relaxPermissionsAsSandboxUser as w,sandboxUserName as T,startSandboxTimeoutWatchdog as E,wrapCommandWithSandboxUser as D}from"./helpers/sandboxUser.js";import{copyWithoutFollowingSymlinks as O,createDirectoryWithoutFollowingSymlinks as k,isContainedPath as A,writeFileWithoutFollowingSymlinks as j}from"./helpers/safeFs.js";import{copyPackageManagerProjectFiles as M,runCommandInTemporaryPackageManagerProject as N}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as P}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as F}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as I}from"./helpers/startHttpServer.js";import{DecisionCode as L}from"./types/decisionCode.js";export{i as CsvParseError,L as DecisionCode,e as HARNESS_FILE_PRESETS,p as SANDBOX_ENV_PREFIX,m as SANDBOX_USER_ENV_NAME,h as SANDBOX_WATCHDOG_GRACE_SECONDS,s as TEST_CASE_RESULT_PREFIX,g as TIMEOUT_COMMAND,M as copyPackageManagerProjectFiles,O as copyWithoutFollowingSymlinks,k as createDirectoryWithoutFollowingSymlinks,l as encodeFileForTestCaseResult,t as findDefaultStdioHarnessFiles,_ as forceRemove,v as getSandboxUserEnvOverrides,y as getTrustedHelperEnv,A as isContainedPath,n as isDefaultStdioHarnessSource,b as isSandboxWrappedCommand,x as killSandboxUserProcesses,F as languageIdToSourceCodeGrammar,S as makeAccessibleToSandboxUser,r as parseArgs,a as parseCsv,o as parseCsvRecords,C as prependInsideSandboxWrapper,u as printTestCaseResult,w as relaxPermissionsAsSandboxUser,d as removeCommentsAndStringsInSourceCode,f as removeCommentsInSourceCode,N as runCommandInTemporaryPackageManagerProject,P as runPythonInTemporaryUvProject,T as sandboxUserName,I as startHttpServer,E as startSandboxTimeoutWatchdog,c as testCaseResultSchema,D as wrapCommandWithSandboxUser,j as writeFileWithoutFollowingSymlinks};
|
|
1
|
+
import{HARNESS_FILE_PRESETS as e,findDefaultStdioHarnessFiles as t,isDefaultStdioHarnessSource as n}from"./helpers/defaultStdioHarness.js";import{parseArgs as r}from"./helpers/parseArgs.js";import{CsvParseError as i,parseCsv as a,parseCsvRecords as o}from"./helpers/parseCsv.js";import{TEST_CASE_RESULT_PREFIX as s,testCaseResultSchema as c}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as l,printTestCaseResult as u}from"./helpers/printTestCaseResult.js";import{removeCommentsAndStringsInSourceCode as d,removeCommentsInSourceCode as f}from"./helpers/removeCommentsInSourceCode.js";import{SANDBOX_ENV_PREFIX as p,SANDBOX_USER_ENV_NAME as m,SANDBOX_WATCHDOG_GRACE_SECONDS as h,TIMEOUT_COMMAND as g,forceRemove as _,getSandboxUserEnvOverrides as v,getTrustedHelperEnv as y,isSandboxWrappedCommand as b,killSandboxUserProcesses as x,makeAccessibleToSandboxUser as S,prependInsideSandboxWrapper as C,relaxPermissionsAsSandboxUser as w,sandboxUserName as T,startSandboxTimeoutWatchdog as E,wrapCommandWithSandboxUser as D}from"./helpers/sandboxUser.js";import{copyWithoutFollowingSymlinks as O,createDirectoryWithoutFollowingSymlinks as k,isContainedPath as A,writeFileWithoutFollowingSymlinks as j}from"./helpers/safeFs.js";import{copyPackageManagerProjectFiles as M,runCommandInTemporaryPackageManagerProject as N}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as P}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as F}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as I}from"./helpers/startHttpServer.js";import{DecisionCode as L}from"./types/decisionCode.js";import{codeRuleSchema as R,normalizeCodeRule as z,problemMarkdownFrontMatterSchema as B}from"./types/problem.js";export{i as CsvParseError,L as DecisionCode,e as HARNESS_FILE_PRESETS,p as SANDBOX_ENV_PREFIX,m as SANDBOX_USER_ENV_NAME,h as SANDBOX_WATCHDOG_GRACE_SECONDS,s as TEST_CASE_RESULT_PREFIX,g as TIMEOUT_COMMAND,R as codeRuleSchema,M as copyPackageManagerProjectFiles,O as copyWithoutFollowingSymlinks,k as createDirectoryWithoutFollowingSymlinks,l as encodeFileForTestCaseResult,t as findDefaultStdioHarnessFiles,_ as forceRemove,v as getSandboxUserEnvOverrides,y as getTrustedHelperEnv,A as isContainedPath,n as isDefaultStdioHarnessSource,b as isSandboxWrappedCommand,x as killSandboxUserProcesses,F as languageIdToSourceCodeGrammar,S as makeAccessibleToSandboxUser,z as normalizeCodeRule,r as parseArgs,a as parseCsv,o as parseCsvRecords,C as prependInsideSandboxWrapper,u as printTestCaseResult,B as problemMarkdownFrontMatterSchema,w as relaxPermissionsAsSandboxUser,d as removeCommentsAndStringsInSourceCode,f as removeCommentsInSourceCode,N as runCommandInTemporaryPackageManagerProject,P as runPythonInTemporaryUvProject,T as sandboxUserName,I as startHttpServer,E as startSandboxTimeoutWatchdog,c as testCaseResultSchema,D as wrapCommandWithSandboxUser,j as writeFileWithoutFollowingSymlinks};
|
package/dist/presets/command.cjs
CHANGED
|
@@ -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("../helpers/sandboxUser.cjs"),i=require("../types/decisionCode.cjs"),a=require("../helpers/
|
|
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("../helpers/sandboxUser.cjs"),i=require("../types/decisionCode.cjs"),a=require("../helpers/cleanWorkingDirectory.cjs"),o=require("../helpers/copyTestCaseFileInput.cjs"),s=require("../helpers/findEntryPointFile.cjs"),c=require("../helpers/findLanguageDefinitionByPath.cjs"),l=require("../helpers/judgeByStaticAnalysis.cjs"),u=require("../helpers/readOutputFiles.cjs"),d=require("../helpers/runCustomRunner.cjs"),f=require("../helpers/runIsolationCheckInDebugMode.cjs"),p=require("../helpers/readProblemMarkdownFrontMatter.cjs"),m=require("../helpers/readTestCases.cjs"),h=require("../helpers/resolveCwds.cjs"),g=require("../helpers/spawnSyncWithTimeout.cjs");let _=require("node:path");_=e.__toESM(_,1);let v=require("zod");const y=v.z.object({language:v.z.union([v.z.string(),v.z.array(v.z.string())]).optional()});async function b(e,n){let r=t.parseArgs(process.argv),i=y.parse(r.params),{cwds:a,isDebugMode:o}=await h.resolveCwds(e,r.cwd),s=await p.readProblemMarkdownFrontMatter(e),c=await(n.readTestCases??E)(e),l={buildTimeoutSeconds:n.limits?.buildTimeoutSeconds??10,maxOutputLength:n.limits?.maxOutputLength??5e4},u=typeof s.timeLimitMs==`number`?s.timeLimitMs/1e3:n.runTimeoutSeconds??2,d={problemMarkdownFrontMatter:s,testCases:c,limits:l,timeLimitSeconds:u};if(o){let t=(l.buildTimeoutSeconds+c.length*u)*1e3;if(!await f.passesIsolationCheckInDebugMode(e,a,i,{expectedMaxDurationMs:t})){process.exitCode=1;return}}for(let t of a){o&&h.printDebugCwdBanner(e,t);let r=await x(t.cwd,i,d,n);o&&!S(t,r)&&(process.exitCode=1,h.printDebugExpectationFailureBanner(e,t))}}async function x(e,t,f,p){r.makeAccessibleToSandboxUser(e);let{problemMarkdownFrontMatter:m,testCases:h,limits:g,timeLimitSeconds:_}=f,v=h[0]?.id??`prebuild`,y=await l.judgeByStaticAnalysis(e,m);if(y)return n.printTestCaseResult({testCaseId:v,...y}),{allAccepted:!1};let b=await s.findEntryPointFile(e,t.language);if(!b)return n.printTestCaseResult({testCaseId:v,decisionCode:i.DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,stderr:`main file not found${t.language?`: language: ${t.language}`:``}`}),{allAccepted:!1};let x=c.findLanguageDefinitionByPath(b);if(!x)return n.printTestCaseResult({testCaseId:v,decisionCode:i.DecisionCode.WRONG_ANSWER,stderr:`unsupported language`}),{allAccepted:!1};let S={...process.env,CI:``,FORCE_COLOR:`0`},T=b;if(p.buildSubmission!==!1){if(x.prebuild)try{await x.prebuild(e);let n=await s.findEntryPointFile(e,t.language);n&&(T=n)}catch(e){return n.printTestCaseResult({testCaseId:v,decisionCode:i.DecisionCode.BUILD_ERROR,stderr:e instanceof Error?e.message:String(e)}),{allAccepted:!1}}let r=x.buildCommand?.(T);if(r){let t=C(r,{cwd:e,env:S,testCaseId:v,limits:g});if(t)return n.printTestCaseResult(t),{allAccepted:!1}}}let E=await a.snapshotWorkingDirectory(e);if(h.length===0)return n.printTestCaseResult({testCaseId:`default`,decisionCode:i.DecisionCode.ACCEPTED}),{allAccepted:!0};let k=h.shared?.fileInputPath;for(let t of h){k&&await o.copyTestCaseFileInput(k,e),t.fileInputPath&&await o.copyTestCaseFileInput(t.fileInputPath,e);let s=x.command(T),c=t.input??``,l;try{p.resolveInput&&(c=await p.resolveInput({testCase:t,cwd:e,env:S})),l=p.runCommand?await d.runCustomRunner(()=>p.runCommand?.({testCase:t,command:r.wrapCommandWithSandboxUser(s),stdin:c,cwd:e,env:{...S,...r.getSandboxUserEnvOverrides(S)},timeLimitSeconds:_})):D(s,{stdin:c,cwd:e,env:S,timeLimitSeconds:_})}catch(r){return n.printTestCaseResult({testCaseId:t.id,decisionCode:i.DecisionCode.RUNTIME_ERROR,stdin:c,stderr:w(r)}),await a.cleanWorkingDirectory(e,E),{allAccepted:!1}}let f=await u.readOutputFiles(e,m.requiredOutputFilePaths??[]),h={timeLimitSeconds:_,outputLimitLength:g.maxOutputLength,problemMarkdownFrontMatter:{memoryLimitByte:m.memoryLimitByte,requiredOutputFilePaths:m.requiredOutputFilePaths}},v=O({runResult:l,outputFiles:f,context:h}),y=v;if(v.decisionCode===i.DecisionCode.ACCEPTED)try{let e=await p.test?.({testCase:t,runResult:l,outputFiles:f,context:h});e&&(y={decisionCode:e.decisionCode??v.decisionCode,feedbackMarkdown:e.feedbackMarkdown,stderr:e.stderr})}catch(e){y={decisionCode:i.DecisionCode.RUNTIME_ERROR,stderr:w(e)}}if(n.printTestCaseResult({testCaseId:t.id,...y,exitStatus:l.status,stdin:l.stdin,stdout:l.stdout.slice(0,g.maxOutputLength)||void 0,stderr:(y.stderr??l.stderr).slice(0,g.maxOutputLength)||void 0,timeSeconds:l.timeSeconds,memoryBytes:l.memoryBytes,outputFiles:f.length>0?f:void 0}),await a.cleanWorkingDirectory(e,E),y.decisionCode!==i.DecisionCode.ACCEPTED)return{allAccepted:!1}}return{allAccepted:!0}}function S(e,t){return t.allAccepted===(e.expectedResult===`accepted`)}function C(e,t){let n=g.spawnSyncWithTimeout(e[0],e.slice(1),{cwd:t.cwd,encoding:`utf8`,env:t.env},t.limits.buildTimeoutSeconds),r=n.status??void 0;if(n.status!==0)return{testCaseId:t.testCaseId,decisionCode:i.DecisionCode.BUILD_ERROR,exitStatus:r,stdout:n.stdout.slice(0,t.limits.maxOutputLength),stderr:n.stderr.slice(0,t.limits.maxOutputLength),timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes};if(n.timeSeconds>t.limits.buildTimeoutSeconds)return{testCaseId:t.testCaseId,decisionCode:i.DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,exitStatus:r,stdout:n.stdout.slice(0,t.limits.maxOutputLength),stderr:n.stderr.slice(0,t.limits.maxOutputLength),timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes};if(n.stdout.length>t.limits.maxOutputLength||n.stderr.length>t.limits.maxOutputLength)return{testCaseId:t.testCaseId,decisionCode:i.DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED,exitStatus:r,stdout:n.stdout.slice(0,t.limits.maxOutputLength),stderr:n.stderr.slice(0,t.limits.maxOutputLength),timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes}}function w(e){return e instanceof Error?e.message:String(e)}function T(e){return{id:e.id,input:e.input,fileInputPath:e.fileInputPath}}async function E(e){let t=await m.readTestCases(_.default.join(e,`test_cases`)),n=t.map(e=>T(e));return t.shared?.fileInputPath?Object.assign(n,{shared:{fileInputPath:t.shared.fileInputPath}}):n}function D(e,t){let n=g.spawnSyncWithTimeout(e[0],e.slice(1),{cwd:t.cwd,encoding:`utf8`,input:t.stdin,env:t.env},t.timeLimitSeconds);return{stdin:t.stdin,stdout:n.stdout,stderr:n.stderr,status:n.status??void 0,timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes}}function O(e){if(e.runResult.status!==0)return{decisionCode:i.DecisionCode.RUNTIME_ERROR,stderr:e.runResult.stderr};if(e.runResult.timeSeconds>e.context.timeLimitSeconds)return{decisionCode:i.DecisionCode.TIME_LIMIT_EXCEEDED,stderr:e.runResult.stderr};if(e.runResult.memoryBytes>(e.context.problemMarkdownFrontMatter.memoryLimitByte??1/0))return{decisionCode:i.DecisionCode.MEMORY_LIMIT_EXCEEDED,stderr:e.runResult.stderr};if(e.runResult.outputLimitExceeded||e.runResult.stdout.length>e.context.outputLimitLength||e.runResult.stderr.length>e.context.outputLimitLength)return{decisionCode:i.DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED,stderr:e.runResult.stderr};let t=e.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length??0;return e.outputFiles.length<t?{decisionCode:i.DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR}:{decisionCode:i.DecisionCode.ACCEPTED}}exports.commandJudgePreset=b;
|
|
2
2
|
//# sourceMappingURL=command.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.cjs","names":["z","parseArgs","resolveCwds","checkProblemDirIsolation","readProblemMarkdownFrontMatter","judgeByStaticAnalysis","findEntryPointFile","DecisionCode","findLanguageDefinitionByPath","snapshotWorkingDirectory","copyTestCaseFileInput","runCustomRunner","wrapCommandWithSandboxUser","getSandboxUserEnvOverrides","cleanWorkingDirectory","readOutputFiles","spawnSyncWithTimeout","readFileTestCases","path"],"sources":["../../src/presets/command.ts"],"sourcesContent":["import path from 'node:path';\n\nimport { z } from 'zod';\n\nimport { checkProblemDirIsolation } from '../helpers/checkProblemDirIsolation.js';\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 { printDebugBanner } from '../helpers/printDebugBanner.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport { runCustomRunner } from '../helpers/runCustomRunner.js';\nimport {\n getSandboxUserEnvOverrides,\n makeAccessibleToSandboxUser,\n wrapCommandWithSandboxUser,\n} from '../helpers/sandboxUser.js';\nimport { readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases as readFileTestCases } from '../helpers/readTestCases.js';\nimport {\n printDebugCwdBanner,\n printDebugExpectationFailureBanner,\n resolveCwds,\n type ResolvedCwd,\n} from '../helpers/resolveCwds.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport { DecisionCode } from '../types/decisionCode.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 = 2;\nconst MAX_STDOUT_LENGTH = 50_000;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\ntype JudgeParams = z.infer<typeof judgeParamsSchema>;\n\ninterface BaseCommandTestCase {\n id: string;\n input?: string;\n fileInputPath?: string;\n}\n\ntype CommandJudgeCaseResult = Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown' | 'stderr'>;\n\nexport interface CommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n outputLimitExceeded?: boolean;\n}\n\ninterface CommandJudgeContext {\n timeLimitSeconds: number;\n outputLimitLength: number;\n problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;\n}\n\nexport interface CommandJudgeLimits {\n buildTimeoutSeconds: number;\n maxOutputLength: number;\n}\n\nexport interface CommandJudgePresetOptions<\n TTestCase extends BaseCommandTestCase = BaseCommandTestCase,\n TRunResult extends CommandRunResult = CommandRunResult,\n> {\n /**\n * Set to false when the custom runner builds the submitted source itself.\n * Defaults to true.\n */\n buildSubmission?: boolean;\n limits?: CommandJudgeLimits;\n runTimeoutSeconds?: number;\n readTestCases?: (problemDir: string) => Promise<readonly TTestCase[]>;\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 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 }) => Promise<TRunResult> | TRunResult;\n test?: (context: {\n testCase: TTestCase;\n runResult: TRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: CommandJudgeContext;\n }) => Promise<Partial<CommandJudgeCaseResult>> | Partial<CommandJudgeCaseResult> | undefined;\n}\n\n/**\n * A preset function for judging by executable command.\n *\n * Keep problem-specific logic in `resolveInput` and `test`.\n *\n * @example\n * Create `judge.ts`:\n * ```ts\n * import { commandJudgePreset } from '@exercode/problem-utils/presets/command';\n * import { DecisionCode } from '@exercode/problem-utils';\n *\n * await commandJudgePreset(import.meta.dirname, {\n * readTestCases: async () => [\n * { id: '01', input: '1 2' },\n * ],\n * test: ({ runResult }) => {\n * return runResult.stdout.trim() === '3'\n * ? { decisionCode: DecisionCode.ACCEPTED }\n * : { decisionCode: DecisionCode.WRONG_ANSWER };\n * },\n * });\n * ```\n *\n * Run with the required parameters:\n * ```bash\n * bun judge.ts model_answers/python '{ \"language\": \"python\" }'\n * ```\n *\n * Run without a cwd argument to judge each `<problemDir>/model_answers/*` directory\n * for debugging. A prominent banner is printed for each run.\n * ```bash\n * bun judge.ts\n * ```\n */\nexport async function commandJudgePreset<\n TTestCase extends BaseCommandTestCase = BaseCommandTestCase,\n TRunResult extends CommandRunResult = CommandRunResult,\n>(problemDir: string, options: CommandJudgePresetOptions<TTestCase, TRunResult>): Promise<void> {\n const args = parseArgs(process.argv);\n const params = judgeParamsSchema.parse(args.params);\n\n const { cwds, isDebugMode } = await resolveCwds(problemDir, args.cwd);\n\n if (isDebugMode) {\n const acceptedCwd = cwds.find((cwd) => cwd.expectedResult === 'accepted');\n if (acceptedCwd) {\n const isolationCheckResult = await checkProblemDirIsolation(problemDir, acceptedCwd, params);\n if (!isolationCheckResult.passed) {\n process.exitCode = 1;\n return;\n }\n } else {\n printDebugBanner([\n '[DEBUG MODE] isolated problem directory check skipped',\n '',\n 'No accepted model answer is available for checking that the copied judge still accepts a valid submission.',\n ]);\n }\n }\n\n for (const resolvedCwd of cwds) {\n if (isDebugMode) printDebugCwdBanner(problemDir, resolvedCwd);\n const result = await runCommandJudgeForCwd<TTestCase, TRunResult>(problemDir, resolvedCwd.cwd, params, options);\n if (isDebugMode && !matchesExpectedResult(resolvedCwd, result)) {\n process.exitCode = 1;\n printDebugExpectationFailureBanner(problemDir, resolvedCwd);\n }\n }\n}\n\nasync function runCommandJudgeForCwd<\n TTestCase extends BaseCommandTestCase,\n TRunResult extends CommandRunResult = CommandRunResult,\n>(\n problemDir: string,\n cwd: string,\n params: JudgeParams,\n options: CommandJudgePresetOptions<TTestCase, TRunResult>\n): Promise<{ allAccepted: boolean }> {\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(cwd);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await (options.readTestCases ?? readCommandTestCases)(problemDir);\n const prebuildTestCaseId = testCases[0]?.id ?? 'prebuild';\n const limits = {\n buildTimeoutSeconds: options.limits?.buildTimeoutSeconds ?? BUILD_TIMEOUT_SECONDS,\n maxOutputLength: options.limits?.maxOutputLength ?? MAX_STDOUT_LENGTH,\n };\n const runTimeoutSeconds = options.runTimeoutSeconds ?? JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const staticAnalysisResult = await judgeByStaticAnalysis(cwd, problemMarkdownFrontMatter);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId: prebuildTestCaseId, ...staticAnalysisResult });\n return { allAccepted: false };\n }\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return { allAccepted: false };\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return { allAccepted: false };\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let mainFilePath = originalMainFilePath;\n if (options.buildSubmission !== false) {\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n const prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n if (prebuiltMainFilePath) mainFilePath = prebuiltMainFilePath;\n } catch (error) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return { allAccepted: false };\n }\n }\n\n const buildCommand = languageDefinition.buildCommand?.(mainFilePath);\n if (buildCommand) {\n const buildResult = runBuild(buildCommand, {\n cwd,\n env,\n testCaseId: prebuildTestCaseId,\n limits,\n });\n if (buildResult) {\n printTestCaseResult(buildResult);\n return { allAccepted: false };\n }\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n return { allAccepted: true };\n }\n\n const sharedFileInputPath = (testCases as { shared?: { fileInputPath?: string } }).shared?.fileInputPath;\n\n for (const testCase of testCases) {\n if (sharedFileInputPath) await copyTestCaseFileInput(sharedFileInputPath, cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, cwd);\n\n const timeLimitSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : runTimeoutSeconds;\n\n const command = languageDefinition.command(mainFilePath);\n let stdin = testCase.input ?? '';\n let runResult: TRunResult;\n try {\n if (options.resolveInput) {\n stdin = await options.resolveInput({ testCase, cwd, env });\n }\n\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,\n env: { ...env, ...getSandboxUserEnvOverrides(env) },\n timeLimitSeconds,\n }) as Promise<TRunResult>\n )\n : (runCommand(command, {\n stdin,\n cwd,\n env,\n timeLimitSeconds,\n }) as TRunResult);\n } catch (error) {\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(cwd, cwdSnapshot);\n return { allAccepted: false };\n }\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n const judgeContext: CommandJudgeContext = {\n timeLimitSeconds,\n outputLimitLength: limits.maxOutputLength,\n problemMarkdownFrontMatter: {\n memoryLimitByte: problemMarkdownFrontMatter.memoryLimitByte,\n requiredOutputFilePaths: problemMarkdownFrontMatter.requiredOutputFilePaths,\n },\n };\n const baseJudgeResult = evaluateByLimits({ runResult, outputFiles, context: judgeContext });\n let judgeResult = baseJudgeResult;\n if (baseJudgeResult.decisionCode === DecisionCode.ACCEPTED) {\n try {\n const extendedJudgeResult = await options.test?.({ testCase, runResult, outputFiles, context: judgeContext });\n if (extendedJudgeResult) {\n judgeResult = {\n decisionCode: extendedJudgeResult.decisionCode ?? baseJudgeResult.decisionCode,\n feedbackMarkdown: extendedJudgeResult.feedbackMarkdown,\n stderr: extendedJudgeResult.stderr,\n };\n }\n } catch (error) {\n judgeResult = {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: errorToMessage(error),\n };\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n ...judgeResult,\n exitStatus: runResult.status,\n stdin: runResult.stdin,\n stdout: runResult.stdout.slice(0, limits.maxOutputLength) || undefined,\n stderr: (judgeResult.stderr ?? runResult.stderr).slice(0, limits.maxOutputLength) || undefined,\n timeSeconds: runResult.timeSeconds,\n memoryBytes: runResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n await cleanWorkingDirectory(cwd, cwdSnapshot);\n if (judgeResult.decisionCode !== DecisionCode.ACCEPTED) return { allAccepted: false };\n }\n\n return { allAccepted: true };\n}\n\nfunction matchesExpectedResult(resolvedCwd: ResolvedCwd, result: { allAccepted: boolean }): boolean {\n return result.allAccepted === (resolvedCwd.expectedResult === 'accepted');\n}\n\nfunction runBuild(\n buildCommand: readonly [string, ...string[]],\n context: { cwd: string; env: NodeJS.ProcessEnv; testCaseId: string; limits: CommandJudgeLimits }\n): (Omit<TestCaseResult, 'testCaseId'> & { testCaseId: string }) | undefined {\n const spawnResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: context.cwd, encoding: 'utf8', env: context.env },\n context.limits.buildTimeoutSeconds\n );\n const exitStatus = spawnResult.status ?? undefined;\n\n if (spawnResult.status !== 0) {\n return {\n testCaseId: context.testCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus,\n stdout: spawnResult.stdout.slice(0, context.limits.maxOutputLength),\n stderr: spawnResult.stderr.slice(0, context.limits.maxOutputLength),\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n }\n\n if (spawnResult.timeSeconds > context.limits.buildTimeoutSeconds) {\n return {\n testCaseId: context.testCaseId,\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n exitStatus,\n stdout: spawnResult.stdout.slice(0, context.limits.maxOutputLength),\n stderr: spawnResult.stderr.slice(0, context.limits.maxOutputLength),\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n }\n\n if (\n spawnResult.stdout.length > context.limits.maxOutputLength ||\n spawnResult.stderr.length > context.limits.maxOutputLength\n ) {\n return {\n testCaseId: context.testCaseId,\n decisionCode: DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED,\n exitStatus,\n stdout: spawnResult.stdout.slice(0, context.limits.maxOutputLength),\n stderr: spawnResult.stderr.slice(0, context.limits.maxOutputLength),\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n }\n\n return;\n}\n\nfunction errorToMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction toCommandTestCase(value: {\n id: string;\n input?: string;\n fileInputPath?: string;\n fileOutputPath?: string;\n output?: string;\n}): BaseCommandTestCase {\n return { id: value.id, input: value.input, fileInputPath: value.fileInputPath };\n}\n\nasync function readCommandTestCases<TTestCase extends BaseCommandTestCase = BaseCommandTestCase>(\n problemDir: string\n): Promise<readonly TTestCase[]> {\n const fileTestCases = await readFileTestCases(path.join(problemDir, 'test_cases'));\n const commandTestCases = fileTestCases.map((testCase) => toCommandTestCase(testCase) as TTestCase);\n if (fileTestCases.shared?.fileInputPath) {\n return Object.assign(commandTestCases, { shared: { fileInputPath: fileTestCases.shared.fileInputPath } });\n }\n return commandTestCases;\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n context: {\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n }\n): CommandRunResult {\n const spawnResult = spawnSyncWithTimeout(\n command[0],\n command.slice(1),\n { cwd: context.cwd, encoding: 'utf8', input: context.stdin, env: context.env },\n context.timeLimitSeconds\n );\n\n return {\n stdin: context.stdin,\n stdout: spawnResult.stdout,\n stderr: spawnResult.stderr,\n status: spawnResult.status ?? undefined,\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n}\n\nfunction evaluateByLimits(context: {\n runResult: CommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: CommandJudgeContext;\n}): CommandJudgeCaseResult {\n if (context.runResult.status !== 0) {\n return { decisionCode: DecisionCode.RUNTIME_ERROR, stderr: context.runResult.stderr };\n }\n\n if (context.runResult.timeSeconds > context.context.timeLimitSeconds) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr: context.runResult.stderr };\n }\n\n if (\n context.runResult.memoryBytes >\n (context.context.problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)\n ) {\n return { decisionCode: DecisionCode.MEMORY_LIMIT_EXCEEDED, stderr: context.runResult.stderr };\n }\n\n if (\n context.runResult.outputLimitExceeded ||\n context.runResult.stdout.length > context.context.outputLimitLength ||\n context.runResult.stderr.length > context.context.outputLimitLength\n ) {\n return { decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED, stderr: context.runResult.stderr };\n }\n\n const requiredOutputFileCount = context.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0;\n if (context.outputFiles.length < requiredOutputFileCount) {\n return { decisionCode: DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR };\n }\n\n return { decisionCode: DecisionCode.ACCEPTED };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command.cjs","names":["z","parseArgs","resolveCwds","readProblemMarkdownFrontMatter","passesIsolationCheckInDebugMode","judgeByStaticAnalysis","findEntryPointFile","DecisionCode","findLanguageDefinitionByPath","snapshotWorkingDirectory","copyTestCaseFileInput","runCustomRunner","wrapCommandWithSandboxUser","getSandboxUserEnvOverrides","cleanWorkingDirectory","readOutputFiles","spawnSyncWithTimeout","readFileTestCases","path"],"sources":["../../src/presets/command.ts"],"sourcesContent":["import path from 'node:path';\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 { runCustomRunner } from '../helpers/runCustomRunner.js';\nimport { passesIsolationCheckInDebugMode } from '../helpers/runIsolationCheckInDebugMode.js';\nimport {\n getSandboxUserEnvOverrides,\n makeAccessibleToSandboxUser,\n wrapCommandWithSandboxUser,\n} from '../helpers/sandboxUser.js';\nimport { readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases as readFileTestCases } from '../helpers/readTestCases.js';\nimport {\n printDebugCwdBanner,\n printDebugExpectationFailureBanner,\n resolveCwds,\n type ResolvedCwd,\n} from '../helpers/resolveCwds.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport { DecisionCode } from '../types/decisionCode.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 = 2;\nconst MAX_STDOUT_LENGTH = 50_000;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\ntype JudgeParams = z.infer<typeof judgeParamsSchema>;\n\ninterface BaseCommandTestCase {\n id: string;\n input?: string;\n fileInputPath?: string;\n}\n\ntype CommandJudgeCaseResult = Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown' | 'stderr'>;\n\nexport interface CommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n outputLimitExceeded?: boolean;\n}\n\ninterface CommandJudgeContext {\n timeLimitSeconds: number;\n outputLimitLength: number;\n problemMarkdownFrontMatter: Pick<ProblemMarkdownFrontMatter, 'memoryLimitByte' | 'requiredOutputFilePaths'>;\n}\n\nexport interface CommandJudgeLimits {\n buildTimeoutSeconds: number;\n maxOutputLength: number;\n}\n\ninterface ResolvedCommandProblem<TTestCase extends BaseCommandTestCase> {\n problemMarkdownFrontMatter: ProblemMarkdownFrontMatter;\n testCases: readonly TTestCase[];\n limits: CommandJudgeLimits;\n timeLimitSeconds: number;\n}\n\nexport interface CommandJudgePresetOptions<\n TTestCase extends BaseCommandTestCase = BaseCommandTestCase,\n TRunResult extends CommandRunResult = CommandRunResult,\n> {\n /**\n * Set to false when the custom runner builds the submitted source itself.\n * Defaults to true.\n */\n buildSubmission?: boolean;\n limits?: CommandJudgeLimits;\n runTimeoutSeconds?: number;\n readTestCases?: (problemDir: string) => Promise<readonly TTestCase[]>;\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 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 }) => Promise<TRunResult> | TRunResult;\n test?: (context: {\n testCase: TTestCase;\n runResult: TRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: CommandJudgeContext;\n }) => Promise<Partial<CommandJudgeCaseResult>> | Partial<CommandJudgeCaseResult> | undefined;\n}\n\n/**\n * A preset function for judging by executable command.\n *\n * Keep problem-specific logic in `resolveInput` and `test`.\n *\n * @example\n * Create `judge.ts`:\n * ```ts\n * import { commandJudgePreset } from '@exercode/problem-utils/presets/command';\n * import { DecisionCode } from '@exercode/problem-utils';\n *\n * await commandJudgePreset(import.meta.dirname, {\n * readTestCases: async () => [\n * { id: '01', input: '1 2' },\n * ],\n * test: ({ runResult }) => {\n * return runResult.stdout.trim() === '3'\n * ? { decisionCode: DecisionCode.ACCEPTED }\n * : { decisionCode: DecisionCode.WRONG_ANSWER };\n * },\n * });\n * ```\n *\n * Run with the required parameters:\n * ```bash\n * bun judge.ts model_answers/python '{ \"language\": \"python\" }'\n * ```\n *\n * Run without a cwd argument to judge each `<problemDir>/model_answers/*` directory\n * for debugging. A prominent banner is printed for each run.\n * ```bash\n * bun judge.ts\n * ```\n */\nexport async function commandJudgePreset<\n TTestCase extends BaseCommandTestCase = BaseCommandTestCase,\n TRunResult extends CommandRunResult = CommandRunResult,\n>(problemDir: string, options: CommandJudgePresetOptions<TTestCase, TRunResult>): Promise<void> {\n const args = parseArgs(process.argv);\n const params = judgeParamsSchema.parse(args.params);\n\n const { cwds, isDebugMode } = await resolveCwds(problemDir, args.cwd);\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await (options.readTestCases ?? readCommandTestCases)(problemDir);\n const limits = {\n buildTimeoutSeconds: options.limits?.buildTimeoutSeconds ?? BUILD_TIMEOUT_SECONDS,\n maxOutputLength: options.limits?.maxOutputLength ?? MAX_STDOUT_LENGTH,\n };\n const timeLimitSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : (options.runTimeoutSeconds ?? JUDGE_DEFAULT_TIMEOUT_SECONDS);\n const problem: ResolvedCommandProblem<TTestCase> = {\n problemMarkdownFrontMatter,\n testCases,\n limits,\n timeLimitSeconds,\n };\n\n if (isDebugMode) {\n const expectedMaxDurationMs = (limits.buildTimeoutSeconds + testCases.length * timeLimitSeconds) * 1000;\n if (!(await passesIsolationCheckInDebugMode(problemDir, cwds, params, { expectedMaxDurationMs }))) {\n process.exitCode = 1;\n return;\n }\n }\n\n for (const resolvedCwd of cwds) {\n if (isDebugMode) printDebugCwdBanner(problemDir, resolvedCwd);\n const result = await runCommandJudgeForCwd<TTestCase, TRunResult>(resolvedCwd.cwd, params, problem, options);\n if (isDebugMode && !matchesExpectedResult(resolvedCwd, result)) {\n process.exitCode = 1;\n printDebugExpectationFailureBanner(problemDir, resolvedCwd);\n }\n }\n}\n\nasync function runCommandJudgeForCwd<\n TTestCase extends BaseCommandTestCase,\n TRunResult extends CommandRunResult = CommandRunResult,\n>(\n cwd: string,\n params: JudgeParams,\n problem: ResolvedCommandProblem<TTestCase>,\n options: CommandJudgePresetOptions<TTestCase, TRunResult>\n): Promise<{ allAccepted: boolean }> {\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(cwd);\n\n const { problemMarkdownFrontMatter, testCases, limits, timeLimitSeconds } = problem;\n const prebuildTestCaseId = testCases[0]?.id ?? 'prebuild';\n\n const staticAnalysisResult = await judgeByStaticAnalysis(cwd, problemMarkdownFrontMatter);\n if (staticAnalysisResult) {\n printTestCaseResult({ testCaseId: prebuildTestCaseId, ...staticAnalysisResult });\n return { allAccepted: false };\n }\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return { allAccepted: false };\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return { allAccepted: false };\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let mainFilePath = originalMainFilePath;\n if (options.buildSubmission !== false) {\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n const prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n if (prebuiltMainFilePath) mainFilePath = prebuiltMainFilePath;\n } catch (error) {\n printTestCaseResult({\n testCaseId: prebuildTestCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return { allAccepted: false };\n }\n }\n\n const buildCommand = languageDefinition.buildCommand?.(mainFilePath);\n if (buildCommand) {\n const buildResult = runBuild(buildCommand, {\n cwd,\n env,\n testCaseId: prebuildTestCaseId,\n limits,\n });\n if (buildResult) {\n printTestCaseResult(buildResult);\n return { allAccepted: false };\n }\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n return { allAccepted: true };\n }\n\n const sharedFileInputPath = (testCases as { shared?: { fileInputPath?: string } }).shared?.fileInputPath;\n\n for (const testCase of testCases) {\n if (sharedFileInputPath) await copyTestCaseFileInput(sharedFileInputPath, cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, cwd);\n\n const command = languageDefinition.command(mainFilePath);\n let stdin = testCase.input ?? '';\n let runResult: TRunResult;\n try {\n if (options.resolveInput) {\n stdin = await options.resolveInput({ testCase, cwd, env });\n }\n\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,\n env: { ...env, ...getSandboxUserEnvOverrides(env) },\n timeLimitSeconds,\n }) as Promise<TRunResult>\n )\n : (runCommand(command, {\n stdin,\n cwd,\n env,\n timeLimitSeconds,\n }) as TRunResult);\n } catch (error) {\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stdin,\n stderr: errorToMessage(error),\n });\n await cleanWorkingDirectory(cwd, cwdSnapshot);\n return { allAccepted: false };\n }\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n const judgeContext: CommandJudgeContext = {\n timeLimitSeconds,\n outputLimitLength: limits.maxOutputLength,\n problemMarkdownFrontMatter: {\n memoryLimitByte: problemMarkdownFrontMatter.memoryLimitByte,\n requiredOutputFilePaths: problemMarkdownFrontMatter.requiredOutputFilePaths,\n },\n };\n const baseJudgeResult = evaluateByLimits({ runResult, outputFiles, context: judgeContext });\n let judgeResult = baseJudgeResult;\n if (baseJudgeResult.decisionCode === DecisionCode.ACCEPTED) {\n try {\n const extendedJudgeResult = await options.test?.({ testCase, runResult, outputFiles, context: judgeContext });\n if (extendedJudgeResult) {\n judgeResult = {\n decisionCode: extendedJudgeResult.decisionCode ?? baseJudgeResult.decisionCode,\n feedbackMarkdown: extendedJudgeResult.feedbackMarkdown,\n stderr: extendedJudgeResult.stderr,\n };\n }\n } catch (error) {\n judgeResult = {\n decisionCode: DecisionCode.RUNTIME_ERROR,\n stderr: errorToMessage(error),\n };\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n ...judgeResult,\n exitStatus: runResult.status,\n stdin: runResult.stdin,\n stdout: runResult.stdout.slice(0, limits.maxOutputLength) || undefined,\n stderr: (judgeResult.stderr ?? runResult.stderr).slice(0, limits.maxOutputLength) || undefined,\n timeSeconds: runResult.timeSeconds,\n memoryBytes: runResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n await cleanWorkingDirectory(cwd, cwdSnapshot);\n if (judgeResult.decisionCode !== DecisionCode.ACCEPTED) return { allAccepted: false };\n }\n\n return { allAccepted: true };\n}\n\nfunction matchesExpectedResult(resolvedCwd: ResolvedCwd, result: { allAccepted: boolean }): boolean {\n return result.allAccepted === (resolvedCwd.expectedResult === 'accepted');\n}\n\nfunction runBuild(\n buildCommand: readonly [string, ...string[]],\n context: { cwd: string; env: NodeJS.ProcessEnv; testCaseId: string; limits: CommandJudgeLimits }\n): (Omit<TestCaseResult, 'testCaseId'> & { testCaseId: string }) | undefined {\n const spawnResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: context.cwd, encoding: 'utf8', env: context.env },\n context.limits.buildTimeoutSeconds\n );\n const exitStatus = spawnResult.status ?? undefined;\n\n if (spawnResult.status !== 0) {\n return {\n testCaseId: context.testCaseId,\n decisionCode: DecisionCode.BUILD_ERROR,\n exitStatus,\n stdout: spawnResult.stdout.slice(0, context.limits.maxOutputLength),\n stderr: spawnResult.stderr.slice(0, context.limits.maxOutputLength),\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n }\n\n if (spawnResult.timeSeconds > context.limits.buildTimeoutSeconds) {\n return {\n testCaseId: context.testCaseId,\n decisionCode: DecisionCode.BUILD_TIME_LIMIT_EXCEEDED,\n exitStatus,\n stdout: spawnResult.stdout.slice(0, context.limits.maxOutputLength),\n stderr: spawnResult.stderr.slice(0, context.limits.maxOutputLength),\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n }\n\n if (\n spawnResult.stdout.length > context.limits.maxOutputLength ||\n spawnResult.stderr.length > context.limits.maxOutputLength\n ) {\n return {\n testCaseId: context.testCaseId,\n decisionCode: DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED,\n exitStatus,\n stdout: spawnResult.stdout.slice(0, context.limits.maxOutputLength),\n stderr: spawnResult.stderr.slice(0, context.limits.maxOutputLength),\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n }\n\n return;\n}\n\nfunction errorToMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction toCommandTestCase(value: {\n id: string;\n input?: string;\n fileInputPath?: string;\n fileOutputPath?: string;\n output?: string;\n}): BaseCommandTestCase {\n return { id: value.id, input: value.input, fileInputPath: value.fileInputPath };\n}\n\nasync function readCommandTestCases<TTestCase extends BaseCommandTestCase = BaseCommandTestCase>(\n problemDir: string\n): Promise<readonly TTestCase[]> {\n const fileTestCases = await readFileTestCases(path.join(problemDir, 'test_cases'));\n const commandTestCases = fileTestCases.map((testCase) => toCommandTestCase(testCase) as TTestCase);\n if (fileTestCases.shared?.fileInputPath) {\n return Object.assign(commandTestCases, { shared: { fileInputPath: fileTestCases.shared.fileInputPath } });\n }\n return commandTestCases;\n}\n\nfunction runCommand(\n command: readonly [string, ...string[]],\n context: {\n stdin: string;\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n }\n): CommandRunResult {\n const spawnResult = spawnSyncWithTimeout(\n command[0],\n command.slice(1),\n { cwd: context.cwd, encoding: 'utf8', input: context.stdin, env: context.env },\n context.timeLimitSeconds\n );\n\n return {\n stdin: context.stdin,\n stdout: spawnResult.stdout,\n stderr: spawnResult.stderr,\n status: spawnResult.status ?? undefined,\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n };\n}\n\nfunction evaluateByLimits(context: {\n runResult: CommandRunResult;\n outputFiles: NonNullable<TestCaseResult['outputFiles']>;\n context: CommandJudgeContext;\n}): CommandJudgeCaseResult {\n if (context.runResult.status !== 0) {\n return { decisionCode: DecisionCode.RUNTIME_ERROR, stderr: context.runResult.stderr };\n }\n\n if (context.runResult.timeSeconds > context.context.timeLimitSeconds) {\n return { decisionCode: DecisionCode.TIME_LIMIT_EXCEEDED, stderr: context.runResult.stderr };\n }\n\n if (\n context.runResult.memoryBytes >\n (context.context.problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)\n ) {\n return { decisionCode: DecisionCode.MEMORY_LIMIT_EXCEEDED, stderr: context.runResult.stderr };\n }\n\n if (\n context.runResult.outputLimitExceeded ||\n context.runResult.stdout.length > context.context.outputLimitLength ||\n context.runResult.stderr.length > context.context.outputLimitLength\n ) {\n return { decisionCode: DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED, stderr: context.runResult.stderr };\n }\n\n const requiredOutputFileCount = context.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0;\n if (context.outputFiles.length < requiredOutputFileCount) {\n return { decisionCode: DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR };\n }\n\n return { decisionCode: DecisionCode.ACCEPTED };\n}\n"],"mappings":"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"}
|
package/dist/presets/command.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{parseArgs as e}from"../helpers/parseArgs.js";import{printTestCaseResult as t}from"../helpers/printTestCaseResult.js";import{getSandboxUserEnvOverrides as n,makeAccessibleToSandboxUser as r,wrapCommandWithSandboxUser as i}from"../helpers/sandboxUser.js";import{DecisionCode as a}from"../types/decisionCode.js";import{
|
|
1
|
+
import{parseArgs as e}from"../helpers/parseArgs.js";import{printTestCaseResult as t}from"../helpers/printTestCaseResult.js";import{getSandboxUserEnvOverrides as n,makeAccessibleToSandboxUser as r,wrapCommandWithSandboxUser as i}from"../helpers/sandboxUser.js";import{DecisionCode as a}from"../types/decisionCode.js";import{cleanWorkingDirectory as o,snapshotWorkingDirectory as s}from"../helpers/cleanWorkingDirectory.js";import{copyTestCaseFileInput as c}from"../helpers/copyTestCaseFileInput.js";import{findEntryPointFile as l}from"../helpers/findEntryPointFile.js";import{findLanguageDefinitionByPath as u}from"../helpers/findLanguageDefinitionByPath.js";import{judgeByStaticAnalysis as d}from"../helpers/judgeByStaticAnalysis.js";import{readOutputFiles as f}from"../helpers/readOutputFiles.js";import{runCustomRunner as p}from"../helpers/runCustomRunner.js";import{passesIsolationCheckInDebugMode as m}from"../helpers/runIsolationCheckInDebugMode.js";import{readProblemMarkdownFrontMatter as h}from"../helpers/readProblemMarkdownFrontMatter.js";import{readTestCases as g}from"../helpers/readTestCases.js";import{printDebugCwdBanner as _,printDebugExpectationFailureBanner as v,resolveCwds as y}from"../helpers/resolveCwds.js";import{spawnSyncWithTimeout as b}from"../helpers/spawnSyncWithTimeout.js";import x from"node:path";import{z as S}from"zod";const C=S.object({language:S.union([S.string(),S.array(S.string())]).optional()});async function w(t,n){let r=e(process.argv),i=C.parse(r.params),{cwds:a,isDebugMode:o}=await y(t,r.cwd),s=await h(t),c=await(n.readTestCases??A)(t),l={buildTimeoutSeconds:n.limits?.buildTimeoutSeconds??10,maxOutputLength:n.limits?.maxOutputLength??5e4},u=typeof s.timeLimitMs==`number`?s.timeLimitMs/1e3:n.runTimeoutSeconds??2,d={problemMarkdownFrontMatter:s,testCases:c,limits:l,timeLimitSeconds:u};if(o){let e=(l.buildTimeoutSeconds+c.length*u)*1e3;if(!await m(t,a,i,{expectedMaxDurationMs:e})){process.exitCode=1;return}}for(let e of a){o&&_(t,e);let r=await T(e.cwd,i,d,n);o&&!E(e,r)&&(process.exitCode=1,v(t,e))}}async function T(e,m,h,g){r(e);let{problemMarkdownFrontMatter:_,testCases:v,limits:y,timeLimitSeconds:b}=h,x=v[0]?.id??`prebuild`,S=await d(e,_);if(S)return t({testCaseId:x,...S}),{allAccepted:!1};let C=await l(e,m.language);if(!C)return t({testCaseId:x,decisionCode:a.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,stderr:`main file not found${m.language?`: language: ${m.language}`:``}`}),{allAccepted:!1};let w=u(C);if(!w)return t({testCaseId:x,decisionCode:a.WRONG_ANSWER,stderr:`unsupported language`}),{allAccepted:!1};let T={...process.env,CI:``,FORCE_COLOR:`0`},E=C;if(g.buildSubmission!==!1){if(w.prebuild)try{await w.prebuild(e);let t=await l(e,m.language);t&&(E=t)}catch(e){return t({testCaseId:x,decisionCode:a.BUILD_ERROR,stderr:e instanceof Error?e.message:String(e)}),{allAccepted:!1}}let n=w.buildCommand?.(E);if(n){let r=D(n,{cwd:e,env:T,testCaseId:x,limits:y});if(r)return t(r),{allAccepted:!1}}}let k=await s(e);if(v.length===0)return t({testCaseId:`default`,decisionCode:a.ACCEPTED}),{allAccepted:!0};let A=v.shared?.fileInputPath;for(let r of v){A&&await c(A,e),r.fileInputPath&&await c(r.fileInputPath,e);let s=w.command(E),l=r.input??``,u;try{g.resolveInput&&(l=await g.resolveInput({testCase:r,cwd:e,env:T})),u=g.runCommand?await p(()=>g.runCommand?.({testCase:r,command:i(s),stdin:l,cwd:e,env:{...T,...n(T)},timeLimitSeconds:b})):j(s,{stdin:l,cwd:e,env:T,timeLimitSeconds:b})}catch(n){return t({testCaseId:r.id,decisionCode:a.RUNTIME_ERROR,stdin:l,stderr:O(n)}),await o(e,k),{allAccepted:!1}}let d=await f(e,_.requiredOutputFilePaths??[]),m={timeLimitSeconds:b,outputLimitLength:y.maxOutputLength,problemMarkdownFrontMatter:{memoryLimitByte:_.memoryLimitByte,requiredOutputFilePaths:_.requiredOutputFilePaths}},h=M({runResult:u,outputFiles:d,context:m}),v=h;if(h.decisionCode===a.ACCEPTED)try{let e=await g.test?.({testCase:r,runResult:u,outputFiles:d,context:m});e&&(v={decisionCode:e.decisionCode??h.decisionCode,feedbackMarkdown:e.feedbackMarkdown,stderr:e.stderr})}catch(e){v={decisionCode:a.RUNTIME_ERROR,stderr:O(e)}}if(t({testCaseId:r.id,...v,exitStatus:u.status,stdin:u.stdin,stdout:u.stdout.slice(0,y.maxOutputLength)||void 0,stderr:(v.stderr??u.stderr).slice(0,y.maxOutputLength)||void 0,timeSeconds:u.timeSeconds,memoryBytes:u.memoryBytes,outputFiles:d.length>0?d:void 0}),await o(e,k),v.decisionCode!==a.ACCEPTED)return{allAccepted:!1}}return{allAccepted:!0}}function E(e,t){return t.allAccepted===(e.expectedResult===`accepted`)}function D(e,t){let n=b(e[0],e.slice(1),{cwd:t.cwd,encoding:`utf8`,env:t.env},t.limits.buildTimeoutSeconds),r=n.status??void 0;if(n.status!==0)return{testCaseId:t.testCaseId,decisionCode:a.BUILD_ERROR,exitStatus:r,stdout:n.stdout.slice(0,t.limits.maxOutputLength),stderr:n.stderr.slice(0,t.limits.maxOutputLength),timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes};if(n.timeSeconds>t.limits.buildTimeoutSeconds)return{testCaseId:t.testCaseId,decisionCode:a.BUILD_TIME_LIMIT_EXCEEDED,exitStatus:r,stdout:n.stdout.slice(0,t.limits.maxOutputLength),stderr:n.stderr.slice(0,t.limits.maxOutputLength),timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes};if(n.stdout.length>t.limits.maxOutputLength||n.stderr.length>t.limits.maxOutputLength)return{testCaseId:t.testCaseId,decisionCode:a.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED,exitStatus:r,stdout:n.stdout.slice(0,t.limits.maxOutputLength),stderr:n.stderr.slice(0,t.limits.maxOutputLength),timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes}}function O(e){return e instanceof Error?e.message:String(e)}function k(e){return{id:e.id,input:e.input,fileInputPath:e.fileInputPath}}async function A(e){let t=await g(x.join(e,`test_cases`)),n=t.map(e=>k(e));return t.shared?.fileInputPath?Object.assign(n,{shared:{fileInputPath:t.shared.fileInputPath}}):n}function j(e,t){let n=b(e[0],e.slice(1),{cwd:t.cwd,encoding:`utf8`,input:t.stdin,env:t.env},t.timeLimitSeconds);return{stdin:t.stdin,stdout:n.stdout,stderr:n.stderr,status:n.status??void 0,timeSeconds:n.timeSeconds,memoryBytes:n.memoryBytes}}function M(e){if(e.runResult.status!==0)return{decisionCode:a.RUNTIME_ERROR,stderr:e.runResult.stderr};if(e.runResult.timeSeconds>e.context.timeLimitSeconds)return{decisionCode:a.TIME_LIMIT_EXCEEDED,stderr:e.runResult.stderr};if(e.runResult.memoryBytes>(e.context.problemMarkdownFrontMatter.memoryLimitByte??1/0))return{decisionCode:a.MEMORY_LIMIT_EXCEEDED,stderr:e.runResult.stderr};if(e.runResult.outputLimitExceeded||e.runResult.stdout.length>e.context.outputLimitLength||e.runResult.stderr.length>e.context.outputLimitLength)return{decisionCode:a.OUTPUT_SIZE_LIMIT_EXCEEDED,stderr:e.runResult.stderr};let t=e.context.problemMarkdownFrontMatter.requiredOutputFilePaths?.length??0;return e.outputFiles.length<t?{decisionCode:a.MISSING_REQUIRED_OUTPUT_FILE_ERROR}:{decisionCode:a.ACCEPTED}}export{w as commandJudgePreset};
|
|
2
2
|
//# sourceMappingURL=command.js.map
|