@exercode/problem-utils 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +28 -0
  2. package/dist/cli/checkAllProblems.cjs +1 -1
  3. package/dist/cli/checkAllProblems.js +1 -1
  4. package/dist/cli/runSingleHarness.cjs +1 -1
  5. package/dist/cli/runSingleHarness.js +1 -1
  6. package/dist/helpers/cleanWorkingDirectory.cjs +1 -1
  7. package/dist/helpers/cleanWorkingDirectory.js +1 -1
  8. package/dist/helpers/compareExpectedOutputFiles.cjs +2 -0
  9. package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -0
  10. package/dist/helpers/compareExpectedOutputFiles.d.ts +45 -0
  11. package/dist/helpers/compareExpectedOutputFiles.js +2 -0
  12. package/dist/helpers/compareExpectedOutputFiles.js.map +1 -0
  13. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.cjs +1 -1
  14. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.cjs.map +1 -1
  15. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.js +1 -1
  16. package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.js.map +1 -1
  17. package/dist/helpers/defaultStdioHarness.cjs +1 -1
  18. package/dist/helpers/defaultStdioHarness.js +1 -1
  19. package/dist/helpers/findModelAnswerDirs.cjs +1 -1
  20. package/dist/helpers/findModelAnswerDirs.js +1 -1
  21. package/dist/helpers/judgeByStaticAnalysis.cjs +1 -1
  22. package/dist/helpers/judgeByStaticAnalysis.js +1 -1
  23. package/dist/helpers/readOutputFiles.cjs +1 -1
  24. package/dist/helpers/readOutputFiles.js +1 -1
  25. package/dist/helpers/readProblemMarkdownFrontMatter.cjs +1 -1
  26. package/dist/helpers/readProblemMarkdownFrontMatter.js +1 -1
  27. package/dist/helpers/readTestCases.cjs +1 -1
  28. package/dist/helpers/readTestCases.cjs.map +1 -1
  29. package/dist/helpers/readTestCases.d.ts +6 -0
  30. package/dist/helpers/readTestCases.js +1 -1
  31. package/dist/helpers/readTestCases.js.map +1 -1
  32. package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
  33. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
  34. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
  35. package/dist/helpers/safeFs.cjs +1 -1
  36. package/dist/helpers/safeFs.js +1 -1
  37. package/dist/helpers/sandboxUser.cjs +2 -2
  38. package/dist/helpers/sandboxUser.cjs.map +1 -1
  39. package/dist/helpers/sandboxUser.d.ts +5 -0
  40. package/dist/helpers/sandboxUser.js +2 -2
  41. package/dist/helpers/sandboxUser.js.map +1 -1
  42. package/dist/helpers/startHttpServer.cjs +1 -1
  43. package/dist/helpers/startHttpServer.js +1 -1
  44. package/dist/helpers/temporaryProblemDirCopy.cjs +1 -1
  45. package/dist/helpers/temporaryProblemDirCopy.cjs.map +1 -1
  46. package/dist/helpers/temporaryProblemDirCopy.d.ts +4 -1
  47. package/dist/helpers/temporaryProblemDirCopy.js +1 -1
  48. package/dist/helpers/temporaryProblemDirCopy.js.map +1 -1
  49. package/dist/index.cjs +1 -1
  50. package/dist/index.d.ts +3 -0
  51. package/dist/index.js +1 -1
  52. package/dist/presets/command.cjs +1 -1
  53. package/dist/presets/command.cjs.map +1 -1
  54. package/dist/presets/command.d.ts +35 -3
  55. package/dist/presets/command.js +1 -1
  56. package/dist/presets/command.js.map +1 -1
  57. package/dist/presets/evaluation.cjs +3 -3
  58. package/dist/presets/evaluation.js +3 -3
  59. package/dist/presets/guiCommand.cjs +3 -3
  60. package/dist/presets/guiCommand.cjs.map +1 -1
  61. package/dist/presets/guiCommand.d.ts +14 -2
  62. package/dist/presets/guiCommand.js +3 -3
  63. package/dist/presets/guiCommand.js.map +1 -1
  64. package/dist/presets/llm.cjs +1 -1
  65. package/dist/presets/llm.cjs.map +1 -1
  66. package/dist/presets/llm.d.ts +12 -10
  67. package/dist/presets/llm.js +1 -1
  68. package/dist/presets/llm.js.map +1 -1
  69. package/dist/presets/stdio.cjs +1 -1
  70. package/dist/presets/stdio.cjs.map +1 -1
  71. package/dist/presets/stdio.d.ts +3 -1
  72. package/dist/presets/stdio.js +1 -1
  73. package/dist/presets/stdio.js.map +1 -1
  74. package/dist/types/language.cjs +2 -2
  75. package/dist/types/language.js +2 -2
  76. package/package.json +1 -1
package/README.md CHANGED
@@ -30,3 +30,31 @@ bun x exercode-problem debug model_answers/python '{ "stdin": "1 2" }'
30
30
  The all-problem check judges serially by default because time limits are measured in wall-clock time; pass `--concurrency <n>` to parallelize when the checked problems are not timing-sensitive.
31
31
 
32
32
  A standard stdin/stdout problem must NOT commit a `judge.ts` or `debug.ts` that is identical to the default stdio harness: the absence of `judge.ts` marks the problem as standard, and committed copies would drift from the server's defaults. The CLI rejects such files; a file kept intentionally (e.g. to demonstrate the default harness) can add an explanatory comment to be treated as custom.
