@gfdelarue/testkit 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +297 -3
- package/dist/app/bugs/batch.js +1 -1
- package/dist/app/bugs/bisect-dependencies.js +1 -0
- package/dist/app/bugs/bisect-identity.js +1 -0
- package/dist/app/bugs/bisect-report.js +1 -0
- package/dist/app/bugs/bisect-search.js +1 -0
- package/dist/app/bugs/bisect.js +2 -0
- package/dist/app/bugs/draft.js +3 -3
- package/dist/app/bugs/facts.js +2 -2
- package/dist/app/bugs/file.js +1 -1
- package/dist/app/bugs/issue.js +4 -4
- package/dist/app/bugs/record.js +2 -2
- package/dist/app/campaigns/brief.js +5 -5
- package/dist/app/prove/changes.js +3 -0
- package/dist/app/prove/index.js +2 -0
- package/dist/app/prove/mutants.js +3 -0
- package/dist/app/prove/report.js +3 -0
- package/dist/app/prove/verdict.js +1 -0
- package/dist/app/run/engine-orchestration.js +1 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/request.js +1 -1
- package/dist/app/run/time-zone.js +1 -0
- package/dist/app/sandbox/index.js +1 -0
- package/dist/app/sandbox/run.js +1 -0
- package/dist/app/shake/conditions.js +1 -0
- package/dist/app/shake/index.js +4 -0
- package/dist/app/shake/report.js +6 -0
- package/dist/app/shake/verdicts.js +1 -0
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-flags.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/commands/bug/bisect.js +1 -0
- package/dist/cli/commands/bug/file.js +3 -3
- package/dist/cli/commands/bug/record.js +2 -2
- package/dist/cli/commands/prove.js +3 -0
- package/dist/cli/commands/run.js +2 -2
- package/dist/cli/commands/shake.js +1 -0
- package/dist/domain/file-timeout.js +1 -1
- package/dist/engines/contracts/fixture-manifest.js +1 -1
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/adapter/driver.js +2 -2
- package/dist/engines/node-vitest/adapter/evidence.js +2 -2
- package/dist/engines/node-vitest/adapter/reporter.js +1 -1
- package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
- package/dist/engines/node-vitest/selection.js +1 -1
- package/dist/engines/playwright/adapter/config.js +20 -5
- package/dist/process/environment.js +1 -1
- package/dist/process/execa-bundle.json +1 -1
- package/dist/repo/history.js +5 -5
- package/dist/types/workspace.d.ts +2 -0
- package/docs/bug-records.md +32 -23
- package/docs/release-notes.md +64 -0
- package/package.json +17 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{BUG_GUIDE as
|
|
1
|
+
import{BUG_GUIDE as r,renderBugGuide as a}from"../bugs/guide.js";const o=50;export function formatShare(t){return`${(t*100).toFixed(1)}%`}export function formatCount(t,n){return`${String(t)} ${n}${t===1?"":"s"}`}export function campaignSummaryPath(t){return`.testkit/campaigns/${t}/summary.md`}export function renderCampaignBrief(t,n,e){return[`# Testkit campaign ${t.id}`,`Target: ${t.target}`,renderAreaMapReport(n),"## How to work",["- Write Testkit tests for the target.","- A failing test is a bug only when an authority says what the product should do: documentation, a specification, an API contract, or the code's own stated intent.","- Never skip a failing test, and never assert the broken behaviour to make a test pass.","- Keep credentials, tokens, and passwords out of records and tests.","- To check that your tests notice a fault in the target's code, write the fault as a mutant and run `testkit prove --mutants <file>`. `testkit prove --help` lists its options, and the README gives the mutant format. A mutant that survives points at a missing assertion."].join(`
|
|
2
2
|
`),"## For each confirmed bug",["1. Write the bug record as a JSON file. The sections below give its format.","2. Run `testkit bug record <file>`. It prints the draft id.","3. Commit and push the test.","4. Run `testkit bug file <id>`."].join(`
|
|
3
|
-
`),...
|
|
3
|
+
`),...e===""?[]:[`The product directory is \`${e}\`. Run these commands there, or pass \`--dir\`.`],a(r),"## How to end",`Run \`testkit campaign finish\`. It writes the summary for the pull request that adds the tests to \`${campaignSummaryPath(t.id)}\` and prints it.`].join(`
|
|
4
4
|
|
|
5
|
-
`)}export function renderAreaMapReport(
|
|
6
|
-
`)),
|
|
5
|
+
`)}export function renderAreaMapReport(t){const n=["## Area map"];return t.areas.length===0?n.push(`\`testkit.config.ts\` has no \`areas\`. The repository has ${formatCount(t.unclaimed.length,"source file")}, and none belongs to an area.`):(n.push([...t.areas.map(e=>`- \`${e.id}\`, ${e.name}: ${formatCount(e.files,"file")}, ${formatShare(e.share)} of source bytes`),`- Unclaimed: ${formatCount(t.unclaimed.length,"file")}, ${formatShare(t.unclaimedShare)} of source bytes`,...t.doubleClaims.length===0?[]:[`- Claimed by more than one area: ${formatCount(t.doubleClaims.length,"file")}, ${formatShare(t.doubleClaimedShare)} of source bytes`]].join(`
|
|
6
|
+
`)),t.unclaimed.length>0&&n.push("Unclaimed source files:",i(t.unclaimed.map(e=>`\`${e.path}\``))),t.doubleClaims.length>0&&n.push("Source files that more than one area claims:",i(t.doubleClaims.map(e=>`\`${e.path}\`: ${e.areas.map(s=>`\`${s}\``).join(", ")}`)))),n.push("Before you hunt, write or refresh `areas` in `testkit.config.ts` so that every source file belongs to exactly one area. Each path is a glob from the repository root.",["```ts","areas: {",' checkout: { name: "Checkout", paths: ["src/checkout/**"] },',"},","```"].join(`
|
|
7
7
|
`)),n.join(`
|
|
8
8
|
|
|
9
|
-
`)}function i(
|
|
9
|
+
`)}function i(t){const n=t.length-o;return[...t.slice(0,o).map(e=>`- ${e}`),...n>0?[`- and ${String(n)} more`]:[]].join(`
|
|
10
10
|
`)}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{requireManagedHostCommandSuccess as l}from"../../process/index.js";import{REPOSITORY_COMMAND_GUARD_ARGS as m,runManagedGitCommand as f}from"../../repo/managed-git.js";const _=/\.(?:[cm]?[jt]s|[jt]sx)$/u,p=[/\.d\.[cm]?ts$/u,/\.(?:test|spec)\.[cm]?[jt]sx?$/u,/\.testkit\.ts$/u,/\.test\.setup\.m?js$/u,/(?:^|\/)(?:__testkit__|__setup__|__tests__|__mocks__|__fixtures__|fixtures|tests?|e2e|node_modules)\//u,/\.config\.[cm]?[jt]s$/u],h=/\.(?:(?:test|spec)\.[cm]?[jt]sx?|testkit\.ts)$/u,x=/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/u;export function isTestFile(t){return h.test(t)}export function isProductSource(t){return _.test(t)&&!p.some(e=>e.test(t))}export async function resolveDiffBase(t,e){const n=await f(t,["merge-base",e??"origin/HEAD","HEAD"]);if(n.exitCode===0)return n.stdout.trim();throw new Error(e===void 0?"Testkit could not find where this branch left origin's default branch. Pass --since <ref>, such as --since origin/main.":`Git cannot find a common ancestor of ${e} and HEAD: ${n.stderr.trim().split(`
|
|
2
|
+
`).at(-1)??""}`)}export async function listChangedSources(t,e,s,n=isProductSource){const r=s===""?".":s,c=await d(t,["diff","-U0","--no-color","--no-ext-diff","--src-prefix=a/","--dst-prefix=b/","--diff-filter=AMR",e,"--",r]),a=await d(t,["ls-files","-z","--others","--exclude-standard","--",r]),o=[...parseChangedLines(c)].map(([i,u])=>({lines:u,path:i}));for(const i of a.split("\0"))i!==""&&o.push({lines:"all",path:i});return o.filter(i=>n(i.path)).sort((i,u)=>i.path.localeCompare(u.path))}export function parseChangedLines(t){const e=new Map;let s=null;for(const n of t.split(`
|
|
3
|
+
`)){if(n.startsWith("+++ ")){const o=n.slice(4).trimEnd();s=o.startsWith("b/")?new Set:null,s!==null&&e.set(o.slice(2),s);continue}const r=x.exec(n);if(r===null||s===null)continue;const c=Number(r[1]),a=r[2]?Number(r[2]):1;for(let o=c;o<c+a;o+=1)s.add(o)}return e}async function d(t,e){const s=[...m,"-c","core.quotePath=false",...e];return l(`git ${e[0]}`,await f(t,s)).stdout}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import f from"node:fs";import j from"node:http";import d from"node:path";import{setTimeout as P}from"node:timers/promises";import{errorMessage as g}from"../../kernel/error-serialization.js";import{isUnknownRecord as R}from"../../kernel/unknown-values.js";import{makeTempDir as T,removeTempDir as I}from"../../process/files/temp-dir.js";import{resolveBugCheckout as x,writeJsonFile as C,writeTextFile as F}from"../bugs/store.js";import{resolveRequestedFiles as A}from"../file-selection.js";import{createProductSandbox as N}from"../sandbox/index.js";import{runInSandbox as S}from"../sandbox/run.js";import{listChangedSources as O,resolveDiffBase as G}from"./changes.js";import{generateMutants as q,instrument as $,locateAuthoredMutant as B,mutatedProbeId as y}from"./mutants.js";import{renderProveReport as H}from"./report.js";import{judgeMutantRun as J}from"./verdict.js";const L=/^\/([am]\d+\*?)$/u,U=["SIGINT","SIGTERM","SIGHUP"];export async function prove(e){const r=await x(e),t=e.authored===void 0?await G(r.repoRoot,e.since):null,n=e.authored===void 0?await _(r.repoRoot,t??"",r.productPath,e.maxMutants):e.authored.map((o,a)=>Q(r,o,a)),c={base:t,headCommit:r.headCommit,id:new Date().toISOString().replace(/[:.]/gu,"-")};if(n.length===0)return v(r.productDir,{...c,baselineMs:null,mutants:[]});const l=V(),s=await N(r.repoRoot,r.productPath);let i=null;try{i=await W(),l.check();const o={...e.selection,...e.selection.file===void 0?{}:{file:A(e.selection.file,r.productDir,r.cwd)}},a=[...new Set(n.map(u=>u.file))];for(const u of a){const p=n.filter(E=>E.file===u);s.write(u,$(s.read(u),p,i.target,"probe"))}const b=Date.now(),h=await S(s,o),w=Date.now()-b;if(l.check(),h.kind!=="passed")throw new Error(`The selected tests must pass before Testkit can prove them, and in a copy of the checkout ${K(h)}. Run the same selection with testkit run, make it pass, then run this again.`);const M=await i.take();for(const u of a)s.restore(u);const D=Math.max(30,Math.ceil(3*w/1e3)),m=[];for(const u of n){const p=M.has(u.id)?await z(s,o,u,i,D):k(u,{status:"not-reached"},null);l.check(),m.push(p),e.onMutant?.(p,m.length,n.length)}return v(r.productDir,{...c,baselineMs:w,mutants:m})}finally{i?.close(),await s.dispose(),l.dispose()}}export function readAuthoredMutants(e){let r;try{r=JSON.parse(f.readFileSync(e,"utf8"))}catch(t){throw new Error(`Testkit cannot read the mutants in ${e}: ${g(t)}`,{cause:t})}if(!Array.isArray(r)||r.length===0)throw new Error(`${e} must hold a non-empty JSON array of mutants`);return r.map((t,n)=>{const c=`${e} $[${String(n)}]`;if(!R(t))throw new Error(`${c} must be an object`);const{file:l,find:s,line:i,note:o,replace:a}=t;if(typeof l!="string"||l==="")throw new Error(`${c}.file must be a product path`);if(typeof i!="number"||!Number.isInteger(i)||i<1)throw new Error(`${c}.line must be a line number`);if(typeof s!="string"||s==="")throw new Error(`${c}.find must be the code to replace`);if(typeof a!="string")throw new Error(`${c}.replace must be the replacement code`);if(o!==void 0&&typeof o!="string")throw new Error(`${c}.note must be text`);return{file:l,find:s,line:i,replace:a,...o===void 0?{}:{note:o}}})}async function _(e,r,t,n){const c=(await O(e,r,t)).flatMap(o=>q(o.path,f.readFileSync(d.join(e,o.path),"utf8"),o.lines)),l=new Map;for(const o of c){const a=`${o.file}:${String(o.line)}`;l.set(a,[...l.get(a)??[],o])}const s=[...l.values()],i=[];for(let o=0;i.length<n&&s.some(a=>a.length>o);o+=1)for(const a of s)o<a.length&&i.length<n&&i.push(a[o]);return i.sort((o,a)=>o.file.localeCompare(a.file)||o.start-a.start).map((o,a)=>({...o,id:`m${String(a+1)}`}))}function Q(e,r,t){const n=d.relative(e.productDir,d.resolve(e.productDir,r.file));if(n===""||n.split(d.sep)[0]===".."||d.isAbsolute(n))throw new Error(`Mutant ${String(t+1)} names ${r.file}, which is not a file in the product directory`);const c=d.posix.join(e.productPath,n.split(d.sep).join("/")),l=f.readFileSync(d.join(e.repoRoot,c),"utf8");return{...B({...r,file:c},l),id:`a${String(t+1)}`}}async function z(e,r,t,n,c){e.write(t.file,$(e.read(t.file),[t],n.target,"mutate"));const l=Date.now();try{const s=await S(e,r,{fileTimeoutSeconds:c}).catch(a=>({kind:"run-failed",reason:g(a)})),i=await n.take(),o=J(s,{mutant:i.has(y(t.id)),unmutated:[...i].some(a=>a!==y(t.id))},t.file);return k(t,o,Date.now()-l)}finally{e.restore(t.file)}}function k(e,r,t){return{column:e.column,durationMs:t,file:e.file,id:e.id,line:e.line,...e.note===void 0?{}:{note:e.note},original:e.original,replacement:e.replacement,source:e.source,...r}}function K(e){return e.kind==="run-failed"?`the run failed outside its tests: ${e.reason}`:`these tests failed: ${e.failures.map(t=>`${t.file}${t.title===null?"":` \u203A ${t.title}`}`).join(", ")}`}function v(e,r){const t={errored:0,killed:0,"not-reached":0,survived:0};for(const s of r.mutants)t[s.status]+=1;const n=t.killed+t.survived+t["not-reached"],c=d.join(e,".testkit","prove",r.id),l={...r,counts:t,recordDir:c,score:n===0?null:t.killed/n};return C(d.join(c,"prove.json"),{...l,recordDir:void 0}),F(d.join(c,"prove.md"),H(l)),l}function V(){let e=null;const r=U.map(t=>{const n=()=>{e=t};return process.on(t,n),[t,n]});return{check(){if(e!==null)throw new Error(`testkit prove stopped on ${e}`)},dispose(){for(const[t,n]of r)process.off(t,n)}}}async function W(){const e=T("testkit-prove-"),r=d.join(e,"reached"),t=new Set,n=j.createServer((s,i)=>{const o=L.exec(s.url??"")?.[1];o!==void 0&&t.add(o),i.setHeader("access-control-allow-origin","*"),i.setHeader("connection","close"),i.end()});await new Promise(s=>n.listen(0,"127.0.0.1",s));const{port:c}=n.address(),l=()=>new Promise(s=>{n.getConnections((i,o)=>{s(o)})});return{target:{file:r,url:`http://127.0.0.1:${String(c)}/`},close(){n.close(),I(e)},async take(){for(let i=0;i<20&&await l()>0;i+=1)await P(50);const s=new Set(t);if(t.clear(),!f.existsSync(r))return s;for(const i of f.readFileSync(r,"utf8").split(`
|
|
2
|
+
`))i!==""&&s.add(i);return f.rmSync(r),s}}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import n from"typescript";const k=new Map([[n.SyntaxKind.LessThanToken,"<="],[n.SyntaxKind.LessThanEqualsToken,"<"],[n.SyntaxKind.GreaterThanToken,">="],[n.SyntaxKind.GreaterThanEqualsToken,">"],[n.SyntaxKind.EqualsEqualsEqualsToken,"!=="],[n.SyntaxKind.ExclamationEqualsEqualsToken,"==="],[n.SyntaxKind.EqualsEqualsToken,"!="],[n.SyntaxKind.ExclamationEqualsToken,"=="],[n.SyntaxKind.AmpersandAmpersandToken,"||"],[n.SyntaxKind.BarBarToken,"&&"],[n.SyntaxKind.PlusToken,"-"],[n.SyntaxKind.MinusToken,"+"],[n.SyntaxKind.AsteriskToken,"/"],[n.SyntaxKind.SlashToken,"*"]]),g=new Set([n.SyntaxKind.AmpersandAmpersandToken,n.SyntaxKind.BarBarToken,n.SyntaxKind.QuestionQuestionToken]);export function generateMutants(t,i,e){const s=f(t,i),o=[],l=a=>{if(n.isEnumMember(a)||n.isTypeNode(a)||$(a))return;const r=n.isBinaryExpression(a)?a.operatorToken.getStart(s):a.getStart(s),c=s.getLineAndCharacterOfPosition(r).line+1;if(e==="all"||e.has(c)){const d=T(a,s);d!==null&&o.push(S(a,s,d))}n.forEachChild(a,l)};return l(s),o.map(a=>({...a,file:t,id:"",source:"generated"}))}export function locateAuthoredMutant(t,i){const e=f(t.file,i),s=`${t.file}:${String(t.line)}`,o=e.getPositionOfLineAndCharacter(t.line-1,0),l=i.indexOf(`
|
|
2
|
+
`,o)===-1?i.length:i.indexOf(`
|
|
3
|
+
`,o),a=i.slice(o,l).indexOf(t.find);if(a===-1)throw new Error(`${s} does not contain ${JSON.stringify(t.find)}`);const r=O(e,o+a,o+a+t.find.length);if(r===null)throw new Error(`${JSON.stringify(t.find)} at ${s} is not a whole expression or statement`);if(K(r))throw new Error(`${JSON.stringify(t.find)} at ${s} is called, assigned, deleted or optionally chained, so Testkit cannot record when it runs. Replace the whole expression around it.`);const c={...S(r,e,{end:r.getEnd(),start:r.getStart(e),text:t.replace}),file:t.file,id:"",...t.note===void 0?{}:{note:t.note},source:"authored"};if(f(t.file,instrument(i,[c],{file:"",url:""},"mutate")).parseDiagnostics.length>f(t.file,i).parseDiagnostics.length)throw new Error(`Replacing ${JSON.stringify(t.find)} at ${s} does not leave valid code`);return c}export function instrument(t,i,e,s){const o=i.flatMap(r=>s==="mutate"?E(r,x(e,mutatedProbeId(r.id))):y(r,x(e,r.id))),l=new Set(i.filter(r=>r.semicolon).map(r=>r.start));for(const r of l)o.push({end:r,rank:3e9,start:r,text:";"});o.sort((r,c)=>c.start-r.start||r.rank-c.rank);let a=t;for(const r of o)a=`${a.slice(0,r.start)}${r.text}${a.slice(r.end)}`;return a}export function mutatedProbeId(t){return`${t}*`}function y(t,i){return t.kind==="statement"?u(t,`${t.statementBlock?"{":""}${i};`,t.statementBlock?"}":""):u(t.anchor,`(${i},`,")")}function E(t,i){if(t.kind==="statement"){const o=`${i};${t.edit.text}`;return[{...t.edit,rank:-1,text:t.statementBlock?`{${o}}`:o}]}const{anchor:e,edit:s}=t;return s.start<=e.start&&s.end>=e.end?[{...s,rank:-1,text:`(${i},${s.text})`}]:[{...s,rank:-1},...u(e,`(${i},`,")")]}function u(t,i,e){const s=t.end-t.start;return[{end:t.start,rank:1e9+s,start:t.start,text:i},{end:t.end,rank:2e9-s,start:t.end,text:e}]}function x(t,i){return`((R,i)=>{try{${`const g=globalThis;let s=R.get(g,"__testkitReached");if(!s){s=new Set();R.set(g,"__testkitReached",s)}if(!s.has(i)){s.add(i);const p=R.get(g,"process"),f=p&&p.getBuiltinModule&&p.getBuiltinModule("node:fs");if(f)f.appendFileSync(${JSON.stringify(t.file)},i+"\\n");else{const h=R.get(g,"fetch");if(h)h(${JSON.stringify(t.url)}+i,{mode:"no-cors",keepalive:true}).catch(()=>{})}}`}}catch{}})(Reflect,${JSON.stringify(i)})`}function T(t,i){if(n.isBinaryExpression(t)){const e=k.get(t.operatorToken.kind);return e===void 0||e.length===1&&"+-".includes(e)&&w(t)?null:{end:t.operatorToken.end,start:t.operatorToken.getStart(i),text:e}}return(t.kind===n.SyntaxKind.TrueKeyword||t.kind===n.SyntaxKind.FalseKeyword)&&!m(t)?{end:t.end,start:t.getStart(i),text:t.kind===n.SyntaxKind.TrueKeyword?"false":"true"}:n.isPrefixUnaryExpression(t)&&t.operator===n.SyntaxKind.ExclamationToken?{end:t.operand.getStart(i),start:t.getStart(i),text:""}:null}function h(t,i){let e=t;for(;;)if(n.isParenthesizedExpression(e))e=e.expression;else if(n.isPrefixUnaryExpression(e)&&e.operator===n.SyntaxKind.ExclamationToken)e=e.operand;else if(n.isBinaryExpression(e)&&g.has(e.operatorToken.kind))e=e.left;else return{end:e.end,start:e.getStart(i)}}function m(t){let i=t.parent;for(;n.isParenthesizedExpression(i);)i=i.parent;return(n.isAsExpression(i)||n.isTypeAssertionExpression(i))&&n.isConstTypeReference(i.type)}function K(t){let i=t;for(;n.isParenthesizedExpression(i.parent)||n.isAsExpression(i.parent)||n.isSatisfiesExpression(i.parent)||n.isNonNullExpression(i.parent)||n.isTypeAssertionExpression(i.parent);)i=i.parent;const{parent:e}=i,s=(n.isPropertyAccessExpression(e)||n.isElementAccessExpression(e))&&e.questionDotToken!==void 0,o=(n.isCallExpression(e)||n.isNewExpression(e))&&e.expression===i||n.isTaggedTemplateExpression(e)&&e.tag===i,l=n.isDeleteExpression(e)||n.isPostfixUnaryExpression(e)||n.isPrefixUnaryExpression(e)&&(e.operator===n.SyntaxKind.PlusPlusToken||e.operator===n.SyntaxKind.MinusMinusToken),a=n.isBinaryExpression(e)&&e.left===i&&e.operatorToken.kind>=n.SyntaxKind.FirstAssignment&&e.operatorToken.kind<=n.SyntaxKind.LastAssignment;return n.isOptionalChain(t)||s||o||l||a}function $(t){return n.canHaveModifiers(t)&&(n.getModifiers(t)??[]).some(i=>i.kind===n.SyntaxKind.DeclareKeyword)}function w(t){const i=e=>n.isStringLiteralLike(e)||n.isTemplateExpression(e);return i(t.left)||i(t.right)}function S(t,i,e){const s=t.getStart(i),o=t.getEnd(),l=i.text,a=i.getLineAndCharacterOfPosition(s),r=n.isStatement(t)&&!n.isBlock(t),c=p=>n.isBlock(p)||n.isSourceFile(p)||n.isCaseOrDefaultClause(p)||n.isModuleBlock(p),d=r?t:A(t,i,s);return{anchor:h(t,i),column:a.character+1,edit:e,end:o,kind:r?"statement":"expression",line:a.line+1,original:l.slice(s,o),replacement:`${l.slice(s,e.start)}${e.text}${l.slice(e.end,o)}`,semicolon:d!==null&&c(d.parent),start:s,statementBlock:r&&!c(t.parent)}}function A(t,i,e){let s=t.parent;for(;!n.isSourceFile(s)&&s.getStart(i)===e;){if(n.isExpressionStatement(s))return s;s=s.parent}return null}function O(t,i,e){let s=null;const o=l=>{l.getStart(t)>i||l.getEnd()<e||(l.getStart(t)===i&&l.getEnd()===e&&(n.isExpression(l)||n.isStatement(l))&&(s??=l),n.forEachChild(l,o))};return o(t),s}function f(t,i){const e=/\.tsx$/u.test(t)?n.ScriptKind.TSX:/\.[cm]?ts$/u.test(t)?n.ScriptKind.TS:/\.jsx?$/u.test(t)?n.ScriptKind.JSX:n.ScriptKind.JS;return n.createSourceFile(t,i,n.ScriptTarget.Latest,!0,e)}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const c=[["survived","Survived: tests ran the changed code and none failed","wrong-assertion"],["not-reached","Not reached: no test, in any process, ran the changed code","no-test"],["errored","Errored: the run failed outside its tests",null],["killed","Killed",null]];export function renderProveReport(e){const{counts:n}=e,t=["# Testkit prove","",e.base===null?"Mutants: authored":`Changes since: ${e.base.slice(0,12)}`,`Head: ${e.headCommit.slice(0,12)}`,"",e.mutants.length===0?"No changed product code to mutate.":`${a(e)}. Killed ${String(n.killed)}, survived ${String(n.survived)}, not reached ${String(n["not-reached"])}, errored ${String(n.errored)}.`];for(const[s,i,o]of c){const l=e.mutants.filter(r=>r.status===s);l.length!==0&&(t.push("",`## ${i}${o===null?"":` (\`${o}\`)`}`,""),t.push(...l.map(r=>`- ${describeMutant(r)}`)))}return`${t.join(`
|
|
2
|
+
`)}
|
|
3
|
+
`}export function describeMutant(e){const n=`\`${d(e.original)}\` \u2192 \`${d(e.replacement)||"(removed)"}\``,t=`${e.file}:${String(e.line)}:${String(e.column)}`,s=e.note===void 0?"":` (${e.note})`,i=e.killedBy===void 0?"":`, by ${e.killedBy.file}${e.killedBy.title===null?"":` \u203A ${e.killedBy.title}`}`,o=e.reason===void 0?"":`: ${e.reason}`;return`${t} ${n}${s}${i}${o}`}function a(e){return e.score===null?"No mutant was judged":`Mutation score ${String(Math.round(e.score*100))}%`}function d(e){const n=e.replace(/\s+/gu," ").trim();return n.length>80?`${n.slice(0,77)}...`:n}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function judgeMutantRun(t,u,e){return u.unmutated?{reason:`the run ran code other than the mutated ${e}, so a build probably reused the output of an earlier run. Add ${e} to that build's inputs`,status:"errored"}:t.kind==="run-failed"?{reason:t.reason,status:"errored"}:u.mutant?t.kind==="passed"?{status:"survived"}:{killedBy:t.failures[0],status:"killed"}:{reason:`the run never ran the mutated ${e}, though the unmutated run did. A build may have reused an earlier run's output, so add ${e} to its inputs, or the code runs only some of the time`,status:"errored"}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../../engines/api/index.js";import{findNearestPlaywrightConfigFile as
|
|
1
|
+
import"../../engines/api/index.js";import{findNearestPlaywrightConfigFile as s}from"../../engines/playwright/runtime/config-file.js";export function engineSupportInputs(e){switch(e.task.engineId){case"node-vitest":return{configuration:e.request.retries!==void 0&&e.request.vitest!==void 0?{...e.request.vitest,retry:e.request.retries}:e.request.vitest,supportModules:{}};case"playwright":return{configuration:{...u(e),...e.request.retries===void 0?{}:{retries:e.request.retries}},supportModules:i(e)}}}export function defaultEnvironmentValues(e){return{...e.baseUrl===null?{}:{BASE_URL:e.baseUrl},...e.databaseUrl===null?{}:{DATABASE_URL:e.databaseUrl}}}export function engineInputEnvironment(e){const r=Object.entries(e.runtimeEnvironment).filter(([t])=>t.toUpperCase()!=="BASE_URL"),n=new Set(r.map(([t])=>t.toUpperCase()));return{...Object.fromEntries(Object.entries(e.environment.env).filter(([t])=>!n.has(t.toUpperCase()))),...Object.fromEntries(r)}}function i(e){const r=s(e.request.productDir,e.request.ownerRoots[e.task.serviceName]??[]);return r===null?{}:{playwrightConfig:r}}function u(e){const r=e.environment.baseUrl;return r!==null&&o(r)?{...e.environment.ui,allowRemote:!0}:{...e.environment.ui}}function o(e){try{return!a(new URL(e).hostname)}catch{return!1}}function a(e){const r=e.toLowerCase();return r==="localhost"||r==="::1"||r==="0.0.0.0"||r==="127.0.0.1"||r.endsWith(".localhost")||r.startsWith("127.")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveRuntimeClosure as Ee}from"../../config/loader/dependencies.js";import{claimNextTask as Ke,collectSuites as Te}from"./planning.js";import{buildRunPlanningMetadata as Ae,buildScheduledQueue as be}from"./scheduler/index.js";import{addTrackerError as Me,buildServiceTrackers as xe,finalizeServiceResult as ge,recordGraphError as Ie,recordTaskOutcome as Pe,resolveRunGateFailure as qe}from"./results.js";import{loadTimings as ze,saveTimings as Oe}from"../../evidence/index.js";import{writeRunArtifact as Ne}from"../../evidence/artifacts.js";import{stableJson as $e}from"../../evidence/storage.js";import{createRunEnvironmentRecord as Le}from"../../evidence/environment-record.js";import{createRunLogRegistry as je}from"../../evidence/logs.js";import{createOperationTimingRegistry as De,timeOperation as J}from"../../evidence/operation-timings.js";import{createRunReporterFromListener as Ge}from"./run-events.js";import{createCallerRedaction as Ve}from"./caller-redaction.js";import{createSetupOperationRegistry as Be}from"../../evidence/setup-progress.js";import{createLocalRunEnvironment as We}from"../../environment/run-environment.js";import{writeRunMaintenance as Ue}from"../../evidence/maintenance.js";import{createConfigDrivenEnvironments as _e,planConfiguredCapacity as He}from"../environments/config-driven.js";import{declaresProvisioning as Je,declaresValues as he,holdBeforeRelease as Qe,routeEnvironments as Xe}from"../environments/routing.js";import{createStaticValueEnvironments as Ye}from"../environments/static-values.js";import{createSuppliedEnvironments as Ze}from"../environments/supplied.js";import{environmentTaskIdentity as et}from"./environment.js";import{createTaskLeases as tt}from"./task-leases.js";import{createTaskLocks as rt}from"./scheduler/locks.js";import{readPackageMetadata as nt,safeHostname as it,safeUsername as at}from"../../process/host/metadata.js";import{collectGitMetadata as st}from"../../repo/metadata.js";import{resolveExecutionConfig as ot}from"../../config/loader/decode-execution.js";import{projectGroupTestPolicy as ct}from"../../config/loader/discover-tests.js";import{resolveTaskPolicy as ut}from"../../config/loader/test-selection.js";import"../../environment/run-environment.js";import{createEngineManager as lt}from"../../engines/factory.js";import{createWorker as mt,runWorker as ft}from"./worker-loop.js";import{ensureRequestedFilesMatch as dt}from"./run-guards.js";import{createLiveSnapshotWriter as pt}from"./live-run.js";import{finalizeRunArtifacts as gt}from"./run-finalization.js";import{createRunFinisher as ht}from"./run-finisher.js";import{createRunEvidencePublisher as yt}from"../../evidence/run-evidence.js";import{buildRunArtifact as vt}from"../../evidence/reporting.js";import{createOperationReporterBridge as kt}from"./operation-progress.js";import{formatError as z,serializeError as Z}from"../../kernel/error-serialization.js";export async function runAll(e,a,o,n,m=[]){const k=n.fileNames||[];dt(e,a,o,k);const{productDir:c}=n,p=We(c),h=lt(c),y=new Map(m.map(r=>[r.name,r])),t=Date.now(),S=e[0]?.artifacts??null,E=[...new Set(e.flatMap(r=>r.uses.flatMap(l=>Object.keys(y.get(l)?.testkit.serviceEnv??{}))))].sort(),f={git:await st(c),host:{hostname:it(),username:at()},testkitVersion:nt().version},C=e[0]?.redact??[],R=Ve(process.env,C),ee=n.onEvent;let te=()=>{};const v=ee?Ge(r=>{r.type==="run.task_started"&&te(),ee(R.redact(r))},{debug:n.debug}):null,M=je(c),B={workerCount:0},O=resolveRunExecutionConfig(e[0],n),W=collectServicePlans(e,y,a,o,n,O,v);v?.setServicePlans?.(W);const N=xe(W,t),$=(r,l="product")=>{for(const s of N.values())s.skipped||Me(s,r,{classification:l});v?.runError?.(r)},ve=o.map(r=>({...r})),Q={typeValues:a,suiteSelectors:ve,fileNames:k,groupFilter:n.serviceFilter||null,planning:null};let w=()=>{};const ke=kt(v),b=De({onChange:r=>{ke(r),w()}}),re=Be({logRegistry:M,onChange:()=>{w()}}),d=Le({logRegistry:M,redactKeys:C,setupRegistry:re,onPublishedChange:()=>{w()}});R.useSourceTargets(()=>d.redactionTargets()),d.recordGroups(W.map(r=>({dbBackend:r.dbBackend,name:r.config.name})));const X=W.filter(r=>!r.skipped),ne=n.environments?.supplied??{},{plans:x,provisionedGraphKeys:ie,staticGraphKeys:ae}=assignRuntimeGraphs(X,new Set(Object.keys(ne)));let g;const u=await p.start({evidence:yt({redactKeys:C,redactionTargets:()=>d.redactionTargets(),resetResults:!0}),keepAlive:!0}),L=ht(u),U=pt({productDir:c,runId:u.runId,configs:e,trackers:N,startedAt:t,execution:O,workerState:B,selection:Q,archiveConfig:S,declaredEnvironmentKeys:E,metadata:f,environmentRecord:d,operationTimingRegistry:b,reporter:v});w=()=>{U.update()},te=()=>{U.flush()};const j={artifacts:null},D={result:null},se=r=>{const l=R.redact(r);try{return Ue(c,u.runId,l),l}catch(s){const F=`Could not write the maintenance record: ${R.redact(z(s))}`;return{...l,status:"failed",error:l.error===null?F:`${l.error}; ${F}`}}},oe=r=>(D.result=r.maintenance,r.maintenance.status==="failed"&&v?.warning?.(`Post-run maintenance failed: ${r.maintenance.error??"unknown error"}`),r.sealed),Re=async(r,l)=>(u.beginFinalization(),oe(await L.finish(r,s=>{const F=l(s);return j.artifacts={runArtifact:F.runArtifact},F},"normal",se)));let K,I;const ce=(r,l,s)=>({productDir:c,runId:u.runId,results:r,startedAt:t,finishedAt:l,execution:O,workerCount:B.workerCount,selection:Q,archiveConfig:S,declaredEnvironmentKeys:E,metadata:f,environmentRecord:d,operationTimingRegistry:b,reporter:v,runInterruptReason:u.getInterruptReason(),runStatus:s,publishRunFinalization:Re});w();let T=null,ue=Symbol("no release failure");try{try{if(X.length>0){const s=ze(c),F=_e(u,new Map(x.map(i=>[i.config.name,i.config.uses])),c,d),we=Ye(new Map(x.filter(i=>ae.has(i.runtimeKey)).map(i=>[i.config.name,i.uses])),c,d),Se=Ze(ne,{productDir:c,record:d,signal:u.signal}),le=i=>ie.has(i)?F:ae.has(i)?we:Se,me=Xe(new Map(x.map(({runtimeKey:i})=>[i,le(i)]))),{capacity:_,queue:G}=await J(b,{name:"testkit.plan",kind:"planning",summary:"plan test execution",scope:{serviceCount:X.length}},()=>({capacity:He(x.filter(i=>ie.has(i.runtimeKey))),queue:be(x.map(i=>prepareSchedulerPlan(i,A=>me.hasSetup({file:A,graphKey:i.runtimeKey,targetName:i.config.name}))),{timings:s})}));Q.planning=Ae(G),d.recordCapacity(_),v?.setTotalFileCount?.(G.length),B.workerCount=Math.max(1,Math.min(O.taskConcurrency,G.length));const fe=Array.from({length:B.workerCount},(i,A)=>mt(A+1,c));_.graphs.length>0&&u.provision(_.graphs,{reporter:v,environmentRecord:d,logRegistry:M,setupRegistry:re,operationTimingRegistry:b,skipSchemaSourceVerify:n.skipSchemaSourceVerify});const de=tt(c),Ce={recordRunError:$,runId:u.runId,signal:u.signal,isStopRequested:()=>u.isStopRequested(),environments:Ft(me,n.environments?.hold),leases:St(de,d),locks:rt(),request:Et(c,e,O,n.vitest),environmentKeys:new Map(x.flatMap(({config:i,runtimeKey:A})=>{const P=le(A),q=P.preview?.(i.name)??null;return q===null?[]:[[i.name,$e([q,P.checksServices?.(i.name)??!1])]]}))},pe=[];try{const A=(await J(b,{name:"testkit.execute",kind:"execution",summary:"execute local worker queue",scope:{workerCount:fe.length,taskCount:G.length}},async()=>Promise.allSettled(fe.map(P=>ft(P,G,Ce,h,N,pe,Ke,(q,H,V,Y)=>{Pe(q,H,V,Y),V.environmentValuesMissing!==void 0&&d.recordMissingValues(et(H),V.environmentValuesMissing),w()},(q,H,V,Y,Fe)=>{Ie(q,H,V,Y,Fe),w()},v))))).flatMap(workerResultErrorsForOrchestrator);for(const P of A)$(P);A.length>0&&w(),d.recordRuntimeStats(u.getStats())}finally{_.graphs.length>0&&await J(b,{name:"testkit.cleanup",kind:"cleanup",summary:"stop managed runtimes and services"},()=>Ct(d,()=>u.cleanupAll()).catch(i=>{throw ue=i,$(Z(i).message||z(i),"infra"),i})),de.close()}Oe(c,s,pe)}}catch(s){T=Z(s),s!==ue&&$(T.message||z(s)),w()}U.finish(),g=T===null?0:u.isStopRequested()?130:1,I=Date.now(),K=e.map(s=>ge(N.get(s.name),t,I));const r=qe(K,n.allowEmpty===!0);await J(b,{name:"testkit.collect",kind:"collection",summary:"close and collect run logs"},()=>M.closeAll());const l=await gt(ce(K,I,u.isStopRequested()?"interrupted":T===null&&r===null?null:"failed"));return T!==null&&(g=1),K.some(s=>s.failed||s.infraFailed)&&(g=1),r!==null&&(g=1),l.shouldFailArchivePublication&&(g=1),l.shouldFailArchiveCleanup&&(g=1),(l.runArtifact.run.status==="interrupted"||u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted")&&(g=Math.max(g,130)),{runArtifact:l.runArtifact,results:R.redact(K),exitCode:g,...T===null?{}:{failure:R.redact(T)},...D.result===null?{}:{maintenance:R.redact(D.result)}}}catch(r){const l=Z(r);$(l.message||z(r),"infra"),w(),U.finish(),u.beginFinalization(),g=u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted"?130:1,I=Date.now(),K=e.map(F=>ge(N.get(F.name),t,I)),await ye(M);let s=j.artifacts;if(s===null)try{s=await Rt(ce(K,I,u.isStopRequested()?"interrupted":"failed"),L,j,l,se,oe)}catch{if(j.artifacts===null)throw r;s=j.artifacts}return(s.runArtifact.run.status==="interrupted"||u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted")&&(g=Math.max(g,130)),{runArtifact:s.runArtifact,results:R.redact(K),exitCode:g,failure:R.redact(T??l),...D.result===null?{}:{maintenance:R.redact(D.result)}}}finally{u.dispose(),await ye(M),wt(d,n.environments?.observeRecord,v)}}async function Rt(e,a,o,n,m,k){const c=await a.finish("failed",p=>{const h=p.artifactStatus==="interrupted"?p:{artifactStatus:"failed",interruptReason:null},y=e.declaredEnvironmentKeys??[],t=vt({productDir:e.productDir,runId:e.runId,results:e.results,startedAt:e.startedAt,finishedAt:e.finishedAt,execution:e.execution,workerCount:e.workerCount,typeValues:e.selection.typeValues,suiteSelectors:e.selection.suiteSelectors,fileNames:e.selection.fileNames,groupFilter:e.selection.groupFilter,planning:e.selection.planning??null,archiveConfig:e.archiveConfig,declaredEnvironmentKeys:y,metadata:e.metadata,environment:e.environmentRecord.snapshot(),operationTimings:e.operationTimingRegistry?.listOperations()??[],runInterruptReason:h.interruptReason,runStatus:h.artifactStatus});t.finalizationFailure={...n};const E={runArtifact:Ne(e.productDir,t,{archiveConfig:e.archiveConfig,declaredEnvironmentKeys:y,redactKeys:e.environmentRecord.redactKeys,redactionTargets:e.environmentRecord.redactionTargets()})};return o.artifacts=E,E},"recovery",m);return k(c)}function wt(e,a,o){if(a!==void 0)try{a(e.snapshot())}catch(n){try{o?.warning?.(`Environment record observer failed: ${z(n)}`)}catch{}}}function St(e,a){return{...e,remove(o){const n=a.releaseFor(o.id);try{e.remove(o)}catch(m){throw n?.releaseFailed(m),m}n?.released()}}}async function Ct(e,a){try{await a()}catch(o){throw e.recordCleanup(o),o}e.recordCleanup(null)}async function ye(e){try{await e.closeAll()}catch{}}export function workerResultErrorsForOrchestrator(e){return e.status==="rejected"?[z(e.reason)]:[...new Set(e.value.unpersistedErrors)]}function Ft(e,a){return a===void 0?e:Qe(e,a)}function Et(e,a,o,n){return{vitest:n,productDir:e,fileTimeoutSeconds:o.fileTimeoutSeconds,ownerRoots:Object.fromEntries(a.map(m=>[m.name,m.roots]))}}export function resolveRunExecutionConfig(e,a){return ot({cli:{fileTimeoutSeconds:a.fileTimeoutSeconds,taskConcurrency:a.taskConcurrency},repo:e?{fileTimeoutSeconds:e.testkit.execution.fileTimeoutSeconds,taskConcurrency:e.testkit.execution.taskConcurrency}:void 0})}export function assignRuntimeGraphs(e,a){const o=e.filter(t=>t.suites.some(S=>S.files.length>0)),n=new Set(o.filter(t=>!a.has(t.config.name)&&Je(t.runtimeConfigs))),m=new Set([...n].map(t=>t.runtimeKey)),k=t=>!n.has(t)&&!a.has(t.config.name)&&he(t.runtimeConfigs),c=new Set(o.filter(t=>k(t)&&t.runtimeKey!=="").map(t=>t.runtimeKey)),p=new Set(o.map(t=>t.runtimeKey)),h=t=>t.runtimeKey!==""&&!m.has(t.runtimeKey)&&(k(t)||!c.has(t.runtimeKey))?t.runtimeKey:p.has(t.config.name)?`group:${t.config.name}`:t.config.name,y=o.map(t=>n.has(t)?t:{...t,runtimeKey:h(t)});return{plans:y,provisionedGraphKeys:m,staticGraphKeys:new Set(y.filter(t=>!m.has(t.runtimeKey)&&!a.has(t.config.name)&&he(t.runtimeConfigs)).map(t=>t.runtimeKey))}}export function prepareSchedulerPlan(e,a){const o=ct(e.config.testkit);return{graphKey:e.runtimeKey,owner:e.config.name,suites:e.suites.map(n=>({...n,tasks:n.files.map(m=>({file:m,hasSetup:a(m),...ut(o,n,m)}))}))}}export function collectServicePlans(e,a,o,n,m,k,c=null){return e.map(p=>{const h=p.uses.map(f=>{const C=a.get(f);if(C===void 0)throw new Error(`Group "${p.name}" uses unknown service "${f}"`);return C}),y=h.some(f=>f.testkit.database!==void 0)?"local":null,t=Te(p,o,n,m.fileNames||[],m);if(t.length===0)return c?.serviceSkipped?.(p,`no matching files (types=${o.join(",")||"all"} suites=${n.map(f=>f.raw).join(",")||"all"} files=${(m.fileNames||[]).join(",")||"all"})`),{config:p,dbBackend:y,execution:k,skipped:!0,suites:[],runtimeConfigs:[],runtimeNames:[],runtimeKey:null,uses:h};for(const f of t)for(const C of f.skippedFiles)c?.plannedSkip?.({serviceName:p.name,testType:f.testType,engineId:f.engineId,profileId:f.profileId,file:C.path,reason:C.reason});const S=Ee(h,a),E=S.map(f=>f.name).sort();return{config:p,dbBackend:y,execution:k,skipped:!1,suites:t,runtimeConfigs:S,runtimeNames:E,runtimeKey:E.join("|"),uses:h}})}
|
|
1
|
+
import{resolveRuntimeClosure as Ee}from"../../config/loader/dependencies.js";import{claimNextTask as Ke,collectSuites as Te}from"./planning.js";import{buildRunPlanningMetadata as Ae,buildScheduledQueue as be}from"./scheduler/index.js";import{addTrackerError as Me,buildServiceTrackers as xe,finalizeServiceResult as ge,recordGraphError as Ie,recordTaskOutcome as Pe,resolveRunGateFailure as qe}from"./results.js";import{loadTimings as ze,saveTimings as Oe}from"../../evidence/index.js";import{writeRunArtifact as Ne}from"../../evidence/artifacts.js";import{stableJson as $e}from"../../evidence/storage.js";import{createRunEnvironmentRecord as Le}from"../../evidence/environment-record.js";import{createRunLogRegistry as je}from"../../evidence/logs.js";import{createOperationTimingRegistry as De,timeOperation as J}from"../../evidence/operation-timings.js";import{createRunReporterFromListener as Ge}from"./run-events.js";import{createCallerRedaction as Ve}from"./caller-redaction.js";import{createSetupOperationRegistry as Be}from"../../evidence/setup-progress.js";import{createLocalRunEnvironment as We}from"../../environment/run-environment.js";import{writeRunMaintenance as Ue}from"../../evidence/maintenance.js";import{createConfigDrivenEnvironments as _e,planConfiguredCapacity as He}from"../environments/config-driven.js";import{declaresProvisioning as Je,declaresValues as he,holdBeforeRelease as Qe,routeEnvironments as Xe}from"../environments/routing.js";import{createStaticValueEnvironments as Ye}from"../environments/static-values.js";import{createSuppliedEnvironments as Ze}from"../environments/supplied.js";import{environmentTaskIdentity as et}from"./environment.js";import{createTaskLeases as tt}from"./task-leases.js";import{createTaskLocks as rt}from"./scheduler/locks.js";import{readPackageMetadata as nt,safeHostname as it,safeUsername as at}from"../../process/host/metadata.js";import{collectGitMetadata as st}from"../../repo/metadata.js";import{resolveExecutionConfig as ot}from"../../config/loader/decode-execution.js";import{projectGroupTestPolicy as ct}from"../../config/loader/discover-tests.js";import{resolveTaskPolicy as ut}from"../../config/loader/test-selection.js";import"../../environment/run-environment.js";import{createEngineManager as lt}from"../../engines/factory.js";import{createWorker as mt,runWorker as ft}from"./worker-loop.js";import{ensureRequestedFilesMatch as dt}from"./run-guards.js";import{createLiveSnapshotWriter as pt}from"./live-run.js";import{finalizeRunArtifacts as gt}from"./run-finalization.js";import{createRunFinisher as ht}from"./run-finisher.js";import{createRunEvidencePublisher as yt}from"../../evidence/run-evidence.js";import{buildRunArtifact as vt}from"../../evidence/reporting.js";import{createOperationReporterBridge as kt}from"./operation-progress.js";import{formatError as z,serializeError as Z}from"../../kernel/error-serialization.js";export async function runAll(e,a,o,n,m=[]){const g=n.fileNames||[];dt(e,a,o,g);const{productDir:c}=n,p=We(c),y=lt(c),v=new Map(m.map(r=>[r.name,r])),t=Date.now(),S=e[0]?.artifacts??null,E=[...new Set(e.flatMap(r=>r.uses.flatMap(l=>Object.keys(v.get(l)?.testkit.serviceEnv??{}))))].sort(),f={git:await st(c),host:{hostname:it(),username:at()},testkitVersion:nt().version},C=e[0]?.redact??[],R=Ve(process.env,C),ee=n.onEvent;let te=()=>{};const k=ee?Ge(r=>{r.type==="run.task_started"&&te(),ee(R.redact(r))},{debug:n.debug}):null,M=je(c),B={workerCount:0},O=resolveRunExecutionConfig(e[0],n),W=collectServicePlans(e,v,a,o,n,O,k);k?.setServicePlans?.(W);const N=xe(W,t),$=(r,l="product")=>{for(const s of N.values())s.skipped||Me(s,r,{classification:l});k?.runError?.(r)},ve=o.map(r=>({...r})),Q={typeValues:a,suiteSelectors:ve,fileNames:g,groupFilter:n.serviceFilter||null,planning:null};let w=()=>{};const ke=kt(k),b=De({onChange:r=>{ke(r),w()}}),re=Be({logRegistry:M,onChange:()=>{w()}}),d=Le({logRegistry:M,redactKeys:C,setupRegistry:re,onPublishedChange:()=>{w()}});R.useSourceTargets(()=>d.redactionTargets()),d.recordGroups(W.map(r=>({dbBackend:r.dbBackend,name:r.config.name})));const X=W.filter(r=>!r.skipped),ne=n.environments?.supplied??{},{plans:x,provisionedGraphKeys:ie,staticGraphKeys:ae}=assignRuntimeGraphs(X,new Set(Object.keys(ne)));let h;const u=await p.start({evidence:yt({redactKeys:C,redactionTargets:()=>d.redactionTargets(),resetResults:!0}),keepAlive:!0}),L=ht(u),U=pt({productDir:c,runId:u.runId,configs:e,trackers:N,startedAt:t,execution:O,workerState:B,selection:Q,archiveConfig:S,declaredEnvironmentKeys:E,metadata:f,environmentRecord:d,operationTimingRegistry:b,reporter:k});w=()=>{U.update()},te=()=>{U.flush()};const j={artifacts:null},D={result:null},se=r=>{const l=R.redact(r);try{return Ue(c,u.runId,l),l}catch(s){const F=`Could not write the maintenance record: ${R.redact(z(s))}`;return{...l,status:"failed",error:l.error===null?F:`${l.error}; ${F}`}}},oe=r=>(D.result=r.maintenance,r.maintenance.status==="failed"&&k?.warning?.(`Post-run maintenance failed: ${r.maintenance.error??"unknown error"}`),r.sealed),Re=async(r,l)=>(u.beginFinalization(),oe(await L.finish(r,s=>{const F=l(s);return j.artifacts={runArtifact:F.runArtifact},F},"normal",se)));let K,I;const ce=(r,l,s)=>({productDir:c,runId:u.runId,results:r,startedAt:t,finishedAt:l,execution:O,workerCount:B.workerCount,selection:Q,archiveConfig:S,declaredEnvironmentKeys:E,metadata:f,environmentRecord:d,operationTimingRegistry:b,reporter:k,runInterruptReason:u.getInterruptReason(),runStatus:s,publishRunFinalization:Re});w();let T=null,ue=Symbol("no release failure");try{try{if(X.length>0){const s=ze(c),F=_e(u,new Map(x.map(i=>[i.config.name,i.config.uses])),c,d),we=Ye(new Map(x.filter(i=>ae.has(i.runtimeKey)).map(i=>[i.config.name,i.uses])),c,d),Se=Ze(ne,{productDir:c,record:d,signal:u.signal}),le=i=>ie.has(i)?F:ae.has(i)?we:Se,me=Xe(new Map(x.map(({runtimeKey:i})=>[i,le(i)]))),{capacity:_,queue:G}=await J(b,{name:"testkit.plan",kind:"planning",summary:"plan test execution",scope:{serviceCount:X.length}},()=>({capacity:He(x.filter(i=>ie.has(i.runtimeKey))),queue:be(x.map(i=>prepareSchedulerPlan(i,A=>me.hasSetup({file:A,graphKey:i.runtimeKey,targetName:i.config.name}))),{timings:s})}));Q.planning=Ae(G),d.recordCapacity(_),k?.setTotalFileCount?.(G.length),B.workerCount=Math.max(1,Math.min(O.taskConcurrency,G.length));const fe=Array.from({length:B.workerCount},(i,A)=>mt(A+1,c));_.graphs.length>0&&u.provision(_.graphs,{reporter:k,environmentRecord:d,logRegistry:M,setupRegistry:re,operationTimingRegistry:b,skipSchemaSourceVerify:n.skipSchemaSourceVerify});const de=tt(c),Ce={recordRunError:$,runId:u.runId,signal:u.signal,isStopRequested:()=>u.isStopRequested(),environments:Ft(me,n.environments?.hold),leases:St(de,d),locks:rt(),request:Et(c,e,O,n.vitest,n.retries),environmentKeys:new Map(x.flatMap(({config:i,runtimeKey:A})=>{const P=le(A),q=P.preview?.(i.name)??null;return q===null?[]:[[i.name,$e([q,P.checksServices?.(i.name)??!1])]]}))},pe=[];try{const A=(await J(b,{name:"testkit.execute",kind:"execution",summary:"execute local worker queue",scope:{workerCount:fe.length,taskCount:G.length}},async()=>Promise.allSettled(fe.map(P=>ft(P,G,Ce,y,N,pe,Ke,(q,H,V,Y)=>{Pe(q,H,V,Y),V.environmentValuesMissing!==void 0&&d.recordMissingValues(et(H),V.environmentValuesMissing),w()},(q,H,V,Y,Fe)=>{Ie(q,H,V,Y,Fe),w()},k))))).flatMap(workerResultErrorsForOrchestrator);for(const P of A)$(P);A.length>0&&w(),d.recordRuntimeStats(u.getStats())}finally{_.graphs.length>0&&await J(b,{name:"testkit.cleanup",kind:"cleanup",summary:"stop managed runtimes and services"},()=>Ct(d,()=>u.cleanupAll()).catch(i=>{throw ue=i,$(Z(i).message||z(i),"infra"),i})),de.close()}Oe(c,s,pe)}}catch(s){T=Z(s),s!==ue&&$(T.message||z(s)),w()}U.finish(),h=T===null?0:u.isStopRequested()?130:1,I=Date.now(),K=e.map(s=>ge(N.get(s.name),t,I));const r=qe(K,n.allowEmpty===!0);await J(b,{name:"testkit.collect",kind:"collection",summary:"close and collect run logs"},()=>M.closeAll());const l=await gt(ce(K,I,u.isStopRequested()?"interrupted":T===null&&r===null?null:"failed"));return T!==null&&(h=1),K.some(s=>s.failed||s.infraFailed)&&(h=1),r!==null&&(h=1),l.shouldFailArchivePublication&&(h=1),l.shouldFailArchiveCleanup&&(h=1),(l.runArtifact.run.status==="interrupted"||u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted")&&(h=Math.max(h,130)),{runArtifact:l.runArtifact,results:R.redact(K),exitCode:h,...T===null?{}:{failure:R.redact(T)},...D.result===null?{}:{maintenance:R.redact(D.result)}}}catch(r){const l=Z(r);$(l.message||z(r),"infra"),w(),U.finish(),u.beginFinalization(),h=u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted"?130:1,I=Date.now(),K=e.map(F=>ge(N.get(F.name),t,I)),await ye(M);let s=j.artifacts;if(s===null)try{s=await Rt(ce(K,I,u.isStopRequested()?"interrupted":"failed"),L,j,l,se,oe)}catch{if(j.artifacts===null)throw r;s=j.artifacts}return(s.runArtifact.run.status==="interrupted"||u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted")&&(h=Math.max(h,130)),{runArtifact:s.runArtifact,results:R.redact(K),exitCode:h,failure:R.redact(T??l),...D.result===null?{}:{maintenance:R.redact(D.result)}}}finally{u.dispose(),await ye(M),wt(d,n.environments?.observeRecord,k)}}async function Rt(e,a,o,n,m,g){const c=await a.finish("failed",p=>{const y=p.artifactStatus==="interrupted"?p:{artifactStatus:"failed",interruptReason:null},v=e.declaredEnvironmentKeys??[],t=vt({productDir:e.productDir,runId:e.runId,results:e.results,startedAt:e.startedAt,finishedAt:e.finishedAt,execution:e.execution,workerCount:e.workerCount,typeValues:e.selection.typeValues,suiteSelectors:e.selection.suiteSelectors,fileNames:e.selection.fileNames,groupFilter:e.selection.groupFilter,planning:e.selection.planning??null,archiveConfig:e.archiveConfig,declaredEnvironmentKeys:v,metadata:e.metadata,environment:e.environmentRecord.snapshot(),operationTimings:e.operationTimingRegistry?.listOperations()??[],runInterruptReason:y.interruptReason,runStatus:y.artifactStatus});t.finalizationFailure={...n};const E={runArtifact:Ne(e.productDir,t,{archiveConfig:e.archiveConfig,declaredEnvironmentKeys:v,redactKeys:e.environmentRecord.redactKeys,redactionTargets:e.environmentRecord.redactionTargets()})};return o.artifacts=E,E},"recovery",m);return g(c)}function wt(e,a,o){if(a!==void 0)try{a(e.snapshot())}catch(n){try{o?.warning?.(`Environment record observer failed: ${z(n)}`)}catch{}}}function St(e,a){return{...e,remove(o){const n=a.releaseFor(o.id);try{e.remove(o)}catch(m){throw n?.releaseFailed(m),m}n?.released()}}}async function Ct(e,a){try{await a()}catch(o){throw e.recordCleanup(o),o}e.recordCleanup(null)}async function ye(e){try{await e.closeAll()}catch{}}export function workerResultErrorsForOrchestrator(e){return e.status==="rejected"?[z(e.reason)]:[...new Set(e.value.unpersistedErrors)]}function Ft(e,a){return a===void 0?e:Qe(e,a)}function Et(e,a,o,n,m){return{vitest:n,...m===void 0?{}:{retries:m},productDir:e,fileTimeoutSeconds:o.fileTimeoutSeconds,ownerRoots:Object.fromEntries(a.map(g=>[g.name,g.roots]))}}export function resolveRunExecutionConfig(e,a){return ot({cli:{fileTimeoutSeconds:a.fileTimeoutSeconds,taskConcurrency:a.taskConcurrency},repo:e?{fileTimeoutSeconds:e.testkit.execution.fileTimeoutSeconds,taskConcurrency:e.testkit.execution.taskConcurrency}:void 0})}export function assignRuntimeGraphs(e,a){const o=e.filter(t=>t.suites.some(S=>S.files.length>0)),n=new Set(o.filter(t=>!a.has(t.config.name)&&Je(t.runtimeConfigs))),m=new Set([...n].map(t=>t.runtimeKey)),g=t=>!n.has(t)&&!a.has(t.config.name)&&he(t.runtimeConfigs),c=new Set(o.filter(t=>g(t)&&t.runtimeKey!=="").map(t=>t.runtimeKey)),p=new Set(o.map(t=>t.runtimeKey)),y=t=>t.runtimeKey!==""&&!m.has(t.runtimeKey)&&(g(t)||!c.has(t.runtimeKey))?t.runtimeKey:p.has(t.config.name)?`group:${t.config.name}`:t.config.name,v=o.map(t=>n.has(t)?t:{...t,runtimeKey:y(t)});return{plans:v,provisionedGraphKeys:m,staticGraphKeys:new Set(v.filter(t=>!m.has(t.runtimeKey)&&!a.has(t.config.name)&&he(t.runtimeConfigs)).map(t=>t.runtimeKey))}}export function prepareSchedulerPlan(e,a){const o=ct(e.config.testkit);return{graphKey:e.runtimeKey,owner:e.config.name,suites:e.suites.map(n=>({...n,tasks:n.files.map(m=>({file:m,hasSetup:a(m),...ut(o,n,m)}))}))}}export function collectServicePlans(e,a,o,n,m,g,c=null){return e.map(p=>{const y=p.uses.map(f=>{const C=a.get(f);if(C===void 0)throw new Error(`Group "${p.name}" uses unknown service "${f}"`);return C}),v=y.some(f=>f.testkit.database!==void 0)?"local":null,t=Te(p,o,n,m.fileNames||[],m);if(t.length===0)return c?.serviceSkipped?.(p,`no matching files (types=${o.join(",")||"all"} suites=${n.map(f=>f.raw).join(",")||"all"} files=${(m.fileNames||[]).join(",")||"all"})`),{config:p,dbBackend:v,execution:g,skipped:!0,suites:[],runtimeConfigs:[],runtimeNames:[],runtimeKey:null,uses:y};for(const f of t)for(const C of f.skippedFiles)c?.plannedSkip?.({serviceName:p.name,testType:f.testType,engineId:f.engineId,profileId:f.profileId,file:C.path,reason:C.reason});const S=Ee(y,a),E=S.map(f=>f.name).sort();return{config:p,dbBackend:v,execution:g,skipped:!1,suites:t,runtimeConfigs:S,runtimeNames:E,runtimeKey:E.join("|"),uses:y}})}
|
package/dist/app/run/request.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveProductDir as y}from"../../process/files/product-dir.js";import{loadRunConfigs as g}from"../../config/loader/managed-configs.js";import{parseFileTimeoutOption as k}from"../../domain/file-timeout.js";import{parseSuiteOption as
|
|
1
|
+
import{resolveProductDir as y}from"../../process/files/product-dir.js";import{loadRunConfigs as g}from"../../config/loader/managed-configs.js";import{parseFileTimeoutOption as k}from"../../domain/file-timeout.js";import{parseSuiteOption as w,parseTypeOption as F}from"../../domain/suite-selection.js";import{resolveRequestedFiles as O}from"../file-selection.js";import{parseTaskConcurrencyOptions as h}from"../../config/loader/decode-execution.js";import{readEnvironmentFile as R}from"../environments/environment-file.js";import{hasSuppliedInput as T,projectSuppliedEnvironment as E,resolveSuppliedInput as b,suppliedBindings as N}from"../environments/supplied-input.js";import{resolveVitestSelection as V}from"../../engines/catalog.js";export async function buildRunRequest(e,l=null,r=process.cwd(),n=process.cwd(),p=process.env){const f=h(e.taskConcurrency,e.workers),a=e.fileTimeoutSeconds==null?null:k(e.fileTimeoutSeconds);if(e.retries!==void 0&&!/^\d{1,2}$/u.test(e.retries))throw new Error(`--retries takes a whole number from 0 to 99, not ${JSON.stringify(e.retries)}`);const c=F(e.type,l),u=w(e.suite),S=e.file??[],o=y(r,e.dir),m=O(S,o,n),s=e.supplied??{},t=T(s)?b(s,s.environmentFile===void 0?null:R(s.environmentFile,n),p):null,i=V(e.vitest??{},o,p),{groups:d,services:v}=await g({dir:o,group:e.group,testSelection:{group:e.group,fileNames:m,typeValues:c,suiteSelectors:u},vitest:t===null?i:{...i,host:{...i.host,...N(t)}}});return{groups:d,services:v,productDir:o,supplied:t===null?null:j(t,d),typeValues:c,suiteSelectors:u,runOptions:{vitest:i,allowEmpty:e.allowEmpty,fileNames:m,taskConcurrency:f,fileTimeoutSeconds:a,...e.retries===void 0?{}:{retries:Number(e.retries)},serviceFilter:e.group||null,ignoreSkipRules:e.ignoreSkipRules,skipSchemaSourceVerify:e.skipSchemaSourceVerify}}}function j(e,l){return Object.fromEntries(l.map(r=>[r.name,E(e,r.uses)]))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function requireTimeZone(e){try{new Intl.DateTimeFormat("en-US",{timeZone:e})}catch(n){throw new Error(`--timezone takes an IANA time zone such as Pacific/Kiritimati, not ${JSON.stringify(e)}`,{cause:n})}return e}export function useTimeZone(e){if(e===void 0)return()=>{};const n=process.env.TZ;return process.env.TZ=requireTimeZone(e),()=>{n===void 0?delete process.env.TZ:process.env.TZ=n}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import s from"node:fs";import n from"node:path";import{defaultTempDirParent as b,makeTempDir as D,removeTempDir as F}from"../../process/files/temp-dir.js";import{requireManagedHostCommandSuccess as j}from"../../process/index.js";import{REPOSITORY_COMMAND_GUARD_ARGS as w,runManagedGitCommand as g}from"../../repo/managed-git.js";import{executeToolchainCommand as $}from"../../toolchains/index.js";import{destroyEnvironment as E}from"../destroy/index.js";const k=new Set([".git",".testkit","node_modules"]),I=6e5,M=[...[".env*",".npmrc",".yarnrc",".yarnrc.yml",".pnpmfile.cjs","bunfig.toml"].map(e=>`:(glob)**/${e}`),":(exclude,glob)**/node_modules/**"],S=/^(?:@gfdelarue\/testkit|vitest|@vitest\/[^/]+|@playwright\/test|playwright|playwright-core)$/u;export function isTestTooling(e){return S.test(e)}export async function createProductSandbox(e,r,t={}){const o=D("testkit-sandbox-",{parentDir:s.realpathSync.native(b())}),i=n.join(o,n.basename(e)),l=[];try{if(t.commit===void 0)for(const a of await h(e,["--cached","--others","--exclude-standard"]))y(e,i,a);else await O(e,t.commit,i);const c=(await h(e,["--others","--ignored","--exclude-standard","--directory"])).map(a=>a.replace(/\/$/u,"")),u=c.filter(a=>n.posix.basename(a)==="node_modules");if(t.commit===void 0)for(const a of c)u.includes(a)||y(e,i,a);else for(const a of await h(e,["--others","--ignored","--exclude-standard","--",...M]))s.existsSync(n.join(i,a))||y(e,i,a);for(const a of t.installs??[])await $(a.command,[...a.args],{cwd:n.join(i,a.directory),env:process.env,timeoutMs:I});const v=(t.installs??[]).length>0||t.toolingOnly===!0?S:null;for(const a of u)T(e,i,a,l,v);const d=A(n.dirname(e));d!==null&&v===null?p(n.join(o,"node_modules"),d,l):d!==null&&x(d,n.join(o,"node_modules"),l);for(const a of t.overlay??[])y(e,i,a)}catch(c){throw _(o,l),c}const f=n.join(i,r);return{productDir:f,root:i,copy(c,u){s.cpSync(m(i,c),m(i,u),{recursive:!0})},async dispose(){try{s.existsSync(n.join(f,".testkit"))&&await E({dir:f})}finally{_(o,l)}},read:c=>s.readFileSync(n.join(e,c),"utf8"),remove(c){s.rmSync(m(i,c),{force:!0,recursive:!0})},restore(c){s.copyFileSync(n.join(e,c),m(i,c))},write(c,u){s.writeFileSync(m(i,c),u)}}}function m(e,r){const t=n.join(e,r);if(!s.existsSync(n.dirname(t)))throw new Error(`${r} is not in the copy of the checkout`);const o=n.relative(s.realpathSync.native(e),s.realpathSync.native(n.dirname(t)));if(o.split(n.sep)[0]===".."||n.isAbsolute(o))throw new Error(`${r} resolves outside the copy of the checkout, so Testkit does not change it`);return t}async function O(e,r,t){s.mkdirSync(t,{recursive:!0}),j("git restore",await g(e,[...w,`--work-tree=${t}`,"restore",`--source=${r}`,"--worktree","--ignore-skip-worktree-bits","--",":/"]))}async function h(e,r){return j("git ls-files",await g(e,[...w,"ls-files","-z",...r])).stdout.split("\0").filter(o=>o.length>0)}function y(e,r,t){const o=n.join(e,t),i=s.statSync(o,{throwIfNoEntry:!1});i===void 0||t.split("/").some(l=>k.has(l))||(s.mkdirSync(n.dirname(n.join(r,t)),{recursive:!0}),i.isFile()?s.copyFileSync(o,n.join(r,t)):i.isDirectory()&&s.cpSync(o,n.join(r,t),{dereference:!0,filter:l=>!k.has(n.basename(l)),recursive:!0}))}function T(e,r,t,o,i,l=""){const f=n.join(e,t);for(const c of s.readdirSync(f,{withFileTypes:!0})){const u=n.join(t,c.name);if(c.name.startsWith("@")&&c.isDirectory())T(e,r,u,o,i,c.name);else{if(i!==null&&!i.test(l===""?c.name:`${l}/${c.name}`))continue;if(s.lstatSync(n.join(r,u),{throwIfNoEntry:!1})!==void 0)continue;c.isFile()?(s.mkdirSync(n.join(r,t),{recursive:!0}),s.copyFileSync(n.join(e,u),n.join(r,u))):p(n.join(r,u),N(e,r,n.join(e,u)),o)}}}function N(e,r,t){const o=s.existsSync(t)?s.realpathSync.native(t):t,i=n.relative(s.realpathSync.native(e),o);return i!==""&&!i.startsWith("..")&&!n.isAbsolute(i)&&!i.includes("node_modules")?n.join(r,i):o}function x(e,r,t,o=""){for(const i of s.readdirSync(e,{withFileTypes:!0})){const l=o===""?i.name:`${o}/${i.name}`;o===""&&i.name.startsWith("@")&&i.isDirectory()?x(n.join(e,i.name),n.join(r,i.name),t,i.name):S.test(l)&&(s.mkdirSync(r,{recursive:!0}),p(n.join(r,i.name),s.realpathSync.native(n.join(e,i.name)),t))}}function A(e){for(let r=e;;r=n.dirname(r)){const t=n.join(r,"node_modules");if(s.existsSync(t))return s.realpathSync.native(t);if(n.dirname(r)===r)return null}}function p(e,r,t){const o=s.statSync(r,{throwIfNoEntry:!1});o!==void 0&&(s.mkdirSync(n.dirname(e),{recursive:!0}),o.isDirectory()?s.symlinkSync(r,e,"junction"):s.copyFileSync(r,e),t.push(e))}function _(e,r){for(const t of r)s.rmSync(t,{force:!0,recursive:!1});F(e)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import i from"node:path";import{readRunArtifact as o}from"../bugs/run-reading.js";import{runAll as f}from"../run/orchestrator.js";import{buildRunRequest as d}from"../run/request.js";export async function runInSandbox(u,r,e={}){const n=await runSandboxArtifact(u,r,e);return o(n,i.relative(u.root,u.productDir).split(i.sep).join("/"))}export async function runSandboxArtifact(u,r,e={}){const{productDir:n}=u,t=await d({allowEmpty:!1,dir:n,...r.file===void 0?{}:{file:r.file},...e.fileTimeoutSeconds===void 0?{}:{fileTimeoutSeconds:String(e.fileTimeoutSeconds)},...r.group===void 0?{}:{group:r.group},ignoreSkipRules:!1,skipSchemaSourceVerify:!1,...r.suite===void 0?{}:{suite:r.suite},...e.taskConcurrency===void 0?{}:{taskConcurrency:String(e.taskConcurrency)},...r.type===void 0?{}:{type:r.type},...r.vitest===void 0?{}:{vitest:r.vitest}},null,n,n);return(await f(t.groups,t.typeValues,t.suiteSelectors,{...t.runOptions,productDir:t.productDir,...e.retries===void 0?{}:{retries:e.retries}},t.services)).runArtifact}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SHAKE_CONDITIONS=["control","shuffle","copies","timezone-east","timezone-west","load"],SHAKE_TIME_ZONES={"timezone-east":"Pacific/Kiritimati","timezone-west":"Pacific/Pago_Pago"};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{randomInt as x}from"node:crypto";import S from"node:fs";import D from"node:os";import r from"node:path";import{Worker as P}from"node:worker_threads";import{errorMessage as b}from"../../kernel/error-serialization.js";import{readRunArtifact as C}from"../bugs/run-reading.js";import{resolveBugCheckout as R,writeJsonFile as T,writeTextFile as E}from"../bugs/store.js";import{resolveRequestedFiles as O}from"../file-selection.js";import{isTestFile as M,listChangedSources as A,resolveDiffBase as I}from"../prove/changes.js";import{buildRunRequest as B}from"../run/request.js";import{useTimeZone as F}from"../run/time-zone.js";import{createProductSandbox as N}from"../sandbox/index.js";import{runSandboxArtifact as Z}from"../sandbox/run.js";import{SHAKE_CONDITIONS as q,SHAKE_TIME_ZONES as v}from"./conditions.js";import{judgeConditions as $}from"./verdicts.js";import{renderShakeReport as H}from"./report.js";const K=/\.shake-copy-\d+\./u,W=`export function setup() {
|
|
2
|
+
return {};
|
|
3
|
+
}
|
|
4
|
+
`;export async function shake(e){const t=await R(e),h=Object.fromEntries(Object.entries(e.vitest??{}).filter(([o,d])=>o.startsWith("vitest-")&&o!=="vitest-shuffle"&&typeof d=="string")),g=e.files===void 0?await J(t,e.since,h):O(e.files,t.productDir,t.cwd),c=g.length===0?[]:e.only??q,p=new Date().toISOString().replace(/[:.]/gu,"-"),f=r.join(t.productDir,".testkit","shake",p),u=[],s=new Map;if(c.length>0){const o=await N(t.repoRoot,t.productPath),d={files:g,recordDir:f,sandbox:o,vitest:h};try{for(let a=1;a<=e.runs;a+=1)for(const n of c){const l=await k(d,n,a,e.copies);if(u.length===0&&l.outcome==="errored")throw new Error(`Testkit could not run the files to shake: ${l.reason??""}`);u.push(l),e.onRun?.(l,u.length,c.length*e.runs)}for(const a of $(c,u)){if(!Y(a))continue;const n=await k(d,a.condition,0,e.copies,a.failingSeeds.at(0));s.set(a.condition,n.outcome==="errored"?{outcome:"errored",reason:n.reason??""}:{outcome:n.failures.some(l=>a.blamed.includes(l))?"failed":"passed"})}}finally{await o.dispose()}}const i={conditions:$(c,u).map(o=>{const d=s.get(o.condition);return d===void 0?o:{...o,replay:d}}),copies:e.copies,files:g,headCommit:t.headCommit,id:p,productPath:t.productPath,recordDir:f,runs:u,runsPerCondition:e.runs,vitest:h};return T(r.join(f,"shake.json"),{...i,recordDir:void 0}),E(r.join(f,"shake.md"),H(i)),i}function Y(e){return e.failed===0||e.verdict==="no effect"?!1:e.condition==="control"?e.verdict==="fails":e.condition==="shuffle"||v[e.condition]!==void 0}async function J(e,t,h){const g=await I(e.repoRoot,t),c=await A(e.repoRoot,g,e.productPath,M);if(c.length===0)return[];const{groups:p}=await B({allowEmpty:!0,dir:e.productDir,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,vitest:h},null,e.productDir,e.productDir),f=new Set(p.flatMap(s=>s.suites.flatMap(i=>i.files.map(o=>o.replaceAll("\\","/"))))),u=e.productPath===""?"":`${e.productPath}/`;return c.map(s=>s.path.slice(u.length)).filter(s=>f.has(s))}async function k(e,t,h,g,c){const{sandbox:p}=e,f=t==="shuffle"?c??x(1,1e9):void 0,u=F(v[t]),s=t==="load"?V():null,i=t==="copies"?U(p,e.files,g):null,o=i?.tests??e.files,d=r.relative(p.root,p.productDir).split(r.sep).join("/"),a=Date.now();let n,l=null;try{const m=await Z(p,{file:o,vitest:f===void 0?e.vitest:{...e.vitest,"vitest-shuffle":String(f)}},{retries:0,...t==="copies"?{taskConcurrency:o.length}:{}});n=C(m,d),l=r.join(p.productDir,".testkit","results","runs",m.archive.runId)}catch(m){n={kind:"run-failed",reason:b(m)}}finally{u(),await s?.stop();for(const m of i?.written??[])p.remove(m)}n.kind!=="passed"&&l!==null&&L(l,r.join(e.recordDir,"runs",`${t}-${h===0?"replay":String(h)}`));const w={condition:t,durationMs:Date.now()-a,index:h,...f===void 0?{}:{seed:f}};if(n.kind==="passed")return{...w,failures:[],outcome:"passed"};if(n.kind==="run-failed")return{...w,failures:[],outcome:"errored",reason:n.reason};const j=n.failures.map(m=>`${(d===""?m.file:m.file.slice(d.length+1)).replace(K,".")}${m.title===null?"":` \u203A ${m.title}`}`);return{...w,failures:[...new Set(j)].sort(),outcome:"failed"}}function L(e,t){try{S.cpSync(e,t,{recursive:!0})}catch{return}}function U(e,t,h){const g=r.relative(e.root,e.productDir).split(r.sep).join("/"),c=s=>r.posix.join(g,s),p=s=>S.existsSync(r.join(e.productDir,s)),f=[],u=[];for(const s of t){const i=r.posix.dirname(s),o=r.posix.basename(s),d=o.slice(0,o.indexOf(".")),a=o.slice(0,-r.posix.extname(o).length),n=[`${a}.setup.mjs`,`${a}.setup.js`,`__setup__/${a}.mjs`,`__setup__/${a}.js`];n.some(l=>p(r.posix.join(i,l)))||(e.write(c(r.posix.join(i,n[0])),W),u.push(c(r.posix.join(i,n[0])))),f.push(s);for(let l=2;l<=h;l+=1){const w=`${d}.shake-copy-${String(l)}${o.slice(d.length)}`,j=w.slice(0,-r.posix.extname(w).length),m=[[o,w],...n.map(y=>[y,y.replace(a,j)]),[`__snapshots__/${o}.snap`,`__snapshots__/${w}.snap`],[`${o}-snapshots`,`${w}-snapshots`]];for(const[y,_]of m)p(r.posix.join(i,y))&&(e.copy(c(r.posix.join(i,y)),c(r.posix.join(i,_))),u.push(c(r.posix.join(i,_))));f.push(r.posix.join(i,w))}}return{tests:f,written:u}}function V(){const e=Array.from({length:D.availableParallelism()},()=>new P("for (;;) {}",{eval:!0}));return{async stop(){await Promise.all(e.map(t=>t.terminate()))}}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{shellWord as l}from"../bugs/issue.js";import{SHAKE_TIME_ZONES as a}from"./conditions.js";import{wilsonInterval as h}from"./verdicts.js";const m={control:"control: no change",copies:"copies: the files run beside copies of themselves",load:"load: every CPU core busy",shuffle:"shuffle: plain test files and tests in a seeded random order","timezone-east":`timezone-east: TZ=${a["timezone-east"]??""}`,"timezone-west":`timezone-west: TZ=${a["timezone-west"]??""}`};export function renderShakeReport(e){if(e.files.length===0)return`# Testkit shake
|
|
2
|
+
|
|
3
|
+
No test file to shake.
|
|
4
|
+
`;const i=e.conditions.find(n=>n.condition==="control"),o=["# Testkit shake","",`Files: ${e.files.join(", ")}`,`Head: ${e.headCommit.slice(0,12)}`,`Record: ${u(e)}`,"","| Condition | Runs | Failed | Errored | Verdict | p |","| --- | --- | --- | --- | --- | --- |",...e.conditions.map(n=>`| ${m[n.condition]} | ${String(n.runs)} | ${String(n.failed)} | ${String(n.errored)} | ${n.verdict} | ${n.pValue===null?"":n.pValue.toFixed(3)} |`)];for(const n of e.conditions.filter(d=>d.failed>0||d.errored>0)){o.push("",`## ${n.condition}: ${n.verdict}`,"");for(const[t,r]of Object.entries(n.failingTests)){const s=i===void 0||n.condition==="control"?"":`, and in ${f(i.failingTests[t]??0,i.judged,"control runs")}`,c=s!==""&&!n.blamed.includes(t)?", so the condition isn't blamed for it":"";o.push(`- ${t} failed in ${f(r,n.judged,"runs")}${s}${c}`)}const d=e.runs.filter(t=>t.condition===n.condition&&t.outcome==="errored");for(const t of d)o.push(`- run ${String(t.index)} failed outside its tests: ${t.reason??""}`);n.blamed.length!==0&&(o.push("","Reproduce:","",...reproduceCommands(e,n).map(t=>` ${t}`)),n.replay!==void 0&&o.push("",$(n.replay)))}return e.runs.some(n=>n.outcome!=="passed")&&o.push("",`Testkit keeps the run bundle of each run that didn't pass, when the run made one, in ${u(e)}runs/.`),`${o.join(`
|
|
5
|
+
`)}
|
|
6
|
+
`}function $(e){const i="Testkit ran the first command once more after the shake";return e.outcome==="errored"?`${i}, but that run failed outside its tests: ${e.reason??""}`:e.outcome==="failed"?`${i}, and the failure came back.`:`${i}, and the failure didn't come back, so it comes only some of the time.`}function f(e,i,o){const[n,d]=h(e,i).map(t=>Math.round(t*100));return`${String(e)} of ${String(i)} ${o} (95% CI ${String(n)}\u2013${String(d)}%)`}function u(e){return`${e.productPath===""?"":`${e.productPath}/`}.testkit/shake/${e.id}/`}export function reproduceCommands(e,i){const o=[...e.productPath===""?[]:["--dir",e.productPath],...e.files.flatMap(s=>["--file",s]),...Object.entries(e.vitest).flatMap(([s,c])=>[`--${s}`,c])],n=s=>["npx","testkit",...s].map(l).join(" "),d=a[i.condition];if(d!==void 0)return[n(["run",...o,"--timezone",d,"--retries","0"])];if(i.condition==="control"&&i.verdict==="fails")return[n(["run",...o,"--retries","0"])];if(i.condition==="shuffle")return i.failingSeeds.slice(0,3).map(s=>n(["run",...o,"--vitest-shuffle",String(s),"--retries","0"]));const t=["--runs",String(e.runsPerCondition)],r=i.condition==="copies"?["--copies",String(e.copies)]:[];return[n(["shake",...o,"--only",i.condition,...t,...r])]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const h=.05;export function judgeConditions(l,e){const t=l.includes("control")?f(e,"control"):null;return l.map(u=>{const r=f(e,u),s=Object.entries(r.tests).filter(([o,d])=>u==="control"||t===null||d/r.judged>(t.tests[o]??0)/Math.max(t.judged,1)).map(([o])=>o),n=u==="control"||t===null||t.judged===0?null:t,a=n===null?new Map:g(r,n);return{blamed:s,condition:u,errored:r.errored,failed:r.failed.length,failingSeeds:r.failed.flatMap(o=>o.seed!==void 0&&o.failures.some(d=>s.includes(d))?[o.seed]:[]),failingTests:r.tests,judged:r.judged,pValue:s.some(o=>a.has(o))?Math.min(...s.map(o=>a.get(o)??1)):null,runs:r.runs,verdict:m(u,r,t,s,a)}})}function g(l,e){const t=[...new Set([...Object.keys(l.tests),...Object.keys(e.tests)])],u=t.map(n=>[l.tests[n]??0,e.tests[n]??0]),r=u.map(([n,a])=>{const o=n+a,d=Math.min(o,l.judged);return fisherAtLeast(d,l.judged,o-d,e.judged)});let s=1;for(;r.filter(n=>n<=.05/s).length>s;)s+=1;return new Map(t.map((n,a)=>{const[o,d]=u[a];return[n,Math.min(1,s*fisherAtLeast(o,l.judged,d,e.judged))]}))}export function wilsonInterval(l,e){if(e===0)return[0,1];const t=1.96,u=l/e,r=(u+t*t/(2*e))/(1+t*t/e),s=t*Math.sqrt(u*(1-u)/e+t*t/(4*e*e))/(1+t*t/e);return[Math.max(0,r-s),Math.min(1,r+s)]}export function fisherAtLeast(l,e,t,u){const r=e+u,s=l+t;let n=0;for(let a=l;a<=Math.min(e,s);a+=1)n+=Math.exp(i(s,a)+i(r-s,e-a)-i(r,e));return Math.min(1,n)}function f(l,e){const t=l.filter(n=>n.condition===e),u=t.filter(n=>n.outcome==="errored").length,r=t.filter(n=>n.outcome==="failed"),s={};for(const n of r.flatMap(a=>a.failures))s[n]=(s[n]??0)+1;return{errored:u,failed:r,judged:t.length-u,runs:t.length,tests:s}}function m(l,e,t,u,r){if(e.judged===0)return"errored";if(l==="control")return e.failed.length===0?"steady":Object.values(e.tests).every(a=>a===e.judged)?"fails":"flaky";if(u.length===0)return"no effect";const s=a=>(r.get(a)??1)<.05;return u.some(a=>e.tests[a]<e.judged||e.judged<2?!1:t===null||(t.tests[a]??0)===0&&s(a))?"reproduces":u.some(s)?"raises failures":"inconclusive"}function i(l,e){return c(l)-c(e)-c(l-e)}function c(l){let e=0;for(let t=2;t<=l;t+=1)e+=Math.log(t);return e}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as
|
|
1
|
+
import{Args as i,Flags as e}from"./framework/definitions.js";import{defineCommand as t}from"./framework/command.js";import{runArgs as l,runFlags as n,schemaOperationFlags as o,sharedFlags as r,suppliedEnvironmentFlags as c,testSelectionFlags as a,timezoneFlag as u,vitestSelectionFlags as s}from"./command-flags.js";import{publicTestTypeListText as d}from"../domain/test-types.js";export const commandCatalog=Object.freeze({artifacts:t({summary:"List, verify, or inspect artifacts from immutable Testkit runs",enableJsonFlag:!0,args:{file:i.string({description:"Optional test file selector",required:!1})},flags:{...a,kind:e.string({description:"Limit artifacts to one kind"}),format:e.string({description:"Output format",options:["list","text"],default:"list"}),run:e.string({description:"Read one immutable run ID instead of latest"}),list:e.boolean({description:"List retained immutable runs",default:!1}),verify:e.boolean({description:"Verify the selected run manifest and checksums",default:!1})}}),"bug bisect":t({summary:"Find the commit at which a recorded bug's test starts to fail",enableJsonFlag:!0,args:{id:i.string({description:"Draft id that `testkit bug record` printed",required:!0})},flags:{dir:e.string({description:"Product directory"}),"max-probes":e.string({description:"Most commits to run the test at, besides HEAD (default 20)"})}}),"bug file":t({summary:"Run a recorded bug's test and file the bug as an issue",enableJsonFlag:!0,args:{id:i.string({description:"Draft id that `testkit bug record` printed; with --batch, any number of them",multiple:!0})},flags:{batch:e.boolean({description:"File the named drafts, or every unfiled draft when none is named, and report each one",default:!1}),dir:e.string({description:"Product directory"}),...c,...s}}),"bug guide":t({summary:"Print the bug record format, the severities, and the category lists",enableJsonFlag:!0}),"bug record":t({summary:"Record a confirmed bug as a local draft",enableJsonFlag:!0,args:{file:i.string({description:"Bug record JSON file, or - to read standard input",required:!0})},flags:{dir:e.string({description:"Product directory"}),...s,...u}}),"campaign finish":t({summary:"Finish the active campaign and write its pull request summary",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"campaign start":t({summary:"Start a bug-hunting campaign and print the agent's brief",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),id:e.string({description:"Campaign id, instead of one made from the target and the date"}),target:e.string({description:"What the agent should hunt, in one line of at most 200 characters",required:!0})}}),"campaign status":t({summary:"Show the active campaign, its recorded bugs, and the area map coverage",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"ci report":t({summary:"Post the last run as a commit status, a step summary, and one pull request comment",enableJsonFlag:!0,flags:{context:e.string({description:"Commit status context, instead of testkit"}),dir:e.string({description:"Product directory"})}}),cleanup:t({summary:"Clean stale local testkit state",enableJsonFlag:!0,flags:{...r,"dry-run":e.boolean({description:"Show cleanup actions without deleting state",default:!1}),cache:e.string({description:"Clean cache state: runtime, bundles, or all",multiple:!0,options:["runtime","bundles","all"]}),resources:e.boolean({description:"Clean Testkit-owned Docker resources that are stale or orphaned",default:!1})}}),"db schema refresh":t({summary:"Refresh a source database schema cache",enableJsonFlag:!0,flags:{...r,...o}}),"db schema verify":t({summary:"Verify local schema replay against the source database schema",enableJsonFlag:!0,flags:{...r,...o,"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1})}}),destroy:t({summary:"Remove runtime state, databases, and Docker resources; run results and other .testkit files stay",enableJsonFlag:!0,flags:r}),discover:t({summary:"Discover managed tests and report their metadata",enableJsonFlag:!0,flags:{...a,...s,type:e.string({char:"t",multiple:!0,description:`Filter by suite type(s): ${d({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Filter by suite selector(s)"}),file:e.string({char:"f",multiple:!0,description:"Filter by exact file path(s)"}),"runnable-only":e.boolean({description:"Show only files not skipped by repo rules",default:!1}),strict:e.boolean({description:"Fail instead of reporting discovery/config diagnostics",default:!1}),output:e.string({description:"Write machine-readable discovery JSON to a file"}),"output-mode":e.string({description:"Human-readable output mode",options:["compact","verbose"],default:"compact"})}}),doctor:t({summary:"Run built-in config, discovery, and hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),typecheck:e.boolean({description:"Include generated TypeScript checks",default:!0,allowNo:!0})}}),"guard unsafe-entrypoint":t({summary:"Fail a local script that must be run through Testkit",flags:{command:e.string({char:"c",description:"Disabled command name shown in the error message",required:!0}),use:e.string({char:"u",description:"Replacement command shown in the error message",required:!0})}}),lint:t({summary:"Run built-in Testkit repository hygiene checks",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"})}}),"report prepare":t({summary:"Prepare a local report with optional template, bug records, and selected run evidence",enableJsonFlag:!0,args:{directory:i.string({description:"New bundle directory",required:!0})},flags:{dir:e.string({description:"Product directory containing bug records and runs"}),template:e.string({description:"Copy this file unchanged to report.md"}),instructions:e.string({description:"Print this repository-owned drafting guide for the author"}),bug:e.string({description:"Include this recorded bug ID as context",multiple:!0}),run:e.string({description:"Immutable run ID containing the selected artifacts"}),artifact:e.string({description:"Exact artifact ref.path from testkit artifacts --json",multiple:!0})}}),"report publish":t({summary:"Publish a prepared directory at one Git revision without changing your checkout",enableJsonFlag:!0,args:{directory:i.string({description:"Directory containing only the files to publish",required:!0})},flags:{dir:e.string({description:"Product directory used to resolve provider credentials"}),repository:e.string({description:"Destination GitHub or Gitea repository URL, or owner/name",required:!0}),branch:e.string({description:"Destination branch",required:!0}),path:e.string({description:"Bundle directory within the destination repository",required:!0}),message:e.string({description:"Publication commit message"})}}),"run status":t({summary:"Show active Testkit run progress",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to show"}),full:e.boolean({description:"Include the full live status payload in JSON output"})}}),"run stop":t({summary:"Stop active Testkit run work",enableJsonFlag:!0,flags:{...r,run:e.string({description:"Specific run id to stop; defaults to the newest active run"}),all:e.boolean({description:"Stop all active runs for this product",default:!1}),reason:e.string({description:"Operator-visible reason recorded with cancelled runs and dirty slots"})}}),prove:t({summary:"Mutate changed product code and check that the selected tests notice",enableJsonFlag:!0,flags:{...a,...s,type:n.type,suite:n.suite,file:n.file,since:e.string({description:"Mutate code changed since the merge base with this ref (default origin/HEAD)"}),"max-mutants":e.string({description:"Most mutants to run (default 25)"}),mutants:e.string({description:"JSON file of authored mutants to run instead of mutating the changed lines"}),"fail-under":e.string({description:"Exit 1 when the mutation score is below this percentage"})}}),run:t({summary:"Run test suites",enableJsonFlag:!0,args:l,flags:n}),shake:t({summary:"Rerun test files under conditions that expose flaky tests, and report which condition fails them",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),...s,file:e.string({char:"f",multiple:!0,description:"Test file to shake"}),since:e.string({description:"Shake the test files changed since the merge base with this ref"}),runs:e.string({description:"Runs for each condition (default 5)"}),copies:e.string({description:"Copies of each file that run at once in the copies condition (default 3)"}),only:e.string({multiple:!0,description:"Run only these conditions: control, shuffle, copies, timezone-east, timezone-west, load"})}}),"setup playwright":t({summary:"Install the managed Playwright Chromium runtime",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),force:e.boolean({default:!1,description:"Force Playwright to repair the browser installation"}),"with-deps":e.boolean({default:!1,description:"Install required host dependencies with Chromium"})}}),status:t({summary:"Show local testkit state",enableJsonFlag:!0,flags:r}),stop:t({summary:"Stop all Testkit-owned runs",enableJsonFlag:!0,flags:{...r,reason:e.string({description:"Operator-visible reason recorded with cancelled runs"})}}),typecheck:t({summary:"Typecheck testkit config, helpers, and suites",enableJsonFlag:!0,flags:{dir:e.string({description:"Product directory"}),group:a.group,...s}}),workspace:t({summary:"Inspect Testkit workstation data for a product",enableJsonFlag:!0,flags:{...a,"target-limit":e.integer({description:"Maximum number of quick-run target previews",default:24}),"no-status":e.boolean({description:"Skip runtime status collection",default:!1})}})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RETIRED_TEST_TYPES as
|
|
1
|
+
import{RETIRED_TEST_TYPES as r,rejectRetiredTestType as n}from"../domain/retired-test-types.js";import{publicTestTypeList as o,publicTestTypeListText as s}from"../domain/test-types.js";import{CliUsageError as p}from"./framework/errors.js";import{Args as c,Flags as e}from"./framework/index.js";export const sharedFlags={dir:e.string({description:"Explicit product directory"}),service:e.string({description:"Limit the operation to one service"})},schemaOperationFlags={"timeout-seconds":e.string({description:"Seconds the operation may run, not counting configured template steps (default 600). A longer limit lengthens the caps on Testkit's own database calls in proportion"})},testSelectionFlags={dir:sharedFlags.dir,group:e.string({description:"Limit the operation to one test group"}),service:e.string({description:"Removed in 0.6.0; use --group"})};export function selectedGroup(t){if(t.service!==void 0)throw new p(`Testkit 0.6.0 removed --service from run, discover, artifacts, and workspace; use --group ${t.service}`);return t.group??null}export const vitestSelectionFlags={"vitest-config":e.string({description:"Run plain test files with this Vitest config, relative to the product directory"}),"vitest-project":e.string({description:"Run plain test files from this Vitest project only"}),"vitest-runner":e.string({description:"Run plain test files with the repository's Vitest (repo) or Testkit's own (managed)",options:["repo","managed"]}),"vitest-shuffle":e.string({description:"Shuffle the order of plain test files and their tests with this seed"})},timezoneFlag={timezone:e.string({description:"Run the tests and the services they use in this IANA time zone, such as Pacific/Kiritimati"}),retries:e.string({description:"Retry a failed test this many times, whatever the Vitest and Playwright configs say (0 turns retries off)"})},suppliedEnvironmentFlags={"base-url":e.string({description:"Supply the default endpoint, BASE_URL, instead of provisioning it"}),endpoint:e.string({multiple:!0,description:"Supply a named endpoint as NAME=URL"}),database:e.string({multiple:!0,description:"Supply a named PostgreSQL database as NAME=URL"}),env:e.string({multiple:!0,description:"Supply an environment value as NAME=VALUE"}),"env-from":e.string({multiple:!0,description:"Supply an environment value from a host variable as NAME=HOST_KEY"}),"environment-file":e.string({description:"Supply values, prepared setup data and handle descriptors from a version 1 environment file"})};export function runTypeArgument(t=r){return c.string({check:i=>{n(i,`Type "${i}"`,t)},description:`Optional suite type shortcut: ${s({includeAll:!0})}`,required:!1,options:o({includeAll:!0})})}export const runArgs={type:runTypeArgument()},runFlags={...testSelectionFlags,...suppliedEnvironmentFlags,...vitestSelectionFlags,...timezoneFlag,type:e.string({char:"t",multiple:!0,description:`Run specific suite type(s): ${s({includeAll:!0})}`}),suite:e.string({char:"s",multiple:!0,description:"Run specific suite(s)"}),file:e.string({char:"f",multiple:!0,description:"Run specific file(s)"}),"task-concurrency":e.string({description:"Maximum number of concurrent test tasks for the whole run"}),workers:e.string({description:"Deprecated alias for --task-concurrency"}),"file-timeout-seconds":e.string({description:"Per-file wall-clock timeout in seconds"}),"allow-empty":e.boolean({description:"Permit a successful run when no runnable files execute",default:!1}),"ignore-skip-rules":e.boolean({description:"Run files even if testkit.config.ts marks them skipped",default:!1}),"skip-schema-source-verify":e.boolean({description:"Skip the check that local schema replay matches the source database schema",default:!1}),"output-mode":e.string({description:"Reporter mode",options:["compact","debug","events"]}),debug:e.boolean({description:"Alias for --output-mode debug",default:!1})};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{commandCatalog as i}from"./command-catalog.js";export const commandRegistry=Object.freeze([t("artifacts",()=>import("./commands/artifacts.js")),t("bug file",()=>import("./commands/bug/file.js")),t("bug guide",()=>import("./commands/bug/guide.js")),t("bug record",()=>import("./commands/bug/record.js")),t("campaign finish",()=>import("./commands/campaign/finish.js")),t("campaign start",()=>import("./commands/campaign/start.js")),t("campaign status",()=>import("./commands/campaign/status.js")),t("ci report",()=>import("./commands/ci/report.js")),t("cleanup",()=>import("./commands/cleanup.js")),t("db schema refresh",()=>import("./commands/db/schema/refresh.js")),t("db schema verify",()=>import("./commands/db/schema/verify.js")),t("destroy",()=>import("./commands/destroy.js")),t("discover",()=>import("./commands/discover.js")),t("doctor",()=>import("./commands/doctor.js")),t("guard unsafe-entrypoint",()=>import("./commands/guard/unsafe-entrypoint.js")),t("lint",()=>import("./commands/lint.js")),t("report prepare",()=>import("./commands/report/prepare.js")),t("report publish",()=>import("./commands/report/publish.js")),t("run status",()=>import("./commands/run/status.js")),t("run stop",()=>import("./commands/run/stop.js")),t("run",()=>import("./commands/run.js")),t("setup playwright",()=>import("./commands/setup/playwright.js")),t("status",()=>import("./commands/status.js")),t("stop",()=>import("./commands/stop.js")),t("typecheck",()=>import("./commands/typecheck.js")),t("workspace",()=>import("./commands/workspace.js"))]),removedCommands=new Map([["cloud","The testkit cloud commands were removed in Testkit 0.4.0. Testkit no longer publishes runs to Testkit Cloud."]]);function t(r,o){return Object.freeze({...i[r],id:r.split(" "),load:o})}
|
|
1
|
+
import{commandCatalog as i}from"./command-catalog.js";export const commandRegistry=Object.freeze([t("artifacts",()=>import("./commands/artifacts.js")),t("bug bisect",()=>import("./commands/bug/bisect.js")),t("bug file",()=>import("./commands/bug/file.js")),t("bug guide",()=>import("./commands/bug/guide.js")),t("bug record",()=>import("./commands/bug/record.js")),t("campaign finish",()=>import("./commands/campaign/finish.js")),t("campaign start",()=>import("./commands/campaign/start.js")),t("campaign status",()=>import("./commands/campaign/status.js")),t("ci report",()=>import("./commands/ci/report.js")),t("cleanup",()=>import("./commands/cleanup.js")),t("db schema refresh",()=>import("./commands/db/schema/refresh.js")),t("db schema verify",()=>import("./commands/db/schema/verify.js")),t("destroy",()=>import("./commands/destroy.js")),t("discover",()=>import("./commands/discover.js")),t("doctor",()=>import("./commands/doctor.js")),t("guard unsafe-entrypoint",()=>import("./commands/guard/unsafe-entrypoint.js")),t("lint",()=>import("./commands/lint.js")),t("prove",()=>import("./commands/prove.js")),t("report prepare",()=>import("./commands/report/prepare.js")),t("report publish",()=>import("./commands/report/publish.js")),t("run status",()=>import("./commands/run/status.js")),t("run stop",()=>import("./commands/run/stop.js")),t("run",()=>import("./commands/run.js")),t("shake",()=>import("./commands/shake.js")),t("setup playwright",()=>import("./commands/setup/playwright.js")),t("status",()=>import("./commands/status.js")),t("stop",()=>import("./commands/stop.js")),t("typecheck",()=>import("./commands/typecheck.js")),t("workspace",()=>import("./commands/workspace.js"))]),removedCommands=new Map([["cloud","The testkit cloud commands were removed in Testkit 0.4.0. Testkit no longer publishes runs to Testkit Cloud."]]);function t(r,o){return Object.freeze({...i[r],id:r.split(" "),load:o})}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import c from"node:path";import{bisectBug as r}from"../../../app/bugs/bisect.js";import{CliUsageError as h}from"../../framework/errors.js";export default async function l({args:e,flags:s,json:t,log:a,setExitCode:m}){const i=s["max-probes"]??"20";if(!/^\d+$/u.test(i)||Number(i)<1)throw new h(`--max-probes must be a whole number above 0, not ${JSON.stringify(i)}`);const n=await r({cwd:process.cwd(),dir:s.dir,id:e.id,maxProbes:Number(i),onProbe:t?void 0:o=>{const d=o.dependencies==="installed"?" (installed its dependencies)":o.dependencies==="tooling"?" (with only the checkout's test tooling)":"";a(`${o.commit.slice(0,12)} ${o.verdict}${d}${o.reason===void 0?"":`: ${o.reason}`}`)}});return t||a(f(n,Number(i))),n.good===null&&m(1),{ok:n.good!==null,...n}}function f(e,s){const t=o=>o.slice(0,12),a=`Run it again with --max-probes ${String(s*2)} to look further.`,m=`Each run's results are under ${c.relative(process.cwd(),e.evidenceDir)||"."}.`;if(e.good===null){const o={budget:`and Testkit ran out of probes. ${a}`,found:"",history:"the first commit on the first-parent line.",shallow:"the oldest commit in this shallow clone. Fetch more history to look further.",skipped:"and Testkit could not judge it at the commits before that."}[e.stop];return`The test fails at every commit Testkit ran it at, back to ${t(e.bad)}, ${o} The draft is unchanged. ${m}`}const i=e.exact?`The test passes at ${t(e.good)} and fails from its child ${t(e.bad)}, the commit that broke it.`:`The test passes at ${t(e.good)} and fails at ${t(e.bad)}. ${e.stop==="budget"?`Testkit ran out of probes before the commits between them. ${a}`:"Testkit could not judge it at the commits between them, so the fault came in among them."}`,n=e.blame===null?"Git blame of the fault lines names no commit.":e.probes.some(o=>o.commit===e.blame)?e.blame===e.bad&&e.exact?`Git blame of the fault lines names ${t(e.blame)} too.`:`Git blame of the fault lines names ${t(e.blame)} instead.`:e.blameOnLine?`Git blame of the fault lines names ${t(e.blame)}, before the commits Testkit ran.`:`Git blame of the fault lines names ${t(e.blame)}, a commit off the first-parent line.`;return`${i} ${n} The draft records the result, and \`testkit bug file\` puts it in the issue. ${m}`}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{bugBatchReport as
|
|
2
|
-
`)),{ok:!0,created:!0,...u}}async function
|
|
3
|
-
`)),d}async function
|
|
1
|
+
import{bugBatchReport as S,fileBugBatch as F,planBugBatchRuns as q,prepareBugBatch as A,rejectedBugBatchDraft as j}from"../../../app/bugs/batch.js";import{bugRerun as E,fileBug as M,prepareBugFiling as P}from"../../../app/bugs/file.js";import{readRunArtifact as U,readRunArtifactFile as x}from"../../../app/bugs/run-reading.js";import{buildRunRequest as h}from"../../../app/run/request.js";import{useTimeZone as w}from"../../../app/run/time-zone.js";import{CliUsageError as B}from"../../framework/errors.js";import{executeRunRequest as v}from"../../operations/run/operation.js";import{renderAlreadyFiledBug as z,renderBugBatchResult as T,renderBugBatchRun as V,renderBugFileResult as Z,renderBugFileRun as C}from"../../renderers/bug/text.js";export default async function D(c){const{args:r,flags:e,json:a,log:o}=c,t={baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]};if(e.batch)return J(c,t);if(r.id.length===0)throw new B("Missing required argument id");if(r.id.length>1)throw new B(`Unexpected argument ${JSON.stringify(r.id[1])}. Pass --batch to file several drafts.`);const[s]=r.id,i=await P({cwd:process.cwd(),dir:e.dir,env:process.env,id:s,supplied:t});if(i.status==="already-filed")return a||o(z(i.issue)),{ok:!0,created:!1,draft:i.draft,issue:i.issue};a||o(C(i));const n=await E(i,e,t);w(n.ready.draft.timezone);const f=await h({allowEmpty:!1,dir:e.dir,file:n.files,ignoreSkipRules:!1,skipSchemaSourceVerify:!1,...n.ready.draft.retries===void 0?{}:{retries:String(n.ready.draft.retries)},supplied:t,vitest:n.ready.draft.vitest},null,process.cwd(),process.cwd(),process.env),d=await v(f,{json:a,outputMode:"compact"}),u=await M(n.ready,U(d.runArtifact,i.checkout.productPath));return a||o(Z(u).join(`
|
|
2
|
+
`)),{ok:!0,created:!0,...u}}async function J({args:c,flags:r,json:e,log:a,setExitCode:o},t){const s=await A({cwd:process.cwd(),dir:r.dir,env:process.env,ids:c.id,supplied:t}),i=await q(s,r,t),n=[...s.settled,...i.rejected],f=[];for(const{drafts:u,files:m,retries:y,timezone:R,vitest:b}of i.runs){e||a(V(u,m));const k=w(R);try{const{runArtifact:l}=await N(m,b,y,r.dir,t,e),{productPath:p}=s.checkout;f.push(...u.map(g=>({ready:g,run:x(l,p,g.draft.test.file)})))}catch(l){n.push(...u.map(p=>j(p.draft.id,l)))}finally{k()}}n.push(...await F(f));const d=S(s,n);return d.ok||o(1),e||a(T(d.drafts).join(`
|
|
3
|
+
`)),d}async function N(c,r,e,a,o,t){const s=await h({allowEmpty:!1,dir:a,file:c,ignoreSkipRules:!1,...e===void 0?{}:{retries:String(e)},skipSchemaSourceVerify:!1,supplied:o,vitest:r},null,process.cwd(),process.cwd(),process.env);return v(s,{json:t,outputMode:"compact"})}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{recordBug as n}from"../../../app/bugs/record.js";import{renderBugRecordResult as u}from"../../renderers/bug/text.js";export default async function d({args:t,flags:
|
|
2
|
-
`)),{ok:!0,...
|
|
1
|
+
import{recordBug as n}from"../../../app/bugs/record.js";import{renderBugRecordResult as u}from"../../renderers/bug/text.js";export default async function d({args:t,flags:e,json:i,log:o}){const r=await n({dir:e.dir,file:t.file,retries:e.retries,timezone:e.timezone,vitest:e});return i||o(u(r).join(`
|
|
2
|
+
`)),{ok:!0,...r}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import $ from"node:fs";import c from"node:path";import{prove as M,readAuthoredMutants as S}from"../../app/prove/index.js";import{describeMutant as b,renderProveReport as l}from"../../app/prove/report.js";import{selectedGroup as y}from"../command-flags.js";import{CliUsageError as m}from"../framework/errors.js";export default async function U({flags:e,json:t,log:r,setExitCode:a}){const f=p(e["max-mutants"]??"25","--max-mutants"),i=e["fail-under"]===void 0?null:p(e["fail-under"],"--fail-under");if(e.mutants!==void 0&&e.since!==void 0)throw new m("Pass --mutants or --since, not both. Authored mutants name their own lines.");const o=y(e),n=await M({cwd:process.cwd(),dir:e.dir,maxMutants:f,...e.mutants===void 0?{}:{authored:S(c.resolve(e.mutants))},...e.since===void 0?{}:{since:e.since},selection:{...e.file===void 0?{}:{file:e.file},...o===null?{}:{group:o},...e.suite===void 0?{}:{suite:e.suite},...e.type===void 0?{}:{type:e.type},vitest:e},onMutant:t?void 0:(s,w,h)=>{r(`[${String(w)}/${String(h)}] ${s.status} ${b(s)}`)}}),u=l(n);process.env.GITHUB_STEP_SUMMARY&&$.appendFileSync(process.env.GITHUB_STEP_SUMMARY,`${u}
|
|
2
|
+
`),t||r(`${u}
|
|
3
|
+
Record: ${c.relative(process.cwd(),n.recordDir)||"."}`);const d=i!==null&&n.mutants.length>0&&(n.score===null||n.score*100<i);return d&&a(1),{ok:!d,...n}}function p(e,t){if(!/^\d+$/u.test(e))throw new m(`${t} must be a whole number, not ${JSON.stringify(e)}`);return Number(e)}
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{applyKnownFailures as
|
|
2
|
-
`)),
|
|
1
|
+
import{applyKnownFailures as m,loadBugPolicy as a}from"../../app/bugs/gate.js";import{buildRunRequest as l}from"../../app/run/request.js";import{useTimeZone as w}from"../../app/run/time-zone.js";import{selectedGroup as y}from"../command-flags.js";import{executeRunRequest as k}from"../operations/run/operation.js";import{renderKnownFailures as v}from"../renderers/bug/text.js";export default async function b({args:t,flags:e,json:i,log:u,notice:c,setExitCode:d}){const p=y(e);w(e.timezone);const o=await l({dir:e.dir,group:p??void 0,taskConcurrency:e["task-concurrency"],workers:e.workers,fileTimeoutSeconds:e["file-timeout-seconds"],retries:e.retries,type:e.type,suite:e.suite,file:e.file,allowEmpty:e["allow-empty"],ignoreSkipRules:e["ignore-skip-rules"],skipSchemaSourceVerify:e["skip-schema-source-verify"],supplied:{baseUrl:e["base-url"],database:e.database,endpoint:e.endpoint,env:e.env,envFrom:e["env-from"],environmentFile:e["environment-file"]},vitest:e},t.type||null,process.cwd(),process.cwd()),s=await a(o.productDir),r=await k(o,{outputMode:e["output-mode"]||"compact",json:i,debug:e.debug}),n=await m({env:process.env,exitCode:r.exitCode,failedOutsideTests:r.failure!==void 0,policy:s,productDir:o.productDir,run:r.runArtifact});return n.knownFailures!==null&&(r.outputMode==="compact"||r.outputMode==="debug")&&u(v(n.knownFailures).join(`
|
|
2
|
+
`)),r.maintenance?.status==="failed"&&c(`Post-run maintenance failed: ${r.maintenance.error??"unknown error"}. The run's result is unchanged.`),d(n.exitCode),n.knownFailures===null?r:{...r,...n}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SHAKE_CONDITIONS as d}from"../../app/shake/conditions.js";import{shake as $}from"../../app/shake/index.js";import{renderShakeReport as y}from"../../app/shake/report.js";import{CliUsageError as r}from"../framework/errors.js";export default async function k({flags:n,json:i,log:e,setExitCode:t}){const f=u(n.runs??"5","--runs",1),w=u(n.copies??"3","--copies",2);if(n["vitest-shuffle"]!==void 0)throw new r("shake chooses its own seeds. Pass --vitest-shuffle to testkit run to replay one.");if(n.file!==void 0&&n.since!==void 0)throw new r("Pass --file or --since, not both.");const s=await $({copies:w,cwd:process.cwd(),dir:n.dir,...n.file===void 0?{}:{files:n.file},...n.only===void 0?{}:{only:S(n.only)},runs:f,...n.since===void 0?{}:{since:n.since},vitest:n,onRun:i?void 0:(o,m,p)=>{const h=o.outcome==="passed"?"":` ${o.failures.join(", ")||(o.reason??"")}`;e(`[${String(m)}/${String(p)}] ${o.condition} ${o.outcome}${h}`)}});i||e(y(s));const c=s.runs.every(o=>o.outcome==="passed");return c||t(1),{ok:c,...s}}function u(n,i,e){if(!/^\d+$/u.test(n)||Number(n)<e||Number(n)>100)throw new r(`${i} must be a whole number from ${String(e)} to 100, not ${JSON.stringify(n)}`);return Number(n)}function S(n){const i=d,e=n.find(t=>!i.includes(t));if(e!==void 0)throw new r(`--only takes ${d.join(", ")}, not ${JSON.stringify(e)}`);return d.filter(t=>n.includes(t))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export const DEFAULT_FILE_TIMEOUT_SECONDS=60,INTERNAL_FILE_TIMEOUT_SECONDS_ENV="TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS";export function parseFileTimeoutOption(e){return n(e,"--file-timeout-seconds")}export function normalizeFileTimeoutSeconds(e,r="execution.fileTimeoutSeconds"){return normalizePositiveInteger(e,r)}function n(e,r){const t=Number.parseInt(String(e),10);if(!Number.isInteger(t)||t<=0)throw new Error(`Invalid ${r} value "${String(e)}". Expected a positive integer.`);return t}export function normalizePositiveInteger(e,r){const t=Number(e);if(!Number.isInteger(t)||t<=0)throw new Error(`${r} must be a positive integer.`);return t}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isUnknownRecord as s}from"../../kernel/unknown-values.js";export function
|
|
1
|
+
import{isUnknownRecord as s}from"../../kernel/unknown-values.js";export function parseRuntimeFixtureManifestEntries(t,r){if(s(t)&&Object.keys(t).length===0)return[];if(!s(t))throw new Error(`${r} must contain an object`);if(Reflect.get(t,"version")!==1)throw new Error(`${r}.version must be 1`);i(t,"testFile",r);const n=Reflect.get(t,"entries");if(!Array.isArray(n))throw new Error(`${r}.entries must be an array`);return n.map((e,o)=>g(e,`${r}.entries[${String(o)}]`))}function g(t,r){if(!s(t))throw new Error(`${r} must be an object`);return{absolutePath:i(t,"absolutePath",r),aliases:c(t,"aliases",r),contentType:i(t,"contentType",r),filename:i(t,"filename",r),path:i(t,"path",r),root:i(t,"root",r),rootRelativePath:i(t,"rootRelativePath",r),sha256:i(t,"sha256",r),sizeBytes:p(t,"sizeBytes",r)}}function i(t,r,n){const e=Reflect.get(t,r);if(typeof e!="string"||e.length===0)throw new Error(`${n}.${String(r)} must be a non-empty string`);return e}function c(t,r,n){const e=Reflect.get(t,r);if(!Array.isArray(e))throw new Error(`${n}.${String(r)} must be an array`);return e.map((o,f)=>{if(typeof o!="string"||o.length===0)throw new Error(`${n}.${String(r)}[${String(f)}] must be a non-empty string`);return o})}function p(t,r,n){const e=Reflect.get(t,r);if(typeof e!="number"||!Number.isSafeInteger(e)||e<0)throw new Error(`${n}.${String(r)} must be a non-negative integer`);return e}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeTempDirAsync as U}from"../../../process/files/temp-dir.js";import{toError as B}from"../../../kernel/error-serialization.js";import{isDeepStrictEqual as ee}from"node:util";import{classifyManagedProcessResult as te,engineExecutionResult as re,terminalOutcome as P,emptyEvidence as L,failureFromError as j,firstOutputLine as K,processExitDetails as ne,processExitFailure as W,taskBeforeStartOutcome as ie,managedProcessFailure as z,taskTimeoutFailure as oe}from"../../adapters/execution.js";import{requireRootDirectory as se,requireAbsoluteWorkspacePath as H,requireConsumerFile as ae,safeSegment as J}from"../../adapters/workspace.js";import w from"node:fs/promises";import I from"node:path";import{pathToFileURL as ce}from"node:url";import{isEngineTaskDeadlineExceededReason as de,resolveEngineTaskInputs as le}from"../../api/index.js";import{createInvocationProcessEnvironment as ue,runManagedProcess as pe}from"../../../process/index.js";import{managedVitestAliases as me}from"../../installations/node-vitest.js";import{decodeVitestSelection as fe,resolveRepositoryVitest as Ee}from"../selection.js";import{collectNodeVitestEvidence as ve,readNodeVitestRunReport as Te}from"./evidence.js";import{assertNodeVitestSpawnEnvironmentBudget as X,NODE_VITEST_RUNTIME_INPUT_FILE_ENV as Ie,NODE_VITEST_RUNTIME_INPUT_SHA256_ENV as ye,selectNodeVitestForwardedEnvironment as De,splitManagedNodeVitestEnvironment as he,verifyOwnedNodeVitestRuntimeInput as we,writeOwnedNodeVitestRuntimeInput as Ne}from"../runtime/runtime-input.js";import{NODE_VITEST_DRIVER_FILE as Y,writeNodeVitestWorkspace as ge}from"./workspace.js";import{nodeVitestReportOptions as _e}from"./reporter.js";import{engineEvidenceIdentity as Ve}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as Oe}from"../../adapters/node-startup-cache.js";const g={codePrefix:"NODE_VITEST",label:"Node/Vitest"},G="Unhandled Reporter Error";class $ extends Error{constructor(t){const r=B(t);super(r.message,{cause:r}),this.name="NodeVitestConsumerPreparationError"}}export const nodeVitestExecutorDependencies={removeOwnedDirectory:e=>w.rm(e,{force:!0,recursive:!0}),writeRuntimeInput:Ne,writeWorkspace:ge};export async function executeNodeVitestEngine(e,t,r,m=nodeVitestExecutorDependencies){const l=await Re(e,t,m,r),o=Date.now(),s=l.tasks.map(c=>{e.observer?.taskStarted(c.task);const f=c.preparationFailure;return ie(c.task,e.signal,o,g)??(f===null?null:P(c.task.id,"failed",o,L(),f))}),p=l.tasks.filter((c,f)=>s[f]===null),i=[],v=p.length===0?[]:await Se(e,l,p,o,i);let E=0;return re(e,s.map(c=>c??v[E++]),ke(e,l),i)}async function Re(e,t,r,m){const l=e.profile.id,o=await se(e.workspace.rootDirectory);H(e.workspace.temporaryDirectory,"temporary directory"),H(e.workspace.artifactDirectory,"artifact directory"),await w.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await w.mkdir(e.workspace.artifactDirectory,{recursive:!0});const s=await w.realpath(e.workspace.temporaryDirectory),p=await w.realpath(e.workspace.artifactDirectory),i=await U(`node-vitest-${J(e.invocationId)}-`,{parentDir:s});m.defer(()=>r.removeOwnedDirectory(i));const v=I.join(i,"reports");await w.mkdir(v,{mode:448});const E=await ue(i);let c=[];for(const[n,u]of e.tasks.entries()){const a=le(e,u),N=`${n.toString().padStart(3,"0")}-${J(u.id)}`,y=await U(`${N}-`,{parentDir:p});try{const x=await Z(()=>ae(o,u.file,"Node/Vitest task file")),C=I.join(v,`${N}.json`),M={file:x,reportFile:C,sourceFile:u.file,taskId:u.id},A=I.join(y,"data");c.push({artifactDirectory:y,inputs:a,task:u,preparationFailure:null,prepared:{dataDirectory:A,module:M}})}catch(x){const C=Q(x);c.push({artifactDirectory:y,inputs:a,task:u,preparationFailure:C,prepared:null})}}const f=c.filter(n=>n.prepared!==null),d=I.join(i,"setup.mjs"),T=I.join(i,"input-bootstrap.mjs"),h=I.join(i,"driver-request.json"),R=I.join(v,"run.json"),_=I.join(i,"startup-failure.txt");let V=null,S=null,D=t.vitest;const F=`--import=${JSON.stringify(ce(T).href)}`;let O={...E.environment,FORCE_COLOR:"0",NODE_OPTIONS:F,NO_COLOR:"1"};if(f.length>0)try{xe(f);const{configuration:n}=f[0].inputs,u=n===void 0?null:fe(n),a=u!==null&&u.runner!=="managed"?u:null;a!==null&&(D=Ee(o));const N=Object.keys(f[0].inputs.supportModules);if(N.length>0)throw new $(new Error(`Node/Vitest engine does not support support module(s): ${N.join(", ")}`));let y=await Z(()=>Ce(e,l,f,a?.host??null));if(a===null){const k=he(y.environment);y={...y,environment:k.overlay},O={...O,...k.process}}else{const k=b=>process.platform==="win32"?b.toUpperCase():b,q=new Set(Object.keys(y.environment).map(k));O={...Object.fromEntries(Object.entries(a.host).filter(([b])=>!q.has(k(b)))),...y.environment,NODE_OPTIONS:[F,a.host.NODE_OPTIONS].join(" ").trim()}}V=await r.writeRuntimeInput(i,y);const{file:x,sha256:C,sizeBytes:M}=V;await r.writeWorkspace({inputBootstrapFile:T,runtimeInput:{expectedFile:x,expectedInvocationId:e.invocationId,expectedSha256:C,expectedSizeBytes:M,startupFailure:{driverFile:Y,file:_,vitestWorkersDirectory:I.join(D.root,"dist","workers")}},setupFile:d,vitestModule:D.index});const A={cacheDirectory:I.join(i,"cache"),invocationId:e.invocationId,runReportFile:R,setupFile:d,startupFailureFile:_,vitest:{entrypoint:D.entrypoint,name:"vitest",version:D.version}};S=a===null?{...A,aliases:me(t),config:!1,managed:{externalModules:[t.authoringEntrypoints.data.replaceAll(I.sep,"/")]},project:null,test:{disableConsoleIntercept:!0,environment:"node",fileParallelism:!1,isolate:!0,maxWorkers:1,pool:"forks"}}:{...A,aliases:{},config:a.config,managed:null,project:a.project,test:{}}}catch(n){const u=Q(n);c=c.map(a=>a.prepared===null?a:{...a,preparationFailure:u,prepared:null})}return Object.freeze({driverRequest:S,driverRequestFile:h,installation:t,processEnvironment:O,profileId:l,rootDirectory:o,runReportFile:R,runtimeInput:V,startupFailureFile:_,tasks:Object.freeze(c),vitest:D})}async function Se(e,t,r,m,l){const o=n=>r.map(({task:u})=>P(u.id,"failed",m,L(),n(u.timeoutMs)));try{if(t.runtimeInput===null||t.driverRequest===null)throw new Error("Node/Vitest run was not prepared");await we(t.runtimeInput)}catch(n){return o(()=>({...j(n,"NODE_VITEST_INPUT_INVALID"),classification:"infra"}))}const s=await Fe(e,t,t.runtimeInput,r[0]),p=Math.min(...r.map(({task:n})=>n.deadlineMs))-Date.now();if(p<=0)return o(n=>oe(n,g));const i=t.driverRequest.managed===null?{}:{hookTimeout:p,teardownTimeout:p,testTimeout:p},v=r.map(({prepared:n})=>n.module),E={...t.driverRequest,modules:v,test:{...t.driverRequest.test,...i}};await w.writeFile(t.driverRequestFile,JSON.stringify(E),{flag:"wx",mode:384});const c=await pe({args:[Y,t.driverRequestFile],cwd:t.rootDirectory,displayLabel:"Node/Vitest",env:s,executable:process.execPath,signal:e.signal,timeoutMs:p}),f=await w.readFile(t.startupFailureFile,"utf8").catch(()=>null),d=await Te(e.invocationId,t.runReportFile),T=d instanceof Error?[]:d.unhandledErrors.filter(({type:n})=>n!==G),h=[];for(const{artifactDirectory:n,prepared:u}of r)h.push(await ve({identity:_e(e.invocationId,u.module).identity,artifactDirectory:n,dataDirectory:u.dataDirectory,reportFile:u.module.reportFile,rootDirectory:t.rootDirectory,stderr:c.stderr,stdout:c.stdout,unhandledErrors:T.filter(({testPath:a})=>a===void 0||I.resolve(a)===I.resolve(u.module.file))}).catch(a=>nodeVitestEvidenceCollectionFailure(c,a)));const R=h.map(n=>"reportSummary"in n?n.reportSummary:null),_=de(e.signal.reason),V=c.cancelled||c.timedOut||_,S=V&&d instanceof Error&&R.includes(null),D=T.length>0||R.some(n=>n?.success===!1||n?.numTotalTests===0),F=d instanceof Error?0:c.exitCode??d.exitCode,O=d instanceof Error?S?{exitCode:0,unhandledErrors:[]}:d:{...d,exitCode:F===1&&(D||d.afterTests!==void 0)?0:F};return!(d instanceof Error)&&d.afterTests!==void 0&&l.push(`Vitest failed the run after its tests ${D?"finished":"passed"}: ${d.afterTests.join("; ")}`),r.map(({task:n},u)=>{const a=h[u];if(f!==null&&!V)return P(n.id,"failed",m,"reportSummary"in a?a.evidence:L(),{classification:"infra",code:"NODE_VITEST_START_FAILED",message:`Vitest could not start: ${f}`});if(!("reportSummary"in a))return P(n.id,"failed",m,L(),a);const{failure:N,status:y}=classifyNodeVitestTaskResult(c,O,a,n.timeoutMs,_,S);return P(n.id,y,m,a.evidence,N)})}export function classifyNodeVitestTaskResult(e,t,r,m,l,o=!1){const s=d=>({failure:d,status:"failed"}),p=z(e,g);if(p!==null)return s(p);const i=r.reportSummary,v=i===null||!o?te(e,m,l,g):null;if(v!==null)return v;const E=t instanceof Error?void 0:t.unhandledErrors.find(({type:d})=>d===G),c=t instanceof Error?void 0:t.failure??(E&&{...E,phase:"reporter"});if(c!==void 0){const{message:d,phase:T,stack:h}=c;return{failure:{classification:"infra",code:"NODE_VITEST_RUN_FAILED",details:{phase:T},message:`Vitest ${T} failed: ${d}`,...h===void 0?{}:{stack:h}},status:T==="config"?"not_run":"failed"}}if(r.environmentFailure!==null)return s(r.environmentFailure);if(i===null||t instanceof Error){const d=K(e.stderr)??K(e.stdout),T=e.exitCode===0?null:W(e,g,d,"Vitest").failure;return s({...j(r.reportError??t,"NODE_VITEST_REPORT_INVALID"),...T===null?{}:{details:T.details,message:T.message},classification:"infra"})}const f=o?null:e.exitSignal;if(t.exitCode!==0||f!==null){const d=f===null?`exited with code ${t.exitCode.toString()}`:`was terminated by ${f}`;return s({classification:"infra",code:"NODE_VITEST_EXIT_CODE",details:ne(e),message:`Vitest ${d}, which its test results do not explain`})}return i.numTotalTests===0&&r.reportErrorCount===0?s({code:"NODE_VITEST_NO_TESTS",message:"Vitest did not discover any tests for the task file"}):r.dataEvidenceError!==null?s(j(r.dataEvidenceError,"NODE_VITEST_DATA_EVIDENCE_INVALID")):!i.success||i.numFailedTests>0||r.reportErrorCount>0?W(e,g,r.reportFailureMessage,"Vitest"):i.numPassedTests===0&&(i.numPendingTests>0||i.numTodoTests>0)?{failure:null,status:"skipped"}:i.numPassedTests===0?s({code:"NODE_VITEST_REPORT_FAILED",message:"Vitest report did not contain a completed passing test"}):{failure:null,status:"passed"}}export function nodeVitestEvidenceCollectionFailure(e,t){const r=B(t);return z(e,g,r)??{...j(r,"NODE_VITEST_EVIDENCE_FAILED"),classification:"infra"}}async function Fe(e,t,r,{inputs:m,task:l}){const o=e.target.baseUrl,s={...t.processEnvironment,...o===null?{}:{BASE_URL:o},TESTKIT_ACTIVE:"1",[Ie]:r.file,[ye]:r.sha256,TESTKIT_INVOCATION_ID:e.invocationId,TESTKIT_LEASE_DIR:l.orchestration.lease.directory,TESTKIT_LEASE_ID:l.orchestration.lease.id,TESTKIT_MODE:l.orchestration.mode,...l.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:l.orchestration.runtimeId}},p=t.driverRequest?.managed!==null;p&&X(s);const i={...t.processEnvironment,...m.environment};Object.assign(s,await Oe(e.workspace.cacheDirectory,i));try{p&&X(s)}catch{delete s.NODE_COMPILE_CACHE}return s}function xe(e){const t=({prepared:o})=>o.module.file,r=e.find((o,s)=>e.findIndex(p=>t(p)===t(o))!==s);if(r!==void 0)throw new Error(`Node/Vitest task ${r.task.id} selects a file that another task in the request selects`);const m=({inputs:o,task:s})=>[o.environment,o.supportModules,s.orchestration,s.timeoutMs],l=e.find(o=>!ee(m(o),m(e[0])));if(l!==void 0)throw new Error(`Node/Vitest task ${l.task.id} needs a different environment, support modules, orchestration or timeout than ${e[0].task.id}`)}function Ce(e,t,r,m){const{baseUrl:l,databases:o,endpoints:s,handles:p}=e.target;return{...m===null?{}:{host:m},baseUrl:l,databases:o,endpoints:s,engineId:"node-vitest",environment:De(r[0].inputs.environment),handles:p,invocationId:e.invocationId,profileId:t,tasks:r.map(({inputs:i,prepared:v,task:E})=>({context:{baseUrl:l,engineId:"node-vitest",invocationId:e.invocationId,profileId:t,serviceName:E.serviceName,task:{file:E.file,id:E.id,suiteName:E.suiteName,...E.testType===void 0?{}:{testType:E.testType}}},dataDirectory:v.dataDirectory,file:v.module.file,fixtures:i.fixtures,...i.setupData===void 0?{}:{setupData:i.setupData},taskId:E.id})),version:1}}function ke(e,t){return{...Ve({engineId:"node-vitest",installationKind:t.driverRequest?.managed===null?"repository-package":"root-direct-packages",profile:e.profile}),directPackages:{vitest:t.vitest.version}}}function Q(e){return{...j(e,"NODE_VITEST_PREPARE_FAILED"),classification:e instanceof $?"product":"infra"}}async function Z(e){try{return await e()}catch(t){throw new $(t)}}
|
|
1
|
+
import{makeTempDirAsync as U}from"../../../process/files/temp-dir.js";import{toError as B}from"../../../kernel/error-serialization.js";import{isDeepStrictEqual as te}from"node:util";import{classifyManagedProcessResult as re,engineExecutionResult as ne,terminalOutcome as P,emptyEvidence as L,failureFromError as j,firstOutputLine as K,processExitDetails as ie,processExitFailure as W,taskBeforeStartOutcome as oe,managedProcessFailure as z,taskTimeoutFailure as se}from"../../adapters/execution.js";import{requireRootDirectory as ae,requireAbsoluteWorkspacePath as H,requireConsumerFile as ce,safeSegment as J}from"../../adapters/workspace.js";import w from"node:fs/promises";import I from"node:path";import{pathToFileURL as de}from"node:url";import{isEngineTaskDeadlineExceededReason as le,resolveEngineTaskInputs as ue}from"../../api/index.js";import{createInvocationProcessEnvironment as pe,runManagedProcess as fe}from"../../../process/index.js";import{managedVitestAliases as me}from"../../installations/node-vitest.js";import{decodeVitestSelection as Ee,resolveRepositoryVitest as ve}from"../selection.js";import{collectNodeVitestEvidence as Te,readNodeVitestRunReport as Ie}from"./evidence.js";import{assertNodeVitestSpawnEnvironmentBudget as X,NODE_VITEST_RUNTIME_INPUT_FILE_ENV as ye,NODE_VITEST_RUNTIME_INPUT_SHA256_ENV as De,selectNodeVitestForwardedEnvironment as he,splitManagedNodeVitestEnvironment as we,verifyOwnedNodeVitestRuntimeInput as Ne,writeOwnedNodeVitestRuntimeInput as ge}from"../runtime/runtime-input.js";import{NODE_VITEST_DRIVER_FILE as Y,writeNodeVitestWorkspace as _e}from"./workspace.js";import{nodeVitestReportOptions as Ve}from"./reporter.js";import{engineEvidenceIdentity as Oe}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as Re}from"../../adapters/node-startup-cache.js";const g={codePrefix:"NODE_VITEST",label:"Node/Vitest"},G="Unhandled Reporter Error";class $ extends Error{constructor(t){const r=B(t);super(r.message,{cause:r}),this.name="NodeVitestConsumerPreparationError"}}export const nodeVitestExecutorDependencies={removeOwnedDirectory:e=>w.rm(e,{force:!0,recursive:!0}),writeRuntimeInput:ge,writeWorkspace:_e};export async function executeNodeVitestEngine(e,t,r,f=nodeVitestExecutorDependencies){const u=await Se(e,t,f,r),o=Date.now(),s=u.tasks.map(c=>{e.observer?.taskStarted(c.task);const m=c.preparationFailure;return oe(c.task,e.signal,o,g)??(m===null?null:P(c.task.id,"failed",o,L(),m))}),p=u.tasks.filter((c,m)=>s[m]===null),i=[],v=p.length===0?[]:await Fe(e,u,p,o,i);let E=0;return ne(e,s.map(c=>c??v[E++]),Pe(e,u),i)}async function Se(e,t,r,f){const u=e.profile.id,o=await ae(e.workspace.rootDirectory);H(e.workspace.temporaryDirectory,"temporary directory"),H(e.workspace.artifactDirectory,"artifact directory"),await w.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await w.mkdir(e.workspace.artifactDirectory,{recursive:!0});const s=await w.realpath(e.workspace.temporaryDirectory),p=await w.realpath(e.workspace.artifactDirectory),i=await U(`node-vitest-${J(e.invocationId)}-`,{parentDir:s});f.defer(()=>r.removeOwnedDirectory(i));const v=I.join(i,"reports");await w.mkdir(v,{mode:448});const E=await pe(i);let c=[];for(const[n,l]of e.tasks.entries()){const a=ue(e,l),N=`${n.toString().padStart(3,"0")}-${J(l.id)}`,y=await U(`${N}-`,{parentDir:p});try{const x=await Z(()=>ce(o,l.file,"Node/Vitest task file")),C=I.join(v,`${N}.json`),M={file:x,reportFile:C,sourceFile:l.file,taskId:l.id},A=I.join(y,"data");c.push({artifactDirectory:y,inputs:a,task:l,preparationFailure:null,prepared:{dataDirectory:A,module:M}})}catch(x){const C=Q(x);c.push({artifactDirectory:y,inputs:a,task:l,preparationFailure:C,prepared:null})}}const m=c.filter(n=>n.prepared!==null),d=I.join(i,"setup.mjs"),T=I.join(i,"input-bootstrap.mjs"),h=I.join(i,"driver-request.json"),R=I.join(v,"run.json"),_=I.join(i,"startup-failure.txt");let V=null,S=null,D=t.vitest;const F=`--import=${JSON.stringify(de(T).href)}`;let O={...E.environment,FORCE_COLOR:"0",NODE_OPTIONS:F,NO_COLOR:"1"};if(m.length>0)try{Ce(m);const{configuration:n}=m[0].inputs,l=n===void 0?null:Ee(n),a=l!==null&&l.runner!=="managed"?l:null;a!==null&&(D=ve(o));const N=Object.keys(m[0].inputs.supportModules);if(N.length>0)throw new $(new Error(`Node/Vitest engine does not support support module(s): ${N.join(", ")}`));let y=await Z(()=>ke(e,u,m,a?.host??null));if(a===null){const k=we(y.environment);y={...y,environment:k.overlay},O={...O,...k.process}}else{const k=b=>process.platform==="win32"?b.toUpperCase():b,ee=new Set(Object.keys(y.environment).map(k));O={...Object.fromEntries(Object.entries(a.host).filter(([b])=>!ee.has(k(b)))),...y.environment,NODE_OPTIONS:[F,a.host.NODE_OPTIONS].join(" ").trim()}}V=await r.writeRuntimeInput(i,y);const{file:x,sha256:C,sizeBytes:M}=V;await r.writeWorkspace({inputBootstrapFile:T,runtimeInput:{expectedFile:x,expectedInvocationId:e.invocationId,expectedSha256:C,expectedSizeBytes:M,startupFailure:{driverFile:Y,file:_,vitestWorkersDirectory:I.join(D.root,"dist","workers")}},setupFile:d,vitestModule:D.index});const A={cacheDirectory:I.join(i,"cache"),invocationId:e.invocationId,runReportFile:R,setupFile:d,startupFailureFile:_,vitest:{entrypoint:D.entrypoint,name:"vitest",version:D.version}};S=a===null?{...A,aliases:me(t),config:!1,managed:{externalModules:[t.authoringEntrypoints.data.replaceAll(I.sep,"/")]},project:null,test:{disableConsoleIntercept:!0,environment:"node",fileParallelism:!1,isolate:!0,maxWorkers:1,pool:"forks",...q(l)}}:{...A,aliases:{},config:a.config,managed:null,project:a.project,test:q(l)}}catch(n){const l=Q(n);c=c.map(a=>a.prepared===null?a:{...a,preparationFailure:l,prepared:null})}return Object.freeze({driverRequest:S,driverRequestFile:h,installation:t,processEnvironment:O,profileId:u,rootDirectory:o,runReportFile:R,runtimeInput:V,startupFailureFile:_,tasks:Object.freeze(c),vitest:D})}async function Fe(e,t,r,f,u){const o=n=>r.map(({task:l})=>P(l.id,"failed",f,L(),n(l.timeoutMs)));try{if(t.runtimeInput===null||t.driverRequest===null)throw new Error("Node/Vitest run was not prepared");await Ne(t.runtimeInput)}catch(n){return o(()=>({...j(n,"NODE_VITEST_INPUT_INVALID"),classification:"infra"}))}const s=await xe(e,t,t.runtimeInput,r[0]),p=Math.min(...r.map(({task:n})=>n.deadlineMs))-Date.now();if(p<=0)return o(n=>se(n,g));const i=t.driverRequest.managed===null?{}:{hookTimeout:p,teardownTimeout:p,testTimeout:p},v=r.map(({prepared:n})=>n.module),E={...t.driverRequest,modules:v,test:{...t.driverRequest.test,...i}};await w.writeFile(t.driverRequestFile,JSON.stringify(E),{flag:"wx",mode:384});const c=await fe({args:[Y,t.driverRequestFile],cwd:t.rootDirectory,displayLabel:"Node/Vitest",env:s,executable:process.execPath,signal:e.signal,timeoutMs:p}),m=await w.readFile(t.startupFailureFile,"utf8").catch(()=>null),d=await Ie(e.invocationId,t.runReportFile),T=d instanceof Error?[]:d.unhandledErrors.filter(({type:n})=>n!==G),h=[];for(const{artifactDirectory:n,prepared:l}of r)h.push(await Te({identity:Ve(e.invocationId,l.module).identity,artifactDirectory:n,dataDirectory:l.dataDirectory,reportFile:l.module.reportFile,rootDirectory:t.rootDirectory,stderr:c.stderr,stdout:c.stdout,unhandledErrors:T.filter(({testPath:a})=>a===void 0||I.resolve(a)===I.resolve(l.module.file))}).catch(a=>nodeVitestEvidenceCollectionFailure(c,a)));const R=h.map(n=>"reportSummary"in n?n.reportSummary:null),_=le(e.signal.reason),V=c.cancelled||c.timedOut||_,S=V&&d instanceof Error&&R.includes(null),D=T.length>0||R.some(n=>n?.success===!1||n?.numTotalTests===0),F=d instanceof Error?0:c.exitCode??d.exitCode,O=d instanceof Error?S?{exitCode:0,unhandledErrors:[]}:d:{...d,exitCode:F===1&&(D||d.afterTests!==void 0)?0:F};return!(d instanceof Error)&&d.afterTests!==void 0&&u.push(`Vitest failed the run after its tests ${D?"finished":"passed"}: ${d.afterTests.join("; ")}`),r.map(({task:n},l)=>{const a=h[l];if(m!==null&&!V)return P(n.id,"failed",f,"reportSummary"in a?a.evidence:L(),{classification:"infra",code:"NODE_VITEST_START_FAILED",message:`Vitest could not start: ${m}`});if(!("reportSummary"in a))return P(n.id,"failed",f,L(),a);const{failure:N,status:y}=classifyNodeVitestTaskResult(c,O,a,n.timeoutMs,_,S);return P(n.id,y,f,a.evidence,N)})}export function classifyNodeVitestTaskResult(e,t,r,f,u,o=!1){const s=d=>({failure:d,status:"failed"}),p=z(e,g);if(p!==null)return s(p);const i=r.reportSummary,v=i===null||!o?re(e,f,u,g):null;if(v!==null)return v;const E=t instanceof Error?void 0:t.unhandledErrors.find(({type:d})=>d===G),c=t instanceof Error?void 0:t.failure??(E&&{...E,phase:"reporter"});if(c!==void 0){const{message:d,phase:T,stack:h}=c;return{failure:{classification:"infra",code:"NODE_VITEST_RUN_FAILED",details:{phase:T},message:`Vitest ${T} failed: ${d}`,...h===void 0?{}:{stack:h}},status:T==="config"?"not_run":"failed"}}if(r.environmentFailure!==null)return s(r.environmentFailure);if(i===null||t instanceof Error){const d=K(e.stderr)??K(e.stdout),T=e.exitCode===0?null:W(e,g,d,"Vitest").failure;return s({...j(r.reportError??t,"NODE_VITEST_REPORT_INVALID"),...T===null?{}:{details:T.details,message:T.message},classification:"infra"})}const m=o?null:e.exitSignal;if(t.exitCode!==0||m!==null){const d=m===null?`exited with code ${t.exitCode.toString()}`:`was terminated by ${m}`;return s({classification:"infra",code:"NODE_VITEST_EXIT_CODE",details:ie(e),message:`Vitest ${d}, which its test results do not explain`})}return i.numTotalTests===0&&r.reportErrorCount===0?s({code:"NODE_VITEST_NO_TESTS",message:"Vitest did not discover any tests for the task file"}):r.dataEvidenceError!==null?s(j(r.dataEvidenceError,"NODE_VITEST_DATA_EVIDENCE_INVALID")):!i.success||i.numFailedTests>0||r.reportErrorCount>0?W(e,g,r.reportFailureMessage,"Vitest"):i.numPassedTests===0&&(i.numPendingTests>0||i.numTodoTests>0)?{failure:null,status:"skipped"}:i.numPassedTests===0?s({code:"NODE_VITEST_REPORT_FAILED",message:"Vitest report did not contain a completed passing test"}):{failure:null,status:"passed"}}export function nodeVitestEvidenceCollectionFailure(e,t){const r=B(t);return z(e,g,r)??{...j(r,"NODE_VITEST_EVIDENCE_FAILED"),classification:"infra"}}async function xe(e,t,r,{inputs:f,task:u}){const o=e.target.baseUrl,s={...t.processEnvironment,...o===null?{}:{BASE_URL:o},TESTKIT_ACTIVE:"1",[ye]:r.file,[De]:r.sha256,TESTKIT_INVOCATION_ID:e.invocationId,TESTKIT_LEASE_DIR:u.orchestration.lease.directory,TESTKIT_LEASE_ID:u.orchestration.lease.id,TESTKIT_MODE:u.orchestration.mode,...u.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:u.orchestration.runtimeId}},p=t.driverRequest?.managed!==null;p&&X(s);const i={...t.processEnvironment,...f.environment};Object.assign(s,await Re(e.workspace.cacheDirectory,i));try{p&&X(s)}catch{delete s.NODE_COMPILE_CACHE}return s}function Ce(e){const t=({prepared:o})=>o.module.file,r=e.find((o,s)=>e.findIndex(p=>t(p)===t(o))!==s);if(r!==void 0)throw new Error(`Node/Vitest task ${r.task.id} selects a file that another task in the request selects`);const f=({inputs:o,task:s})=>[o.environment,o.supportModules,s.orchestration,s.timeoutMs],u=e.find(o=>!te(f(o),f(e[0])));if(u!==void 0)throw new Error(`Node/Vitest task ${u.task.id} needs a different environment, support modules, orchestration or timeout than ${e[0].task.id}`)}function ke(e,t,r,f){const{baseUrl:u,databases:o,endpoints:s,handles:p}=e.target;return{...f===null?{}:{host:f},baseUrl:u,databases:o,endpoints:s,engineId:"node-vitest",environment:he(r[0].inputs.environment),handles:p,invocationId:e.invocationId,profileId:t,tasks:r.map(({inputs:i,prepared:v,task:E})=>({context:{baseUrl:u,engineId:"node-vitest",invocationId:e.invocationId,profileId:t,serviceName:E.serviceName,task:{file:E.file,id:E.id,suiteName:E.suiteName,...E.testType===void 0?{}:{testType:E.testType}}},dataDirectory:v.dataDirectory,file:v.module.file,fixtures:i.fixtures,...i.setupData===void 0?{}:{setupData:i.setupData},taskId:E.id})),version:1}}function Pe(e,t){return{...Oe({engineId:"node-vitest",installationKind:t.driverRequest?.managed===null?"repository-package":"root-direct-packages",profile:e.profile}),directPackages:{vitest:t.vitest.version}}}function Q(e){return{...j(e,"NODE_VITEST_PREPARE_FAILED"),classification:e instanceof $?"product":"infra"}}async function Z(e){try{return await e()}catch(t){throw new $(t)}}function q(e){return e===null?{}:{...e.shuffle===null?{}:{sequence:{seed:e.shuffle,shuffle:{files:!0,tests:!0}}},...e.retry===void 0?{}:{retry:e.retry}}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{existsSync as
|
|
2
|
-
`),process.exitCode=1}finally{
|
|
1
|
+
import{existsSync as x,realpathSync as w}from"node:fs";import V from"node:fs/promises";import S from"node:path";import{inspect as C}from"node:util";import{toError as T}from"../../../kernel/error-serialization.js";import{isUnknownRecord as N}from"../../../kernel/unknown-values.js";import{writeNativeReport as I}from"../../adapters/native-report.js";import{importResolvedPackageEntrypoint as O}from"../../installations/package-resolution.js";import{nodeVitestReportOptions as P,reportedError as d,TestkitVitestReporter as b}from"./reporter.js";const s=JSON.parse(await V.readFile(process.argv[2]??"","utf8")),F=new Map(s.modules.map(e=>[w(e.file),e])),q=P(s.invocationId,{reportFile:s.runReportFile,sourceFile:".",taskId:"run"}),u={unhandledErrors:[]},_=16*1024*1024;let v;const M=setInterval(()=>{x(s.startupFailureFile)&&process.exit(1)},100);M.unref();let E;process.env.TEST="true",process.env.VITEST="true",process.env.NODE_ENV??="test";try{const{createVitest:e}=await O(s.vitest),{retry:t,sequence:n,...o}=s.test,i={...t===void 0?{}:{retry:t},...n===void 0?{}:{sequence:n}},p={cacheDir:s.cacheDirectory,resolve:{alias:Object.entries(s.aliases).map(([l,f])=>({find:k(l),replacement:f})),...s.managed===null?{}:{tsconfigPaths:!0}},test:{...o,...s.managed===null?{}:{server:{deps:{external:s.managed.externalModules.map(k)}}}}},c=await e("test",{...{allowOnly:!1,includeTaskLocation:!0,passWithNoTests:!1,update:"none"},...i,config:s.config??void 0,watch:!1},p);v=c,c.config.snapshotOptions.updateSnapshot="none",c.snapshot.options.updateSnapshot="none";for(const l of c.projects)l.config.setupFiles.push(s.setupFile);const r=await D(c),a=s.test.sequence===void 0?r:r.sort((l,f)=>l.moduleId.localeCompare(f.moduleId)),h=a.map(({moduleId:l,project:f})=>({...$(l),project:f.name})),{reporters:y}=c;s.managed!==null&&(y.length=0),y.unshift(new b(s.invocationId,h)),E=W(c);const m=c;await(m.standalone===void 0?m.init():m.standalone()),u.unhandledErrors=(await c.runTestSpecifications(a,!1)).unhandledErrors.map(A),E.testsFinished()}catch(e){u.failure={phase:v===void 0?"config":"run",...d(T(e))},process.stderr.write(`${C(e)}
|
|
2
|
+
`),process.exitCode=1}finally{x(s.startupFailureFile)&&process.exit(1),await v?.close().catch(e=>{u.failure??={phase:"close",...d(T(e))}})}const R=E?.finish(u.failure===void 0)??[];R.length>0&&(u.afterTests=R),await I(q,{...u,exitCode:process.exitCode??0},_);async function D(e){if(s.config===!1)return s.modules.map(({file:o})=>{const i=o.split(S.sep).join("/");return e.getProjectByName("").createSpecification(i)});const t=(await e.globTestSpecifications()).filter(({moduleId:o,project:i})=>F.has(w(o))&&(s.project===null||i.name===s.project)),n=s.modules.find(({file:o})=>!t.some(({moduleId:i})=>$(i).file===o));if(n!==void 0){const o=n.sourceFile.split(S.sep).join("/");throw new Error(`Vitest's configuration no longer selects ${o}, which Testkit listed earlier`)}return t}function W(e){const t={close:0,coverage:0,reporters:0,tests:0},n={close:[],coverage:[]};let o=null;const i=e.logger.error.bind(e.logger);e.logger.error=(...r)=>{o!==null&&n[o].push(r.map(String).join(" ")),i(...r)};const p=()=>{const r=Number(process.exitCode??0);return process.exitCode=void 0,r},g=e,c=g.report.bind(e);return g.report=async(r,...a)=>{if(r!=="onTestRunEnd")return c(r,...a);t.tests=p(),await c(r,...a),t.reporters=p(),o="coverage"},{testsFinished(){t.coverage=p(),o="close"},finish(r){if(t.close=p(),o=null,process.exitCode=[t.close,t.coverage,t.reporters,t.tests].find(h=>h!==0)??0,!r)return[];const a=[];return t.reporters!==0&&a.push(`a reporter set exit code ${t.reporters.toString()}`),t.tests===0&&t.coverage!==0&&a.push(...j(n.coverage,`exit code ${t.coverage.toString()}`)),t.close!==0&&a.push(...j(n.close,`exit code ${t.close.toString()} while Vitest closed`)),a}}}function j(e,t){return e.length>0?e:[t]}function $(e){const t=F.get(w(e));if(t===void 0)throw new Error(`Vitest selected ${e}, which Testkit did not request`);return t}function A(e){if(!N(e)||typeof e.message!="string")return d(T(e));const{name:t,stack:n,type:o,VITEST_TEST_PATH:i}=e;return{...d({message:e.message,...typeof t=="string"?{name:t}:{},...typeof n=="string"?{stack:n}:{}}),...typeof o=="string"?{type:o}:{},...typeof i=="string"?{testPath:i}:{}}}function k(e){return new RegExp(`^${e.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")}$`,"u")}
|