33
+
34
+ ## Test cases
35
+
36
+ A problem keeps its test cases under `test_cases/`. A test case id is the shared name of the following entries, and each entry is optional:
37
+
38
+ | Entry | Meaning |
39
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
40
+ | `<id>.in` | Standard input. Omit it (or leave it empty) when the program reads nothing. |
41
+ | `<id>.out` | Expected standard output. |
42
+ | `<id>.fin/` | Files copied into the working directory before the run (input files). |
43
+ | `<id>.fout/` | Expected output files, compared with the files of the same relative paths in the working directory. |
44
+ | `_shared.fin/` | Files copied into the working directory before every test case. |
45
+ | `<id>.json` | Configuration for a custom `judge.ts` that reads it itself; the presets ignore it, and it does not create a test case on its own. |
46
+
47
+ A test case whose id contains `example` (the judge server's rule, e.g. `example_1` or `01_example_small`) is an example shown to learners; every other case is hidden.
48
+
49
+ Standard output and text files are compared as space-separated tokens: consecutive white spaces count as one separator, and an expected token that contains a decimal point and parses as a finite number (e.g. `3.14`, but not `1`, `1e-3` or `1.0e309`) accepts a value within an absolute or relative error of `1e-6`. A file is text when it is valid UTF-8 without NUL bytes; other files (e.g. images) must match byte for byte. A received file larger than 8 MiB counts as not produced, an expected file larger than 8 MiB is an authoring error (the case is reported as a runtime error), and a file larger than 1 MiB is left out of the reported pair. When a file differs, the result carries `<name>_expected.<ext>` and `<name>_received.<ext>` so Exercode can show both (Exercode decides per test case whether a learner may see them, as it does for expected stdout).
50
+
51
+ How a missing expectation is treated depends on the harness:
52
+
53
+ - `stdioJudgePreset` (the default for problems without `judge.ts`) requires `<id>.out` or a non-empty `<id>.fout/` for every test case, so a standard problem cannot accept a run without checking it. A problem whose `problem.md` declares `requiredOutputFilePaths`, code rules (`requiredRegExpsInCode` etc.), `requiredSubmissionFilePaths` or `isManualScoringRequired` is exempt, because it is judged by those instead.
54
+ - `commandJudgePreset` without a `test` option checks whatever expectations exist, and a test case with neither only has to run within the limits. A `test` option replaces that comparison; it receives `testCase.output`, `testCase.fileOutputPath` and `cwd` and can call the exported `judgeAgainstExpectations` (or `compareStdoutAsSpaceSeparatedTokens` and `compareExpectedOutputFiles`). A custom `readTestCases` may return any test case type with `id` (plus optional `input` and `fileInputPath`); the default verdict judges any case that exposes a string `output` or `fileOutputPath`, which the default reader's `CommandTestCase` does.
55
+ - `guiCommandJudgePreset` passes the expectations to the problem's `test`, which decides everything.
56
+ - `llmJudgePreset` runs no program, so it copies no `.fin/`; it hands `<id>.in` as the prompt input and the whole entry (including `fileOutputPath`) to the problem's `test`.
57
+ - `stdioDebugPreset` copies the answer directory to a temporary directory and builds and runs it there together with `_shared.fin/` and the first example case's `.fin/` (the case's files win; hidden cases' inputs are never handed to learner code), so the answer directory is left untouched (its `node_modules`, and those of its ancestors, are linked into the copy rather than duplicated); files the program writes are reported only through `requiredOutputFilePaths`.
58
+ - `evaluationJudgePreset` does not use `test_cases/`.
59
+
60
+ `readTestCases` is exported for harnesses that enumerate `test_cases/` themselves.
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/defaultStdioHarness.cjs"),n=require("../types/testCaseResult.cjs"),r=require("../types/decisionCode.cjs"),i=require("../helpers/temporaryProblemDirCopy.cjs"),a=require("../helpers/runHarnessProcess.cjs"),o=require("../helpers/readProblemMarkdownFrontMatter.cjs"),s=require("../helpers/readTestCases.cjs"),c=require("../helpers/findModelAnswerDirs.cjs"),l=require("./runSingleHarness.cjs");let u=require("node:fs/promises");u=e.__toESM(u,1);let d=require("node:path");d=e.__toESM(d,1);const f=1e3,p=new Map(Object.entries(r.DecisionCode).map(([e,t])=>[t,e]));async function m(e){let n=v(e),r=d.default.resolve(n.rootDir),i=e=>(d.default.relative(r,e)||`.`).replaceAll(d.default.sep,`/`),a=await y(r),u=a.filter(e=>{let t=i(e);return n.only.length>0&&!n.only.some(e=>t.includes(e))?!1:!n.skip.some(e=>t.includes(e))});if(u.length===0)return console.error(a.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${r}.`:`All ${a.length} problem directories under ${r} were excluded by --only/--skip.`),1;let f=[],p=[];for(let e of u){let n=await t.findDefaultStdioHarnessFiles(e);if(n.length>0){f.push(`${i(e)}: ${l.formatDefaultHarnessError(n)}`);continue}let r=await c.findModelAnswerDirs(e);if(r.length===0){f.push(`${i(e)}: no model answers found under model_answers/`);continue}if(!await x(d.default.join(e,`judge.ts`))&&(await s.readTestCases(d.default.join(e,`test_cases`))).length===0){let t;try{t=await o.readProblemMarkdownFrontMatter(e)}catch(t){f.push(`${i(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!o.judgesWithoutTestCases(t)){f.push(`${i(e)}: ${l.MISSING_TEST_CASES_ERROR}`);continue}}let a=await c.findFailingModelAnswerDirs(e);p.push(...r.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...a.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of f)console.error(`✗ ${e}`);let m=d.default.resolve(process.argv[1]??``),g=0,_=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(n.concurrency,p.length))},async()=>{for(;_<p.length;){let e=p[_++];if(!e)return;let t=`${i(e.problemDir)} ${d.default.relative(e.problemDir,e.answerDir).replaceAll(d.default.sep,`/`)}`,n=await h(e,m);n===void 0?(g++,console.info(`✓ ${t}`)):(f.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${g} passed, ${f.length} failed (${p.length} runs, ${u.length} problems)`),f.length===0?0:1}async function h(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i.copyProblemDirToTemporaryRoot(e.problemDir))}catch(e){return S(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let o,s;try{o=g(e,await a.runHarnessProcess([`run`,t,`judge`,d.default.relative(e.problemDir,e.answerDir)],{cwd:r,env:_(),timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){o=S(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{s=await i.forciblyRemoveDirectory(n)}if(s)return o;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return o===void 0?c:S(`${o}; ${c}`)}function g(e,t){let{stdout:i,stderr:a}=t;if(t.failureReason!==void 0)return S(t.failureReason);if(t.exitCode!==0)return S(`harness exited with ${t.exitCode??`a signal`}${a.trim()?`: ${a.trim()}`:``}`);let o=i.split(/\r?\n/).filter(e=>e.startsWith(n.TEST_CASE_RESULT_PREFIX)),s=[];for(let e of o){let t;try{t=n.testCaseResultSchema.safeParse(JSON.parse(e.slice(n.TEST_CASE_RESULT_PREFIX.length)))}catch{t=void 0}if(!t?.success)return S(`malformed test case result line: ${e}`);s.push(t.data)}if(s.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==r.DecisionCode.ACCEPTED);return e?S(`${p.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===r.DecisionCode.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function _(){let e={...process.env};return delete e.EXERCODE_SANDBOX_USER,e}function v(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function y(e){let t=[];return await b(e,t),t.toSorted()}async function b(e,t){let n=await u.default.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await b(d.default.join(e,r.name),t)}async function x(e){try{return await u.default.stat(e),!0}catch{return!1}}function S(e){return e.length<=f?e:`${e.slice(0,f)}...`}exports.checkAllProblems=m;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/testCaseResult.cjs"),n=require("../helpers/defaultStdioHarness.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../types/decisionCode.cjs"),a=require("../helpers/temporaryProblemDirCopy.cjs"),o=require("../helpers/runHarnessProcess.cjs"),s=require("../helpers/readProblemMarkdownFrontMatter.cjs"),c=require("../helpers/findModelAnswerDirs.cjs"),l=require("./runSingleHarness.cjs");let u=require("node:path");u=e.__toESM(u,1);let d=require("node:fs/promises");d=e.__toESM(d,1);const f=1e3,p=new Map(Object.entries(i.DecisionCode).map(([e,t])=>[t,e]));async function m(e){let t=v(e),i=u.default.resolve(t.rootDir),a=e=>(u.default.relative(i,e)||`.`).replaceAll(u.default.sep,`/`),o=await y(i),d=o.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(d.length===0)return console.error(o.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${o.length} problem directories under ${i} were excluded by --only/--skip.`),1;let f=[],p=[];for(let e of d){let t=await n.findDefaultStdioHarnessFiles(e);if(t.length>0){f.push(`${a(e)}: ${l.formatDefaultHarnessError(t)}`);continue}let i=await c.findModelAnswerDirs(e);if(i.length===0){f.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await x(u.default.join(e,`judge.ts`))&&(await r.readTestCases(u.default.join(e,`test_cases`))).length===0){let t;try{t=await s.readProblemMarkdownFrontMatter(e)}catch(t){f.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!s.judgesWithoutTestCases(t)){f.push(`${a(e)}: ${l.MISSING_TEST_CASES_ERROR}`);continue}}let o=await c.findFailingModelAnswerDirs(e);p.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...o.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of f)console.error(`✗ ${e}`);let m=u.default.resolve(process.argv[1]??``),g=0,_=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,p.length))},async()=>{for(;_<p.length;){let e=p[_++];if(!e)return;let t=`${a(e.problemDir)} ${u.default.relative(e.problemDir,e.answerDir).replaceAll(u.default.sep,`/`)}`,n=await h(e,m);n===void 0?(g++,console.info(`✓ ${t}`)):(f.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${g} passed, ${f.length} failed (${p.length} runs, ${d.length} problems)`),f.length===0?0:1}async function h(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await a.copyProblemDirToTemporaryRoot(e.problemDir))}catch(e){return S(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let i,s;try{i=g(e,await o.runHarnessProcess([`run`,t,`judge`,u.default.relative(e.problemDir,e.answerDir)],{cwd:r,env:_(),timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){i=S(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{s=await a.forciblyRemoveDirectory(n)}if(s)return i;let c=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return i===void 0?c:S(`${i}; ${c}`)}function g(e,n){let{stdout:r,stderr:a}=n;if(n.failureReason!==void 0)return S(n.failureReason);if(n.exitCode!==0)return S(`harness exited with ${n.exitCode??`a signal`}${a.trim()?`: ${a.trim()}`:``}`);let o=r.split(/\r?\n/).filter(e=>e.startsWith(t.TEST_CASE_RESULT_PREFIX)),s=[];for(let e of o){let n;try{n=t.testCaseResultSchema.safeParse(JSON.parse(e.slice(t.TEST_CASE_RESULT_PREFIX.length)))}catch{n=void 0}if(!n?.success)return S(`malformed test case result line: ${e}`);s.push(n.data)}if(s.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=s.find(e=>e.decisionCode!==i.DecisionCode.ACCEPTED);return e?S(`${p.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return s.every(e=>e.decisionCode===i.DecisionCode.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function _(){let e={...process.env};return delete e.EXERCODE_SANDBOX_USER,e}function v(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function y(e){let t=[];return await b(e,t),t.toSorted()}async function b(e,t){let n=await d.default.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await b(u.default.join(e,r.name),t)}async function x(e){try{return await d.default.stat(e),!0}catch{return!1}}function S(e){return e.length<=f?e:`${e.slice(0,f)}...`}exports.checkAllProblems=m;
2
2
  //# sourceMappingURL=checkAllProblems.cjs.map
@@ -1,2 +1,2 @@
1
- import{findDefaultStdioHarnessFiles as e}from"../helpers/defaultStdioHarness.js";import{TEST_CASE_RESULT_PREFIX as t,testCaseResultSchema as n}from"../types/testCaseResult.js";import{DecisionCode as r}from"../types/decisionCode.js";import{copyProblemDirToTemporaryRoot as i,forciblyRemoveDirectory as a}from"../helpers/temporaryProblemDirCopy.js";import{runHarnessProcess as o}from"../helpers/runHarnessProcess.js";import{judgesWithoutTestCases as s,readProblemMarkdownFrontMatter as c}from"../helpers/readProblemMarkdownFrontMatter.js";import{readTestCases as l}from"../helpers/readTestCases.js";import{findFailingModelAnswerDirs as u,findModelAnswerDirs as d}from"../helpers/findModelAnswerDirs.js";import{MISSING_TEST_CASES_ERROR as f,formatDefaultHarnessError as p}from"./runSingleHarness.js";import m from"node:fs/promises";import h from"node:path";const g=1e3,_=new Map(Object.entries(r).map(([e,t])=>[t,e]));async function v(t){let n=S(t),r=h.resolve(n.rootDir),i=e=>(h.relative(r,e)||`.`).replaceAll(h.sep,`/`),a=await C(r),o=a.filter(e=>{let t=i(e);return n.only.length>0&&!n.only.some(e=>t.includes(e))?!1:!n.skip.some(e=>t.includes(e))});if(o.length===0)return console.error(a.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${r}.`:`All ${a.length} problem directories under ${r} were excluded by --only/--skip.`),1;let m=[],g=[];for(let t of o){let n=await e(t);if(n.length>0){m.push(`${i(t)}: ${p(n)}`);continue}let r=await d(t);if(r.length===0){m.push(`${i(t)}: no model answers found under model_answers/`);continue}if(!await T(h.join(t,`judge.ts`))&&(await l(h.join(t,`test_cases`))).length===0){let e;try{e=await c(t)}catch(e){m.push(`${i(t)}: failed to read the problem markdown front matter: ${e instanceof Error?e.message:String(e)}`);continue}if(!s(e)){m.push(`${i(t)}: ${f}`);continue}}let a=await u(t);g.push(...r.map(e=>({problemDir:t,answerDir:e,expectation:`accepted`})),...a.map(e=>({problemDir:t,answerDir:e,expectation:`rejected`})))}for(let e of m)console.error(`✗ ${e}`);let _=h.resolve(process.argv[1]??``),v=0,b=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(n.concurrency,g.length))},async()=>{for(;b<g.length;){let e=g[b++];if(!e)return;let t=`${i(e.problemDir)} ${h.relative(e.problemDir,e.answerDir).replaceAll(h.sep,`/`)}`,n=await y(e,_);n===void 0?(v++,console.info(`✓ ${t}`)):(m.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${v} passed, ${m.length} failed (${g.length} runs, ${o.length} problems)`),m.length===0?0:1}async function y(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await i(e.problemDir))}catch(e){return E(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let s,c;try{s=b(e,await o([`run`,t,`judge`,h.relative(e.problemDir,e.answerDir)],{cwd:r,env:x(),timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){s=E(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{c=await a(n)}if(c)return s;let l=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return s===void 0?l:E(`${s}; ${l}`)}function b(e,i){let{stdout:a,stderr:o}=i;if(i.failureReason!==void 0)return E(i.failureReason);if(i.exitCode!==0)return E(`harness exited with ${i.exitCode??`a signal`}${o.trim()?`: ${o.trim()}`:``}`);let s=a.split(/\r?\n/).filter(e=>e.startsWith(t)),c=[];for(let e of s){let r;try{r=n.safeParse(JSON.parse(e.slice(t.length)))}catch{r=void 0}if(!r?.success)return E(`malformed test case result line: ${e}`);c.push(r.data)}if(c.length===0)return`no test case results were printed`;if(e.expectation===`accepted`){let e=c.find(e=>e.decisionCode!==r.ACCEPTED);return e?E(`${_.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return c.every(e=>e.decisionCode===r.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function x(){let e={...process.env};return delete e.EXERCODE_SANDBOX_USER,e}function S(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function C(e){let t=[];return await w(e,t),t.toSorted()}async function w(e,t){let n=await m.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await w(h.join(e,r.name),t)}async function T(e){try{return await m.stat(e),!0}catch{return!1}}function E(e){return e.length<=g?e:`${e.slice(0,g)}...`}export{v as checkAllProblems};
1
+ import{TEST_CASE_RESULT_PREFIX as e,testCaseResultSchema as t}from"../types/testCaseResult.js";import{findDefaultStdioHarnessFiles as n}from"../helpers/defaultStdioHarness.js";import{readTestCases as r}from"../helpers/readTestCases.js";import{DecisionCode as i}from"../types/decisionCode.js";import{copyProblemDirToTemporaryRoot as a,forciblyRemoveDirectory as o}from"../helpers/temporaryProblemDirCopy.js";import{runHarnessProcess as s}from"../helpers/runHarnessProcess.js";import{judgesWithoutTestCases as c,readProblemMarkdownFrontMatter as l}from"../helpers/readProblemMarkdownFrontMatter.js";import{findFailingModelAnswerDirs as u,findModelAnswerDirs as d}from"../helpers/findModelAnswerDirs.js";import{MISSING_TEST_CASES_ERROR as f,formatDefaultHarnessError as p}from"./runSingleHarness.js";import m from"node:path";import h from"node:fs/promises";const g=1e3,_=new Map(Object.entries(i).map(([e,t])=>[t,e]));async function v(e){let t=S(e),i=m.resolve(t.rootDir),a=e=>(m.relative(i,e)||`.`).replaceAll(m.sep,`/`),o=await C(i),s=o.filter(e=>{let n=a(e);return t.only.length>0&&!t.only.some(e=>n.includes(e))?!1:!t.skip.some(e=>n.includes(e))});if(s.length===0)return console.error(o.length===0?`No problem directories (containing problem.md or <id>.problem.md) found under ${i}.`:`All ${o.length} problem directories under ${i} were excluded by --only/--skip.`),1;let h=[],g=[];for(let e of s){let t=await n(e);if(t.length>0){h.push(`${a(e)}: ${p(t)}`);continue}let i=await d(e);if(i.length===0){h.push(`${a(e)}: no model answers found under model_answers/`);continue}if(!await T(m.join(e,`judge.ts`))&&(await r(m.join(e,`test_cases`))).length===0){let t;try{t=await l(e)}catch(t){h.push(`${a(e)}: failed to read the problem markdown front matter: ${t instanceof Error?t.message:String(t)}`);continue}if(!c(t)){h.push(`${a(e)}: ${f}`);continue}}let o=await u(e);g.push(...i.map(t=>({problemDir:e,answerDir:t,expectation:`accepted`})),...o.map(t=>({problemDir:e,answerDir:t,expectation:`rejected`})))}for(let e of h)console.error(`✗ ${e}`);let _=m.resolve(process.argv[1]??``),v=0,b=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t.concurrency,g.length))},async()=>{for(;b<g.length;){let e=g[b++];if(!e)return;let t=`${a(e.problemDir)} ${m.relative(e.problemDir,e.answerDir).replaceAll(m.sep,`/`)}`,n=await y(e,_);n===void 0?(v++,console.info(`✓ ${t}`)):(h.push(`${t}: ${n}`),console.error(`✗ ${t}: ${n}`))}})),console.info(`\n${v} passed, ${h.length} failed (${g.length} runs, ${s.length} problems)`),h.length===0?0:1}async function y(e,t){let n,r;try{({tempRoot:n,copiedProblemDir:r}=await a(e.problemDir))}catch(e){return E(`failed to copy the problem directory to a temporary location: ${e instanceof Error?e.message:String(e)}`)}let i,c;try{i=b(e,await s([`run`,t,`judge`,m.relative(e.problemDir,e.answerDir)],{cwd:r,env:x(),timeoutMs:6e5,maxOutputBytes:67108864,tempRoot:n}))}catch(e){i=E(`harness execution failed: ${e instanceof Error?e.message:String(e)}`)}finally{c=await o(n)}if(c)return i;let l=`failed to remove the temporary copy at ${n} (judged code may have left permission-locked files)`;return i===void 0?l:E(`${i}; ${l}`)}function b(n,r){let{stdout:a,stderr:o}=r;if(r.failureReason!==void 0)return E(r.failureReason);if(r.exitCode!==0)return E(`harness exited with ${r.exitCode??`a signal`}${o.trim()?`: ${o.trim()}`:``}`);let s=a.split(/\r?\n/).filter(t=>t.startsWith(e)),c=[];for(let n of s){let r;try{r=t.safeParse(JSON.parse(n.slice(e.length)))}catch{r=void 0}if(!r?.success)return E(`malformed test case result line: ${n}`);c.push(r.data)}if(c.length===0)return`no test case results were printed`;if(n.expectation===`accepted`){let e=c.find(e=>e.decisionCode!==i.ACCEPTED);return e?E(`${_.get(e.decisionCode)??e.decisionCode} on test case ${e.testCaseId}${e.stderr?.trim()?`: ${e.stderr.trim()}`:``}`):void 0}return c.every(e=>e.decisionCode===i.ACCEPTED)?`expected at least one failing test case, but all test cases were accepted`:void 0}function x(){let e={...process.env};return delete e.EXERCODE_SANDBOX_USER,e}function S(e){let t={rootDir:`.`,concurrency:1,only:[],skip:[]},n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(i===void 0)break;if(i===`--concurrency`||i===`--only`||i===`--skip`){let n=e[++r];if(n===void 0)throw Error(`${i} requires a value`);if(i===`--concurrency`){if(t.concurrency=Number(n),!Number.isInteger(t.concurrency)||t.concurrency<=0)throw Error(`--concurrency requires a positive integer, but got ${n}`)}else i===`--only`?t.only.push(n):t.skip.push(n)}else if(i.startsWith(`--`))throw Error(`Unknown option: ${i}`);else if(n)throw Error(`Only one root directory can be specified, but got both ${t.rootDir} and ${i}`);else t.rootDir=i,n=!0}return t}async function C(e){let t=[];return await w(e,t),t.toSorted()}async function w(e,t){let n=await h.readdir(e,{withFileTypes:!0});if(n.some(e=>e.isFile()&&(e.name===`problem.md`||e.name.endsWith(`.problem.md`)))){t.push(e);return}for(let r of n)!r.isDirectory()||r.name===`node_modules`||r.name.startsWith(`.`)||await w(m.join(e,r.name),t)}async function T(e){try{return await h.stat(e),!0}catch{return!1}}function E(e){return e.length<=g?e:`${e.slice(0,g)}...`}export{v as checkAllProblems};
2
2
  //# sourceMappingURL=checkAllProblems.js.map
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/defaultStdioHarness.cjs"),n=require("../helpers/readProblemMarkdownFrontMatter.cjs"),r=require("../helpers/readTestCases.cjs"),i=require("../presets/stdio.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("node:child_process");o=e.__toESM(o,1);let s=require("node:fs");s=e.__toESM(s,1);const c=`a standard stdio problem (without judge.ts) needs at least one test case under test_cases/, static-analysis rules (e.g. requiredRegExpsInCode), or isManualScoringRequired`;async function l(e){let l=process.cwd(),d=await t.findDefaultStdioHarnessFiles(l);if(d.length>0)return console.error(u(d)),1;let f=e===`judge`?`judge.ts`:`debug.ts`;if(s.default.existsSync(a.default.join(l,f))){let e=o.default.spawnSync(process.execPath,[`run`,f,...process.argv.slice(2)],{cwd:l,stdio:`inherit`});return e.error?(console.error(`failed to run ${f}: ${e.error.message}`),1):e.status??1}return e===`debug`&&s.default.existsSync(a.default.join(l,`judge.ts`))?(console.error(`This problem has a custom judge.ts but no debug.ts; add a debug.ts to support debugging.`),1):e===`judge`&&(await r.readTestCases(a.default.join(l,`test_cases`))).length===0&&!n.judgesWithoutTestCases(await n.readProblemMarkdownFrontMatter(l))?(console.error(c),1):(await(e===`judge`?i.stdioJudgePreset(l):i.stdioDebugPreset(l)),0)}function u(e){let t=e.join(` and `);return`${t} must not be committed because the content is identical to the default stdio harness.
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/defaultStdioHarness.cjs"),n=require("../helpers/readTestCases.cjs"),r=require("../helpers/readProblemMarkdownFrontMatter.cjs"),i=require("../presets/stdio.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);let s=require("node:child_process");s=e.__toESM(s,1);const c=`a standard stdio problem (without judge.ts) needs at least one test case under test_cases/, static-analysis rules (e.g. requiredRegExpsInCode), or isManualScoringRequired`;async function l(e){let l=process.cwd(),d=await t.findDefaultStdioHarnessFiles(l);if(d.length>0)return console.error(u(d)),1;let f=e===`judge`?`judge.ts`:`debug.ts`;if(a.default.existsSync(o.default.join(l,f))){let e=s.default.spawnSync(process.execPath,[`run`,f,...process.argv.slice(2)],{cwd:l,stdio:`inherit`});return e.error?(console.error(`failed to run ${f}: ${e.error.message}`),1):e.status??1}return e===`debug`&&a.default.existsSync(o.default.join(l,`judge.ts`))?(console.error(`This problem has a custom judge.ts but no debug.ts; add a debug.ts to support debugging.`),1):e===`judge`&&(await n.readTestCases(o.default.join(l,`test_cases`))).length===0&&!r.judgesWithoutTestCases(await r.readProblemMarkdownFrontMatter(l))?(console.error(c),1):(await(e===`judge`?i.stdioJudgePreset(l):i.stdioDebugPreset(l)),0)}function u(e){let t=e.join(` and `);return`${t} must not be committed because the content is identical to the default stdio harness.
2
2
  A problem without judge.ts is automatically judged with stdioJudgePreset and debugged with stdioDebugPreset,
3
3
  so delete ${t} and run the problem with \`bun x exercode-problem judge <answerDir>\` or \`bun x exercode-problem debug <answerDir>\`.
4
4
  If the file intentionally demonstrates the default harness, add an explanatory comment to mark it as custom.`}exports.MISSING_TEST_CASES_ERROR=c,exports.formatDefaultHarnessError=u,exports.runSingleHarness=l;
@@ -1,4 +1,4 @@
1
- import{findDefaultStdioHarnessFiles as e}from"../helpers/defaultStdioHarness.js";import{judgesWithoutTestCases as t,readProblemMarkdownFrontMatter as n}from"../helpers/readProblemMarkdownFrontMatter.js";import{readTestCases as r}from"../helpers/readTestCases.js";import{stdioDebugPreset as i,stdioJudgePreset as a}from"../presets/stdio.js";import o from"node:path";import s from"node:child_process";import c from"node:fs";const l=`a standard stdio problem (without judge.ts) needs at least one test case under test_cases/, static-analysis rules (e.g. requiredRegExpsInCode), or isManualScoringRequired`;async function u(u){let f=process.cwd(),p=await e(f);if(p.length>0)return console.error(d(p)),1;let m=u===`judge`?`judge.ts`:`debug.ts`;if(c.existsSync(o.join(f,m))){let e=s.spawnSync(process.execPath,[`run`,m,...process.argv.slice(2)],{cwd:f,stdio:`inherit`});return e.error?(console.error(`failed to run ${m}: ${e.error.message}`),1):e.status??1}return u===`debug`&&c.existsSync(o.join(f,`judge.ts`))?(console.error(`This problem has a custom judge.ts but no debug.ts; add a debug.ts to support debugging.`),1):u===`judge`&&(await r(o.join(f,`test_cases`))).length===0&&!t(await n(f))?(console.error(l),1):(await(u===`judge`?a(f):i(f)),0)}function d(e){let t=e.join(` and `);return`${t} must not be committed because the content is identical to the default stdio harness.
1
+ import{findDefaultStdioHarnessFiles as e}from"../helpers/defaultStdioHarness.js";import{readTestCases as t}from"../helpers/readTestCases.js";import{judgesWithoutTestCases as n,readProblemMarkdownFrontMatter as r}from"../helpers/readProblemMarkdownFrontMatter.js";import{stdioDebugPreset as i,stdioJudgePreset as a}from"../presets/stdio.js";import o from"node:fs";import s from"node:path";import c from"node:child_process";const l=`a standard stdio problem (without judge.ts) needs at least one test case under test_cases/, static-analysis rules (e.g. requiredRegExpsInCode), or isManualScoringRequired`;async function u(u){let f=process.cwd(),p=await e(f);if(p.length>0)return console.error(d(p)),1;let m=u===`judge`?`judge.ts`:`debug.ts`;if(o.existsSync(s.join(f,m))){let e=c.spawnSync(process.execPath,[`run`,m,...process.argv.slice(2)],{cwd:f,stdio:`inherit`});return e.error?(console.error(`failed to run ${m}: ${e.error.message}`),1):e.status??1}return u===`debug`&&o.existsSync(s.join(f,`judge.ts`))?(console.error(`This problem has a custom judge.ts but no debug.ts; add a debug.ts to support debugging.`),1):u===`judge`&&(await t(s.join(f,`test_cases`))).length===0&&!n(await r(f))?(console.error(l),1):(await(u===`judge`?a(f):i(f)),0)}function d(e){let t=e.join(` and `);return`${t} must not be committed because the content is identical to the default stdio harness.
2
2
  A problem without judge.ts is automatically judged with stdioJudgePreset and debugged with stdioDebugPreset,
3
3
  so delete ${t} and run the problem with \`bun x exercode-problem judge <answerDir>\` or \`bun x exercode-problem debug <answerDir>\`.
4
4
  If the file intentionally demonstrates the default harness, add an explanatory comment to mark it as custom.`}export{l as MISSING_TEST_CASES_ERROR,d as formatDefaultHarnessError,u as runSingleHarness};
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("node:fs");r=e.__toESM(r,1);async function i(e){let t=new Map;return await o(e,e,t),t}async function a(e,t){await s(e,e,t)}async function o(e,t,r){for(let i of await l(t)){let a=n.default.join(t,i.name);r.set(n.default.relative(e,a),c(i)),i.isDirectory()&&await o(e,a,r)}}async function s(e,r,i){for(let a of await l(r)){let o=n.default.join(r,a.name),l=i.get(n.default.relative(e,o));if(l===void 0||l!==c(a)){await t.forceRemove(o);continue}a.isDirectory()&&await s(e,o,i)}}function c(e){return e.isSymbolicLink()?`symlink`:e.isDirectory()?`directory`:e.isFile()?`file`:`other`}async function l(e){try{return await r.default.promises.readdir(e,{withFileTypes:!0})}catch(n){if(!t.sandboxUserName)throw n;return t.relaxPermissionsAsSandboxUser(e),await r.default.promises.readdir(e,{withFileTypes:!0})}}exports.cleanWorkingDirectory=a,exports.snapshotWorkingDirectory=i;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);async function i(e){let t=new Map;return await o(e,e,t),t}async function a(e,t){await s(e,e,t)}async function o(e,t,n){for(let i of await l(t)){let a=r.default.join(t,i.name);n.set(r.default.relative(e,a),c(i)),i.isDirectory()&&await o(e,a,n)}}async function s(e,n,i){for(let a of await l(n)){let o=r.default.join(n,a.name),l=i.get(r.default.relative(e,o));if(l===void 0||l!==c(a)){await t.forceRemove(o);continue}a.isDirectory()&&await s(e,o,i)}}function c(e){return e.isSymbolicLink()?`symlink`:e.isDirectory()?`directory`:e.isFile()?`file`:`other`}async function l(e){try{return await n.default.promises.readdir(e,{withFileTypes:!0})}catch(r){if(!t.sandboxUserName)throw r;return t.relaxPermissionsAsSandboxUser(e),await n.default.promises.readdir(e,{withFileTypes:!0})}}exports.cleanWorkingDirectory=a,exports.snapshotWorkingDirectory=i;
2
2
  //# sourceMappingURL=cleanWorkingDirectory.cjs.map
@@ -1,2 +1,2 @@
1
- import{forceRemove as e,relaxPermissionsAsSandboxUser as t,sandboxUserName as n}from"./sandboxUser.js";import r from"node:path";import i from"node:fs";async function a(e){let t=new Map;return await s(e,e,t),t}async function o(e,t){await c(e,e,t)}async function s(e,t,n){for(let i of await u(t)){let a=r.join(t,i.name);n.set(r.relative(e,a),l(i)),i.isDirectory()&&await s(e,a,n)}}async function c(t,n,i){for(let a of await u(n)){let o=r.join(n,a.name),s=i.get(r.relative(t,o));if(s===void 0||s!==l(a)){await e(o);continue}a.isDirectory()&&await c(t,o,i)}}function l(e){return e.isSymbolicLink()?`symlink`:e.isDirectory()?`directory`:e.isFile()?`file`:`other`}async function u(e){try{return await i.promises.readdir(e,{withFileTypes:!0})}catch(r){if(!n)throw r;return t(e),await i.promises.readdir(e,{withFileTypes:!0})}}export{o as cleanWorkingDirectory,a as snapshotWorkingDirectory};
1
+ import{forceRemove as e,relaxPermissionsAsSandboxUser as t,sandboxUserName as n}from"./sandboxUser.js";import r from"node:fs";import i from"node:path";async function a(e){let t=new Map;return await s(e,e,t),t}async function o(e,t){await c(e,e,t)}async function s(e,t,n){for(let r of await u(t)){let a=i.join(t,r.name);n.set(i.relative(e,a),l(r)),r.isDirectory()&&await s(e,a,n)}}async function c(t,n,r){for(let a of await u(n)){let o=i.join(n,a.name),s=r.get(i.relative(t,o));if(s===void 0||s!==l(a)){await e(o);continue}a.isDirectory()&&await c(t,o,r)}}function l(e){return e.isSymbolicLink()?`symlink`:e.isDirectory()?`directory`:e.isFile()?`file`:`other`}async function u(e){try{return await r.promises.readdir(e,{withFileTypes:!0})}catch(i){if(!n)throw i;return t(e),await r.promises.readdir(e,{withFileTypes:!0})}}export{o as cleanWorkingDirectory,a as snapshotWorkingDirectory};
2
2
  //# sourceMappingURL=cleanWorkingDirectory.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./compareStdoutAsSpaceSeparatedTokens.cjs"),n=require("./printTestCaseResult.cjs"),r=require("./readOutputFiles.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);const o=8388608;async function s(e,t){let r=[],s=[],c=(await i.default.promises.readdir(t,{withFileTypes:!0,recursive:!0})).filter(e=>e.isFile()).map(e=>({absolutePath:a.default.join(e.parentPath,e.name),relativePath:a.default.posix.join(...a.default.relative(t,e.parentPath).split(a.default.sep),e.name)})).toSorted((e,t)=>e.relativePath<t.relativePath?-1:+(e.relativePath>t.relativePath));for(let{absolutePath:t,relativePath:l}of c){if((await i.default.promises.stat(t)).size>8388608)throw Error(`expected output file ${l} exceeds ${o} bytes`);let c=await i.default.promises.readFile(t),p=await d(e,a.default.join(e,l));p&&f(c,p)||(r.push(l),c.length<=1048576&&s.push(n.encodeFileForTestCaseResult(u(l,`expected`),c)),p&&p.length<=1048576&&s.push(n.encodeFileForTestCaseResult(u(l,`received`),p)))}return{matches:r.length===0,mismatchedPaths:r,outputFiles:s}}async function c(e){let{stdout:n,expectedStdout:r,fileOutputPath:i,cwd:a}=e,o=[...e.outputFiles],c=r===void 0||t.compareStdoutAsSpaceSeparatedTokens(n,r);if(i!==void 0){let e=await s(a,i);e.matches||(c=!1,o=l(o,e))}return{matches:c,outputFiles:o}}function l(e,t){let n=new Set(t.outputFiles.map(e=>e.path)),r=new Set(t.mismatchedPaths.filter(e=>n.has(u(e,`received`))));return[...e.filter(e=>!r.has(e.path)&&!n.has(e.path)),...t.outputFiles]}function u(e,t){let{dir:n,name:r,ext:i}=a.default.posix.parse(e);return a.default.posix.join(n,`${r}_${t}${i}`)}async function d(e,t){if(await r.isSafeSubmissionOutputPath(e,t))try{let e=await i.default.promises.stat(t);return!e.isFile()||e.size>8388608?void 0:await i.default.promises.readFile(t)}catch{return}}function f(e,n){return m(e)?m(n)?t.compareStdoutAsSpaceSeparatedTokens(n.toString(`utf8`),e.toString(`utf8`)):!1:n.equals(e)}const p=new TextDecoder(`utf-8`,{fatal:!0});function m(e){if(e.includes(0))return!1;try{return p.decode(e),!0}catch{return!1}}exports.MAX_COMPARED_FILE_BYTES=o,exports.MAX_REPORTED_FILE_BYTES=1048576,exports.compareExpectedOutputFiles=s,exports.judgeAgainstExpectations=c,exports.mergeComparisonOutputFiles=l;
2
+ //# sourceMappingURL=compareExpectedOutputFiles.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compareExpectedOutputFiles.cjs","names":["fs","path","encodeFileForTestCaseResult","compareStdoutAsSpaceSeparatedTokens","isSafeSubmissionOutputPath"],"sources":["../../src/helpers/compareExpectedOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nimport { compareStdoutAsSpaceSeparatedTokens } from './compareStdoutAsSpaceSeparatedTokens.js';\nimport { encodeFileForTestCaseResult } from './printTestCaseResult.js';\nimport { isSafeSubmissionOutputPath } from './readOutputFiles.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\n\n// A submission controls the received files, so bound what the trusted harness reads and reports.\nexport const MAX_COMPARED_FILE_BYTES = 8 * 1024 * 1024;\nexport const MAX_REPORTED_FILE_BYTES = 1024 * 1024;\n\nexport interface ExpectedOutputFilesComparison {\n /** Whether every file under the expected directory has a matching file in the working directory. */\n matches: boolean;\n /** Relative paths (from the working directory) of the files that are missing or different. */\n mismatchedPaths: string[];\n /**\n * `<name>_expected.<ext>` / `<name>_received.<ext>` pairs for each mismatched file, in the\n * format Exercode renders side by side. A received file that is missing, unreadable, not a\n * regular file or larger than `MAX_COMPARED_FILE_BYTES` yields only the expected entry, and a\n * file larger than `MAX_REPORTED_FILE_BYTES` is left out of the pair. Exercode decides per test case whether a learner may see these files.\n */\n outputFiles: OutputFile[];\n}\n\n/**\n * Compare the files under a test case's `.fout` directory with the files the submission wrote to\n * its working directory. Text files are compared like stdout (space-separated tokens with a\n * numeric tolerance); binary files must match byte for byte.\n */\nexport async function compareExpectedOutputFiles(\n cwd: string,\n fileOutputPath: string\n): Promise<ExpectedOutputFilesComparison> {\n const mismatchedPaths: string[] = [];\n const outputFiles: OutputFile[] = [];\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n const expectedFiles = dirents\n .filter((dirent) => dirent.isFile())\n .map((dirent) => ({\n absolutePath: path.join(dirent.parentPath, dirent.name),\n // POSIX separators: `requiredOutputFilePaths` and the result format use them on every platform.\n relativePath: path.posix.join(...path.relative(fileOutputPath, dirent.parentPath).split(path.sep), dirent.name),\n }))\n .toSorted((a, b) => (a.relativePath < b.relativePath ? -1 : a.relativePath > b.relativePath ? 1 : 0));\n for (const { absolutePath, relativePath } of expectedFiles) {\n // A larger expectation could never be matched by a received file, so it is an authoring error.\n const expectedStats = await fs.promises.stat(absolutePath);\n if (expectedStats.size > MAX_COMPARED_FILE_BYTES) {\n throw new Error(`expected output file ${relativePath} exceeds ${MAX_COMPARED_FILE_BYTES} bytes`);\n }\n const expected = await fs.promises.readFile(absolutePath);\n const received = await readReceivedFile(cwd, path.join(cwd, relativePath));\n if (received && fileContentsMatch(expected, received)) continue;\n\n mismatchedPaths.push(relativePath);\n // A file too large to ship in a result line is left out of the pair rather than truncated.\n if (expected.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'expected'), expected));\n }\n if (received && received.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'received'), received));\n }\n }\n return { matches: mismatchedPaths.length === 0, mismatchedPaths, outputFiles };\n}\n\n/**\n * The default verdict of the presets: `.out` decides stdout and `.fout/` decides output files, both\n * checked so a result carries every mismatch; either expectation may be absent. Returns whether the\n * run matched and the output files to report (the plain copies of mismatched required output files\n * replaced by their comparison pairs).\n */\nexport async function judgeAgainstExpectations(context: {\n stdout: string;\n expectedStdout: string | undefined;\n fileOutputPath: string | undefined;\n cwd: string;\n outputFiles: readonly OutputFile[];\n}): Promise<{ matches: boolean; outputFiles: OutputFile[] }> {\n const { stdout, expectedStdout, fileOutputPath, cwd } = context;\n let outputFiles = [...context.outputFiles];\n let matches = expectedStdout === undefined || compareStdoutAsSpaceSeparatedTokens(stdout, expectedStdout);\n if (fileOutputPath !== undefined) {\n const comparison = await compareExpectedOutputFiles(cwd, fileOutputPath);\n if (!comparison.matches) {\n matches = false;\n outputFiles = mergeComparisonOutputFiles(outputFiles, comparison);\n }\n }\n return { matches, outputFiles };\n}\n\n/**\n * Replace the plain copies of mismatched required output files with the comparison pairs, so a\n * result shows `<name>_expected.<ext>` / `<name>_received.<ext>` instead of a lone `<name>.<ext>`.\n */\nexport function mergeComparisonOutputFiles(\n outputFiles: readonly OutputFile[],\n comparison: ExpectedOutputFilesComparison\n): OutputFile[] {\n const comparisonPaths = new Set(comparison.outputFiles.map((file) => file.path));\n // The plain copy gives way to its `_received` entry; a mismatch too large for a pair keeps the\n // plain copy, and a kept file literally named like a pair entry would make the path ambiguous.\n const replacedPaths = new Set(\n comparison.mismatchedPaths.filter((filePath) => comparisonPaths.has(toComparisonPath(filePath, 'received')))\n );\n return [\n ...outputFiles.filter((file) => !replacedPaths.has(file.path) && !comparisonPaths.has(file.path)),\n ...comparison.outputFiles,\n ];\n}\n\n/** `c.txt` → `c_expected.txt`; `out/c.txt` → `out/c_expected.txt`. */\nfunction toComparisonPath(filePath: string, role: 'expected' | 'received'): string {\n const { dir, name, ext } = path.posix.parse(filePath);\n return path.posix.join(dir, `${name}_${role}${ext}`);\n}\n\n// A submission controls this path, so anything that is not a plain, readable file within the size\n// limit (a directory, a FIFO that would block, an unreadable or huge file) counts as \"not produced\".\nasync function readReceivedFile(cwd: string, receivedPath: string): Promise<Buffer | undefined> {\n // A submission-planted symlink to the expected file itself would otherwise compare equal.\n if (!(await isSafeSubmissionOutputPath(cwd, receivedPath))) return undefined;\n try {\n const stats = await fs.promises.stat(receivedPath);\n if (!stats.isFile() || stats.size > MAX_COMPARED_FILE_BYTES) return undefined;\n return await fs.promises.readFile(receivedPath);\n } catch {\n return undefined;\n }\n}\n\nfunction fileContentsMatch(expected: Buffer, received: Buffer): boolean {\n if (!isTextFile(expected)) return received.equals(expected);\n if (!isTextFile(received)) return false;\n return compareStdoutAsSpaceSeparatedTokens(received.toString('utf8'), expected.toString('utf8'));\n}\n\nconst utf8Decoder = new TextDecoder('utf-8', { fatal: true });\n\n// Valid UTF-8 without NUL bytes: a binary format that happens to decode (e.g. a PNG chunk) almost\n// always contains NUL, while text files containing U+FFFD are still treated as text.\nfunction isTextFile(data: Buffer): boolean {\n if (data.includes(0)) return false;\n try {\n utf8Decoder.decode(data);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"yRAYA,MAAa,EAA0B,QAsBvC,eAAsB,EACpB,EACA,EACwC,CACxC,IAAM,EAA4B,CAAC,EAC7B,EAA4B,CAAC,EAE7B,GAAgB,MADAA,EAAAA,QAAG,SAAS,QAAQ,EAAgB,CAAE,cAAe,GAAM,UAAW,EAAK,CAAC,EAAA,CAE/F,OAAQ,GAAW,EAAO,OAAO,CAAC,CAAC,CACnC,IAAK,IAAY,CAChB,aAAcC,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAEtD,aAAcA,EAAAA,QAAK,MAAM,KAAK,GAAGA,EAAAA,QAAK,SAAS,EAAgB,EAAO,UAAU,CAAC,CAAC,MAAMA,EAAAA,QAAK,GAAG,EAAG,EAAO,IAAI,CAChH,EAAE,CAAC,CACF,UAAU,EAAG,IAAO,EAAE,aAAe,EAAE,aAAe,GAAK,IAAE,aAAe,EAAE,aAAqB,EACtG,IAAK,GAAM,CAAE,eAAc,kBAAkB,EAAe,CAG1D,IAAI,MADwBD,EAAAA,QAAG,SAAS,KAAK,CAAY,EAAA,CACvC,KAAA,QAChB,MAAU,MAAM,wBAAwB,EAAa,WAAW,EAAwB,OAAO,EAEjG,IAAM,EAAW,MAAMA,EAAAA,QAAG,SAAS,SAAS,CAAY,EAClD,EAAW,MAAM,EAAiB,EAAKC,EAAAA,QAAK,KAAK,EAAK,CAAY,CAAC,EACrE,GAAY,EAAkB,EAAU,CAAQ,IAEpD,EAAgB,KAAK,CAAY,EAE7B,EAAS,QAAA,SACX,EAAY,KAAKC,EAAAA,4BAA4B,EAAiB,EAAc,UAAU,EAAG,CAAQ,CAAC,EAEhG,GAAY,EAAS,QAAA,SACvB,EAAY,KAAKA,EAAAA,4BAA4B,EAAiB,EAAc,UAAU,EAAG,CAAQ,CAAC,EAEtG,CACA,MAAO,CAAE,QAAS,EAAgB,SAAW,EAAG,kBAAiB,aAAY,CAC/E,CAQA,eAAsB,EAAyB,EAMc,CAC3D,GAAM,CAAE,SAAQ,iBAAgB,iBAAgB,OAAQ,EACpD,EAAc,CAAC,GAAG,EAAQ,WAAW,EACrC,EAAU,IAAmB,IAAA,IAAaC,EAAAA,oCAAoC,EAAQ,CAAc,EACxG,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAa,MAAM,EAA2B,EAAK,CAAc,EAClE,EAAW,UACd,EAAU,GACV,EAAc,EAA2B,EAAa,CAAU,EAEpE,CACA,MAAO,CAAE,UAAS,aAAY,CAChC,CAMA,SAAgB,EACd,EACA,EACc,CACd,IAAM,EAAkB,IAAI,IAAI,EAAW,YAAY,IAAK,GAAS,EAAK,IAAI,CAAC,EAGzE,EAAgB,IAAI,IACxB,EAAW,gBAAgB,OAAQ,GAAa,EAAgB,IAAI,EAAiB,EAAU,UAAU,CAAC,CAAC,CAC7G,EACA,MAAO,CACL,GAAG,EAAY,OAAQ,GAAS,CAAC,EAAc,IAAI,EAAK,IAAI,GAAK,CAAC,EAAgB,IAAI,EAAK,IAAI,CAAC,EAChG,GAAG,EAAW,WAChB,CACF,CAGA,SAAS,EAAiB,EAAkB,EAAuC,CACjF,GAAM,CAAE,MAAK,OAAM,OAAQF,EAAAA,QAAK,MAAM,MAAM,CAAQ,EACpD,OAAOA,EAAAA,QAAK,MAAM,KAAK,EAAK,GAAG,EAAK,GAAG,IAAO,GAAK,CACrD,CAIA,eAAe,EAAiB,EAAa,EAAmD,CAExF,SAAMG,EAAAA,2BAA2B,EAAK,CAAY,EACxD,GAAI,CACF,IAAM,EAAQ,MAAMJ,EAAAA,QAAG,SAAS,KAAK,CAAY,EAEjD,MADI,CAAC,EAAM,OAAO,GAAK,EAAM,KAAA,QAAgC,OACtD,MAAMA,EAAAA,QAAG,SAAS,SAAS,CAAY,CAChD,MAAQ,CACN,MACF,CACF,CAEA,SAAS,EAAkB,EAAkB,EAA2B,CAGtE,OAFK,EAAW,CAAQ,EACnB,EAAW,CAAQ,EACjBG,EAAAA,oCAAoC,EAAS,SAAS,MAAM,EAAG,EAAS,SAAS,MAAM,CAAC,EAD7D,GADA,EAAS,OAAO,CAAQ,CAG5D,CAEA,MAAM,EAAc,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,EAI5D,SAAS,EAAW,EAAuB,CACzC,GAAI,EAAK,SAAS,CAAC,EAAG,MAAO,GAC7B,GAAI,CAEF,OADA,EAAY,OAAO,CAAI,EAChB,EACT,MAAQ,CACN,MAAO,EACT,CACF"}
@@ -0,0 +1,45 @@
1
+ import type { TestCaseResult } from '../types/testCaseResult.js';
2
+ type OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];
3
+ export declare const MAX_COMPARED_FILE_BYTES: number;
4
+ export declare const MAX_REPORTED_FILE_BYTES: number;
5
+ export interface ExpectedOutputFilesComparison {
6
+ /** Whether every file under the expected directory has a matching file in the working directory. */
7
+ matches: boolean;
8
+ /** Relative paths (from the working directory) of the files that are missing or different. */
9
+ mismatchedPaths: string[];
10
+ /**
11
+ * `<name>_expected.<ext>` / `<name>_received.<ext>` pairs for each mismatched file, in the
12
+ * format Exercode renders side by side. A received file that is missing, unreadable, not a
13
+ * regular file or larger than `MAX_COMPARED_FILE_BYTES` yields only the expected entry, and a
14
+ * file larger than `MAX_REPORTED_FILE_BYTES` is left out of the pair. Exercode decides per test case whether a learner may see these files.
15
+ */
16
+ outputFiles: OutputFile[];
17
+ }
18
+ /**
19
+ * Compare the files under a test case's `.fout` directory with the files the submission wrote to
20
+ * its working directory. Text files are compared like stdout (space-separated tokens with a
21
+ * numeric tolerance); binary files must match byte for byte.
22
+ */
23
+ export declare function compareExpectedOutputFiles(cwd: string, fileOutputPath: string): Promise<ExpectedOutputFilesComparison>;
24
+ /**
25
+ * The default verdict of the presets: `.out` decides stdout and `.fout/` decides output files, both
26
+ * checked so a result carries every mismatch; either expectation may be absent. Returns whether the
27
+ * run matched and the output files to report (the plain copies of mismatched required output files
28
+ * replaced by their comparison pairs).
29
+ */
30
+ export declare function judgeAgainstExpectations(context: {
31
+ stdout: string;
32
+ expectedStdout: string | undefined;
33
+ fileOutputPath: string | undefined;
34
+ cwd: string;
35
+ outputFiles: readonly OutputFile[];
36
+ }): Promise<{
37
+ matches: boolean;
38
+ outputFiles: OutputFile[];
39
+ }>;
40
+ /**
41
+ * Replace the plain copies of mismatched required output files with the comparison pairs, so a
42
+ * result shows `<name>_expected.<ext>` / `<name>_received.<ext>` instead of a lone `<name>.<ext>`.
43
+ */
44
+ export declare function mergeComparisonOutputFiles(outputFiles: readonly OutputFile[], comparison: ExpectedOutputFilesComparison): OutputFile[];
45
+ export {};
@@ -0,0 +1,2 @@
1
+ import{compareStdoutAsSpaceSeparatedTokens as e}from"./compareStdoutAsSpaceSeparatedTokens.js";import{encodeFileForTestCaseResult as t}from"./printTestCaseResult.js";import{isSafeSubmissionOutputPath as n}from"./readOutputFiles.js";import r from"node:fs";import i from"node:path";const a=8388608,o=1048576;async function s(e,n){let o=[],s=[],c=(await r.promises.readdir(n,{withFileTypes:!0,recursive:!0})).filter(e=>e.isFile()).map(e=>({absolutePath:i.join(e.parentPath,e.name),relativePath:i.posix.join(...i.relative(n,e.parentPath).split(i.sep),e.name)})).toSorted((e,t)=>e.relativePath<t.relativePath?-1:+(e.relativePath>t.relativePath));for(let{absolutePath:n,relativePath:l}of c){if((await r.promises.stat(n)).size>8388608)throw Error(`expected output file ${l} exceeds ${a} bytes`);let c=await r.promises.readFile(n),p=await d(e,i.join(e,l));p&&f(c,p)||(o.push(l),c.length<=1048576&&s.push(t(u(l,`expected`),c)),p&&p.length<=1048576&&s.push(t(u(l,`received`),p)))}return{matches:o.length===0,mismatchedPaths:o,outputFiles:s}}async function c(t){let{stdout:n,expectedStdout:r,fileOutputPath:i,cwd:a}=t,o=[...t.outputFiles],c=r===void 0||e(n,r);if(i!==void 0){let e=await s(a,i);e.matches||(c=!1,o=l(o,e))}return{matches:c,outputFiles:o}}function l(e,t){let n=new Set(t.outputFiles.map(e=>e.path)),r=new Set(t.mismatchedPaths.filter(e=>n.has(u(e,`received`))));return[...e.filter(e=>!r.has(e.path)&&!n.has(e.path)),...t.outputFiles]}function u(e,t){let{dir:n,name:r,ext:a}=i.posix.parse(e);return i.posix.join(n,`${r}_${t}${a}`)}async function d(e,t){if(await n(e,t))try{let e=await r.promises.stat(t);return!e.isFile()||e.size>8388608?void 0:await r.promises.readFile(t)}catch{return}}function f(t,n){return m(t)?m(n)?e(n.toString(`utf8`),t.toString(`utf8`)):!1:n.equals(t)}const p=new TextDecoder(`utf-8`,{fatal:!0});function m(e){if(e.includes(0))return!1;try{return p.decode(e),!0}catch{return!1}}export{a as MAX_COMPARED_FILE_BYTES,o as MAX_REPORTED_FILE_BYTES,s as compareExpectedOutputFiles,c as judgeAgainstExpectations,l as mergeComparisonOutputFiles};
2
+ //# sourceMappingURL=compareExpectedOutputFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compareExpectedOutputFiles.js","names":[],"sources":["../../src/helpers/compareExpectedOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nimport { compareStdoutAsSpaceSeparatedTokens } from './compareStdoutAsSpaceSeparatedTokens.js';\nimport { encodeFileForTestCaseResult } from './printTestCaseResult.js';\nimport { isSafeSubmissionOutputPath } from './readOutputFiles.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\n\n// A submission controls the received files, so bound what the trusted harness reads and reports.\nexport const MAX_COMPARED_FILE_BYTES = 8 * 1024 * 1024;\nexport const MAX_REPORTED_FILE_BYTES = 1024 * 1024;\n\nexport interface ExpectedOutputFilesComparison {\n /** Whether every file under the expected directory has a matching file in the working directory. */\n matches: boolean;\n /** Relative paths (from the working directory) of the files that are missing or different. */\n mismatchedPaths: string[];\n /**\n * `<name>_expected.<ext>` / `<name>_received.<ext>` pairs for each mismatched file, in the\n * format Exercode renders side by side. A received file that is missing, unreadable, not a\n * regular file or larger than `MAX_COMPARED_FILE_BYTES` yields only the expected entry, and a\n * file larger than `MAX_REPORTED_FILE_BYTES` is left out of the pair. Exercode decides per test case whether a learner may see these files.\n */\n outputFiles: OutputFile[];\n}\n\n/**\n * Compare the files under a test case's `.fout` directory with the files the submission wrote to\n * its working directory. Text files are compared like stdout (space-separated tokens with a\n * numeric tolerance); binary files must match byte for byte.\n */\nexport async function compareExpectedOutputFiles(\n cwd: string,\n fileOutputPath: string\n): Promise<ExpectedOutputFilesComparison> {\n const mismatchedPaths: string[] = [];\n const outputFiles: OutputFile[] = [];\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n const expectedFiles = dirents\n .filter((dirent) => dirent.isFile())\n .map((dirent) => ({\n absolutePath: path.join(dirent.parentPath, dirent.name),\n // POSIX separators: `requiredOutputFilePaths` and the result format use them on every platform.\n relativePath: path.posix.join(...path.relative(fileOutputPath, dirent.parentPath).split(path.sep), dirent.name),\n }))\n .toSorted((a, b) => (a.relativePath < b.relativePath ? -1 : a.relativePath > b.relativePath ? 1 : 0));\n for (const { absolutePath, relativePath } of expectedFiles) {\n // A larger expectation could never be matched by a received file, so it is an authoring error.\n const expectedStats = await fs.promises.stat(absolutePath);\n if (expectedStats.size > MAX_COMPARED_FILE_BYTES) {\n throw new Error(`expected output file ${relativePath} exceeds ${MAX_COMPARED_FILE_BYTES} bytes`);\n }\n const expected = await fs.promises.readFile(absolutePath);\n const received = await readReceivedFile(cwd, path.join(cwd, relativePath));\n if (received && fileContentsMatch(expected, received)) continue;\n\n mismatchedPaths.push(relativePath);\n // A file too large to ship in a result line is left out of the pair rather than truncated.\n if (expected.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'expected'), expected));\n }\n if (received && received.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'received'), received));\n }\n }\n return { matches: mismatchedPaths.length === 0, mismatchedPaths, outputFiles };\n}\n\n/**\n * The default verdict of the presets: `.out` decides stdout and `.fout/` decides output files, both\n * checked so a result carries every mismatch; either expectation may be absent. Returns whether the\n * run matched and the output files to report (the plain copies of mismatched required output files\n * replaced by their comparison pairs).\n */\nexport async function judgeAgainstExpectations(context: {\n stdout: string;\n expectedStdout: string | undefined;\n fileOutputPath: string | undefined;\n cwd: string;\n outputFiles: readonly OutputFile[];\n}): Promise<{ matches: boolean; outputFiles: OutputFile[] }> {\n const { stdout, expectedStdout, fileOutputPath, cwd } = context;\n let outputFiles = [...context.outputFiles];\n let matches = expectedStdout === undefined || compareStdoutAsSpaceSeparatedTokens(stdout, expectedStdout);\n if (fileOutputPath !== undefined) {\n const comparison = await compareExpectedOutputFiles(cwd, fileOutputPath);\n if (!comparison.matches) {\n matches = false;\n outputFiles = mergeComparisonOutputFiles(outputFiles, comparison);\n }\n }\n return { matches, outputFiles };\n}\n\n/**\n * Replace the plain copies of mismatched required output files with the comparison pairs, so a\n * result shows `<name>_expected.<ext>` / `<name>_received.<ext>` instead of a lone `<name>.<ext>`.\n */\nexport function mergeComparisonOutputFiles(\n outputFiles: readonly OutputFile[],\n comparison: ExpectedOutputFilesComparison\n): OutputFile[] {\n const comparisonPaths = new Set(comparison.outputFiles.map((file) => file.path));\n // The plain copy gives way to its `_received` entry; a mismatch too large for a pair keeps the\n // plain copy, and a kept file literally named like a pair entry would make the path ambiguous.\n const replacedPaths = new Set(\n comparison.mismatchedPaths.filter((filePath) => comparisonPaths.has(toComparisonPath(filePath, 'received')))\n );\n return [\n ...outputFiles.filter((file) => !replacedPaths.has(file.path) && !comparisonPaths.has(file.path)),\n ...comparison.outputFiles,\n ];\n}\n\n/** `c.txt` → `c_expected.txt`; `out/c.txt` → `out/c_expected.txt`. */\nfunction toComparisonPath(filePath: string, role: 'expected' | 'received'): string {\n const { dir, name, ext } = path.posix.parse(filePath);\n return path.posix.join(dir, `${name}_${role}${ext}`);\n}\n\n// A submission controls this path, so anything that is not a plain, readable file within the size\n// limit (a directory, a FIFO that would block, an unreadable or huge file) counts as \"not produced\".\nasync function readReceivedFile(cwd: string, receivedPath: string): Promise<Buffer | undefined> {\n // A submission-planted symlink to the expected file itself would otherwise compare equal.\n if (!(await isSafeSubmissionOutputPath(cwd, receivedPath))) return undefined;\n try {\n const stats = await fs.promises.stat(receivedPath);\n if (!stats.isFile() || stats.size > MAX_COMPARED_FILE_BYTES) return undefined;\n return await fs.promises.readFile(receivedPath);\n } catch {\n return undefined;\n }\n}\n\nfunction fileContentsMatch(expected: Buffer, received: Buffer): boolean {\n if (!isTextFile(expected)) return received.equals(expected);\n if (!isTextFile(received)) return false;\n return compareStdoutAsSpaceSeparatedTokens(received.toString('utf8'), expected.toString('utf8'));\n}\n\nconst utf8Decoder = new TextDecoder('utf-8', { fatal: true });\n\n// Valid UTF-8 without NUL bytes: a binary format that happens to decode (e.g. a PNG chunk) almost\n// always contains NUL, while text files containing U+FFFD are still treated as text.\nfunction isTextFile(data: Buffer): boolean {\n if (data.includes(0)) return false;\n try {\n utf8Decoder.decode(data);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"wRAYA,MAAa,EAA0B,QAC1B,EAA0B,QAqBvC,eAAsB,EACpB,EACA,EACwC,CACxC,IAAM,EAA4B,CAAC,EAC7B,EAA4B,CAAC,EAE7B,GAAgB,MADA,EAAG,SAAS,QAAQ,EAAgB,CAAE,cAAe,GAAM,UAAW,EAAK,CAAC,EAAA,CAE/F,OAAQ,GAAW,EAAO,OAAO,CAAC,CAAC,CACnC,IAAK,IAAY,CAChB,aAAc,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAEtD,aAAc,EAAK,MAAM,KAAK,GAAG,EAAK,SAAS,EAAgB,EAAO,UAAU,CAAC,CAAC,MAAM,EAAK,GAAG,EAAG,EAAO,IAAI,CAChH,EAAE,CAAC,CACF,UAAU,EAAG,IAAO,EAAE,aAAe,EAAE,aAAe,GAAK,IAAE,aAAe,EAAE,aAAqB,EACtG,IAAK,GAAM,CAAE,eAAc,kBAAkB,EAAe,CAG1D,IAAI,MADwB,EAAG,SAAS,KAAK,CAAY,EAAA,CACvC,KAAA,QAChB,MAAU,MAAM,wBAAwB,EAAa,WAAW,EAAwB,OAAO,EAEjG,IAAM,EAAW,MAAM,EAAG,SAAS,SAAS,CAAY,EAClD,EAAW,MAAM,EAAiB,EAAK,EAAK,KAAK,EAAK,CAAY,CAAC,EACrE,GAAY,EAAkB,EAAU,CAAQ,IAEpD,EAAgB,KAAK,CAAY,EAE7B,EAAS,QAAA,SACX,EAAY,KAAK,EAA4B,EAAiB,EAAc,UAAU,EAAG,CAAQ,CAAC,EAEhG,GAAY,EAAS,QAAA,SACvB,EAAY,KAAK,EAA4B,EAAiB,EAAc,UAAU,EAAG,CAAQ,CAAC,EAEtG,CACA,MAAO,CAAE,QAAS,EAAgB,SAAW,EAAG,kBAAiB,aAAY,CAC/E,CAQA,eAAsB,EAAyB,EAMc,CAC3D,GAAM,CAAE,SAAQ,iBAAgB,iBAAgB,OAAQ,EACpD,EAAc,CAAC,GAAG,EAAQ,WAAW,EACrC,EAAU,IAAmB,IAAA,IAAa,EAAoC,EAAQ,CAAc,EACxG,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAa,MAAM,EAA2B,EAAK,CAAc,EAClE,EAAW,UACd,EAAU,GACV,EAAc,EAA2B,EAAa,CAAU,EAEpE,CACA,MAAO,CAAE,UAAS,aAAY,CAChC,CAMA,SAAgB,EACd,EACA,EACc,CACd,IAAM,EAAkB,IAAI,IAAI,EAAW,YAAY,IAAK,GAAS,EAAK,IAAI,CAAC,EAGzE,EAAgB,IAAI,IACxB,EAAW,gBAAgB,OAAQ,GAAa,EAAgB,IAAI,EAAiB,EAAU,UAAU,CAAC,CAAC,CAC7G,EACA,MAAO,CACL,GAAG,EAAY,OAAQ,GAAS,CAAC,EAAc,IAAI,EAAK,IAAI,GAAK,CAAC,EAAgB,IAAI,EAAK,IAAI,CAAC,EAChG,GAAG,EAAW,WAChB,CACF,CAGA,SAAS,EAAiB,EAAkB,EAAuC,CACjF,GAAM,CAAE,MAAK,OAAM,OAAQ,EAAK,MAAM,MAAM,CAAQ,EACpD,OAAO,EAAK,MAAM,KAAK,EAAK,GAAG,EAAK,GAAG,IAAO,GAAK,CACrD,CAIA,eAAe,EAAiB,EAAa,EAAmD,CAExF,SAAM,EAA2B,EAAK,CAAY,EACxD,GAAI,CACF,IAAM,EAAQ,MAAM,EAAG,SAAS,KAAK,CAAY,EAEjD,MADI,CAAC,EAAM,OAAO,GAAK,EAAM,KAAA,QAAgC,OACtD,MAAM,EAAG,SAAS,SAAS,CAAY,CAChD,MAAQ,CACN,MACF,CACF,CAEA,SAAS,EAAkB,EAAkB,EAA2B,CAGtE,OAFK,EAAW,CAAQ,EACnB,EAAW,CAAQ,EACjB,EAAoC,EAAS,SAAS,MAAM,EAAG,EAAS,SAAS,MAAM,CAAC,EAD7D,GADA,EAAS,OAAO,CAAQ,CAG5D,CAEA,MAAM,EAAc,IAAI,YAAY,QAAS,CAAE,MAAO,EAAK,CAAC,EAI5D,SAAS,EAAW,EAAuB,CACzC,GAAI,EAAK,SAAS,CAAC,EAAG,MAAO,GAC7B,GAAI,CAEF,OADA,EAAY,OAAO,CAAI,EAChB,EACT,MAAQ,CACN,MAAO,EACT,CACF"}
@@ -1,2 +1,2 @@
1
- "use strict";const e=1e-6;function t(t,n){let r=t.trim().split(/\s+/),i=n.trim().split(/\s+/);if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(!Number.isNaN(Number(n))&&n.includes(`.`)){let t=Number(i),r=Number(n);if(Number.isNaN(r))throw TypeError(`invalid token in test case: ${n}`);if(Number.isNaN(t))return!1;let a=Math.abs(t-r),o=r===0?1/0:Math.abs(a/r);if(a>e&&o>e)return!1}else if(i!==n)return!1}return!0}exports.compareStdoutAsSpaceSeparatedTokens=t;
1
+ "use strict";const e=1e-6;function t(t,n){let r=t.trim()?t.trim().split(/\s+/):[],i=n.trim()?n.trim().split(/\s+/):[];if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(Number.isFinite(Number(n))&&n.includes(`.`)){let t=Number(i),r=Number(n);if(!Number.isFinite(t))return!1;let a=Math.abs(t-r),o=r===0?1/0:Math.abs(a/r);if(a>e&&o>e)return!1}else if(i!==n)return!1}return!0}exports.compareStdoutAsSpaceSeparatedTokens=t;
2
2
  //# sourceMappingURL=compareStdoutAsSpaceSeparatedTokens.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.cjs","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n const receivedTokens = received.trim().split(/\\s+/);\n const expectedTokens = expected.trim().split(/\\s+/);\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n const isDecimal = !Number.isNaN(Number(expectedToken)) && expectedToken.includes('.');\n if (isDecimal) {\n const receivedNumber = Number(receivedToken);\n const expectedNumber = Number(expectedToken);\n\n if (Number.isNaN(expectedNumber)) throw new TypeError(`invalid token in test case: ${expectedToken}`);\n if (Number.isNaN(receivedNumber)) return false;\n\n const absoluteError = Math.abs(receivedNumber - expectedNumber);\n const relativeError = expectedNumber === 0 ? Number.POSITIVE_INFINITY : Math.abs(absoluteError / expectedNumber);\n if (absoluteError > ACCEPTABLE_FLOAT_ERROR && relativeError > ACCEPTABLE_FLOAT_ERROR) return false;\n } else {\n if (receivedToken !== expectedToken) return false;\n }\n }\n\n return true;\n}\n"],"mappings":"aAAA,MAAM,EAAyB,KAE/B,SAAgB,EAAoC,EAAkB,EAA2B,CAE/F,IAAM,EAAiB,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAC5C,EAAiB,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAElD,GAAI,EAAe,SAAW,EAAe,OAAQ,MAAO,GAE5D,IAAK,GAAM,CAAC,EAAG,KAAkB,EAAe,QAAQ,EAAG,CACzD,IAAM,EAAgB,EAAe,GAGrC,GADkB,CAAC,OAAO,MAAM,OAAO,CAAa,CAAC,GAAK,EAAc,SAAS,GAAG,EACrE,CACb,IAAM,EAAiB,OAAO,CAAa,EACrC,EAAiB,OAAO,CAAa,EAE3C,GAAI,OAAO,MAAM,CAAc,EAAG,MAAU,UAAU,+BAA+B,GAAe,EACpG,GAAI,OAAO,MAAM,CAAc,EAAG,MAAO,GAEzC,IAAM,EAAgB,KAAK,IAAI,EAAiB,CAAc,EACxD,EAAgB,IAAmB,EAAI,IAA2B,KAAK,IAAI,EAAgB,CAAc,EAC/G,GAAI,EAAgB,GAA0B,EAAgB,EAAwB,MAAO,EAC/F,MACE,GAAI,IAAkB,EAAe,MAAO,EAEhD,CAEA,MAAO,EACT"}
1
+ {"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.cjs","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n // An empty (or blank) text has no tokens; `''.split(/\\s+/)` would yield one empty token that\n // `Number('')` turns into 0.\n const receivedTokens = received.trim() ? received.trim().split(/\\s+/) : [];\n const expectedTokens = expected.trim() ? expected.trim().split(/\\s+/) : [];\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n // Only a finite decimal gets a tolerance; an overflowing decimal such as `1.0e309` compares exactly.\n const isDecimal = Number.isFinite(Number(expectedToken)) && expectedToken.includes('.');\n if (isDecimal) {\n const receivedNumber = Number(receivedToken);\n const expectedNumber = Number(expectedToken);\n\n if (!Number.isFinite(receivedNumber)) return false;\n\n const absoluteError = Math.abs(receivedNumber - expectedNumber);\n const relativeError = expectedNumber === 0 ? Number.POSITIVE_INFINITY : Math.abs(absoluteError / expectedNumber);\n if (absoluteError > ACCEPTABLE_FLOAT_ERROR && relativeError > ACCEPTABLE_FLOAT_ERROR) return false;\n } else {\n if (receivedToken !== expectedToken) return false;\n }\n }\n\n return true;\n}\n"],"mappings":"aAAA,MAAM,EAAyB,KAE/B,SAAgB,EAAoC,EAAkB,EAA2B,CAI/F,IAAM,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EACnE,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EAEzE,GAAI,EAAe,SAAW,EAAe,OAAQ,MAAO,GAE5D,IAAK,GAAM,CAAC,EAAG,KAAkB,EAAe,QAAQ,EAAG,CACzD,IAAM,EAAgB,EAAe,GAIrC,GADkB,OAAO,SAAS,OAAO,CAAa,CAAC,GAAK,EAAc,SAAS,GAAG,EACvE,CACb,IAAM,EAAiB,OAAO,CAAa,EACrC,EAAiB,OAAO,CAAa,EAE3C,GAAI,CAAC,OAAO,SAAS,CAAc,EAAG,MAAO,GAE7C,IAAM,EAAgB,KAAK,IAAI,EAAiB,CAAc,EACxD,EAAgB,IAAmB,EAAI,IAA2B,KAAK,IAAI,EAAgB,CAAc,EAC/G,GAAI,EAAgB,GAA0B,EAAgB,EAAwB,MAAO,EAC/F,MACE,GAAI,IAAkB,EAAe,MAAO,EAEhD,CAEA,MAAO,EACT"}
@@ -1,2 +1,2 @@
1
- const e=1e-6;function t(t,n){let r=t.trim().split(/\s+/),i=n.trim().split(/\s+/);if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(!Number.isNaN(Number(n))&&n.includes(`.`)){let t=Number(i),r=Number(n);if(Number.isNaN(r))throw TypeError(`invalid token in test case: ${n}`);if(Number.isNaN(t))return!1;let a=Math.abs(t-r),o=r===0?1/0:Math.abs(a/r);if(a>e&&o>e)return!1}else if(i!==n)return!1}return!0}export{t as compareStdoutAsSpaceSeparatedTokens};
1
+ const e=1e-6;function t(t,n){let r=t.trim()?t.trim().split(/\s+/):[],i=n.trim()?n.trim().split(/\s+/):[];if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(Number.isFinite(Number(n))&&n.includes(`.`)){let t=Number(i),r=Number(n);if(!Number.isFinite(t))return!1;let a=Math.abs(t-r),o=r===0?1/0:Math.abs(a/r);if(a>e&&o>e)return!1}else if(i!==n)return!1}return!0}export{t as compareStdoutAsSpaceSeparatedTokens};
2
2
  //# sourceMappingURL=compareStdoutAsSpaceSeparatedTokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.js","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n const receivedTokens = received.trim().split(/\\s+/);\n const expectedTokens = expected.trim().split(/\\s+/);\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n const isDecimal = !Number.isNaN(Number(expectedToken)) && expectedToken.includes('.');\n if (isDecimal) {\n const receivedNumber = Number(receivedToken);\n const expectedNumber = Number(expectedToken);\n\n if (Number.isNaN(expectedNumber)) throw new TypeError(`invalid token in test case: ${expectedToken}`);\n if (Number.isNaN(receivedNumber)) return false;\n\n const absoluteError = Math.abs(receivedNumber - expectedNumber);\n const relativeError = expectedNumber === 0 ? Number.POSITIVE_INFINITY : Math.abs(absoluteError / expectedNumber);\n if (absoluteError > ACCEPTABLE_FLOAT_ERROR && relativeError > ACCEPTABLE_FLOAT_ERROR) return false;\n } else {\n if (receivedToken !== expectedToken) return false;\n }\n }\n\n return true;\n}\n"],"mappings":"AAAA,MAAM,EAAyB,KAE/B,SAAgB,EAAoC,EAAkB,EAA2B,CAE/F,IAAM,EAAiB,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAC5C,EAAiB,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAElD,GAAI,EAAe,SAAW,EAAe,OAAQ,MAAO,GAE5D,IAAK,GAAM,CAAC,EAAG,KAAkB,EAAe,QAAQ,EAAG,CACzD,IAAM,EAAgB,EAAe,GAGrC,GADkB,CAAC,OAAO,MAAM,OAAO,CAAa,CAAC,GAAK,EAAc,SAAS,GAAG,EACrE,CACb,IAAM,EAAiB,OAAO,CAAa,EACrC,EAAiB,OAAO,CAAa,EAE3C,GAAI,OAAO,MAAM,CAAc,EAAG,MAAU,UAAU,+BAA+B,GAAe,EACpG,GAAI,OAAO,MAAM,CAAc,EAAG,MAAO,GAEzC,IAAM,EAAgB,KAAK,IAAI,EAAiB,CAAc,EACxD,EAAgB,IAAmB,EAAI,IAA2B,KAAK,IAAI,EAAgB,CAAc,EAC/G,GAAI,EAAgB,GAA0B,EAAgB,EAAwB,MAAO,EAC/F,MACE,GAAI,IAAkB,EAAe,MAAO,EAEhD,CAEA,MAAO,EACT"}
1
+ {"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.js","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n // An empty (or blank) text has no tokens; `''.split(/\\s+/)` would yield one empty token that\n // `Number('')` turns into 0.\n const receivedTokens = received.trim() ? received.trim().split(/\\s+/) : [];\n const expectedTokens = expected.trim() ? expected.trim().split(/\\s+/) : [];\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n // Only a finite decimal gets a tolerance; an overflowing decimal such as `1.0e309` compares exactly.\n const isDecimal = Number.isFinite(Number(expectedToken)) && expectedToken.includes('.');\n if (isDecimal) {\n const receivedNumber = Number(receivedToken);\n const expectedNumber = Number(expectedToken);\n\n if (!Number.isFinite(receivedNumber)) return false;\n\n const absoluteError = Math.abs(receivedNumber - expectedNumber);\n const relativeError = expectedNumber === 0 ? Number.POSITIVE_INFINITY : Math.abs(absoluteError / expectedNumber);\n if (absoluteError > ACCEPTABLE_FLOAT_ERROR && relativeError > ACCEPTABLE_FLOAT_ERROR) return false;\n } else {\n if (receivedToken !== expectedToken) return false;\n }\n }\n\n return true;\n}\n"],"mappings":"AAAA,MAAM,EAAyB,KAE/B,SAAgB,EAAoC,EAAkB,EAA2B,CAI/F,IAAM,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EACnE,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EAEzE,GAAI,EAAe,SAAW,EAAe,OAAQ,MAAO,GAE5D,IAAK,GAAM,CAAC,EAAG,KAAkB,EAAe,QAAQ,EAAG,CACzD,IAAM,EAAgB,EAAe,GAIrC,GADkB,OAAO,SAAS,OAAO,CAAa,CAAC,GAAK,EAAc,SAAS,GAAG,EACvE,CACb,IAAM,EAAiB,OAAO,CAAa,EACrC,EAAiB,OAAO,CAAa,EAE3C,GAAI,CAAC,OAAO,SAAS,CAAc,EAAG,MAAO,GAE7C,IAAM,EAAgB,KAAK,IAAI,EAAiB,CAAc,EACxD,EAAgB,IAAmB,EAAI,IAA2B,KAAK,IAAI,EAAgB,CAAc,EAC/G,GAAI,EAAgB,GAA0B,EAAgB,EAAwB,MAAO,EAC/F,MACE,GAAI,IAAkB,EAAe,MAAO,EAEhD,CAEA,MAAO,EACT"}
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs/promises");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);const r={"judge.ts":`stdioJudgePreset`,"debug.ts":`stdioDebugPreset`};async function i(e){let t=await a(n.default.join(e,`judge.ts`)),i=[];if(t!==void 0&&o(t,r[`judge.ts`])&&i.push(`judge.ts`),t===void 0||i.length!==0){let t=await a(n.default.join(e,`debug.ts`));t!==void 0&&o(t,r[`debug.ts`])&&i.push(`debug.ts`)}return i}async function a(e){try{return await t.default.readFile(e,`utf8`)}catch{return}}function o(e,t){let n=new RegExp(String.raw`^\s*import\s*\{\s*${t}(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*,?\s*\}\s*from\s*`+String.raw`(['"])(?:@exercode/problem-utils|judge-utils)/presets/stdio(?:\.js)?\2\s*;?\s*`).exec(e);if(!n)return!1;let r=(n[1]??t).replaceAll(`$`,String.raw`\$`);return new RegExp(String.raw`^await\s+${r}\s*\(\s*import\.meta\.dirname\s*\)\s*;?\s*$`).test(e.slice(n[0].length))}exports.HARNESS_FILE_PRESETS=r,exports.findDefaultStdioHarnessFiles=i,exports.isDefaultStdioHarnessSource=o;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);const r={"judge.ts":`stdioJudgePreset`,"debug.ts":`stdioDebugPreset`};async function i(e){let n=await a(t.default.join(e,`judge.ts`)),i=[];if(n!==void 0&&o(n,r[`judge.ts`])&&i.push(`judge.ts`),n===void 0||i.length!==0){let n=await a(t.default.join(e,`debug.ts`));n!==void 0&&o(n,r[`debug.ts`])&&i.push(`debug.ts`)}return i}async function a(e){try{return await n.default.readFile(e,`utf8`)}catch{return}}function o(e,t){let n=new RegExp(String.raw`^\s*import\s*\{\s*${t}(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*,?\s*\}\s*from\s*`+String.raw`(['"])(?:@exercode/problem-utils|judge-utils)/presets/stdio(?:\.js)?\2\s*;?\s*`).exec(e);if(!n)return!1;let r=(n[1]??t).replaceAll(`$`,String.raw`\$`);return new RegExp(String.raw`^await\s+${r}\s*\(\s*import\.meta\.dirname\s*\)\s*;?\s*$`).test(e.slice(n[0].length))}exports.HARNESS_FILE_PRESETS=r,exports.findDefaultStdioHarnessFiles=i,exports.isDefaultStdioHarnessSource=o;
2
2
  //# sourceMappingURL=defaultStdioHarness.cjs.map
@@ -1,2 +1,2 @@
1
- import e from"node:fs/promises";import t from"node:path";const n={"judge.ts":`stdioJudgePreset`,"debug.ts":`stdioDebugPreset`};async function r(e){let r=await i(t.join(e,`judge.ts`)),o=[];if(r!==void 0&&a(r,n[`judge.ts`])&&o.push(`judge.ts`),r===void 0||o.length!==0){let r=await i(t.join(e,`debug.ts`));r!==void 0&&a(r,n[`debug.ts`])&&o.push(`debug.ts`)}return o}async function i(t){try{return await e.readFile(t,`utf8`)}catch{return}}function a(e,t){let n=new RegExp(String.raw`^\s*import\s*\{\s*${t}(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*,?\s*\}\s*from\s*`+String.raw`(['"])(?:@exercode/problem-utils|judge-utils)/presets/stdio(?:\.js)?\2\s*;?\s*`).exec(e);if(!n)return!1;let r=(n[1]??t).replaceAll(`$`,String.raw`\$`);return new RegExp(String.raw`^await\s+${r}\s*\(\s*import\.meta\.dirname\s*\)\s*;?\s*$`).test(e.slice(n[0].length))}export{n as HARNESS_FILE_PRESETS,r as findDefaultStdioHarnessFiles,a as isDefaultStdioHarnessSource};
1
+ import e from"node:path";import t from"node:fs/promises";const n={"judge.ts":`stdioJudgePreset`,"debug.ts":`stdioDebugPreset`};async function r(t){let r=await i(e.join(t,`judge.ts`)),o=[];if(r!==void 0&&a(r,n[`judge.ts`])&&o.push(`judge.ts`),r===void 0||o.length!==0){let r=await i(e.join(t,`debug.ts`));r!==void 0&&a(r,n[`debug.ts`])&&o.push(`debug.ts`)}return o}async function i(e){try{return await t.readFile(e,`utf8`)}catch{return}}function a(e,t){let n=new RegExp(String.raw`^\s*import\s*\{\s*${t}(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*,?\s*\}\s*from\s*`+String.raw`(['"])(?:@exercode/problem-utils|judge-utils)/presets/stdio(?:\.js)?\2\s*;?\s*`).exec(e);if(!n)return!1;let r=(n[1]??t).replaceAll(`$`,String.raw`\$`);return new RegExp(String.raw`^await\s+${r}\s*\(\s*import\.meta\.dirname\s*\)\s*;?\s*$`).test(e.slice(n[0].length))}export{n as HARNESS_FILE_PRESETS,r as findDefaultStdioHarnessFiles,a as isDefaultStdioHarnessSource};
2
2
  //# sourceMappingURL=defaultStdioHarness.js.map
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs/promises");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);const r=`model_answers`,i=`model_answers.fails`;async function a(e){return s(e,r)}async function o(e){return s(e,i)}async function s(e,r){let i=n.default.join(e,r),a;try{a=await t.default.readdir(i,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}return a.filter(e=>e.isDirectory()).map(e=>n.default.join(i,e.name)).toSorted()}exports.FAILING_MODEL_ANSWERS_DIRNAME=i,exports.MODEL_ANSWERS_DIRNAME=r,exports.findFailingModelAnswerDirs=o,exports.findModelAnswerDirs=a;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);const r=`model_answers`,i=`model_answers.fails`;async function a(e){return s(e,r)}async function o(e){return s(e,i)}async function s(e,r){let i=t.default.join(e,r),a;try{a=await n.default.readdir(i,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}return a.filter(e=>e.isDirectory()).map(e=>t.default.join(i,e.name)).toSorted()}exports.FAILING_MODEL_ANSWERS_DIRNAME=i,exports.MODEL_ANSWERS_DIRNAME=r,exports.findFailingModelAnswerDirs=o,exports.findModelAnswerDirs=a;
2
2
  //# sourceMappingURL=findModelAnswerDirs.cjs.map
@@ -1,2 +1,2 @@
1
- import e from"node:fs/promises";import t from"node:path";const n=`model_answers`,r=`model_answers.fails`;async function i(e){return o(e,n)}async function a(e){return o(e,r)}async function o(n,r){let i=t.join(n,r),a;try{a=await e.readdir(i,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}return a.filter(e=>e.isDirectory()).map(e=>t.join(i,e.name)).toSorted()}export{r as FAILING_MODEL_ANSWERS_DIRNAME,n as MODEL_ANSWERS_DIRNAME,a as findFailingModelAnswerDirs,i as findModelAnswerDirs};
1
+ import e from"node:path";import t from"node:fs/promises";const n=`model_answers`,r=`model_answers.fails`;async function i(e){return o(e,n)}async function a(e){return o(e,r)}async function o(n,r){let i=e.join(n,r),a;try{a=await t.readdir(i,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}return a.filter(e=>e.isDirectory()).map(t=>e.join(i,t.name)).toSorted()}export{r as FAILING_MODEL_ANSWERS_DIRNAME,n as MODEL_ANSWERS_DIRNAME,a as findFailingModelAnswerDirs,i as findModelAnswerDirs};
2
2
  //# sourceMappingURL=findModelAnswerDirs.js.map
@@ -1,4 +1,4 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./removeCommentsInSourceCode.cjs"),n=require("../types/decisionCode.cjs"),r=require("../types/problem.cjs"),i=require("./findLanguageDefinitionByPath.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("node:fs");o=e.__toESM(o,1);async function s(e,s){let l=new Set,u=[];for(let n of await o.default.promises.readdir(e,{withFileTypes:!0,recursive:!0})){if(!n.isFile())continue;let r=a.default.relative(e,a.default.join(n.parentPath,n.name));l.add(r);let s=await o.default.promises.readFile(a.default.join(n.parentPath,n.name),`utf8`);if(s.includes(`�`))continue;let c=i.findLanguageDefinitionByPath(n.name);c&&u.push({path:r,data:c.grammer?t.removeCommentsInSourceCode(c.grammer,s):s})}if(s.requiredSubmissionFilePaths){let e=s.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:n.DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./removeCommentsInSourceCode.cjs"),n=require("../types/decisionCode.cjs"),r=require("../types/problem.cjs"),i=require("./findLanguageDefinitionByPath.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);async function s(e,s){let l=new Set,u=[];for(let n of await a.default.promises.readdir(e,{withFileTypes:!0,recursive:!0})){if(!n.isFile())continue;let r=o.default.relative(e,o.default.join(n.parentPath,n.name));l.add(r);let s=await a.default.promises.readFile(o.default.join(n.parentPath,n.name),`utf8`);if(s.includes(`�`))continue;let c=i.findLanguageDefinitionByPath(n.name);c&&u.push({path:r,data:c.grammer?t.removeCommentsInSourceCode(c.grammer,s):s})}if(s.requiredSubmissionFilePaths){let e=s.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:n.DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
2
2
  次のファイルを追加してから再度提出してください。
3
3
 
4
4
  ${e.map(e=>`- \`${e}\``).join(`
@@ -1,4 +1,4 @@
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:`ファイルが不足しています。
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:fs";import a from"node:path";async function o(o,c){let l=new Set,u=[];for(let t of await i.promises.readdir(o,{withFileTypes:!0,recursive:!0})){if(!t.isFile())continue;let n=a.relative(o,a.join(t.parentPath,t.name));l.add(n);let s=await i.promises.readFile(a.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(`
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./printTestCaseResult.cjs"),n=require("./sandboxUser.cjs"),r=require("./safeFs.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);async function o(e,n){let r=[];for(let o of n)try{let n=i.default.join(e,o);if(!await s(e,n))continue;let c=await a.default.promises.readFile(n);r.push(t.encodeFileForTestCaseResult(o,c))}catch{}return r}async function s(e,t){if(!n.sandboxUserName)return!0;try{let n=await a.default.promises.realpath(t),i=await a.default.promises.realpath(e);return r.isContainedPath(i,n)}catch{return!1}}exports.isSafeSubmissionOutputPath=s,exports.readOutputFiles=o;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./printTestCaseResult.cjs"),n=require("./sandboxUser.cjs"),r=require("./safeFs.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);async function o(e,n){let r=[];for(let o of n)try{let n=a.default.join(e,o);if(!await s(e,n))continue;let c=await i.default.promises.readFile(n);r.push(t.encodeFileForTestCaseResult(o,c))}catch{}return r}async function s(e,t){if(!n.sandboxUserName)return!0;try{let n=await i.default.promises.realpath(t),a=await i.default.promises.realpath(e);return r.isContainedPath(a,n)}catch{return!1}}exports.isSafeSubmissionOutputPath=s,exports.readOutputFiles=o;
2
2
  //# sourceMappingURL=readOutputFiles.cjs.map
@@ -1,2 +1,2 @@
1
- import{encodeFileForTestCaseResult as e}from"./printTestCaseResult.js";import{sandboxUserName as t}from"./sandboxUser.js";import{isContainedPath as n}from"./safeFs.js";import r from"node:path";import i from"node:fs";async function a(t,n){let a=[];for(let s of n)try{let n=r.join(t,s);if(!await o(t,n))continue;let c=await i.promises.readFile(n);a.push(e(s,c))}catch{}return a}async function o(e,r){if(!t)return!0;try{let t=await i.promises.realpath(r),a=await i.promises.realpath(e);return n(a,t)}catch{return!1}}export{o as isSafeSubmissionOutputPath,a as readOutputFiles};
1
+ import{encodeFileForTestCaseResult as e}from"./printTestCaseResult.js";import{sandboxUserName as t}from"./sandboxUser.js";import{isContainedPath as n}from"./safeFs.js";import r from"node:fs";import i from"node:path";async function a(t,n){let a=[];for(let s of n)try{let n=i.join(t,s);if(!await o(t,n))continue;let c=await r.promises.readFile(n);a.push(e(s,c))}catch{}return a}async function o(e,i){if(!t)return!0;try{let t=await r.promises.realpath(i),a=await r.promises.realpath(e);return n(a,t)}catch{return!1}}export{o as isSafeSubmissionOutputPath,a as readOutputFiles};
2
2
  //# sourceMappingURL=readOutputFiles.js.map
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/problem.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("node:fs");r=e.__toESM(r,1);let i=require("front-matter");i=e.__toESM(i,1);function a(e){return e.isManualScoringRequired===!0||(e.requiredRegExpsInCode?.length??0)>0||(e.forbiddenRegExpsInCode?.length??0)>0||(e.forbiddenTextsInCode?.length??0)>0||(e.requiredSubmissionFilePaths?.length??0)>0}async function o(e){for(let a of await r.default.promises.readdir(e,{withFileTypes:!0})){if(!a.isFile()||!(a.name===`problem.md`||a.name.endsWith(`.problem.md`)))continue;let e=await r.default.promises.readFile(n.default.join(a.parentPath,a.name),`utf8`),{attributes:o}=(0,i.default)(e);return t.problemMarkdownFrontMatterSchema.parse(o)}throw Error(`problem markdown not found: ${e}`)}exports.judgesWithoutTestCases=a,exports.readProblemMarkdownFrontMatter=o;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/problem.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("front-matter");i=e.__toESM(i,1);function a(e){return e.isManualScoringRequired===!0||(e.requiredRegExpsInCode?.length??0)>0||(e.forbiddenRegExpsInCode?.length??0)>0||(e.forbiddenTextsInCode?.length??0)>0||(e.requiredSubmissionFilePaths?.length??0)>0}async function o(e){for(let a of await n.default.promises.readdir(e,{withFileTypes:!0})){if(!a.isFile()||!(a.name===`problem.md`||a.name.endsWith(`.problem.md`)))continue;let e=await n.default.promises.readFile(r.default.join(a.parentPath,a.name),`utf8`),{attributes:o}=(0,i.default)(e);return t.problemMarkdownFrontMatterSchema.parse(o)}throw Error(`problem markdown not found: ${e}`)}exports.judgesWithoutTestCases=a,exports.readProblemMarkdownFrontMatter=o;
2
2
  //# sourceMappingURL=readProblemMarkdownFrontMatter.cjs.map
@@ -1,2 +1,2 @@
1
- import{problemMarkdownFrontMatterSchema as e}from"../types/problem.js";import t from"node:path";import n from"node:fs";import r from"front-matter";function i(e){return e.isManualScoringRequired===!0||(e.requiredRegExpsInCode?.length??0)>0||(e.forbiddenRegExpsInCode?.length??0)>0||(e.forbiddenTextsInCode?.length??0)>0||(e.requiredSubmissionFilePaths?.length??0)>0}async function a(i){for(let a of await n.promises.readdir(i,{withFileTypes:!0})){if(!a.isFile()||!(a.name===`problem.md`||a.name.endsWith(`.problem.md`)))continue;let i=await n.promises.readFile(t.join(a.parentPath,a.name),`utf8`),{attributes:o}=r(i);return e.parse(o)}throw Error(`problem markdown not found: ${i}`)}export{i as judgesWithoutTestCases,a as readProblemMarkdownFrontMatter};
1
+ import{problemMarkdownFrontMatterSchema as e}from"../types/problem.js";import t from"node:fs";import n from"node:path";import r from"front-matter";function i(e){return e.isManualScoringRequired===!0||(e.requiredRegExpsInCode?.length??0)>0||(e.forbiddenRegExpsInCode?.length??0)>0||(e.forbiddenTextsInCode?.length??0)>0||(e.requiredSubmissionFilePaths?.length??0)>0}async function a(i){for(let a of await t.promises.readdir(i,{withFileTypes:!0})){if(!a.isFile()||!(a.name===`problem.md`||a.name.endsWith(`.problem.md`)))continue;let i=await t.promises.readFile(n.join(a.parentPath,a.name),`utf8`),{attributes:o}=r(i);return e.parse(o)}throw Error(`problem markdown not found: ${i}`)}export{i as judgesWithoutTestCases,a as readProblemMarkdownFrontMatter};
2
2
  //# sourceMappingURL=readProblemMarkdownFrontMatter.js.map
@@ -1,2 +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:fs");n=e.__toESM(n,1);const r=`_shared`;async function i(e){if(!n.default.existsSync(e))return[];let i=new Set,a=new Map,o=new Map,s=new Map,c=new Map;for(let r of await n.default.promises.readdir(e,{withFileTypes:!0}))if(r.isFile()){let{ext:e,name:s}=t.default.parse(r.name);if(e!==`.in`&&e!==`.out`)continue;let c=await n.default.promises.readFile(t.default.join(r.parentPath,r.name),`utf8`);i.add(s),e===`.in`&&a.set(s,c),e===`.out`&&o.set(s,c)}else if(r.isDirectory()){let{ext:e,name:n}=t.default.parse(r.name);if(e!==`.fin`&&e!==`.fout`)continue;i.add(n),e===`.fin`&&s.set(n,t.default.join(r.parentPath,r.name)),e===`.fout`&&c.set(n,t.default.join(r.parentPath,r.name))}let l=[...i].filter(e=>e!==r).toSorted().map(e=>({id:e,input:a.get(e),output:o.get(e),fileInputPath:s.get(e),fileOutputPath:c.get(e)}));return i.has(r)&&(l.shared={fileInputPath:s.get(r)}),l}exports.readTestCases=i;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);const r=`_shared`;async function i(e){if(!t.default.existsSync(e))return[];let i=new Set,a=new Map,o=new Map,s=new Map,c=new Map;for(let r of await t.default.promises.readdir(e,{withFileTypes:!0}))if(r.isFile()){let{ext:e,name:s}=n.default.parse(r.name);if(e!==`.in`&&e!==`.out`)continue;let c=await t.default.promises.readFile(n.default.join(r.parentPath,r.name),`utf8`);i.add(s),e===`.in`&&a.set(s,c),e===`.out`&&o.set(s,c)}else if(r.isDirectory()){let{ext:e,name:t}=n.default.parse(r.name);if(e!==`.fin`&&e!==`.fout`)continue;i.add(t),e===`.fin`&&s.set(t,n.default.join(r.parentPath,r.name)),e===`.fout`&&c.set(t,n.default.join(r.parentPath,r.name))}let l=[...i].filter(e=>e!==r).toSorted().map(e=>({id:e,input:a.get(e),output:o.get(e),fileInputPath:s.get(e),fileOutputPath:c.get(e)}));return i.has(r)&&(l.shared={fileInputPath:s.get(r)}),l}exports.readTestCases=i;
2
2
  //# sourceMappingURL=readTestCases.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"readTestCases.cjs","names":["fs","path"],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"wJAGA,MAAM,EAAwB,UAE9B,eAAsB,EAAc,EAIlC,CACA,GAAI,CAACA,EAAAA,QAAG,WAAW,CAAS,EAAG,MAAO,CAAC,EAEvC,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAa,IAAI,IACjB,EAAoB,IAAI,IACxB,EAAqB,IAAI,IAE/B,IAAK,IAAM,KAAU,MAAMA,EAAAA,QAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,EAC/E,GAAI,EAAO,OAAO,EAAG,CACnB,GAAM,CAAE,MAAK,QAASC,EAAAA,QAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,OAAS,IAAQ,OAAQ,SAErC,IAAM,EAAO,MAAMD,EAAAA,QAAG,SAAS,SAASC,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAAG,MAAM,EAEzF,EAAM,IAAI,CAAI,EACV,IAAQ,OAAO,EAAU,IAAI,EAAM,CAAI,EACvC,IAAQ,QAAQ,EAAW,IAAI,EAAM,CAAI,CAC/C,MAAO,GAAI,EAAO,YAAY,EAAG,CAC/B,GAAM,CAAE,MAAK,QAASA,EAAAA,QAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,QAAU,IAAQ,QAAS,SAEvC,EAAM,IAAI,CAAI,EACV,IAAQ,QAAQ,EAAkB,IAAI,EAAMA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,EACrF,IAAQ,SAAS,EAAmB,IAAI,EAAMA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,CAC7F,CAGF,IAAM,EAAuD,CAAC,GAAG,CAAK,CAAC,CACpE,OAAQ,GAAO,IAAO,CAAqB,CAAC,CAC5C,SAAS,CAAC,CACV,IAAK,IAAQ,CACZ,KACA,MAAO,EAAU,IAAI,CAAE,EACvB,OAAQ,EAAW,IAAI,CAAE,EACzB,cAAe,EAAkB,IAAI,CAAE,EACvC,eAAgB,EAAmB,IAAI,CAAE,CAC3C,EAAE,EAMJ,OAJI,EAAM,IAAI,CAAqB,IACjC,EAAU,OAAS,CAAE,cAAe,EAAkB,IAAI,CAAqB,CAAE,GAG5E,CACT"}
1
+ {"version":3,"file":"readTestCases.cjs","names":["fs","path"],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\n/**\n * Read the test cases of a `test_cases/` directory. A test case id is the name of every\n * `<id>.in` / `<id>.out` file and `<id>.fin/` / `<id>.fout/` directory; any of them may be absent,\n * and what an absent part means is up to the harness (the presets document their rules).\n * Other entries (e.g. `.json` configuration read by a custom `judge.ts`) are ignored.\n */\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"wJAGA,MAAM,EAAwB,UAQ9B,eAAsB,EAAc,EAIlC,CACA,GAAI,CAACA,EAAAA,QAAG,WAAW,CAAS,EAAG,MAAO,CAAC,EAEvC,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAa,IAAI,IACjB,EAAoB,IAAI,IACxB,EAAqB,IAAI,IAE/B,IAAK,IAAM,KAAU,MAAMA,EAAAA,QAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,EAC/E,GAAI,EAAO,OAAO,EAAG,CACnB,GAAM,CAAE,MAAK,QAASC,EAAAA,QAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,OAAS,IAAQ,OAAQ,SAErC,IAAM,EAAO,MAAMD,EAAAA,QAAG,SAAS,SAASC,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAAG,MAAM,EAEzF,EAAM,IAAI,CAAI,EACV,IAAQ,OAAO,EAAU,IAAI,EAAM,CAAI,EACvC,IAAQ,QAAQ,EAAW,IAAI,EAAM,CAAI,CAC/C,MAAO,GAAI,EAAO,YAAY,EAAG,CAC/B,GAAM,CAAE,MAAK,QAASA,EAAAA,QAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,QAAU,IAAQ,QAAS,SAEvC,EAAM,IAAI,CAAI,EACV,IAAQ,QAAQ,EAAkB,IAAI,EAAMA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,EACrF,IAAQ,SAAS,EAAmB,IAAI,EAAMA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,CAC7F,CAGF,IAAM,EAAuD,CAAC,GAAG,CAAK,CAAC,CACpE,OAAQ,GAAO,IAAO,CAAqB,CAAC,CAC5C,SAAS,CAAC,CACV,IAAK,IAAQ,CACZ,KACA,MAAO,EAAU,IAAI,CAAE,EACvB,OAAQ,EAAW,IAAI,CAAE,EACzB,cAAe,EAAkB,IAAI,CAAE,EACvC,eAAgB,EAAmB,IAAI,CAAE,CAC3C,EAAE,EAMJ,OAJI,EAAM,IAAI,CAAqB,IACjC,EAAU,OAAS,CAAE,cAAe,EAAkB,IAAI,CAAqB,CAAE,GAG5E,CACT"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Read the test cases of a `test_cases/` directory. A test case id is the name of every
3
+ * `<id>.in` / `<id>.out` file and `<id>.fin/` / `<id>.fout/` directory; any of them may be absent,
4
+ * and what an absent part means is up to the harness (the presets document their rules).
5
+ * Other entries (e.g. `.json` configuration read by a custom `judge.ts`) are ignored.
6
+ */
1
7
  export declare function readTestCases(directory: string): Promise<{
2
8
  id: string;
3
9
  input?: string;
@@ -1,2 +1,2 @@
1
- import e from"node:path";import t from"node:fs";const n=`_shared`;async function r(r){if(!t.existsSync(r))return[];let i=new Set,a=new Map,o=new Map,s=new Map,c=new Map;for(let n of await t.promises.readdir(r,{withFileTypes:!0}))if(n.isFile()){let{ext:r,name:s}=e.parse(n.name);if(r!==`.in`&&r!==`.out`)continue;let c=await t.promises.readFile(e.join(n.parentPath,n.name),`utf8`);i.add(s),r===`.in`&&a.set(s,c),r===`.out`&&o.set(s,c)}else if(n.isDirectory()){let{ext:t,name:r}=e.parse(n.name);if(t!==`.fin`&&t!==`.fout`)continue;i.add(r),t===`.fin`&&s.set(r,e.join(n.parentPath,n.name)),t===`.fout`&&c.set(r,e.join(n.parentPath,n.name))}let l=[...i].filter(e=>e!==n).toSorted().map(e=>({id:e,input:a.get(e),output:o.get(e),fileInputPath:s.get(e),fileOutputPath:c.get(e)}));return i.has(n)&&(l.shared={fileInputPath:s.get(n)}),l}export{r as readTestCases};
1
+ import e from"node:fs";import t from"node:path";const n=`_shared`;async function r(r){if(!e.existsSync(r))return[];let i=new Set,a=new Map,o=new Map,s=new Map,c=new Map;for(let n of await e.promises.readdir(r,{withFileTypes:!0}))if(n.isFile()){let{ext:r,name:s}=t.parse(n.name);if(r!==`.in`&&r!==`.out`)continue;let c=await e.promises.readFile(t.join(n.parentPath,n.name),`utf8`);i.add(s),r===`.in`&&a.set(s,c),r===`.out`&&o.set(s,c)}else if(n.isDirectory()){let{ext:e,name:r}=t.parse(n.name);if(e!==`.fin`&&e!==`.fout`)continue;i.add(r),e===`.fin`&&s.set(r,t.join(n.parentPath,n.name)),e===`.fout`&&c.set(r,t.join(n.parentPath,n.name))}let l=[...i].filter(e=>e!==n).toSorted().map(e=>({id:e,input:a.get(e),output:o.get(e),fileInputPath:s.get(e),fileOutputPath:c.get(e)}));return i.has(n)&&(l.shared={fileInputPath:s.get(n)}),l}export{r as readTestCases};
2
2
  //# sourceMappingURL=readTestCases.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readTestCases.js","names":[],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"gDAGA,MAAM,EAAwB,UAE9B,eAAsB,EAAc,EAIlC,CACA,GAAI,CAAC,EAAG,WAAW,CAAS,EAAG,MAAO,CAAC,EAEvC,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAa,IAAI,IACjB,EAAoB,IAAI,IACxB,EAAqB,IAAI,IAE/B,IAAK,IAAM,KAAU,MAAM,EAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,EAC/E,GAAI,EAAO,OAAO,EAAG,CACnB,GAAM,CAAE,MAAK,QAAS,EAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,OAAS,IAAQ,OAAQ,SAErC,IAAM,EAAO,MAAM,EAAG,SAAS,SAAS,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAAG,MAAM,EAEzF,EAAM,IAAI,CAAI,EACV,IAAQ,OAAO,EAAU,IAAI,EAAM,CAAI,EACvC,IAAQ,QAAQ,EAAW,IAAI,EAAM,CAAI,CAC/C,MAAO,GAAI,EAAO,YAAY,EAAG,CAC/B,GAAM,CAAE,MAAK,QAAS,EAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,QAAU,IAAQ,QAAS,SAEvC,EAAM,IAAI,CAAI,EACV,IAAQ,QAAQ,EAAkB,IAAI,EAAM,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,EACrF,IAAQ,SAAS,EAAmB,IAAI,EAAM,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,CAC7F,CAGF,IAAM,EAAuD,CAAC,GAAG,CAAK,CAAC,CACpE,OAAQ,GAAO,IAAO,CAAqB,CAAC,CAC5C,SAAS,CAAC,CACV,IAAK,IAAQ,CACZ,KACA,MAAO,EAAU,IAAI,CAAE,EACvB,OAAQ,EAAW,IAAI,CAAE,EACzB,cAAe,EAAkB,IAAI,CAAE,EACvC,eAAgB,EAAmB,IAAI,CAAE,CAC3C,EAAE,EAMJ,OAJI,EAAM,IAAI,CAAqB,IACjC,EAAU,OAAS,CAAE,cAAe,EAAkB,IAAI,CAAqB,CAAE,GAG5E,CACT"}
1
+ {"version":3,"file":"readTestCases.js","names":[],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\n/**\n * Read the test cases of a `test_cases/` directory. A test case id is the name of every\n * `<id>.in` / `<id>.out` file and `<id>.fin/` / `<id>.fout/` directory; any of them may be absent,\n * and what an absent part means is up to the harness (the presets document their rules).\n * Other entries (e.g. `.json` configuration read by a custom `judge.ts`) are ignored.\n */\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"gDAGA,MAAM,EAAwB,UAQ9B,eAAsB,EAAc,EAIlC,CACA,GAAI,CAAC,EAAG,WAAW,CAAS,EAAG,MAAO,CAAC,EAEvC,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAa,IAAI,IACjB,EAAoB,IAAI,IACxB,EAAqB,IAAI,IAE/B,IAAK,IAAM,KAAU,MAAM,EAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,EAC/E,GAAI,EAAO,OAAO,EAAG,CACnB,GAAM,CAAE,MAAK,QAAS,EAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,OAAS,IAAQ,OAAQ,SAErC,IAAM,EAAO,MAAM,EAAG,SAAS,SAAS,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAAG,MAAM,EAEzF,EAAM,IAAI,CAAI,EACV,IAAQ,OAAO,EAAU,IAAI,EAAM,CAAI,EACvC,IAAQ,QAAQ,EAAW,IAAI,EAAM,CAAI,CAC/C,MAAO,GAAI,EAAO,YAAY,EAAG,CAC/B,GAAM,CAAE,MAAK,QAAS,EAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,QAAU,IAAQ,QAAS,SAEvC,EAAM,IAAI,CAAI,EACV,IAAQ,QAAQ,EAAkB,IAAI,EAAM,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,EACrF,IAAQ,SAAS,EAAmB,IAAI,EAAM,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,CAC7F,CAGF,IAAM,EAAuD,CAAC,GAAG,CAAK,CAAC,CACpE,OAAQ,GAAO,IAAO,CAAqB,CAAC,CAC5C,SAAS,CAAC,CACV,IAAK,IAAQ,CACZ,KACA,MAAO,EAAU,IAAI,CAAE,EACvB,OAAQ,EAAW,IAAI,CAAE,EACzB,cAAe,EAAkB,IAAI,CAAE,EACvC,eAAgB,EAAmB,IAAI,CAAE,CAC3C,EAAE,EAMJ,OAJI,EAAM,IAAI,CAAqB,IACjC,EAAU,OAAS,CAAE,cAAe,EAAkB,IAAI,CAAqB,CAAE,GAG5E,CACT"}
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs"),n=require("./safeFs.cjs");let r=require("node:fs/promises");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:child_process");a=e.__toESM(a,1);let o=require("node:fs");o=e.__toESM(o,1);let s=require("node:os");s=e.__toESM(s,1);const c={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},l={bun:h,cargo:g,go:_,gradle:v,maven:y,npm:b,pnpm:x,ruby:S,uv:C,yarn:w},u=M();async function d(e){let n=await r.default.mkdtemp(i.default.join(s.default.tmpdir(),e.tempDirPrefix??`exercode-`));try{await r.default.cp(e.cwd,n,{recursive:!0}),await k({packageManager:e.packageManager,projectDir:e.projectDir,runDir:n,projectFilePaths:e.projectFilePaths}),t.makeAccessibleToSandboxUser(n);let i=e.env?{...process.env,...e.env}:process.env,a=e.prepareDependencies===!1?void 0:await p(e.packageManager,n),o=typeof e.command==`function`?e.command({runDir:n}):e.command,s=Date.now(),c=e.outputLimitBytes??52428800,l;if(a&&(l=await j(a,{cwd:n,env:i,outputLimitBytes:c,stdin:``,timeLimitSeconds:e.timeLimitSeconds}),m(l)))return f({elapsedTimeSeconds:(Date.now()-s)/1e3,options:e,result:l});let u=e.timeLimitSeconds-(Date.now()-s)/1e3;if(u<=0)return{stdin:e.stdin??``,stdout:l?.stdout??``,stderr:l?.stderr??``,status:0,timeSeconds:e.timeLimitSeconds+.001,memoryBytes:l?.memoryBytes??0,timedOut:!0,signal:l?.signal,outputLimitExceeded:!1};let d=await j(o,{cwd:n,env:i,outputLimitBytes:c,stdin:e.stdin??``,timeLimitSeconds:u}),h=(Date.now()-s)/1e3;return f(l?{elapsedTimeSeconds:h,options:e,result:{...d,timeSeconds:l.timeSeconds+d.timeSeconds,memoryBytes:Math.max(l.memoryBytes,d.memoryBytes)}}:{elapsedTimeSeconds:h,options:e,result:d})}finally{try{t.sandboxUserName&&t.killSandboxUserProcesses()}finally{await t.forceRemove(n)}}}function f(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds||e.elapsedTimeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function p(e,t){return l[e](t)}function m(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function h(e){if(await D(i.default.join(e,`package.json`)))return await E(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function g(e){if(await D(i.default.join(e,`Cargo.toml`)))return await D(i.default.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function _(e){if(await D(i.default.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function v(e){if(!await E(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await D(i.default.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await D(i.default.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function y(e){if(!await D(i.default.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await D(i.default.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await D(i.default.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function b(e){if(await D(i.default.join(e,`package.json`)))return await D(i.default.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function x(e){if(await D(i.default.join(e,`package.json`)))return await D(i.default.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function S(e){if(await D(i.default.join(e,`Gemfile`)))return await D(i.default.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function C(){}async function w(e){if(!await D(i.default.join(e,`package.json`)))return;let t=await T(e),n=await D(i.default.join(e,`yarn.lock`));return t?n?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:n?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function T(e){if(await D(i.default.join(e,`.yarnrc.yml`)))return!0;let t=await O(i.default.join(e,`package.json`)),n=typeof t.packageManager==`string`?t.packageManager:void 0,r=/^yarn@(\d+)/.exec(n??``)?.[1];return r!==void 0&&Number(r)>=2}async function E(e,t){for(let n of t)if(await D(i.default.join(e,n)))return!0;return!1}async function D(e){try{return await r.default.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function O(e){try{let t=JSON.parse(await r.default.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function k(e){for(let t of e.projectFilePaths??c[e.packageManager])await A(i.default.join(e.projectDir,t),i.default.join(e.runDir,t),e.runDir)}async function A(e,t,a){try{await r.default.lstat(e),await n.createDirectoryWithoutFollowingSymlinks(a,i.default.dirname(t)),await n.copyWithoutFollowingSymlinks(e,t)}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}async function j(e,n){let r=u===void 0?void 0:i.default.join(n.cwd,`.exercode-time-result`),o=t.wrapCommandWithSandboxUser(e),s=u===void 0?o:t.prependInsideSandboxWrapper(o,[...u,`--output=${r}`]),c=t.startSandboxTimeoutWatchdog(n.timeLimitSeconds),l=Date.now()+(Math.ceil(n.timeLimitSeconds)+5)*1e3,d;try{d=a.default.spawn(s[0],s.slice(1),{cwd:n.cwd,detached:process.platform!==`win32`,env:{...n.env,...t.getSandboxUserEnvOverrides(n.env)},stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw c.cancel(),e}let f=[],p=[],m=0,h=!1,g=!1,_,v,y=e=>{try{N(d,e)}catch(e){_??=e instanceof Error?e:Error(String(e)),v?.()}},b=(e,t)=>{if(m>=n.outputLimitBytes){t.byteLength>0&&(g=!0,y(`SIGKILL`));return}let r=n.outputLimitBytes-m,i=t.byteLength>r?t.subarray(0,r):t;e.push(i),m+=i.byteLength,t.byteLength>r&&(g=!0,y(`SIGKILL`))};d.stdout.on(`data`,e=>b(f,e)),d.stderr.on(`data`,e=>b(p,e));let x=setTimeout(()=>{h=!0,y(`SIGTERM`)},n.timeLimitSeconds*1e3),S=setTimeout(()=>{h&&y(`SIGKILL`)},n.timeLimitSeconds*1e3+1e3);S.unref();let C,w=!1,{status:T,signal:E}=await new Promise((e,t)=>{let r=!1,i,a=e=>{r||(i=e,y(`SIGKILL`),d.pid===void 0&&(r=!0,t(e)))};v=()=>{C??=setTimeout(()=>{r||(r=!0,t(_??Error(`failed to terminate the submission`)))},Math.max(0,l-Date.now())+2e3)},d.on(`error`,a),d.stdin.on(`error`,e=>{e.code!==`EPIPE`&&a(e)}),d.on(`close`,(n,a)=>{if(!r){if(r=!0,w=!0,i){t(i);return}e({status:n??void 0,signal:a??void 0})}}),d.stdin.end(n.stdin)}).finally(()=>{clearTimeout(x),clearTimeout(S),C&&clearTimeout(C),(w||!_)&&c.cancel()}),{timeSeconds:D,memoryBytes:O}=r===void 0?{timeSeconds:0,memoryBytes:0}:await F(r);return{stdout:Buffer.concat(f).toString(),stderr:Buffer.concat(p).toString(),status:T,timeSeconds:D,memoryBytes:O,timedOut:h,signal:E,outputLimitExceeded:g}}function M(){let e=s.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=a.default.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}function N(e,n){if(e.pid!==void 0){if(t.sandboxUserName){t.killSandboxUserProcesses([n===`SIGKILL`?`KILL`:`TERM`]);return}try{if(process.platform===`win32`){e.kill(n);return}process.kill(-e.pid,n)}catch(e){let t=typeof e==`object`&&e&&`code`in e?e.code:void 0;if(t!==`ESRCH`&&t!==`EPERM`)throw e}}}function P(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}async function F(e){let t,n;try{if(n=await r.default.open(e,o.default.constants.O_RDONLY|o.default.constants.O_NOFOLLOW|o.default.constants.O_NONBLOCK),!(await n.stat()).isFile())throw Error(`${e} is not a regular file`);t=await n.readFile(`utf8`)}catch(t){if(P(t,`ENOENT`))return{timeSeconds:0,memoryBytes:0};throw Error(`failed to read the time measurement at ${e}`,{cause:t})}finally{await n?.close()}let i=/(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(t);return i?{timeSeconds:Number(i[1].replace(`,`,`.`)),memoryBytes:Number(i[2])*1024}:{timeSeconds:0,memoryBytes:0}}exports.copyPackageManagerProjectFiles=k,exports.runCommandInTemporaryPackageManagerProject=d;
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs"),n=require("./safeFs.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:child_process");a=e.__toESM(a,1);let o=require("node:os");o=e.__toESM(o,1);let s=require("node:fs/promises");s=e.__toESM(s,1);const c={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},l={bun:h,cargo:g,go:_,gradle:v,maven:y,npm:b,pnpm:x,ruby:S,uv:C,yarn:w},u=M();async function d(e){let n=await s.default.mkdtemp(i.default.join(o.default.tmpdir(),e.tempDirPrefix??`exercode-`));try{await s.default.cp(e.cwd,n,{recursive:!0}),await k({packageManager:e.packageManager,projectDir:e.projectDir,runDir:n,projectFilePaths:e.projectFilePaths}),t.makeAccessibleToSandboxUser(n);let r=e.env?{...process.env,...e.env}:process.env,i=e.prepareDependencies===!1?void 0:await p(e.packageManager,n),a=typeof e.command==`function`?e.command({runDir:n}):e.command,o=Date.now(),c=e.outputLimitBytes??52428800,l;if(i&&(l=await j(i,{cwd:n,env:r,outputLimitBytes:c,stdin:``,timeLimitSeconds:e.timeLimitSeconds}),m(l)))return f({elapsedTimeSeconds:(Date.now()-o)/1e3,options:e,result:l});let u=e.timeLimitSeconds-(Date.now()-o)/1e3;if(u<=0)return{stdin:e.stdin??``,stdout:l?.stdout??``,stderr:l?.stderr??``,status:0,timeSeconds:e.timeLimitSeconds+.001,memoryBytes:l?.memoryBytes??0,timedOut:!0,signal:l?.signal,outputLimitExceeded:!1};let d=await j(a,{cwd:n,env:r,outputLimitBytes:c,stdin:e.stdin??``,timeLimitSeconds:u}),h=(Date.now()-o)/1e3;return f(l?{elapsedTimeSeconds:h,options:e,result:{...d,timeSeconds:l.timeSeconds+d.timeSeconds,memoryBytes:Math.max(l.memoryBytes,d.memoryBytes)}}:{elapsedTimeSeconds:h,options:e,result:d})}finally{try{t.sandboxUserName&&t.killSandboxUserProcesses()}finally{await t.forceRemove(n)}}}function f(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds||e.elapsedTimeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function p(e,t){return l[e](t)}function m(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function h(e){if(await D(i.default.join(e,`package.json`)))return await E(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function g(e){if(await D(i.default.join(e,`Cargo.toml`)))return await D(i.default.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function _(e){if(await D(i.default.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function v(e){if(!await E(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await D(i.default.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await D(i.default.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function y(e){if(!await D(i.default.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await D(i.default.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await D(i.default.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function b(e){if(await D(i.default.join(e,`package.json`)))return await D(i.default.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function x(e){if(await D(i.default.join(e,`package.json`)))return await D(i.default.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function S(e){if(await D(i.default.join(e,`Gemfile`)))return await D(i.default.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function C(){}async function w(e){if(!await D(i.default.join(e,`package.json`)))return;let t=await T(e),n=await D(i.default.join(e,`yarn.lock`));return t?n?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:n?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function T(e){if(await D(i.default.join(e,`.yarnrc.yml`)))return!0;let t=await O(i.default.join(e,`package.json`)),n=typeof t.packageManager==`string`?t.packageManager:void 0,r=/^yarn@(\d+)/.exec(n??``)?.[1];return r!==void 0&&Number(r)>=2}async function E(e,t){for(let n of t)if(await D(i.default.join(e,n)))return!0;return!1}async function D(e){try{return await s.default.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function O(e){try{let t=JSON.parse(await s.default.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function k(e){for(let t of e.projectFilePaths??c[e.packageManager])await A(i.default.join(e.projectDir,t),i.default.join(e.runDir,t),e.runDir)}async function A(e,t,r){try{await s.default.lstat(e),await n.createDirectoryWithoutFollowingSymlinks(r,i.default.dirname(t)),await n.copyWithoutFollowingSymlinks(e,t)}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}async function j(e,n){let r=u===void 0?void 0:i.default.join(n.cwd,`.exercode-time-result`),o=t.wrapCommandWithSandboxUser(e),s=u===void 0?o:t.prependInsideSandboxWrapper(o,[...u,`--output=${r}`]),c=t.startSandboxTimeoutWatchdog(n.timeLimitSeconds),l=Date.now()+(Math.ceil(n.timeLimitSeconds)+5)*1e3,d;try{d=a.default.spawn(s[0],s.slice(1),{cwd:n.cwd,detached:process.platform!==`win32`,env:{...n.env,...t.getSandboxUserEnvOverrides(n.env)},stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw c.cancel(),e}let f=[],p=[],m=0,h=!1,g=!1,_,v,y=e=>{try{N(d,e)}catch(e){_??=e instanceof Error?e:Error(String(e)),v?.()}},b=(e,t)=>{if(m>=n.outputLimitBytes){t.byteLength>0&&(g=!0,y(`SIGKILL`));return}let r=n.outputLimitBytes-m,i=t.byteLength>r?t.subarray(0,r):t;e.push(i),m+=i.byteLength,t.byteLength>r&&(g=!0,y(`SIGKILL`))};d.stdout.on(`data`,e=>b(f,e)),d.stderr.on(`data`,e=>b(p,e));let x=setTimeout(()=>{h=!0,y(`SIGTERM`)},n.timeLimitSeconds*1e3),S=setTimeout(()=>{h&&y(`SIGKILL`)},n.timeLimitSeconds*1e3+1e3);S.unref();let C,w=!1,{status:T,signal:E}=await new Promise((e,t)=>{let r=!1,i,a=e=>{r||(i=e,y(`SIGKILL`),d.pid===void 0&&(r=!0,t(e)))};v=()=>{C??=setTimeout(()=>{r||(r=!0,t(_??Error(`failed to terminate the submission`)))},Math.max(0,l-Date.now())+2e3)},d.on(`error`,a),d.stdin.on(`error`,e=>{e.code!==`EPIPE`&&a(e)}),d.on(`close`,(n,a)=>{if(!r){if(r=!0,w=!0,i){t(i);return}e({status:n??void 0,signal:a??void 0})}}),d.stdin.end(n.stdin)}).finally(()=>{clearTimeout(x),clearTimeout(S),C&&clearTimeout(C),(w||!_)&&c.cancel()}),{timeSeconds:D,memoryBytes:O}=r===void 0?{timeSeconds:0,memoryBytes:0}:await F(r);return{stdout:Buffer.concat(f).toString(),stderr:Buffer.concat(p).toString(),status:T,timeSeconds:D,memoryBytes:O,timedOut:h,signal:E,outputLimitExceeded:g}}function M(){let e=o.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=a.default.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}function N(e,n){if(e.pid!==void 0){if(t.sandboxUserName){t.killSandboxUserProcesses([n===`SIGKILL`?`KILL`:`TERM`]);return}try{if(process.platform===`win32`){e.kill(n);return}process.kill(-e.pid,n)}catch(e){let t=typeof e==`object`&&e&&`code`in e?e.code:void 0;if(t!==`ESRCH`&&t!==`EPERM`)throw e}}}function P(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}async function F(e){let t,n;try{if(n=await s.default.open(e,r.default.constants.O_RDONLY|r.default.constants.O_NOFOLLOW|r.default.constants.O_NONBLOCK),!(await n.stat()).isFile())throw Error(`${e} is not a regular file`);t=await n.readFile(`utf8`)}catch(t){if(P(t,`ENOENT`))return{timeSeconds:0,memoryBytes:0};throw Error(`failed to read the time measurement at ${e}`,{cause:t})}finally{await n?.close()}let i=/(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(t);return i?{timeSeconds:Number(i[1].replace(`,`,`.`)),memoryBytes:Number(i[2])*1024}:{timeSeconds:0,memoryBytes:0}}exports.copyPackageManagerProjectFiles=k,exports.runCommandInTemporaryPackageManagerProject=d;
2
2
  //# sourceMappingURL=runCommandInTemporaryPackageManagerProject.cjs.map