@gfdelarue/testkit 0.4.3 → 0.4.5
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 +109 -22
- package/bin/testkit-windows-owner-arm64.exe +0 -0
- package/bin/testkit-windows-owner-x64.exe +0 -0
- package/dist/app/bugs/categories.js +1 -1
- package/dist/app/bugs/draft.js +3 -3
- package/dist/app/bugs/facts.d.ts +3 -2
- package/dist/app/bugs/facts.js +2 -2
- package/dist/app/bugs/guide.js +1 -1
- package/dist/app/bugs/run-reading.js +1 -1
- package/dist/app/db-schema/prepare.js +1 -1
- package/dist/app/destroy/index.js +1 -1
- package/dist/app/doctor/index.js +1 -1
- package/dist/app/environments/config-driven.d.ts +5 -0
- package/dist/app/environments/config-driven.js +1 -0
- package/dist/app/environments/routing.d.ts +6 -0
- package/dist/app/environments/routing.js +1 -0
- package/dist/app/environments/supplied.d.ts +3 -0
- package/dist/app/environments/supplied.js +1 -0
- package/dist/app/file-selection.js +1 -1
- package/dist/app/lint/native.js +1 -1
- package/dist/app/lint/options.d.ts +0 -1
- package/dist/app/lint/options.js +1 -1
- package/dist/app/lint/shared.d.ts +0 -2
- package/dist/app/lint/shared.js +1 -1
- package/dist/app/package-inspection.js +1 -1
- package/dist/app/reports/files.d.ts +13 -0
- package/dist/app/reports/files.js +1 -0
- package/dist/app/reports/prepare.d.ts +20 -0
- package/dist/app/reports/prepare.js +2 -0
- package/dist/app/reports/publish.d.ts +21 -0
- package/dist/app/reports/publish.js +1 -0
- package/dist/app/run/engine-evidence-persistence.js +1 -1
- package/dist/app/run/engine-execution-cleanup.js +1 -1
- package/dist/app/run/engine-execution-failures.js +1 -1
- package/dist/app/run/engine-execution.d.ts +10 -8
- package/dist/app/run/engine-execution.js +1 -1
- package/dist/app/run/engine-orchestration.d.ts +15 -34
- package/dist/app/run/engine-orchestration.js +1 -1
- package/dist/app/run/environment.d.ts +33 -0
- package/dist/app/run/environment.js +1 -0
- package/dist/app/run/failure-details.js +1 -1
- package/dist/app/run/live-run.d.ts +3 -1
- package/dist/app/run/live-run.js +1 -1
- package/dist/app/run/orchestrator.d.ts +8 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/planning.d.ts +1 -2
- package/dist/app/run/planning.js +1 -1
- package/dist/app/run/results.d.ts +1 -1
- package/dist/app/run/results.js +1 -1
- package/dist/app/run/run-finalization.d.ts +6 -2
- package/dist/app/run/run-finalization.js +1 -1
- package/dist/app/run/runtime-fixtures.js +1 -1
- package/dist/app/run/runtime-task-types.d.ts +1 -1
- package/dist/app/run/scheduler/index.js +1 -1
- package/dist/app/run/scheduler/locks.d.ts +6 -0
- package/dist/app/run/scheduler/locks.js +1 -0
- package/dist/app/run/selection.d.ts +0 -1
- package/dist/app/run/selection.js +1 -1
- package/dist/app/run/setup/runtime-node-setup-protocol.js +1 -1
- package/dist/app/run/setup/runtime-node-setup-worker.js +1 -1
- package/dist/app/run/task-leases.d.ts +15 -0
- package/dist/app/run/task-leases.js +1 -0
- package/dist/app/run/worker-loop.d.ts +13 -4
- package/dist/app/run/worker-loop.js +1 -1
- package/dist/app/tracker/index.d.ts +6 -1
- package/dist/app/tracker/index.js +1 -1
- package/dist/app/typecheck/index.js +4 -4
- package/dist/cli/command-catalog.d.ts +19 -0
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/commands/report/prepare.d.ts +12 -0
- package/dist/cli/commands/report/prepare.js +5 -0
- package/dist/cli/commands/report/publish.d.ts +13 -0
- package/dist/cli/commands/report/publish.js +2 -0
- package/dist/cli/json-errors.js +1 -1
- package/dist/cli/operations/run/operation.d.ts +3 -1
- package/dist/cli/operations/run/operation.js +2 -2
- package/dist/cli/renderers/doctor/text.js +1 -1
- package/dist/cli/renderers/run/formatting.js +1 -1
- package/dist/config/authoring/index.d.ts +4 -13
- package/dist/config/authoring/index.js +1 -1
- package/dist/config/authoring/preset-env.js +1 -1
- package/dist/config/loader/config-loader.js +1 -1
- package/dist/config/loader/database.js +1 -1
- package/dist/config/loader/decode-execution.d.ts +0 -2
- package/dist/config/loader/decode-execution.js +1 -1
- package/dist/config/loader/discover-tests.js +1 -1
- package/dist/config/loader/discovery.d.ts +0 -1
- package/dist/config/loader/discovery.js +2 -2
- package/dist/config/loader/env.js +1 -1
- package/dist/config/loader/execution-model.d.ts +0 -1
- package/dist/config/loader/execution-model.js +1 -1
- package/dist/config/loader/file-metadata.d.ts +1 -1
- package/dist/config/loader/file-metadata.js +1 -1
- package/dist/config/loader/groups.d.ts +33 -0
- package/dist/config/loader/groups.js +1 -0
- package/dist/config/loader/load.d.ts +0 -2
- package/dist/config/loader/load.js +1 -1
- package/dist/config/loader/runtime-isolation.js +1 -1
- package/dist/config/loader/runtime.d.ts +0 -9
- package/dist/config/loader/runtime.js +1 -1
- package/dist/config/loader/skip-config.js +1 -1
- package/dist/config/loader/validation.d.ts +1 -2
- package/dist/config/loader/validation.js +1 -1
- package/dist/config/loader/values.d.ts +0 -1
- package/dist/config/loader/values.js +1 -1
- package/dist/database/activity-gate.js +1 -1
- package/dist/database/admin.js +2 -2
- package/dist/database/lifecycle-cleanup.d.ts +1 -0
- package/dist/database/lifecycle-cleanup.js +1 -1
- package/dist/database/local-postgres.js +1 -1
- package/dist/database/naming.js +1 -1
- package/dist/database/schema-operations.d.ts +1 -1
- package/dist/database/schema-operations.js +1 -1
- package/dist/database/schema-source.js +3 -3
- package/dist/database/source-url.d.ts +0 -1
- package/dist/database/source-url.js +2 -2
- package/dist/database/state.js +1 -1
- package/dist/database/steps.js +3 -3
- package/dist/domain/file-timeout.d.ts +0 -3
- package/dist/domain/file-timeout.js +1 -1
- package/dist/domain/operation-display.js +1 -1
- package/dist/domain/test-outcomes.d.ts +0 -1
- package/dist/domain/test-outcomes.js +1 -1
- package/dist/engines/adapters/evidence-identity.js +1 -1
- package/dist/engines/adapters/execution.d.ts +0 -1
- package/dist/engines/adapters/execution.js +1 -1
- package/dist/engines/api/capabilities.d.ts +0 -10
- package/dist/engines/api/capabilities.js +1 -1
- package/dist/engines/api/execution-scope.js +1 -1
- package/dist/engines/api/k6-runtime-input.js +1 -1
- package/dist/engines/bundle-cache.js +1 -1
- package/dist/engines/contracts/fixture-manifest.js +1 -1
- package/dist/engines/definitions.d.ts +1 -0
- package/dist/engines/definitions.js +1 -1
- package/dist/engines/installations/playwright-resolution-child.js +1 -1
- package/dist/engines/installations/playwright.js +1 -1
- package/dist/engines/k6/adapter/evidence.js +4 -4
- package/dist/engines/k6/adapter/executor.js +1 -1
- package/dist/engines/k6/adapter/native-summary.js +1 -1
- package/dist/engines/k6/runtime/checks.js +1 -1
- package/dist/engines/k6/runtime/dal-fixtures.js +1 -1
- package/dist/engines/k6/runtime/dal-suite.js +1 -1
- package/dist/engines/k6/runtime/http-suite-runtime.js +1 -1
- package/dist/engines/k6/runtime/http.js +1 -1
- package/dist/engines/k6/runtime/shared/db-normalization.js +1 -1
- package/dist/engines/k6/runtime/shared/fixture-engine.js +2 -2
- package/dist/engines/k6/runtime/shared/http-parsing.js +3 -3
- package/dist/engines/managed-module-resolution-policy-input.d.ts +0 -1
- package/dist/engines/managed-module-resolution-policy-input.js +1 -1
- package/dist/engines/managed-module-resolution-policy.js +1 -1
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/adapter/evidence.js +2 -2
- package/dist/engines/node-vitest/adapter/workspace.js +22 -21
- package/dist/engines/node-vitest/runtime/contract/index.js +1 -1
- package/dist/engines/node-vitest/runtime/data/http.js +1 -1
- package/dist/engines/node-vitest/runtime/data/index.d.ts +2 -2
- package/dist/engines/node-vitest/runtime/data/index.js +1 -1
- package/dist/engines/node-vitest/runtime/data/report.js +5 -5
- package/dist/engines/node-vitest/runtime/data/scenario.d.ts +0 -1
- package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
- package/dist/engines/node-vitest/runtime/data/types.d.ts +1 -29
- package/dist/engines/node-vitest/runtime/runtime-input.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/config.d.ts +1 -1
- package/dist/engines/playwright/adapter/config.js +4 -4
- package/dist/engines/playwright/adapter/evidence-validation.d.ts +0 -3
- package/dist/engines/playwright/adapter/evidence-validation.js +1 -1
- package/dist/engines/playwright/adapter/evidence.js +1 -1
- package/dist/engines/playwright/runtime/config-file.js +1 -1
- package/dist/engines/playwright/runtime/config.js +1 -1
- package/dist/engines/playwright/runtime/runtime-input.d.ts +2 -2
- package/dist/engines/playwright/runtime/runtime-input.js +16 -12
- package/dist/environment/lifecycle-artifacts.js +1 -1
- package/dist/environment/lifecycle-controller.d.ts +1 -11
- package/dist/environment/lifecycle-controller.js +1 -1
- package/dist/environment/lifecycle.d.ts +14 -11
- package/dist/environment/lifecycle.js +1 -1
- package/dist/environment/planning-types.d.ts +0 -2
- package/dist/environment/ports.d.ts +10 -0
- package/dist/environment/ports.js +1 -0
- package/dist/environment/ready-environment.d.ts +2 -5
- package/dist/environment/ready-environment.js +1 -1
- package/dist/environment/run-environment.d.ts +1 -0
- package/dist/environment/run-environment.js +1 -1
- package/dist/environment/runtime-contexts.d.ts +11 -7
- package/dist/environment/runtime-contexts.js +1 -1
- package/dist/environment/runtime-manager.d.ts +1 -9
- package/dist/environment/runtime-manager.js +1 -1
- package/dist/environment/runtime-planning.js +1 -1
- package/dist/environment/runtime-preparation.js +1 -1
- package/dist/environment/runtime-service.d.ts +0 -6
- package/dist/environment/services.d.ts +11 -3
- package/dist/environment/services.js +3 -1
- package/dist/environment/state.d.ts +1 -4
- package/dist/environment/state.js +1 -1
- package/dist/environment/template.d.ts +6 -22
- package/dist/environment/template.js +1 -1
- package/dist/evidence/artifact-config.js +1 -1
- package/dist/evidence/artifact-validation.d.ts +1 -2
- package/dist/evidence/artifact-validation.js +1 -1
- package/dist/evidence/artifacts.js +2 -2
- package/dist/evidence/bundles.js +1 -1
- package/dist/evidence/environment-record.d.ts +22 -0
- package/dist/evidence/environment-record.js +1 -0
- package/dist/evidence/index.d.ts +1 -1
- package/dist/evidence/logs.js +2 -2
- package/dist/evidence/operation-timings.js +1 -1
- package/dist/evidence/read-fields.d.ts +0 -1
- package/dist/evidence/read-fields.js +1 -1
- package/dist/evidence/read-run.js +1 -1
- package/dist/evidence/reading.js +1 -1
- package/dist/evidence/reporting-types.d.ts +19 -0
- package/dist/evidence/reporting.d.ts +2 -2
- package/dist/evidence/reporting.js +1 -1
- package/dist/evidence/storage.d.ts +0 -2
- package/dist/evidence/storage.js +2 -2
- package/dist/evidence/timing.js +1 -1
- package/dist/kernel/error-serialization.d.ts +2 -0
- package/dist/kernel/error-serialization.js +1 -1
- package/dist/kernel/unknown-values.d.ts +5 -0
- package/dist/kernel/unknown-values.js +1 -1
- package/dist/process/environment.js +1 -1
- package/dist/process/execa-bundle.json +3 -3
- package/dist/process/files/json-file.d.ts +0 -1
- package/dist/process/files/json-file.js +1 -1
- package/dist/process/files/paths.d.ts +2 -0
- package/dist/process/files/paths.js +1 -1
- package/dist/process/host/metadata.js +1 -1
- package/dist/process/host/platform.js +1 -1
- package/dist/process/host-process.js +1 -1
- package/dist/process/lifecycle-fence.d.ts +1 -2
- package/dist/process/lifecycle-fence.js +1 -1
- package/dist/process/locks.js +2 -2
- package/dist/process/process-group.js +1 -1
- package/dist/process/process-identity.js +1 -1
- package/dist/process/process.js +30 -30
- package/dist/process/readiness.d.ts +7 -1
- package/dist/process/readiness.js +1 -1
- package/dist/process/recovery.js +1 -1
- package/dist/process/runs/manifest.js +1 -1
- package/dist/process/runs/ownership.js +1 -1
- package/dist/process/runs/product-state.js +1 -1
- package/dist/process/runs/stop.js +1 -1
- package/dist/process/stoppable-settlement.d.ts +26 -0
- package/dist/process/stoppable-settlement.js +1 -0
- package/dist/process/url-redaction.d.ts +1 -0
- package/dist/process/url-redaction.js +1 -0
- package/dist/process/windows-native.d.ts +6 -0
- package/dist/process/windows-native.js +1 -1
- package/dist/repo/ci-environment.js +1 -1
- package/dist/repo/fingerprint-policy.d.ts +0 -2
- package/dist/repo/fingerprint-policy.js +1 -1
- package/dist/repo/physical-tree.js +1 -1
- package/dist/repo/publish-directory.d.ts +20 -0
- package/dist/repo/publish-directory.js +3 -0
- package/dist/repo/state.js +4 -4
- package/dist/steps/build-config.d.ts +0 -1
- package/dist/steps/build-config.js +1 -1
- package/dist/steps/configured-steps.js +1 -1
- package/dist/steps/environment.d.ts +0 -2
- package/dist/steps/environment.js +1 -1
- package/dist/steps/index.d.ts +1 -1
- package/dist/steps/run.js +2 -2
- package/dist/toolchains/archive.js +1 -1
- package/dist/toolchains/index.js +3 -3
- package/dist/toolchains/node-provisioning.js +2 -2
- package/dist/toolchains/vitest/index.js +1 -1
- package/docs/bug-records.md +54 -52
- package/docs/engine-architecture.md +39 -11
- package/package.json +1 -1
- package/testkit-config-schema.md +43 -5
- package/dist/app/run/playwright-configuration.d.ts +0 -2
- package/dist/app/run/playwright-configuration.js +0 -1
- package/dist/environment/readiness.d.ts +0 -12
- package/dist/environment/readiness.js +0 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PrepareReportOptions {
|
|
2
|
+
directory: string;
|
|
3
|
+
dir?: string;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
template?: string;
|
|
6
|
+
instructions?: string;
|
|
7
|
+
bug?: readonly string[];
|
|
8
|
+
run?: string;
|
|
9
|
+
artifact?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function prepareReport(options: PrepareReportOptions): {
|
|
12
|
+
directory: string;
|
|
13
|
+
files: string[];
|
|
14
|
+
bugs: string[];
|
|
15
|
+
run: string | null;
|
|
16
|
+
instructions: {
|
|
17
|
+
path: string;
|
|
18
|
+
text: string;
|
|
19
|
+
} | null;
|
|
20
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createHash as x}from"node:crypto";import c from"node:fs";import a from"node:path";import{loadRunBundleManifest as R}from"../../evidence/bundles.js";import{makeTempDir as k,removeTempDir as T}from"../../process/files/temp-dir.js";import{readStoredDrafts as P}from"../bugs/store.js";import{inspectArtifacts as S}from"../inspect/artifacts.js";import{copyReportFile as v,REPORT_BYTES as j,REPORT_FILE_BYTES as B,REPORT_FILES as F,requireReportPath as O}from"./files.js";export function prepareReport(e){const r=e.cwd??process.cwd(),u=a.resolve(r,e.dir??"."),n=a.resolve(r,e.directory),f=[...new Set(e.bug??[])],w=f.length?P(u):{drafts:[],unreadable:[]},y=f.map(o=>{const s=w.drafts.find(m=>m.draft.id===o)?.draft;if(!s)throw new Error(`No readable recorded bug ${JSON.stringify(o)}. Use an ID from testkit bug record or campaign status.`);return s}),l=[...new Set(e.artifact??[])];if(l.length&&!e.run)throw new Error("Select an immutable --run when copying --artifact evidence.");if(e.run&&!l.length)throw new Error("Choose at least one --artifact from the run. Reports never copy all evidence implicitly.");const i=D(u,e.run,l),h=k("testkit-report-prepare-");let d=!1;try{let o=null;if(e.instructions){const t=a.join(h,"instructions");v(a.resolve(r,e.instructions),t),o={path:e.instructions,text:c.readFileSync(t,"utf8")}}c.mkdirSync(a.dirname(n),{recursive:!0});try{c.mkdirSync(n)}catch(t){throw new Error("Choose a new report directory. Preparation never overwrites an existing path.",{cause:t})}d=!0;const s=a.join(n,"report.md");let m=e.template?v(a.resolve(r,e.template),s):0;e.template||c.writeFileSync(s,"",{flag:"wx",mode:384});for(const t of i){const p=a.join(n,t.destination);if(m+=v(t.absolutePath,p,a.join(u,".testkit","results")),x("sha256").update(c.readFileSync(p)).digest("hex")!==t.sha256)throw new Error("Selected evidence changed after verification. The report was not prepared.")}const g=["report.md",...i.map(t=>t.destination)];if(y.length||i.length){const t=JSON.stringify({bugs:y,run:e.run??null,evidence:i.map(({source:E,destination:b})=>({source:E,destination:b}))},null,2)+`
|
|
2
|
+
`,p=Buffer.byteLength(t);if(p>B)throw new Error("The bug context exceeds 16 MiB. Select fewer bugs.");m+=p,c.writeFileSync(a.join(n,"context.json"),t,{flag:"wx",mode:384}),g.push("context.json")}if(m>j||g.length>F)throw new Error("The prepared report exceeds 64 MiB or 1000 files. Select fewer artifacts.");return{directory:n,files:g,bugs:f,run:e.run??null,instructions:o}}catch(o){throw d&&c.rmSync(n,{recursive:!0,force:!0}),o}finally{T(h)}}function D(e,r,u){if(!r)return[];const n={dir:e,runId:r,file:null,service:null},f=S({...n,mode:"verify"});if(f.mode!=="verify"||!f.verification.ok)throw new Error("The selected run failed integrity verification. Run testkit artifacts --verify for details.");const w=S({...n,mode:"artifacts",includePayload:!1});if(w.mode!=="artifacts")throw new Error("Could not inspect selected run artifacts.");const y=R(e,r),l=`.testkit/results/runs/${r}/`;return u.map(i=>{const h=w.entries.find(s=>s.artifactRef.path===i),d=i.startsWith(l)?i.slice(l.length):"",o=y.files.find(s=>s.path===d);if(!h||!o||!d.startsWith("files/"))throw new Error(`Artifact ${JSON.stringify(i)} is not an artifact in the selected immutable run. Use ref.path from testkit artifacts --run ${r} --json.`);return{source:i,destination:"evidence/"+O(d.slice(6)),absolutePath:h.absolutePath,sha256:o.sha256}})}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface PublishReportOptions {
|
|
2
|
+
directory: string;
|
|
3
|
+
repository: string;
|
|
4
|
+
branch: string;
|
|
5
|
+
path: string;
|
|
6
|
+
dir?: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
9
|
+
message?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function publishReport(options: PublishReportOptions): Promise<{
|
|
12
|
+
revision: string;
|
|
13
|
+
previous: string | null;
|
|
14
|
+
changed: boolean;
|
|
15
|
+
repository: string;
|
|
16
|
+
branch: string;
|
|
17
|
+
path: string;
|
|
18
|
+
url: string;
|
|
19
|
+
files: number;
|
|
20
|
+
bytes: number;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import n from"node:path";import{makeTempDir as u,removeTempDir as w}from"../../process/files/temp-dir.js";import{publishDirectory as $}from"../../repo/publish-directory.js";import{resolveRepositoryConnection as f}from"../tracker/index.js";import{REPORT_TREES as b,requireReportPath as g,snapshotReport as v}from"./files.js";export async function publishReport(e){const s=e.cwd??process.cwd(),a=e.env??process.env;if(!e.repository.trim())throw new Error("Name an explicit destination --repository.");const t=g(e.path);if(t.split("/").length>32)throw new Error("The destination path is too deeply nested.");const o=u("testkit-report-publish-");try{const i=v(n.resolve(s,e.directory),n.join(o,"files"),b-t.split("/").length),{location:r,target:c}=await f({repository:e.repository,productDir:n.resolve(s,e.dir??"."),env:a}),m=`${r.origin}/${r.owner}/${r.name}.git`,p=await $({workspace:o,remote:m,files:i,branch:e.branch,destination:t,message:e.message??`Publish report bundle ${t}`,token:c.token,env:a,maxFetchBytes:256*1024*1024}),l=c.provider==="github"?"tree":"src/commit",h=`${r.origin}/${r.owner}/${r.name}/${l}/${p.revision}/${t.split("/").map(encodeURIComponent).join("/")}`;return{...p,repository:`${r.origin}/${r.owner}/${r.name}`,branch:e.branch,path:t,url:h,files:i.length,bytes:i.reduce((d,y)=>d+y.size,0)}}finally{w(o)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{persistFileEvidence as m,fileEvidenceBudgetError as g}from"../../evidence/file-evidence.js";import{MAX_PERSISTED_FILE_ARTIFACT_BYTES as
|
|
1
|
+
import{persistFileEvidence as m,fileEvidenceBudgetError as g}from"../../evidence/file-evidence.js";import{MAX_PERSISTED_FILE_ARTIFACT_BYTES as p,persistTaskArtifacts as y,persistTaskOutputArtifacts as E}from"../../evidence/index.js";import{toError as l}from"../../kernel/error-serialization.js";export const MAX_ENGINE_FILE_EVIDENCE_BYTES=p;export function persistEngineEvidence({artifactRoot:e,evidence:t,productDir:i,scope:r,task:n,fileEvidenceBudget:a}){const o={engineId:n.engineId,file:n.file,id:n.id,profileId:n.profileId,serviceName:n.serviceName,suiteKey:n.suiteKey,suiteName:n.suiteName,testType:n.testType},u=m({artifactRoot:e,artifacts:t.artifacts,productDir:i,task:o,fileEvidenceBudget:a}),f=E(i,o,t.logs.map((c,d)=>({name:`${r}-engine-${c.stream}-${d.toString()}`,kind:`engine.${r}.log`,stream:c.stream,text:c.text}))),s=[...t.metrics.map((c,d)=>$(c,d,r)),...t.native===void 0?[]:[{name:`${r}-engine-native`,kind:`engine.${r}.native`,contentType:"application/json",summary:`${r} engine-native evidence`,emittedAt:new Date().toISOString(),data:t.native}]];return[...u,...f,...y(i,o,s)]}export function persistTrackedEngineEvidence(e,t){const i=[...e.artifacts.map(n=>({category:"artifacts",evidence:{artifacts:[n],logs:[],metrics:[]},value:n})),...e.logs.map(n=>({category:"logs",evidence:{artifacts:[],logs:[n],metrics:[]},value:n})),...e.metrics.map(n=>({category:"metrics",evidence:{artifacts:[],logs:[],metrics:[n]},value:n})),...e.native===void 0?[]:[{category:"native",evidence:{artifacts:[],logs:[],metrics:[],native:e.native},value:e.native}]],r=new Map;for(const n of i){let a;try{a=v(n.value)}catch(s){t.errors.push(contextualError(`Could not identify ${t.scope} engine ${n.category} evidence`,s));continue}const o=`${n.category}:${a}`,u=(r.get(o)??0)+1;r.set(o,u);const f=t.ledger.countsByCategory[n.category].get(a)??0;if(!(t.mode==="reconcile"&&u<=f))try{const s=persistEngineEvidence({artifactRoot:t.artifactRoot,evidence:n.evidence,productDir:t.productDir,scope:t.scope,task:t.task,...n.category==="artifacts"?{fileEvidenceBudget:B(t.ledger)}:{}});if(s.length===0)throw new Error("Evidence did not produce a persisted artifact");t.references.push(...s),t.ledger.countsByCategory[n.category].set(a,f+1)}catch(s){t.errors.push(contextualError(`Could not persist ${t.scope} engine ${n.category} evidence`,s))}}}export function createEvidenceLedger(e=MAX_ENGINE_FILE_EVIDENCE_BYTES){return{countsByCategory:{artifacts:new Map,logs:new Map,metrics:new Map,native:new Map},fileEvidence:{maximumBytes:Math.min(e,MAX_ENGINE_FILE_EVIDENCE_BYTES),persistedBytes:0}}}export function requireTaskArtifacts(e,t){const i=e.get(t);if(!i)throw new Error(`Artifact state is missing engine task ${t}`);return i}export function requireTaskEvidenceLedger(e,t){const i=e.get(t);if(!i)throw new Error(`Evidence state is missing engine task ${t}`);return i}export function contextualError(e,t){return new Error(`${e}: ${l(t).message}`,{cause:t})}function v(e){const t=JSON.stringify(e);if(typeof t!="string")throw new TypeError("Engine evidence entry must be JSON-serializable");return t}function $(e,t,i){return{name:`${i}-engine-metric-${t.toString()}`,kind:`engine.${i}.metric`,contentType:"application/json",summary:`${e.name}=${e.value.toString()}${e.unit?` ${e.unit}`:""}`,emittedAt:new Date().toISOString(),data:{name:e.name,value:e.value,...e.unit===void 0?{}:{unit:e.unit},...e.dimensions===void 0?{}:{dimensions:e.dimensions}}}}function B(e){return{consume(t){const i=e.fileEvidence.persistedBytes+t;if(i>e.fileEvidence.maximumBytes)throw g(e.fileEvidence.maximumBytes);e.fileEvidence.persistedBytes=i},maximumBytes(){return e.fileEvidence.maximumBytes},remainingBytes(){return e.fileEvidence.maximumBytes-e.fileEvidence.persistedBytes}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getRuntimeNodeSetupCleanupFailures as i}from"./setup/runtime-node-setup.js";export async function cleanupPreparedNodeSetups(
|
|
1
|
+
import{getRuntimeNodeSetupCleanupFailures as i}from"./setup/runtime-node-setup.js";import{toError as c}from"../../kernel/error-serialization.js";export async function cleanupPreparedNodeSetups(n){const r=[];for(const{cleanup:u,taskId:e}of[...n].reverse())try{await u()}catch(t){const s=i(t);s.length===0?r.push({error:c(t),owner:"testkit",taskId:e}):r.push(...s.map(a=>({...a,taskId:e})))}return r}export function applyConsumerNodeSetupCleanupFailures(n,r){const u=new Map;for(const e of r){if(e.owner!=="consumer")continue;const t=u.get(e.taskId)??[];t.push(e.error),u.set(e.taskId,t)}for(const[e,t]of u){const s=n.get(e);if(s===void 0)continue;const a=`Runtime node setup consumer cleanup failed: ${t.map(o=>o.message).join("; ")}`;n.set(e,{...s,error:s.error===null?a:`${s.error}; ${a}`,failureDetails:[...s.failureDetails,...t.map(l)],reason:null,status:"failed"})}}function l(n){return{actual:{owner:"consumer"},classification:"product",count:1,key:"runtime_node_setup_consumer_cleanup_failed",kind:"runtime-node-setup-cleanup",message:n.message,...n.stack===void 0?{}:{stack:n.stack},title:"Runtime node setup consumer cleanup failed"}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isEngineTaskDeadlineExceededReason as g}from"../../engines/api/index.js";import{classifyRuntimeNodeSetupFailure as p,getRuntimeNodeSetupCleanupFailures as m}from"./setup/runtime-node-setup.js";export const ENGINE_REPORTER_CALLBACK_CANCELLED_CODE="TESTKIT_ENGINE_REPORTER_CALLBACK_CANCELLED";export class EngineExecutionBridgeError extends Error{artifactReferencesByTaskId;completedOutcomesByTaskId;disposition;failingTaskId;cause;constructor(n,t,i,r,o=new Map,a=null){super(n,{cause:t}),this.name="EngineExecutionBridgeError",this.cause=t,this.disposition=Object.freeze({...i}),this.failingTaskId=a,this.artifactReferencesByTaskId=r,this.completedOutcomesByTaskId=o}}export class EngineExecutionReporterCallbackError extends Error{cancelled;cause;constructor(n,t){const i=E(t);super(`Engine reporter ${n} callback failed: ${i.message}`,{cause:t}),this.name="EngineExecutionReporterCallbackError",this.cancelled=x(i)===ENGINE_REPORTER_CALLBACK_CANCELLED_CODE,this.cause=t}}export async function executeManagedEngineRequest(e,n){return await e.execute(n)}export function buildBridgeError(e,n,t,i,r,o){const a=n.map(s=>s.error),c=[...e,...a],u=e.map(s=>s.message).join("; "),
|
|
1
|
+
import{isEngineTaskDeadlineExceededReason as g}from"../../engines/api/index.js";import{classifyRuntimeNodeSetupFailure as p,getRuntimeNodeSetupCleanupFailures as m}from"./setup/runtime-node-setup.js";import{toError as E}from"../../kernel/error-serialization.js";export const ENGINE_REPORTER_CALLBACK_CANCELLED_CODE="TESTKIT_ENGINE_REPORTER_CALLBACK_CANCELLED";export class EngineExecutionBridgeError extends Error{artifactReferencesByTaskId;completedOutcomesByTaskId;disposition;failingTaskId;cause;constructor(n,t,i,r,o=new Map,a=null){super(n,{cause:t}),this.name="EngineExecutionBridgeError",this.cause=t,this.disposition=Object.freeze({...i}),this.failingTaskId=a,this.artifactReferencesByTaskId=r,this.completedOutcomesByTaskId=o}}export class EngineExecutionReporterCallbackError extends Error{cancelled;cause;constructor(n,t){const i=E(t);super(`Engine reporter ${n} callback failed: ${i.message}`,{cause:t}),this.name="EngineExecutionReporterCallbackError",this.cancelled=x(i)===ENGINE_REPORTER_CALLBACK_CANCELLED_CODE,this.cause=t}}export async function executeManagedEngineRequest(e,n){return await e.execute(n)}export function buildBridgeError(e,n,t,i,r,o){const a=n.map(s=>s.error),c=[...e,...a],u=e.map(s=>s.message).join("; "),l=a.map(s=>s.message).join("; "),f=e.length===0?`Engine node setup cleanup failed: ${l}`:a.length===0?u:`${u}; node setup cleanup also failed: ${l}`,d=c.length===1?c[0]:new AggregateError(c,"Engine execution bridge failed");return new EngineExecutionBridgeError(f,d,t,i,r,n[0]?.taskId??o)}export function resolveBridgeDisposition(e,n,t,i){return n.length>0?i.classification==="infra"&&i.runtimeImpact==="fail-graph"?infraBridgeDisposition("fail-graph"):n.some(r=>r.owner==="testkit")?infraBridgeDisposition("invalidate-lease"):e.length>0&&e.every(r=>r instanceof EngineExecutionReporterCallbackError)?e.every(r=>r instanceof EngineExecutionReporterCallbackError&&r.cancelled)?productBridgeDisposition("invalidate-lease"):reporterBridgeDisposition("invalidate-lease"):t||e.length>0&&i.classification==="infra"?infraBridgeDisposition(i.runtimeImpact==="fail-graph"?"fail-graph":"invalidate-lease"):productBridgeDisposition("invalidate-lease"):e.length>0&&e.every(r=>r instanceof EngineExecutionReporterCallbackError)?e.every(r=>r instanceof EngineExecutionReporterCallbackError&&r.cancelled)?cancelledBridgeDisposition():reporterBridgeDisposition(i.runtimeImpact):t&&i.classification!=="infra"?infraBridgeDisposition("none"):{...i}}export function managerFailureBridgeDisposition(e,n,t){return!n.aborted||t!==n.reason?infraBridgeDisposition("none"):e.aborted&&t===e.reason?cancelledBridgeDisposition():g(n.reason)?productBridgeDisposition():infraBridgeDisposition("none")}export function runtimeNodeSetupBridgeDisposition(e){const n=p(e),t=m(e).length>0?"invalidate-lease":"none";return n==="cancelled"?cancelledBridgeDisposition():n==="infra"?infraBridgeDisposition("invalidate-lease"):productBridgeDisposition(t)}export function cancelledBridgeDisposition(){return{classification:void 0,notifyRuntimeError:!1,runtimeImpact:"none",status:"cancelled"}}export function infraBridgeDisposition(e="none"){return{classification:"infra",notifyRuntimeError:!0,runtimeImpact:e,status:"not_run"}}export function reporterBridgeDisposition(e="none"){return{classification:"infra",notifyRuntimeError:!1,runtimeImpact:e,status:"not_run"}}export function productBridgeDisposition(e="none"){return{classification:"product",notifyRuntimeError:!1,runtimeImpact:e,status:"failed"}}function x(e){return e.code}
|
|
@@ -2,6 +2,7 @@ import { type EngineExecutionResult, type EngineTaskOutcome } from "../../engine
|
|
|
2
2
|
import { type EngineId } from "../../domain/identifiers.js";
|
|
3
3
|
import { type TestOutcomeStatus } from "../../domain/test-outcomes.js";
|
|
4
4
|
import type { ResolvedEngineExecutionSession } from "../../engines/manager.js";
|
|
5
|
+
import { type ExecutionRequest } from "./engine-orchestration.js";
|
|
5
6
|
import { type PersistedArtifactReference } from "../../evidence/index.js";
|
|
6
7
|
export { EngineExecutionBridgeError, type EngineExecutionBridgeDisposition, type EngineExecutionRuntimeImpact, } from "./engine-execution-failures.js";
|
|
7
8
|
import { type EndpointReadinessResult } from "./endpoint-readiness.js";
|
|
@@ -9,8 +10,8 @@ import { type NormalizedCheckDetail, type TaskFailureDetail } from "./failure-de
|
|
|
9
10
|
import type { RuntimeTaskReporter } from "./runtime-task-types.js";
|
|
10
11
|
import type { ScheduledTask } from "./scheduler/index.js";
|
|
11
12
|
import type { TaskBatch } from "./task-batches.js";
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
13
|
+
import type { Environment } from "./environment.js";
|
|
14
|
+
import type { TaskLease } from "./task-leases.js";
|
|
14
15
|
export type EngineExecutableTask = ScheduledTask | TaskBatch<ScheduledTask>;
|
|
15
16
|
export interface EngineExecutionManager {
|
|
16
17
|
withEngine<Result>(engineId: EngineId, signal: AbortSignal, operation: (session: ResolvedEngineExecutionSession) => Result | PromiseLike<Result>): Promise<Result>;
|
|
@@ -20,11 +21,12 @@ export interface EngineExecutionLifecycle {
|
|
|
20
21
|
readonly runId: string;
|
|
21
22
|
readonly signal: AbortSignal;
|
|
22
23
|
}
|
|
23
|
-
export interface
|
|
24
|
-
readonly
|
|
25
|
-
readonly
|
|
26
|
-
readonly
|
|
27
|
-
readonly
|
|
24
|
+
export interface EngineTaskExecution {
|
|
25
|
+
readonly environment: Environment;
|
|
26
|
+
readonly lease: TaskLease;
|
|
27
|
+
readonly nativeConfigDirectory: string;
|
|
28
|
+
readonly request: ExecutionRequest;
|
|
29
|
+
readonly task: EngineExecutableTask;
|
|
28
30
|
}
|
|
29
31
|
export interface EngineTaskExecutionOutcome {
|
|
30
32
|
artifacts: PersistedArtifactReference[];
|
|
@@ -52,7 +54,7 @@ export interface EngineTaskExecutionResult {
|
|
|
52
54
|
export interface EngineExecutionReporter extends RuntimeTaskReporter<ScheduledTask> {
|
|
53
55
|
taskFinished?(task: Readonly<ScheduledTask>, outcome: Readonly<EngineTaskExecutionOutcome>): unknown;
|
|
54
56
|
}
|
|
55
|
-
export declare function executeEngineTask(manager: EngineExecutionManager, environment
|
|
57
|
+
export declare function executeEngineTask(manager: EngineExecutionManager, { environment, lease, nativeConfigDirectory, request, task: executableTask }: Readonly<EngineTaskExecution>, lifecycle: EngineExecutionLifecycle, reporter?: null | EngineExecutionReporter): Promise<EngineTaskExecutionResult>;
|
|
56
58
|
export declare function mapEngineOutcomes(tasks: readonly ScheduledTask[], result: Readonly<EngineExecutionResult>): Array<{
|
|
57
59
|
outcome: EngineTaskOutcome;
|
|
58
60
|
task: ScheduledTask;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import U from"node:fs";import F from"node:path";import{EngineTaskDeadlineExceededError as Se}from"../../engines/api/index.js";import"../../domain/identifiers.js";import"../../domain/test-outcomes.js";import{resolveEngineProfile as Ie}from"../../engines/catalog.js";import{BUILT_IN_ENGINE_ORCHESTRATION as Me}from"./engine-orchestration.js";import{persistTaskArtifacts as be}from"../../evidence/index.js";import{createEvidenceLedger as se,persistTrackedEngineEvidence as ae,requireTaskArtifacts as Oe,requireTaskEvidenceLedger as Ne}from"./engine-evidence-persistence.js";import{applyConsumerNodeSetupCleanupFailures as ce,cleanupPreparedNodeSetups as ue}from"./engine-execution-cleanup.js";import{buildBridgeError as _e,EngineExecutionBridgeError as Re,executeManagedEngineRequest as Fe,infraBridgeDisposition as q,managerFailureBridgeDisposition as Ae,productBridgeDisposition as z,reporterBridgeDisposition as V,resolveBridgeDisposition as de,runtimeNodeSetupBridgeDisposition as Ce}from"./engine-execution-failures.js";import{captureEngineReporterCallback as fe,DEFAULT_ENGINE_REPORTER_CALLBACK_TIMEOUT_MS as Be,invokeEngineReporterCallback as le}from"./engine-execution-reporting.js";import{createEngineExecutionObserver as xe}from"./engine-execution-observer.js";export{EngineExecutionBridgeError}from"./engine-execution-failures.js";import{waitForEndpointReadinessPreflight as Pe}from"./endpoint-readiness.js";import{collectCheckDetailsFromRuntimeArtifacts as $e,collectFailureDetailsFromRuntimeArtifacts as Le}from"./failure-details.js";import{buildRuntimeFixtureManifest as je}from"./runtime-fixtures.js";import{MAX_RUNTIME_NODE_SETUP_CLEANUP_TIMEOUT_MS as Je,runRuntimeNodeSetup as Ue}from"./setup/runtime-node-setup.js";export async function executeEngineTask(e,t,n,r,s=null){const i=t,u={signal:r.signal,timeoutMs:Be},m="batchTasks"in n?n.batchTasks:[n],w=m[0],S=t.services.get(w.targetName);if(!S)throw new Error(`Runtime instance is missing target config "${w.targetName}"`);const d=S.config;he(d.productDir,"product directory"),he(i.leaseDir,"runtime lease directory");const T=Ie(w.testType,w.engineId),I=Me.resolve(T),O=We(d.testkit.execution.fileTimeoutSeconds),we=Date.now(),K=pe(we,O),C=ze(t),N=Object.hasOwn(C,w.serviceName)?C[w.serviceName]:null;if(I.endpointReadinessOwner==="orchestrator-http"){if(N===null)throw new Error(`Service "${d.name}" requires local.baseUrl for ${T.id} suites`);const B=Date.now(),D=G(r.signal,K);let y;try{y=await Pe({baseUrl:N,serviceName:d.name,lifecycle:r,signal:D.signal,options:Qe(Xe(K))})}finally{D.dispose()}if(!y.ok){const M=r.isStopRequested()||r.signal.aborted,k=Date.now(),c=buildEndpointReadinessOutcomes(m,n,y,M,B,k),a=[];let g=null;for(const E of c){const l=a.length;await fe(a,"taskStarted",()=>s?.taskStarted?.(E.task,d),u),g===null&&a.length>l&&(g=String(E.task.id)),await fe(a,"taskFinished",()=>s?.taskFinished?.(E.task,E),u),g===null&&a.length>l&&(g=String(E.task.id))}if(a.length>0){const E=a.length===1?a[0]:new AggregateError(a,"Engine readiness reporting failed");throw new Re(`${y.reason}; ${a.map(l=>l.message).join("; ")}`,E,de(a,[],!1,M?{classification:void 0,notifyRuntimeError:!1,runtimeImpact:"none",status:"cancelled"}:V("fail-graph")),new Map(m.map(l=>[String(l.id),[]])),new Map(c.map(l=>[String(l.task.id),l])),g)}return{outcomes:c}}}return e.withEngine(T.engineId,r.signal,async B=>{const D=Ve(i,d.productDir);U.mkdirSync(D.temporaryDirectory,{recursive:!0}),U.mkdirSync(D.artifactDirectory,{recursive:!0});const y=new Map(m.map(p=>[String(p.id),[]])),M=new Map(m.map(p=>[String(p.id),se()])),k=new Map,c=[];let a=z(),g=!1;const E=[],l=[],W=[];let X=null;const Q=new Set,x=async(p,o)=>{const f=String(p.id);Q.has(f)||(Q.add(f),await le("taskFinished",()=>s?.taskFinished?.(p,o),u))};try{for(const[p,o]of m.entries()){a=V();const f=String(o.id);X=f,await le("taskStarted",()=>s?.taskStarted?.(o,d),u),a=z();const Z=I.buildRuntimeEnvironment({baseUrl:N,database:S.database,targetConfig:d,task:o}),_=pe(Date.now(),O),ee=G(r.signal,_);let b;try{b=await Ue({cleanupTimeoutMs:Math.min(O,Je),deadlineMs:_,targetConfig:d,task:o,lease:i,runtimeEnv:Z,signal:ee.signal})}catch(h){throw a=Ce(h),h}finally{ee.dispose()}E.push({cleanup:b.cleanup,taskId:f}),a=q();for(const h of b.artifacts){const v=be(d.productDir,o,[h]);y.get(String(o.id))?.push(...v)}a=z();const R={baseUrl:N,lease:i,profile:T,runtimeEnvironment:Z,targetConfig:d,task:o},te=I.resolveConfiguration(R),ke={...te===void 0?{}:{configuration:te},environment:I.buildEnvironment(R),fixtures:nt(je(d.productDir,o.file),`fixture manifest for ${o.file}`),setupData:Ee(b.setupData,`node setup data for ${o.file}`),supportModules:I.resolveSupportModules(R)},De=qe(R);a=q();const ye=c.length,ne=se(),Te=xe({artifactReferencesByTaskId:y,completedOutcomesByTaskId:k,errors:c,executableTask:n,invocationEvidenceLedger:ne,productDir:d.productDir,taskBatchIndex:p,taskEvidenceLedgers:M,tasks:[o],tasksById:new Map([[f,o]]),workspaceArtifactDirectory:D.artifactDirectory},mapEngineOutcome),P=G(r.signal,_);try{const h={invocationId:He(r,i,o),observer:Te,profile:T,signal:P.signal,target:{endpoints:C,inputs:Ge()},tasks:[{deadlineMs:_,id:f,file:o.file,serviceName:o.serviceName,suiteName:o.suiteName,testType:o.testType,timeoutMs:O,inputs:ke,orchestration:De}],workspace:D};let v;try{v=await Fe(B,h)}catch(oe){throw a=Ae(r.signal,P.signal,oe),oe}const re=mapEngineOutcomes([o],v)[0],j=Oe(y,f),ve=Ne(M,f);ae(re.outcome.evidence,{artifactRoot:D.artifactDirectory,errors:c,ledger:ve,mode:"reconcile",productDir:d.productDir,references:j,scope:"task",task:o});const J=mapEngineOutcome(re.outcome,o,j,n,p);k.set(f,J),J.infraFailed&&J.blockedByRuntime&&(a=q("fail-graph"));const ie=et(v);tt(ie)&&ae(ie,{artifactRoot:D.artifactDirectory,errors:c,ledger:ne,mode:"reconcile",productDir:d.productDir,references:j,scope:"invocation",task:o})}finally{P.dispose()}c.length>ye&&(g=!0),E.pop();const $=await ue([{cleanup:b.cleanup,taskId:f}]);if($.length>0){l.push(...$),ce(k,$);const h=k.get(f);if(h!==void 0)try{await x(o,h)}catch(v){c.push(A(v))}break}if(c.length>0)break;const L=k.get(f);if(L===void 0){c.push(new Error(`Engine task ${f} completed without a mapped outcome`)),g=!0;break}W.push(L),a=V();try{await x(o,L)}catch(h){c.push(A(h)),g=!0;break}}}catch(p){const o=c.length;c.push(A(p)),(o>0||a.classification==="infra")&&(g=!0)}const Y=await ue(E);l.push(...Y),ce(k,Y);for(const p of m){const o=k.get(String(p.id));if(o!==void 0)try{await x(p,o)}catch(f){c.push(A(f))}}if(c.length>0||l.length>0)throw _e(c,l,de(c,l,g,a),y,k,X);return{outcomes:W}})}function qe(e){const t=e.targetConfig.testkit.mode;if(t!==void 0&&(typeof t!="string"||t.trim().length===0))throw new Error("Testkit execution mode must be a non-empty string");const n=e.targetConfig.runtimeId;return{lease:{directory:e.lease.leaseDir,id:String(e.lease.leaseId)},mode:t??"test",...n==null?{}:{runtimeId:String(n)}}}export function mapEngineOutcomes(e,t){const n=new Map(e.map(i=>[String(i.id),i])),r=new Map;for(const i of t.outcomes){const u=n.get(i.taskId);if(!u)throw new Error(`Engine manager returned an outcome for unknown task ${i.taskId}`);if(r.has(i.taskId))throw new Error(`Engine manager returned duplicate outcomes for task ${i.taskId}`);r.set(i.taskId,{task:u,outcome:i})}const s=e.map(i=>String(i.id)).filter(i=>!r.has(i));if(s.length>0)throw new Error(`Engine manager did not return outcomes for task(s): ${s.join(", ")}`);return e.map(i=>{const u=r.get(String(i.id));if(!u)throw new Error("Engine outcome mapping invariant failed");return u})}export function buildEndpointReadinessOutcomes(e,t,n,r,s,i){const u=Math.max(0,i-s);return e.map((m,w)=>({task:m,status:r?"cancelled":"not_run",error:null,reason:n.reason,infraFailed:!r,blockedByRuntime:!r,durationMs:w===0?u:0,startedAt:s,finishedAt:i,artifacts:[],failureDetails:r?[]:[...n.failureDetails],checkDetails:[],...ge(t,m)}))}function ze(e){return Object.freeze(Object.fromEntries([...e.services.values()].flatMap(({config:t,baseUrl:n})=>typeof n=="string"&&n.length>0?[[t.name,n]]:[])))}function Ve(e,t){const n=e.engineWorkspaceDirectory??F.join(e.leaseDir,"engine-execution");return{rootDirectory:t,...e.engineCacheDirectory===void 0?{}:{cacheDirectory:e.engineCacheDirectory},temporaryDirectory:F.join(n,"temporary"),artifactDirectory:F.join(n,"artifacts")}}function Ge(){return{environment:{},fixtures:{},supportModules:{}}}function He(e,t,n){return[e.runId,t.leaseId,Ke(n)].join(":")}function Ke(e){return"batchId"in e?e.batchId:String(e.id)}function We(e){const t=e*1e3;if(!Number.isSafeInteger(t)||t<=0)throw new Error("Engine task timeout must be a positive safe integer");return t}function pe(e,t){const n=e+t;if(!Number.isSafeInteger(n)||n<=e)throw new Error("Engine task deadline must be a positive safe integer");return n}function Xe(e){return Math.max(1,e-Date.now())}function Qe(e){const t=me(process.env.TESTKIT_HTTP_ENDPOINT_PREFLIGHT_TIMEOUT_MS,3e4),n=me(process.env.TESTKIT_HTTP_ENDPOINT_PREFLIGHT_ATTEMPT_TIMEOUT_MS,5e3);return{timeoutMs:Math.min(t,e),attemptTimeoutMs:Math.min(n,e)}}function me(e,t){const n=Number(e);return Number.isInteger(n)&&n>0?n:t}function G(e,t){const n=new AbortController;let r=null;const s=()=>{n.abort(e.reason)},i=()=>{const u=t-Date.now();if(u<=0){n.abort(new Se(t));return}r=setTimeout(i,Math.min(u,2147483647)),r.unref()};return e.aborted?s():(e.addEventListener("abort",s,{once:!0}),i()),{signal:n.signal,dispose(){r!==null&&clearTimeout(r),e.removeEventListener("abort",s)}}}export function mapEngineOutcome(e,t,n,r,s){const i=Le(e.evidence.artifacts),u=t.engineId==="node-vitest"&&i.some(T=>T.kind==="vitest-test")&&e.failure!==null&&Ye(e.failure),m=[...e.failure&&!u?[Ze(e.failure)]:[],...i],w=$e(e.evidence.artifacts),S=e.status==="not_run",d=S||e.failure?.classification==="infra";return{task:t,status:e.status,error:e.status==="failed"?e.failure?.message??"Engine task failed without a message":null,reason:e.status==="failed"||e.status==="passed"?null:e.failure?.message??`Engine task ${e.status}`,infraFailed:d,blockedByRuntime:S,durationMs:e.durationMs,startedAt:e.startedAtMs,finishedAt:e.finishedAtMs,artifacts:n,failureDetails:m,checkDetails:w,...ge(r,t,s)}}function ge(e,t,n){if(!("batchTasks"in e))return{};const r=n??e.batchTasks.findIndex(s=>s.id===t.id&&s.file===t.file);if(r<0)throw new Error(`Task ${String(t.id)} is missing from its engine batch`);return{runtimeBatch:{id:e.batchId,index:r,fileCount:e.batchTasks.length,files:e.batchTasks.map(s=>s.file)}}}function Ye(e){return e.code==="NODE_VITEST_REPORT_FAILED"?!0:e.code==="NODE_VITEST_PROCESS_FAILED"&&e.details?.exitCode===1}function Ze(e){const t=e.code??"ENGINE_TASK_FAILED",n={code:t,details:e.details===void 0?null:rt(e.details)};return{kind:"engine-failure",key:t,title:t,...e.classification===void 0?{}:{classification:e.classification},count:1,message:e.message,actual:n,...e.stack===void 0?{}:{stack:e.stack}}}function et(e){return{artifacts:H(e.evidence.artifacts,e.outcomes.flatMap(t=>t.evidence.artifacts)),logs:H(e.evidence.logs,e.outcomes.flatMap(t=>t.evidence.logs)),metrics:H(e.evidence.metrics,e.outcomes.flatMap(t=>t.evidence.metrics)),...e.evidence.native===void 0?{}:{native:e.evidence.native}}}function H(e,t){const n=new Map;for(const r of t){const s=JSON.stringify(r);n.set(s,(n.get(s)??0)+1)}return e.filter(r=>{const s=JSON.stringify(r),i=n.get(s)??0;return i===0?!0:(n.set(s,i-1),!1)})}function tt(e){return e.artifacts.length>0||e.logs.length>0||e.metrics.length>0||e.native!==void 0}function nt(e,t){const n=Ee(e,t);if(n===null||Array.isArray(n)||typeof n!="object")throw new TypeError(`${t} must contain a JSON object`);return n}function Ee(e,t){try{const n=JSON.stringify(e);if(typeof n!="string")throw new TypeError(`${t} must be JSON-serializable`);return JSON.parse(n)}catch(n){throw new TypeError(`${t} must be JSON-serializable`,{cause:n})}}function rt(e){return JSON.parse(JSON.stringify(e))}function he(e,t){if(!F.isAbsolute(e))throw new Error(`Engine execution ${t} must be absolute`);if(!U.statSync(e).isDirectory())throw new Error(`Engine execution ${t} must be a directory`)}function A(e){return e instanceof Error?e:new Error(String(e))}
|
|
1
|
+
import J from"node:fs";import U from"node:path";import{EngineTaskDeadlineExceededError as It}from"../../engines/api/index.js";import"../../domain/identifiers.js";import"../../domain/test-outcomes.js";import{resolveEngineProfile as Mt}from"../../engines/catalog.js";import{resolveEndpointReadinessOwner as bt}from"../../engines/definitions.js";import{BUILT_IN_ENGINE_ORCHESTRATION as Ot,defaultEnvironmentValues as _t,engineInputEnvironment as Nt}from"./engine-orchestration.js";import{persistTaskArtifacts as Rt}from"../../evidence/index.js";import{createEvidenceLedger as at,persistTrackedEngineEvidence as ct,requireTaskArtifacts as Ft,requireTaskEvidenceLedger as At}from"./engine-evidence-persistence.js";import{applyConsumerNodeSetupCleanupFailures as ut,cleanupPreparedNodeSetups as dt}from"./engine-execution-cleanup.js";import{buildBridgeError as Bt,EngineExecutionBridgeError as Ct,executeManagedEngineRequest as xt,infraBridgeDisposition as j,managerFailureBridgeDisposition as Pt,productBridgeDisposition as V,reporterBridgeDisposition as z,resolveBridgeDisposition as ft,runtimeNodeSetupBridgeDisposition as Lt}from"./engine-execution-failures.js";import{captureEngineReporterCallback as lt,DEFAULT_ENGINE_REPORTER_CALLBACK_TIMEOUT_MS as $t,invokeEngineReporterCallback as pt}from"./engine-execution-reporting.js";import{createEngineExecutionObserver as Jt}from"./engine-execution-observer.js";import{toError as R}from"../../kernel/error-serialization.js";export{EngineExecutionBridgeError}from"./engine-execution-failures.js";import{waitForEndpointReadinessPreflight as Ut}from"./endpoint-readiness.js";import{collectCheckDetailsFromRuntimeArtifacts as jt,collectFailureDetailsFromRuntimeArtifacts as Vt}from"./failure-details.js";import{buildRuntimeFixtureManifest as zt}from"./runtime-fixtures.js";import{MAX_RUNTIME_NODE_SETUP_CLEANUP_TIMEOUT_MS as Gt,runRuntimeNodeSetup as Ht}from"./setup/runtime-node-setup.js";export async function executeEngineTask(t,{environment:e,lease:n,nativeConfigDirectory:s,request:o,task:r},a,k=null){const T={signal:a.signal,timeoutMs:$t},E="batchTasks"in r?r.batchTasks:[r],I=E[0],S={name:I.targetName},v=o.productDir;kt(v,"product directory"),kt(n.directory,"runtime lease directory");const O=Mt(I.testType,I.engineId),q=Ot.resolve(O.engineId),_=Qt(o.fileTimeoutSeconds),wt=Date.now(),K=mt(wt,_),W=e.baseUrl;if(W!==null&&bt(O)==="orchestrator-http"){const F=Date.now(),w=G(a.signal,K);let y;try{y=await Ut({baseUrl:W,serviceName:S.name,lifecycle:a,signal:w.signal,options:Zt(Yt(K))})}finally{w.dispose()}if(!y.ok){const M=a.isStopRequested()||a.signal.aborted,h=Date.now(),u=buildEndpointReadinessOutcomes(E,r,y,M,F,h),c=[];let p=null;for(const m of u){const f=c.length;await lt(c,"taskStarted",()=>k?.taskStarted?.(m.task,S),T),p===null&&c.length>f&&(p=String(m.task.id)),await lt(c,"taskFinished",()=>k?.taskFinished?.(m.task,m),T),p===null&&c.length>f&&(p=String(m.task.id))}if(c.length>0){const m=c.length===1?c[0]:new AggregateError(c,"Engine readiness reporting failed");throw new Ct(`${y.reason}; ${c.map(f=>f.message).join("; ")}`,m,ft(c,[],!1,M?{classification:void 0,notifyRuntimeError:!1,runtimeImpact:"none",status:"cancelled"}:z("fail-graph")),new Map(E.map(f=>[String(f.id),[]])),new Map(u.map(f=>[String(f.task.id),f])),p)}return{outcomes:u}}}return t.withEngine(O.engineId,a.signal,async F=>{const w=qt(n,v);J.mkdirSync(w.temporaryDirectory,{recursive:!0}),J.mkdirSync(w.artifactDirectory,{recursive:!0});const y=new Map(E.map(l=>[String(l.id),[]])),M=new Map(E.map(l=>[String(l.id),at()])),h=new Map,u=[];let c=V(),p=!1;const m=[],f=[],X=[];let Q=null;const Y=new Set,A=async(l,i)=>{const d=String(l.id);Y.has(d)||(Y.add(d),await pt("taskFinished",()=>k?.taskFinished?.(l,i),T))};try{for(const[l,i]of E.entries()){c=z();const d=String(i.id);Q=d,await pt("taskStarted",()=>k?.taskStarted?.(i,S),T),c=V();const tt=_t(e),N=mt(Date.now(),_),et=G(a.signal,N);let b;try{b=await Ht({cleanupTimeoutMs:Math.min(_,Gt),deadlineMs:N,targetConfig:{name:S.name,productDir:v},task:i,lease:{leaseDir:n.directory,leaseId:n.id},runtimeEnv:tt,signal:et.signal})}catch(g){throw c=Lt(g),g}finally{et.dispose()}m.push({cleanup:b.cleanup,taskId:d}),c=j();for(const g of b.artifacts){const D=Rt(v,i,[g]);y.get(String(i.id))?.push(...D)}c=V();const B={environment:e,lease:n,nativeConfigDirectory:s,request:o,runtimeEnvironment:tt,task:i},nt=q.resolveConfiguration(B),yt={...nt===void 0?{}:{configuration:nt},environment:Nt(B),fixtures:ie(zt(v,i.file),`fixture manifest for ${i.file}`),setupData:ht(b.setupData,`node setup data for ${i.file}`),supportModules:q.resolveSupportModules(B)},Tt={lease:{directory:n.directory,id:n.id},mode:"test"};c=j();const vt=u.length,rt=at(),Dt=Jt({artifactReferencesByTaskId:y,completedOutcomesByTaskId:h,errors:u,executableTask:r,invocationEvidenceLedger:rt,productDir:v,taskBatchIndex:l,taskEvidenceLedgers:M,tasks:[i],tasksById:new Map([[d,i]]),workspaceArtifactDirectory:w.artifactDirectory},mapEngineOutcome),C=G(a.signal,N);try{const g={invocationId:Wt(a,n,i),observer:Dt,profile:O,signal:C.signal,target:{endpoints:e.endpoints,inputs:Kt()},tasks:[{deadlineMs:N,id:d,file:i.file,serviceName:i.serviceName,suiteName:i.suiteName,testType:i.testType,timeoutMs:_,inputs:yt,orchestration:Tt}],workspace:w};let D;try{D=await xt(F,g)}catch(st){throw c=Pt(a.signal,C.signal,st),st}const it=mapEngineOutcomes([i],D)[0],L=Ft(y,d),St=At(M,d);ct(it.outcome.evidence,{artifactRoot:w.artifactDirectory,errors:u,ledger:St,mode:"reconcile",productDir:v,references:L,scope:"task",task:i});const $=mapEngineOutcome(it.outcome,i,L,r,l);h.set(d,$),$.infraFailed&&$.blockedByRuntime&&(c=j("fail-graph"));const ot=ne(D);re(ot)&&ct(ot,{artifactRoot:w.artifactDirectory,errors:u,ledger:rt,mode:"reconcile",productDir:v,references:L,scope:"invocation",task:i})}finally{C.dispose()}u.length>vt&&(p=!0),m.pop();const x=await dt([{cleanup:b.cleanup,taskId:d}]);if(x.length>0){f.push(...x),ut(h,x);const g=h.get(d);if(g!==void 0)try{await A(i,g)}catch(D){u.push(R(D))}break}if(u.length>0)break;const P=h.get(d);if(P===void 0){u.push(new Error(`Engine task ${d} completed without a mapped outcome`)),p=!0;break}X.push(P),c=z();try{await A(i,P)}catch(g){u.push(R(g)),p=!0;break}}}catch(l){const i=u.length;u.push(R(l)),(i>0||c.classification==="infra")&&(p=!0)}const Z=await dt(m);f.push(...Z),ut(h,Z);for(const l of E){const i=h.get(String(l.id));if(i!==void 0)try{await A(l,i)}catch(d){u.push(R(d))}}if(u.length>0||f.length>0)throw Bt(u,f,ft(u,f,p,c),y,h,Q);return{outcomes:X}})}export function mapEngineOutcomes(t,e){const n=new Map(t.map(r=>[String(r.id),r])),s=new Map;for(const r of e.outcomes){const a=n.get(r.taskId);if(!a)throw new Error(`Engine manager returned an outcome for unknown task ${r.taskId}`);if(s.has(r.taskId))throw new Error(`Engine manager returned duplicate outcomes for task ${r.taskId}`);s.set(r.taskId,{task:a,outcome:r})}const o=t.map(r=>String(r.id)).filter(r=>!s.has(r));if(o.length>0)throw new Error(`Engine manager did not return outcomes for task(s): ${o.join(", ")}`);return t.map(r=>{const a=s.get(String(r.id));if(!a)throw new Error("Engine outcome mapping invariant failed");return a})}export function buildEndpointReadinessOutcomes(t,e,n,s,o,r){const a=Math.max(0,r-o);return t.map((k,T)=>({task:k,status:s?"cancelled":"not_run",error:null,reason:n.reason,infraFailed:!s,blockedByRuntime:!s,durationMs:T===0?a:0,startedAt:o,finishedAt:r,artifacts:[],failureDetails:s?[]:[...n.failureDetails],checkDetails:[],...Et(e,k)}))}function qt(t,e){return{rootDirectory:e,cacheDirectory:t.engineCacheDirectory,temporaryDirectory:U.join(t.engineWorkspaceDirectory,"temporary"),artifactDirectory:U.join(t.engineWorkspaceDirectory,"artifacts")}}function Kt(){return{environment:{},fixtures:{},supportModules:{}}}function Wt(t,e,n){return[t.runId,e.id,Xt(n)].join(":")}function Xt(t){return"batchId"in t?t.batchId:String(t.id)}function Qt(t){const e=t*1e3;if(!Number.isSafeInteger(e)||e<=0)throw new Error("Engine task timeout must be a positive safe integer");return e}function mt(t,e){const n=t+e;if(!Number.isSafeInteger(n)||n<=t)throw new Error("Engine task deadline must be a positive safe integer");return n}function Yt(t){return Math.max(1,t-Date.now())}function Zt(t){const e=gt(process.env.TESTKIT_HTTP_ENDPOINT_PREFLIGHT_TIMEOUT_MS,3e4),n=gt(process.env.TESTKIT_HTTP_ENDPOINT_PREFLIGHT_ATTEMPT_TIMEOUT_MS,5e3);return{timeoutMs:Math.min(e,t),attemptTimeoutMs:Math.min(n,t)}}function gt(t,e){const n=Number(t);return Number.isInteger(n)&&n>0?n:e}function G(t,e){const n=new AbortController;let s=null;const o=()=>{n.abort(t.reason)},r=()=>{const a=e-Date.now();if(a<=0){n.abort(new It(e));return}s=setTimeout(r,Math.min(a,2147483647)),s.unref()};return t.aborted?o():(t.addEventListener("abort",o,{once:!0}),r()),{signal:n.signal,dispose(){s!==null&&clearTimeout(s),t.removeEventListener("abort",o)}}}export function mapEngineOutcome(t,e,n,s,o){const r=Vt(t.evidence.artifacts),a=e.engineId==="node-vitest"&&r.some(S=>S.kind==="vitest-test")&&t.failure!==null&&te(t.failure),k=[...t.failure&&!a?[ee(t.failure)]:[],...r],T=jt(t.evidence.artifacts),E=t.status==="not_run",I=E||t.failure?.classification==="infra";return{task:e,status:t.status,error:t.status==="failed"?t.failure?.message??"Engine task failed without a message":null,reason:t.status==="failed"||t.status==="passed"?null:t.failure?.message??`Engine task ${t.status}`,infraFailed:I,blockedByRuntime:E,durationMs:t.durationMs,startedAt:t.startedAtMs,finishedAt:t.finishedAtMs,artifacts:n,failureDetails:k,checkDetails:T,...Et(s,e,o)}}function Et(t,e,n){if(!("batchTasks"in t))return{};const s=n??t.batchTasks.findIndex(o=>o.id===e.id&&o.file===e.file);if(s<0)throw new Error(`Task ${String(e.id)} is missing from its engine batch`);return{runtimeBatch:{id:t.batchId,index:s,fileCount:t.batchTasks.length,files:t.batchTasks.map(o=>o.file)}}}function te(t){return t.code==="NODE_VITEST_REPORT_FAILED"?!0:t.code==="NODE_VITEST_PROCESS_FAILED"&&t.details?.exitCode===1}function ee(t){const e=t.code??"ENGINE_TASK_FAILED",n={code:e,details:t.details===void 0?null:oe(t.details)};return{kind:"engine-failure",key:e,title:e,...t.classification===void 0?{}:{classification:t.classification},count:1,message:t.message,actual:n,...t.stack===void 0?{}:{stack:t.stack}}}function ne(t){return{artifacts:H(t.evidence.artifacts,t.outcomes.flatMap(e=>e.evidence.artifacts)),logs:H(t.evidence.logs,t.outcomes.flatMap(e=>e.evidence.logs)),metrics:H(t.evidence.metrics,t.outcomes.flatMap(e=>e.evidence.metrics)),...t.evidence.native===void 0?{}:{native:t.evidence.native}}}function H(t,e){const n=new Map;for(const s of e){const o=JSON.stringify(s);n.set(o,(n.get(o)??0)+1)}return t.filter(s=>{const o=JSON.stringify(s),r=n.get(o)??0;return r===0?!0:(n.set(o,r-1),!1)})}function re(t){return t.artifacts.length>0||t.logs.length>0||t.metrics.length>0||t.native!==void 0}function ie(t,e){const n=ht(t,e);if(n===null||Array.isArray(n)||typeof n!="object")throw new TypeError(`${e} must contain a JSON object`);return n}function ht(t,e){try{const n=JSON.stringify(t);if(typeof n!="string")throw new TypeError(`${e} must be JSON-serializable`);return JSON.parse(n)}catch(n){throw new TypeError(`${e} must be JSON-serializable`,{cause:n})}}function oe(t){return JSON.parse(JSON.stringify(t))}function kt(t,e){if(!U.isAbsolute(t))throw new Error(`Engine execution ${e} must be absolute`);if(!J.statSync(t).isDirectory())throw new Error(`Engine execution ${e} must be a directory`)}
|
|
@@ -1,49 +1,30 @@
|
|
|
1
|
-
import { type EngineExecutionInputs, type EngineJsonObject
|
|
2
|
-
import { type EngineId
|
|
3
|
-
import type { DatabaseInfo } from "../../database/index.js";
|
|
1
|
+
import { type EngineExecutionInputs, type EngineJsonObject } from "../../engines/api/index.js";
|
|
2
|
+
import { type EngineId } from "../../domain/identifiers.js";
|
|
4
3
|
import type { ScheduledTask } from "./scheduler/index.js";
|
|
5
|
-
import
|
|
6
|
-
import type {
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly testkit: ExecutionEnvironmentConfig["testkit"] & {
|
|
12
|
-
readonly local?: (NonNullable<ExecutionEnvironmentConfig["testkit"]["local"]> & {
|
|
13
|
-
readonly baseUrl?: null | string;
|
|
14
|
-
readonly cwd?: string;
|
|
15
|
-
}) | null;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export interface EngineOrchestrationLease extends ExecutionEnvironmentLease {
|
|
19
|
-
readonly leaseDir: string;
|
|
20
|
-
readonly leaseId: number | string;
|
|
4
|
+
import type { Environment } from "./environment.js";
|
|
5
|
+
import type { TaskLease } from "./task-leases.js";
|
|
6
|
+
export interface ExecutionRequest {
|
|
7
|
+
readonly engineConfigurations: Readonly<Partial<Record<EngineId, EngineJsonObject>>>;
|
|
8
|
+
readonly fileTimeoutSeconds: number;
|
|
9
|
+
readonly productDir: string;
|
|
21
10
|
}
|
|
22
11
|
export interface EngineInputPreparationContext {
|
|
23
|
-
readonly
|
|
24
|
-
readonly lease:
|
|
25
|
-
readonly
|
|
12
|
+
readonly environment: Environment;
|
|
13
|
+
readonly lease: TaskLease;
|
|
14
|
+
readonly nativeConfigDirectory: string;
|
|
15
|
+
readonly request: ExecutionRequest;
|
|
26
16
|
readonly runtimeEnvironment: Readonly<Record<string, string>>;
|
|
27
|
-
readonly targetConfig: EngineOrchestrationTargetConfig;
|
|
28
|
-
readonly task: ScheduledTask;
|
|
29
|
-
}
|
|
30
|
-
export interface EngineRuntimePreparationContext {
|
|
31
|
-
readonly baseUrl: null | string;
|
|
32
|
-
readonly database: Readonly<DatabaseInfo> | null;
|
|
33
|
-
readonly targetConfig: EngineOrchestrationTargetConfig;
|
|
34
17
|
readonly task: ScheduledTask;
|
|
35
18
|
}
|
|
36
19
|
export interface EngineOrchestrationIntegration {
|
|
37
|
-
buildEnvironment(context: Readonly<EngineInputPreparationContext>): Readonly<Record<string, string>>;
|
|
38
|
-
buildRuntimeEnvironment(context: Readonly<EngineRuntimePreparationContext>): Readonly<Record<string, string>>;
|
|
39
|
-
readonly endpointReadinessOwner: EndpointReadinessOwner;
|
|
40
20
|
readonly engineId: EngineId;
|
|
41
|
-
readonly profileId: RunnerProfileId;
|
|
42
21
|
resolveConfiguration(context: Readonly<EngineInputPreparationContext>): EngineExecutionInputs["configuration"];
|
|
43
22
|
resolveSupportModules(context: Readonly<EngineInputPreparationContext>): EngineExecutionInputs["supportModules"];
|
|
44
23
|
}
|
|
45
24
|
export interface EngineOrchestrationRegistry {
|
|
46
25
|
readonly integrations: readonly EngineOrchestrationIntegration[];
|
|
47
|
-
resolve(
|
|
26
|
+
resolve(engineId: EngineId): EngineOrchestrationIntegration;
|
|
48
27
|
}
|
|
49
28
|
export declare const BUILT_IN_ENGINE_ORCHESTRATION: EngineOrchestrationRegistry;
|
|
29
|
+
export declare function defaultEnvironmentValues(environment: Readonly<Environment>): Readonly<Record<string, string>>;
|
|
30
|
+
export declare function engineInputEnvironment(context: Readonly<EngineInputPreparationContext>): Readonly<Record<string, string>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import s from"node:fs";import u from"node:path";import"../../engines/api/index.js";import"../../domain/identifiers.js";import{findConfigFile as l}from"../../config/loader/index.js";import{findPlaywrightConfigFile as f}from"../../engines/playwright/runtime/config-file.js";import{hasErrorCode as a}from"../../kernel/error-serialization.js";const c=Object.freeze(["tsconfig.json","tsconfig.build.json"]),i=Object.freeze([Object.freeze({engineId:"k6",resolveConfiguration:g,resolveSupportModules:b}),Object.freeze({engineId:"node-vitest",resolveConfiguration:E,resolveSupportModules:p}),Object.freeze({engineId:"playwright",resolveConfiguration:m,resolveSupportModules:d})]);export const BUILT_IN_ENGINE_ORCHESTRATION=Object.freeze({integrations:i,resolve(e){const t=i.find(r=>r.engineId===e);if(!t)throw new Error(`No engine orchestration integration for ${e}`);return t}});export function defaultEnvironmentValues(e){return{...e.baseUrl===null?{}:{BASE_URL:e.baseUrl},...e.databaseUrl===null?{}:{DATABASE_URL:e.databaseUrl}}}export function engineInputEnvironment(e){const t=Object.entries(e.runtimeEnvironment).filter(([n])=>n.toUpperCase()!=="BASE_URL"),r=new Set(t.map(([n])=>n.toUpperCase()));return{...Object.fromEntries(Object.entries(e.environment.env).filter(([n])=>!r.has(n.toUpperCase()))),...Object.fromEntries(t)}}function p(e){const t=l(e.request.productDir),r=U(e.request.productDir,c);return{...t===null?{}:{testkitConfig:t},...r===null?{}:{tsconfig:r}}}function d(e){const t=f(e.nativeConfigDirectory);return t===null?{}:{playwrightConfig:t}}function g(e){return e.request.engineConfigurations.k6}function m(e){const t=e.environment.baseUrl;return t!==null&&C(t)?{...e.environment.ui,allowRemote:!0}:{...e.environment.ui}}function C(e){try{return!h(new URL(e).hostname)}catch{return!1}}function h(e){const t=e.toLowerCase();return t==="localhost"||t==="::1"||t==="0.0.0.0"||t==="127.0.0.1"||t.endsWith(".localhost")||t.startsWith("127.")}function E(){}function b(){return{}}function U(e,t){for(const r of t){const n=u.join(e,r);try{if(s.statSync(n).isFile())return n}catch(o){if(a(o,"ENOENT"))continue;throw new Error(`Could not inspect optional support module ${n}`,{cause:o})}}return null}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { EngineJsonObject } from "../../engines/api/index.js";
|
|
2
|
+
import type { ScheduledTask } from "./scheduler/index.js";
|
|
3
|
+
import type { TaskBatch } from "./task-batches.js";
|
|
4
|
+
export interface Environment {
|
|
5
|
+
readonly baseUrl: null | string;
|
|
6
|
+
readonly databaseUrl: null | string;
|
|
7
|
+
readonly endpoints: Readonly<Record<string, string>>;
|
|
8
|
+
readonly env: Readonly<Record<string, string>>;
|
|
9
|
+
readonly ui: EngineJsonObject | null;
|
|
10
|
+
}
|
|
11
|
+
export interface EnvironmentLease {
|
|
12
|
+
readonly directory: string;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
}
|
|
15
|
+
export type EnvironmentTask = ScheduledTask | TaskBatch<ScheduledTask>;
|
|
16
|
+
export interface EnvironmentGraph {
|
|
17
|
+
readonly key: string;
|
|
18
|
+
readonly targetNames: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface AcquiredEnvironment {
|
|
21
|
+
readonly environment: Environment;
|
|
22
|
+
readonly nativeConfigDirectory: string;
|
|
23
|
+
release(options: Readonly<{
|
|
24
|
+
invalidate: boolean;
|
|
25
|
+
}>): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface EnvironmentEntry {
|
|
28
|
+
acquire(task: Readonly<EnvironmentTask>, lease: Readonly<EnvironmentLease>): Promise<AcquiredEnvironment>;
|
|
29
|
+
canAcquire(task: Readonly<ScheduledTask>): boolean;
|
|
30
|
+
getGraph(graphKey: string): EnvironmentGraph | null;
|
|
31
|
+
getGraphFailure(graphKey: string): null | string;
|
|
32
|
+
markGraphFailed(graphKey: string, message: string): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function normalizeFailureDetail(
|
|
1
|
+
import{isUnknownRecord as F}from"../../kernel/unknown-values.js";export function normalizeFailureDetail(t){const e=a(t);if(!e)return null;const n=s(e.kind),r=typeof e.key=="string"&&e.key.trim().length>0?e.key:null,o=s(e.title);if(!n||!r||!o)return null;const i={kind:n,key:r,title:o,count:J(e.count)??1},c=s(e.classification);c&&(i.classification=c);const l=Array.isArray(e.groupPath)?e.groupPath.filter(S=>typeof S=="string"):[];l.length>0&&(i.groupPath=l),typeof e.labelled=="boolean"&&(i.labelled=e.labelled);const u=s(e.checkName);u&&(i.checkName=u);const f=N(e.suitePath);f.length>0&&(i.suitePath=f);const y=s(e.phase);y&&(i.phase=y);const h=s(e.message);h&&(i.message=h),e.expected!==void 0&&(i.expected=p(e.expected)),e.actual!==void 0&&(i.actual=p(e.actual));const g=s(e.traceId);g&&(i.traceId=g);const k=d(e.request);k&&(i.request=k);const A=d(e.response);A&&(i.response=A);const b=d(e.location);b&&(i.location=b);const z=s(e.stack);return z&&(i.stack=z),i}export function mergeFailureDetails(t){const e=new Map;for(const n of t){const r=JSON.stringify([n.kind,n.labelled??null,n.key,n.title]),o=e.get(r);if(!o){e.set(r,{...n});continue}o.count+=n.count,!o.message&&n.message&&(o.message=n.message)}return[...e.values()].sort((n,r)=>n.kind.localeCompare(r.kind)||n.key.localeCompare(r.key)||n.title.localeCompare(r.title))}export function collectFailureDetailsFromRuntimeArtifacts(t){const e=[];for(const n of Array.isArray(t)?t:[]){const r=a(n);if(!r||r.kind!=="testkit.failure-details")continue;const o=a(r.data);if(!o)continue;const i=s(o.phase);for(const c of Array.isArray(o.failures)?o.failures:[]){const l=a(c);if(!l)continue;const u=normalizeFailureDetail({...l,phase:l.phase||i||null});u&&e.push(u)}}return mergeFailureDetails(e)}export function collectCheckDetailsFromRuntimeArtifacts(t){const e=[];for(const n of Array.isArray(t)?t:[]){const r=a(n);if(!r||r.kind!=="testkit.checks")continue;const o=a(r.data);if(o)for(const i of Array.isArray(o.checks)?o.checks:[]){const c=a(i);if(!c)continue;const l=s(c.name)??"unnamed",u=x(c.passes),f=x(c.fails);e.push({name:l,path:N(c.path),passes:u,fails:f,passed:u>0&&f===0})}}return e}function a(t){return F(t)?t:null}function N(t){if(!Array.isArray(t))return[];const e=[];for(const n of t){const r=s(n);r&&e.push(r)}return e}function s(t){if(typeof t!="string")return null;const e=t.trim();return e.length>0?e:null}function J(t){return typeof t!="number"||!Number.isInteger(t)||t<=0?null:t}function x(t){try{return Math.max(0,Number(t)||0)}catch{return 0}}function d(t){if(!a(t))return null;const e=p(t);return P(e)?e:null}function p(t){try{const e=JSON.stringify(t);if(typeof e!="string")return String(t);const n=JSON.parse(e);return m(n)?n:String(t)}catch{return String(t)}}function m(t){return t===null||typeof t=="boolean"||typeof t=="number"||typeof t=="string"?!0:Array.isArray(t)?t.every(m):P(t)}function P(t){const e=a(t);return e!==null&&Object.values(e).every(m)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ServiceTracker } from "./results.js";
|
|
2
2
|
import { type BuildLiveRunArtifactOptions, type ReportingRecord } from "../../evidence/index.js";
|
|
3
|
+
import type { RunEnvironmentRecord } from "../../evidence/environment-record.js";
|
|
3
4
|
import type { RunLogRegistry } from "../../evidence/logs.js";
|
|
4
5
|
import type { OperationTimingRegistry } from "../../evidence/operation-timings.js";
|
|
5
6
|
import type { SetupOperationRegistry } from "../../evidence/setup-progress.js";
|
|
@@ -15,6 +16,7 @@ interface LiveRunReporter {
|
|
|
15
16
|
}
|
|
16
17
|
export interface CreateLiveSnapshotWriterOptions extends Pick<BuildLiveRunArtifactOptions, "archiveConfig" | "declaredEnvironmentKeys" | "execution" | "metadata" | "productDir" | "runId" | "startedAt"> {
|
|
17
18
|
configs: readonly LiveRunServiceConfig[];
|
|
19
|
+
environmentRecord: Pick<RunEnvironmentRecord, "listServices">;
|
|
18
20
|
logRegistry: Pick<RunLogRegistry, "listServiceLogs" | "listSetupLogs">;
|
|
19
21
|
operationTimingRegistry?: null | OperationTimingRegistry;
|
|
20
22
|
reporter?: LiveRunReporter | null;
|
|
@@ -23,5 +25,5 @@ export interface CreateLiveSnapshotWriterOptions extends Pick<BuildLiveRunArtifa
|
|
|
23
25
|
trackers: ReadonlyMap<string, ServiceTracker>;
|
|
24
26
|
workerState: LiveRunWorkerState;
|
|
25
27
|
}
|
|
26
|
-
export declare function createLiveSnapshotWriter({ productDir, runId, configs, trackers, startedAt, execution, workerState, selection, archiveConfig, declaredEnvironmentKeys, metadata, logRegistry, setupRegistry, operationTimingRegistry, reporter, }: CreateLiveSnapshotWriterOptions): () => void;
|
|
28
|
+
export declare function createLiveSnapshotWriter({ productDir, runId, configs, trackers, startedAt, execution, workerState, selection, archiveConfig, declaredEnvironmentKeys, metadata, environmentRecord, logRegistry, setupRegistry, operationTimingRegistry, reporter, }: CreateLiveSnapshotWriterOptions): () => void;
|
|
27
29
|
export {};
|
package/dist/app/run/live-run.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{finalizeServiceResult as
|
|
1
|
+
import{finalizeServiceResult as w,summarizeDbBackend as b}from"./results.js";import{buildLiveRunArtifact as C}from"../../evidence/reporting.js";import{writeLiveRunArtifact as d}from"../../evidence/index.js";export function createLiveSnapshotWriter({productDir:e,runId:s,configs:o,trackers:l,startedAt:r,execution:p,workerState:n,selection:t,archiveConfig:m=null,declaredEnvironmentKeys:c=[],metadata:f,environmentRecord:g,logRegistry:a,setupRegistry:v,operationTimingRegistry:S=null,reporter:y=null}){return()=>{const u=Date.now(),L=o.map(i=>w(l.get(i.name),r,u));try{d(e,C({productDir:e,runId:s,results:L,startedAt:r,updatedAt:u,execution:p,workerCount:n.workerCount,runtimeInstanceCount:n.runtimeInstanceCount,runtimeStats:n.runtimeStats,typeValues:t.typeValues,suiteSelectors:t.suiteSelectors,fileNames:t.fileNames,serviceFilter:t.serviceFilter,planning:t.planning||null,archiveConfig:m,declaredEnvironmentKeys:c,metadata:f,summarizeDbBackend:b,environmentServices:g.listServices(),serviceLogs:a.listServiceLogs(),setupLogs:a.listSetupLogs(),setupOperations:v.listOperations(),operationTimings:S?.listOperations()||[]}))}catch(i){y?.warning?.(`Live run artifact update skipped: ${R(O(i))}`)}}}function O(e){return(typeof e=="object"&&e!==null||typeof e=="function")&&Reflect.get(e,"message")||e}function R(e){if(typeof e=="symbol")throw new TypeError("Cannot convert a Symbol value to a string");return String(e)}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type CollectedSuite, type CollectSuitesOptions, type PlanningServicePlan } from "./planning.js";
|
|
2
2
|
import { type FinalizedServiceResult } from "./results.js";
|
|
3
3
|
import { type RunEventListener } from "./run-events.js";
|
|
4
|
-
import
|
|
4
|
+
import { type EnvironmentHold } from "../environments/routing.js";
|
|
5
|
+
import { type SuppliedEnvironment } from "../environments/supplied.js";
|
|
6
|
+
import { type NormalizedServiceConfig, type NormalizedSuiteConfig } from "../../config/loader/index.js";
|
|
5
7
|
import { type RuntimeManagerRuntimeOptions } from "../../environment/run-environment.js";
|
|
6
8
|
import { type WorkerReporter, type WorkerResult } from "./worker-loop.js";
|
|
7
9
|
import { type CreateLiveSnapshotWriterOptions } from "./live-run.js";
|
|
@@ -44,7 +46,12 @@ type RuntimeManagerReporter = NonNullable<RuntimeManagerRuntimeOptions["reporter
|
|
|
44
46
|
type LiveRunReporter = NonNullable<CreateLiveSnapshotWriterOptions["reporter"]>;
|
|
45
47
|
type RunFinalizationReporter = NonNullable<FinalizeRunArtifactsOptions<FinalizedServiceResult>["reporter"]>;
|
|
46
48
|
export type RunReporter = OrchestratorReporter & OperationProgressReporter & RuntimeManagerReporter & WorkerReporter & LiveRunReporter & RunFinalizationReporter;
|
|
49
|
+
export interface RunEnvironmentInput {
|
|
50
|
+
readonly hold?: EnvironmentHold;
|
|
51
|
+
readonly supplied?: Readonly<Record<string, SuppliedEnvironment>>;
|
|
52
|
+
}
|
|
47
53
|
export interface RunOptions extends CollectSuitesOptions {
|
|
54
|
+
environments?: RunEnvironmentInput;
|
|
48
55
|
serviceFilter?: null | string;
|
|
49
56
|
allowEmpty?: boolean;
|
|
50
57
|
debug?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveRuntimeConfigs as re}from"../../config/loader/dependencies.js";import{buildRuntimeGraphs as ne,claimNextTask as ie,collectSuites as ae}from"./planning.js";import{buildRunPlanningMetadata as se,buildScheduledQueue as oe}from"./scheduler/index.js";import{addTrackerError as B,buildServiceTrackers as ue,finalizeServiceResult as _,recordGraphError as le,recordTaskOutcome as ce,summarizeDbBackend as me}from"./results.js";import{loadTimings as fe,saveTimings as de,writeRunArtifact as pe}from"../../evidence/index.js";import{createRunLogRegistry as ge}from"../../evidence/logs.js";import{createOperationTimingRegistry as ke,timeOperation as L}from"../../evidence/operation-timings.js";import{createRunReporterFromListener as he}from"./run-events.js";import{createSetupOperationRegistry as Se}from"../../evidence/setup-progress.js";import{createLocalRunEnvironment as ve}from"../../environment/run-environment.js";import{readPackageMetadata as Re,safeHostname as ye,safeUsername as Ce}from"../../process/host/metadata.js";import{collectGitMetadata as we}from"../../repo/metadata.js";import{resolveExecutionConfig as Fe}from"../../config/loader/decode-execution.js";import"../../environment/run-environment.js";import{createEngineManager as Ae}from"../../engines/factory.js";import{createWorker as xe,runWorker as Ie}from"./worker-loop.js";import{ensureRequestedFilesMatch as Ee}from"./run-guards.js";import{createLiveSnapshotWriter as Te}from"./live-run.js";import{finalizeRunArtifacts as be}from"./run-finalization.js";import{buildRunArtifact as ze}from"../../evidence/reporting.js";import{createOperationReporterBridge as Me}from"./operation-progress.js";import{formatError as $,serializeError as J}from"../../kernel/error-serialization.js";export async function runAll(e,l,g,t,p=e){const k=t.fileNames||[];Ee(e,l,g,k);const a=e[0]?.productDir||process.cwd(),m=ve(a),I=Ae(a),w=new Map(p.map(n=>[n.name,n])),h=Date.now(),f=e[0]?.artifacts??null,z=[...new Set(e.flatMap(n=>Object.keys(n.testkit.serviceEnv)))].sort(),Q={git:await we(a),host:{hostname:ye(),username:Ce()},testkitVersion:Re().version},S=t.onEvent?he(t.onEvent,{debug:t.debug}):null,F=ge(a),v={workerCount:0,runtimeInstanceCount:0,runtimeStats:[]},q=resolveRunExecutionConfig(e[0],t),N=collectServicePlans(e,w,l,g,t,q,S);S?.setServicePlans?.(N);const A=ue(N,h),Y=g.map(n=>({...n})),D={typeValues:l,suiteSelectors:Y,fileNames:k,serviceFilter:t.serviceFilter||null,planning:null};let d=()=>{};const Z=Me(S),x=ke({onChange:n=>{Z(n),d()}}),P=Se({logRegistry:F,onChange:()=>{d()}}),O=N.filter(n=>!n.skipped);let o;const r=await m.start({keepAlive:!0,resetResultsBeforePublish:!0});d=Te({productDir:a,runId:r.runId,configs:e,trackers:A,startedAt:h,execution:q,workerState:v,selection:D,archiveConfig:f,declaredEnvironmentKeys:z,metadata:Q,logRegistry:F,setupRegistry:P,operationTimingRegistry:x,reporter:S});const M={artifacts:null},ee=async(n,c)=>(r.beginFinalization(),await r.finish(n,u=>{const i=c(u);return M.artifacts={runArtifact:i.runArtifact},i},"normal"));let R,E;const U=(n,c,s)=>({productDir:a,runId:r.runId,results:n,startedAt:h,finishedAt:c,execution:q,workerCount:v.workerCount,runtimeInstanceCount:v.runtimeInstanceCount,runtimeStats:v.runtimeStats,selection:D,archiveConfig:f,declaredEnvironmentKeys:z,metadata:Q,logRegistry:F,setupRegistry:P,operationTimingRegistry:x,reporter:S,runInterruptReason:r.getInterruptReason(),runStatus:s,publishRunFinalization:ee});d();let y=null;try{try{if(O.length>0){const s=fe(a),{graphs:u,queue:i}=await L(x,{name:"testkit.plan",kind:"planning",summary:"plan test execution",scope:{serviceCount:O.length}},()=>{const T=ne(O),C=oe(O,T,{timings:s});return{graphs:T,queue:C}});D.planning=se(i,u),d(),S?.setTotalFileCount?.(i.length),v.workerCount=Math.max(1,Math.min(q.taskConcurrency,i.length)),v.runtimeInstanceCount=u.reduce((T,C)=>T+C.instanceCount,0);const V=Array.from({length:v.workerCount},(T,C)=>xe(C+1,a));r.provision(u,{reporter:S,logRegistry:F,setupRegistry:P,operationTimingRegistry:x,skipSchemaSourceVerify:t.skipSchemaSourceVerify});const H=[];try{const C=(await L(x,{name:"testkit.execute",kind:"execution",summary:"execute local worker queue",scope:{workerCount:V.length,taskCount:i.length}},async()=>Promise.allSettled(V.map(j=>Ie(j,i,r,I,A,H,ie,(b,G,K,W)=>{ce(b,G,K,W),d()},(b,G,K,W,te)=>{le(b,G,K,W,te),d()},S))))).flatMap(workerResultErrorsForOrchestrator);for(const j of C)for(const b of A.values())b.skipped||B(b,j);C.length>0&&d(),v.runtimeStats=r.getStats(),d()}finally{await L(x,{name:"testkit.cleanup",kind:"cleanup",summary:"stop managed runtimes and services"},()=>r.cleanupAll())}de(a,s,H)}}catch(s){y=J(s);const u=y.message||$(s);for(const i of A.values())i.skipped||B(i,u);d()}o=y===null?0:r.isStopRequested()?130:1,E=Date.now(),R=e.map(s=>_(A.get(s.name),h,E));const n=resolveRunGateFailure(R,t.allowEmpty===!0);await L(x,{name:"testkit.collect",kind:"collection",summary:"close and collect run logs"},()=>F.closeAll());const c=await be(U(R,E,r.isStopRequested()?"interrupted":y===null&&n===null?null:"failed"));return y!==null&&(o=1),R.some(s=>s.failed||s.infraFailed)&&(o=1),n!==null&&(o=1),c.shouldFailArchivePublication&&(o=1),c.shouldFailArchiveCleanup&&(o=1),(c.runArtifact.run.status==="interrupted"||r.isStopRequested()||r.committedFinalization?.artifactStatus==="interrupted")&&(o=Math.max(o,130)),{runArtifact:c.runArtifact,results:R,exitCode:o,...y===null?{}:{failure:y}}}catch(n){const c=J(n),s=c.message||$(n);for(const i of A.values())i.skipped||B(i,s);d(),r.beginFinalization(),o=r.isStopRequested()||r.committedFinalization?.artifactStatus==="interrupted"?130:1,E=Date.now(),R=e.map(i=>_(A.get(i.name),h,E)),await X(F);let u=M.artifacts;if(u===null)try{u=await qe(U(R,E,r.isStopRequested()?"interrupted":"failed"),r,M,c)}catch{if(M.artifacts===null)throw n;u=M.artifacts}return(u.runArtifact.run.status==="interrupted"||r.isStopRequested()||r.committedFinalization?.artifactStatus==="interrupted")&&(o=Math.max(o,130)),{runArtifact:u.runArtifact,results:R,exitCode:o,failure:y??c}}finally{r.dispose(),await X(F)}}async function qe(e,l,g,t){return l.finish("failed",p=>{const k=p.artifactStatus==="interrupted"?p:{artifactStatus:"failed",interruptReason:null},a=e.declaredEnvironmentKeys??[],m=ze({productDir:e.productDir,runId:e.runId,results:e.results,startedAt:e.startedAt,finishedAt:e.finishedAt,execution:e.execution,workerCount:e.workerCount,runtimeInstanceCount:e.runtimeInstanceCount,runtimeStats:e.runtimeStats,typeValues:e.selection.typeValues,suiteSelectors:e.selection.suiteSelectors,fileNames:e.selection.fileNames,serviceFilter:e.selection.serviceFilter,planning:e.selection.planning??null,archiveConfig:e.archiveConfig,declaredEnvironmentKeys:a,metadata:e.metadata,summarizeDbBackend:me,serviceLogs:e.logRegistry.listServiceLogs(),setupLogs:e.logRegistry.listSetupLogs(),setupOperations:e.setupRegistry.listOperations(),operationTimings:e.operationTimingRegistry?.listOperations()??[],runInterruptReason:k.interruptReason,runStatus:k.artifactStatus});m.finalizationFailure={...t};const w={runArtifact:pe(e.productDir,m,{archiveConfig:e.archiveConfig,declaredEnvironmentKeys:a})};return g.artifacts=w,w},"recovery")}async function X(e){try{await e.closeAll()}catch{}}export function workerResultErrorsForOrchestrator(e){return e.status==="rejected"?[$(e.reason)]:[...new Set(e.value.unpersistedErrors)]}export function resolveRunGateFailure(e,l=!1){return e.some(t=>t.failed)?"test failure":e.some(t=>t.infraFailed)?"test infrastructure failure":e.some(t=>t.notRunFileCount>0)?"one or more required test files did not run":e.some(t=>t.skippedFileCount>0)?"one or more selected test files were skipped":e.reduce((t,p)=>t+p.completedFileCount,0)===0&&!l?"no runnable test files executed":null}export function resolveRunExecutionConfig(e,l){return Fe({cli:{fileTimeoutSeconds:l.fileTimeoutSeconds,taskConcurrency:l.taskConcurrency},repo:e?{fileTimeoutSeconds:e.testkit.execution.fileTimeoutSeconds,taskConcurrency:e.testkit.execution.taskConcurrency}:void 0})}export function collectServicePlans(e,l,g,t,p,k,a=null){return e.map(m=>{const I=ae(m,g,t,p.fileNames||[],p);if(I.length===0)return a?.serviceSkipped?.(m,`no matching files (types=${g.join(",")||"all"} suites=${t.map(f=>f.raw).join(",")||"all"} files=${(p.fileNames||[]).join(",")||"all"})`),{config:m,execution:k,skipped:!0,suites:[],runtimeConfigs:[],runtimeNames:[],runtimeKey:null};for(const f of I)for(const z of f.skippedFiles)a?.plannedSkip?.({serviceName:m.name,testType:f.testType,engineId:f.engineId,profileId:f.profileId,file:z.path,reason:z.reason});const w=re(m,l),h=w.map(f=>f.name).sort();return{config:m,execution:k,skipped:!1,suites:I,runtimeConfigs:w,runtimeNames:h,runtimeKey:h.join("|")}})}
|
|
1
|
+
import{resolveRuntimeConfigs as ce}from"../../config/loader/dependencies.js";import{buildRuntimeGraphs as me,claimNextTask as fe,collectSuites as de}from"./planning.js";import{buildRunPlanningMetadata as pe,buildScheduledQueue as ge}from"./scheduler/index.js";import{addTrackerError as U,buildServiceTrackers as ke,finalizeServiceResult as ee,recordGraphError as ve,recordTaskOutcome as he,summarizeDbBackend as Se}from"./results.js";import{loadTimings as Re,saveTimings as Ce,writeRunArtifact as ye}from"../../evidence/index.js";import{createRunEnvironmentRecord as we}from"../../evidence/environment-record.js";import{createRunLogRegistry as Fe}from"../../evidence/logs.js";import{createOperationTimingRegistry as Ae,timeOperation as D}from"../../evidence/operation-timings.js";import{createRunReporterFromListener as Ee}from"./run-events.js";import{createSetupOperationRegistry as xe}from"../../evidence/setup-progress.js";import{createLocalRunEnvironment as be}from"../../environment/run-environment.js";import{createConfigDrivenEnvironments as Ie}from"../environments/config-driven.js";import{declaresProvisioning as Te,holdBeforeRelease as ze,routeEnvironments as Oe}from"../environments/routing.js";import{createSuppliedEnvironments as qe}from"../environments/supplied.js";import{resolveServiceCwd as Me}from"../../config/loader/index.js";import{createTaskLeases as Le}from"./task-leases.js";import{createTaskLocks as De}from"./scheduler/locks.js";import{readPackageMetadata as je,safeHostname as Ne,safeUsername as Pe}from"../../process/host/metadata.js";import{collectGitMetadata as Ge}from"../../repo/metadata.js";import{resolveExecutionConfig as Ke}from"../../config/loader/decode-execution.js";import"../../environment/run-environment.js";import{createEngineManager as Be}from"../../engines/factory.js";import{createWorker as We,runWorker as $e}from"./worker-loop.js";import{ensureRequestedFilesMatch as Qe}from"./run-guards.js";import{createLiveSnapshotWriter as Ue}from"./live-run.js";import{finalizeRunArtifacts as Ve}from"./run-finalization.js";import{buildRunArtifact as He}from"../../evidence/reporting.js";import{createOperationReporterBridge as _e}from"./operation-progress.js";import{formatError as V,serializeError as te}from"../../kernel/error-serialization.js";export async function runAll(e,a,d,t,k=e){const v=t.fileNames||[];Qe(e,a,d,v);const i=e[0]?.productDir||process.cwd(),p=be(i),b=Be(i),F=new Map(k.map(r=>[r.name,r])),h=Date.now(),g=e[0]?.artifacts??null,z=[...new Set(e.flatMap(r=>Object.keys(r.testkit.serviceEnv)))].sort(),H={git:await Ge(i),host:{hostname:Ne(),username:Pe()},testkitVersion:je().version},S=t.onEvent?Ee(t.onEvent,{debug:t.debug}):null,A=Fe(i),R={workerCount:0,runtimeInstanceCount:0,runtimeStats:[]},O=resolveRunExecutionConfig(e[0],t),j=collectServicePlans(e,F,a,d,t,O,S);S?.setServicePlans?.(j);const E=ke(j,h),ne=d.map(r=>({...r})),N={typeValues:a,suiteSelectors:ne,fileNames:v,serviceFilter:t.serviceFilter||null,planning:null};let m=()=>{};const P=we({onChange:()=>{m()}}),ie=_e(S),x=Ae({onChange:r=>{ie(r),m()}}),G=xe({logRegistry:A,onChange:()=>{m()}}),q=j.filter(r=>!r.skipped),_=t.environments?.supplied??{},K=new Set(q.filter(r=>!Object.hasOwn(_,r.config.name)&&Te(r.runtimeConfigs)).map(r=>r.runtimeKey));let l;const n=await p.start({keepAlive:!0,localInfrastructure:K.size>0,resetResultsBeforePublish:!0});m=Ue({productDir:i,runId:n.runId,configs:e,trackers:E,startedAt:h,execution:O,workerState:R,selection:N,archiveConfig:g,declaredEnvironmentKeys:z,metadata:H,environmentRecord:P,logRegistry:A,setupRegistry:G,operationTimingRegistry:x,reporter:S});const M={artifacts:null},ae=async(r,f)=>(n.beginFinalization(),await n.finish(r,c=>{const s=f(c);return M.artifacts={runArtifact:s.runArtifact},s},"normal"));let C,I;const J=(r,f,o)=>({productDir:i,runId:n.runId,results:r,startedAt:h,finishedAt:f,execution:O,workerCount:R.workerCount,runtimeInstanceCount:R.runtimeInstanceCount,runtimeStats:R.runtimeStats,selection:N,archiveConfig:g,declaredEnvironmentKeys:z,metadata:H,environmentRecord:P,logRegistry:A,setupRegistry:G,operationTimingRegistry:x,reporter:S,runInterruptReason:n.getInterruptReason(),runStatus:o,publishRunFinalization:ae});m();let y=null;try{try{if(q.length>0){const o=Re(i),{graphs:c,queue:s}=await D(x,{name:"testkit.plan",kind:"planning",summary:"plan test execution",scope:{serviceCount:q.length}},()=>{const u=me(q),w=ge(q,u,{timings:o});return{graphs:u,queue:w}});N.planning=pe(s,c),m(),S?.setTotalFileCount?.(s.length),R.workerCount=Math.max(1,Math.min(O.taskConcurrency,s.length));const L=c.filter(u=>K.has(u.key));R.runtimeInstanceCount=L.reduce((u,w)=>u+w.instanceCount,0);const X=Array.from({length:R.workerCount},(u,w)=>We(w+1,i));L.length>0&&n.provision(L,{reporter:S,environmentRecord:P,logRegistry:A,setupRegistry:G,operationTimingRegistry:x,skipSchemaSourceVerify:t.skipSchemaSourceVerify});const se=Ie(n),oe=qe(Object.fromEntries(e.map(u=>[u.name,Me(i,u.testkit.local?.cwd)])),_),Y=Le(i),ue={runId:n.runId,signal:n.signal,isStopRequested:()=>n.isStopRequested(),environments:Xe(Oe(new Map(c.map(u=>[u.key,K.has(u.key)?se:oe]))),t.environments?.hold),leases:Y,locks:De(),request:Ye(i,e,O)},Z=[];try{const w=(await D(x,{name:"testkit.execute",kind:"execution",summary:"execute local worker queue",scope:{workerCount:X.length,taskCount:s.length}},async()=>Promise.allSettled(X.map(B=>$e(B,s,ue,b,E,Z,fe,(T,W,$,Q)=>{he(T,W,$,Q),m()},(T,W,$,Q,le)=>{ve(T,W,$,Q,le),m()},S))))).flatMap(workerResultErrorsForOrchestrator);for(const B of w)for(const T of E.values())T.skipped||U(T,B);w.length>0&&m(),R.runtimeStats=n.getStats(),m()}finally{L.length>0&&await D(x,{name:"testkit.cleanup",kind:"cleanup",summary:"stop managed runtimes and services"},()=>n.cleanupAll()),Y.close()}Ce(i,o,Z)}}catch(o){y=te(o);const c=y.message||V(o);for(const s of E.values())s.skipped||U(s,c);m()}l=y===null?0:n.isStopRequested()?130:1,I=Date.now(),C=e.map(o=>ee(E.get(o.name),h,I));const r=resolveRunGateFailure(C,t.allowEmpty===!0);await D(x,{name:"testkit.collect",kind:"collection",summary:"close and collect run logs"},()=>A.closeAll());const f=await Ve(J(C,I,n.isStopRequested()?"interrupted":y===null&&r===null?null:"failed"));return y!==null&&(l=1),C.some(o=>o.failed||o.infraFailed)&&(l=1),r!==null&&(l=1),f.shouldFailArchivePublication&&(l=1),f.shouldFailArchiveCleanup&&(l=1),(f.runArtifact.run.status==="interrupted"||n.isStopRequested()||n.committedFinalization?.artifactStatus==="interrupted")&&(l=Math.max(l,130)),{runArtifact:f.runArtifact,results:C,exitCode:l,...y===null?{}:{failure:y}}}catch(r){const f=te(r),o=f.message||V(r);for(const s of E.values())s.skipped||U(s,o);m(),n.beginFinalization(),l=n.isStopRequested()||n.committedFinalization?.artifactStatus==="interrupted"?130:1,I=Date.now(),C=e.map(s=>ee(E.get(s.name),h,I)),await re(A);let c=M.artifacts;if(c===null)try{c=await Je(J(C,I,n.isStopRequested()?"interrupted":"failed"),n,M,f)}catch{if(M.artifacts===null)throw r;c=M.artifacts}return(c.runArtifact.run.status==="interrupted"||n.isStopRequested()||n.committedFinalization?.artifactStatus==="interrupted")&&(l=Math.max(l,130)),{runArtifact:c.runArtifact,results:C,exitCode:l,failure:y??f}}finally{n.dispose(),await re(A)}}async function Je(e,a,d,t){return a.finish("failed",k=>{const v=k.artifactStatus==="interrupted"?k:{artifactStatus:"failed",interruptReason:null},i=e.declaredEnvironmentKeys??[],p=He({productDir:e.productDir,runId:e.runId,results:e.results,startedAt:e.startedAt,finishedAt:e.finishedAt,execution:e.execution,workerCount:e.workerCount,runtimeInstanceCount:e.runtimeInstanceCount,runtimeStats:e.runtimeStats,typeValues:e.selection.typeValues,suiteSelectors:e.selection.suiteSelectors,fileNames:e.selection.fileNames,serviceFilter:e.selection.serviceFilter,planning:e.selection.planning??null,archiveConfig:e.archiveConfig,declaredEnvironmentKeys:i,metadata:e.metadata,summarizeDbBackend:Se,environmentServices:e.environmentRecord.listServices(),serviceLogs:e.logRegistry.listServiceLogs(),setupLogs:e.logRegistry.listSetupLogs(),setupOperations:e.setupRegistry.listOperations(),operationTimings:e.operationTimingRegistry?.listOperations()??[],runInterruptReason:v.interruptReason,runStatus:v.artifactStatus});p.finalizationFailure={...t};const F={runArtifact:ye(e.productDir,p,{archiveConfig:e.archiveConfig,declaredEnvironmentKeys:i})};return d.artifacts=F,F},"recovery")}async function re(e){try{await e.closeAll()}catch{}}export function workerResultErrorsForOrchestrator(e){return e.status==="rejected"?[V(e.reason)]:[...new Set(e.value.unpersistedErrors)]}export function resolveRunGateFailure(e,a=!1){return e.some(t=>t.failed)?"test failure":e.some(t=>t.infraFailed)?"test infrastructure failure":e.some(t=>t.notRunFileCount>0)?"one or more required test files did not run":e.some(t=>t.skippedFileCount>0)?"one or more selected test files were skipped":e.reduce((t,k)=>t+k.completedFileCount,0)===0&&!a?"no runnable test files executed":null}function Xe(e,a){return a===void 0?e:ze(e,a)}function Ye(e,a,d){return{productDir:e,fileTimeoutSeconds:d.fileTimeoutSeconds,engineConfigurations:Object.fromEntries(a.flatMap(t=>Object.entries(t.engineConfigurations??{})))}}export function resolveRunExecutionConfig(e,a){return Ke({cli:{fileTimeoutSeconds:a.fileTimeoutSeconds,taskConcurrency:a.taskConcurrency},repo:e?{fileTimeoutSeconds:e.testkit.execution.fileTimeoutSeconds,taskConcurrency:e.testkit.execution.taskConcurrency}:void 0})}export function collectServicePlans(e,a,d,t,k,v,i=null){return e.map(p=>{const b=de(p,d,t,k.fileNames||[],k);if(b.length===0)return i?.serviceSkipped?.(p,`no matching files (types=${d.join(",")||"all"} suites=${t.map(g=>g.raw).join(",")||"all"} files=${(k.fileNames||[]).join(",")||"all"})`),{config:p,execution:v,skipped:!0,suites:[],runtimeConfigs:[],runtimeNames:[],runtimeKey:null};for(const g of b)for(const z of g.skippedFiles)i?.plannedSkip?.({serviceName:p.name,testType:g.testType,engineId:g.engineId,profileId:g.profileId,file:z.path,reason:z.reason});const F=ce(p,a),h=F.map(g=>g.name).sort();return{config:p,execution:v,skipped:!1,suites:b,runtimeConfigs:F,runtimeNames:h,runtimeKey:h.join("|")}})}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { RuntimeGraph } from "../../environment/planning-types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PlanningSuiteConfig, PlanningServiceConfig, CollectedSuite, PlanningServicePlan } from "./planning-types.js";
|
|
3
3
|
import { type SuiteSelector } from "../../domain/suite-selection.js";
|
|
4
4
|
import { type PublicTestType } from "../../domain/identifiers.js";
|
|
5
5
|
export interface CollectSuitesOptions {
|
|
6
6
|
ignoreSkipRules?: boolean;
|
|
7
7
|
}
|
|
8
|
-
export declare function taskNeedsLocalRuntime(task: Readonly<PlanningTask>): boolean;
|
|
9
8
|
export declare function collectSuites<Suite extends PlanningSuiteConfig>(config: PlanningServiceConfig<Suite>, typeValues: readonly string[], suiteSelectors: readonly SuiteSelector[], fileNames?: readonly string[], opts?: Readonly<CollectSuitesOptions>): CollectedSuite<Suite>[];
|
|
10
9
|
export declare function orderedTypes(types: readonly string[]): PublicTestType[];
|
|
11
10
|
export declare function claimNextTask<Task extends {
|
package/dist/app/run/planning.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{runtimePlanning as
|
|
1
|
+
import{runtimePlanning as g}from"../../environment/runtime-planning.js";import{matchesSelectedTypes as x,matchesSuiteSelectors as k}from"../../domain/suite-selection.js";import{isPublicTestType as y}from"../../domain/identifiers.js";import{resolveEngineProfile as w}from"../../engines/catalog.js";const S=["ui","e2e","int","contract","data","dal","load"];export function collectSuites(e,t,s,o=[],c={}){const n=new Set(o.map(d)),u=[];let h=0;for(const r of orderedTypes(Object.keys(e.suites)))for(const i of e.suites[r]||[]){if(i.testType!==r)throw new Error(`Suite ${e.name}/${r}:${i.name} declares conflicting test type ${i.testType}`);const p=R(i),l=n.size===0?i.files:i.files.filter(f=>n.has(d(f)));if(!x(r,t)||!k(r,i.name,s)||l.length===0)continue;const{files:a,skippedFiles:m}=P(e,r,i.name,l,c);u.push({...i,files:a,skippedFiles:m,orderIndex:h,sortKey:`${r}:${i.name}`,weight:i.testkit?.weight||(p.capabilities.taskBatching==="compatible"?Math.max(2,Math.max(1,a.length)):Math.max(1,a.length)),totalFileCount:l.length}),h+=1}return u}export function orderedTypes(e){const t=e.filter(o=>!y(o));if(t.length>0)throw new Error(`Unknown test types in suite collection: ${t.join(", ")}`);const s=[];for(const o of S)e.includes(o)&&s.push(o);return s}export function claimNextTask(e,t,s=()=>!0){if(e.length===0)return null;const o=[],c=[];for(let n=0;n<e.length;n+=1)t&&e[n].graphKey===t?o.push(n):c.push(n);for(const n of[...o,...c])if(s(e[n]))return e.splice(n,1)[0];return null}function P(e,t,s,o,c={}){if(c.ignoreSkipRules)return{files:o,skippedFiles:[]};const n=e.testkit.skip,u=e.testkit.fileMetadataByPath||null;if(!n&&!u)return{files:o,skippedFiles:[]};const r=(n?.suites||[]).filter(l=>k(t,s,[l.selector]))[0]?.reason||null,i=[],p=[];for(const l of o){const a=d(l),f=(e.testkit.fileMetadataByPath?.get(a)||null)?.skipReason||n?.fileReasonByPath?.get(a)||r;if(f){p.push({path:a,reason:f});continue}i.push(l)}return{files:i,skippedFiles:p}}function R(e){const t=w(e.testType,e.engineId);if(t.id!==e.profileId)throw new Error(`Execution identity ${e.testType}/${e.engineId}/${e.profileId} conflicts with catalog profile ${t.id}`);return t}function d(e){return String(e).split("\\").join("/")}export function buildRuntimeGraphs(e){const t=e.filter(s=>!s.skipped);for(const s of t)s.assignedGraphKey=s.runtimeKey;return g.buildRuntimeGraphs(t)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RuntimeIsolationPolicy } from "../../
|
|
1
|
+
import type { RuntimeIsolationPolicy } from "../../domain/runtime-isolation-contract.js";
|
|
2
2
|
import type { TestOutcomeStatus } from "../../domain/test-outcomes.js";
|
|
3
3
|
import { type NormalizedCheckDetail, type NormalizedFailureDetail } from "./failure-details.js";
|
|
4
4
|
import type { CollectedSuite, PlanningServiceConfig } from "./planning.js";
|
package/dist/app/run/results.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import b
|
|
1
|
+
import{mergeFailureDetails as b}from"./failure-details.js";import{posixPath as g}from"../../process/files/paths.js";export function buildServiceTrackers(e,i){const l=new Map;for(const t of e){const r=t.skipped?[]:t.suites.map(x),o={name:t.config.name,dbBackend:w(t.config),skipped:t.skipped===!0,suiteCount:r.length,suites:r,suitesByKey:new Map(r.map(n=>[n.key,n])),errors:[],errorSet:new Set,errorDetails:[],errorDetailSet:new Set,startedAt:i,firstTaskAt:null,lastTaskAt:null,totalTaskDurationMs:0};l.set(t.config.name,o)}return l}function x(e){const i=new Map;for(const l of e.files){const t=g(l);i.set(t,k(t,"not_run",null))}for(const l of e.skippedFiles??[])i.set(l.path,k(l.path,"skipped",l.reason));return{key:`${e.testType}:${e.name}`,name:e.name,testType:e.testType,engineId:e.engineId,profileId:e.profileId,orderIndex:e.orderIndex,failedFiles:[],failedFileSet:new Set,fileResultsByPath:i,durationMs:0,error:null}}function k(e,i,l){return{path:e,failed:i==="failed",durationMs:0,error:null,reason:l,status:i,artifacts:[],infraFailed:!1,blockedByRuntime:!1,failureDetails:[],checkDetails:[],runtimeIsolation:null,runtimeBatch:null,runtimeResets:[]}}function w(e){return e.testkit.database?"local":null}export function recordTaskOutcome(e,i,l,t=Date.now()){const r=e.get(i.serviceName);if(!r||r.skipped)return;const o=r.suitesByKey.get(i.suiteKey);if(!o)return;const n=z(l,i,t);r.firstTaskAt=r.firstTaskAt===null?n.startedAt:Math.min(r.firstTaskAt,n.startedAt),r.lastTaskAt=r.lastTaskAt===null?n.finishedAt:Math.max(r.lastTaskAt,n.finishedAt),r.totalTaskDurationMs+=n.durationMs,o.durationMs+=n.durationMs;const f=g(i.file),s=o.fileResultsByPath.get(f),u=s??k(f,n.status,n.reason);u.failed=n.status==="failed",u.durationMs=n.durationMs,u.error=n.error,u.reason=n.reason,u.status=n.status,u.artifacts=n.artifacts,u.infraFailed=n.infraFailed,u.blockedByRuntime=n.blockedByRuntime,u.failureDetails=n.failureDetails,u.checkDetails=n.checkDetails,u.runtimeIsolation=n.runtimeIsolation,u.runtimeBatch=n.runtimeBatch,u.runtimeResets=n.runtimeResets,s||o.fileResultsByPath.set(f,{...u}),n.status==="failed"&&!o.failedFileSet.has(i.file)&&(o.failedFileSet.add(i.file),o.failedFiles.push(i.file)),n.error&&!o.error&&(o.error=n.error)}function z(e,i,l){const t=e.durationMs??0,r=e.finishedAt??l;return{artifacts:e.artifacts??[],blockedByRuntime:e.blockedByRuntime??!1,checkDetails:e.checkDetails??[],durationMs:t,error:e.error??null,failureDetails:b(e.failureDetails??[]),finishedAt:r,infraFailed:e.infraFailed??!1,reason:e.reason??null,runtimeBatch:e.runtimeBatch??null,runtimeIsolation:e.runtimeIsolation??i.runtimeIsolation??null,runtimeResets:e.runtimeResets??[],startedAt:e.startedAt??Math.max(0,r-t),status:e.status}}export function recordGraphError(e,i,l,t=Date.now(),r={}){const o=i?.targetNames||[];for(const n of o){const f=e.get(n);f&&!f.skipped&&(addTrackerError(f,l,r),f.lastTaskAt=t)}}export function addTrackerError(e,i,l={}){const t=i.trim();if(!t)return;const r=l.classification??"product";e.errorSet.has(t)||(e.errorSet.add(t),e.errors.push(t));const o=`${r}\0${t}`;e.errorDetailSet.has(o)||(e.errorDetailSet.add(o),e.errorDetails.push({message:t,classification:r}))}export function finalizeServiceResult(e,i,l){if(!e||e.skipped)return{name:e?.name||"unknown",failed:!1,skipped:!0,suiteCount:0,completedSuiteCount:0,skippedSuiteCount:0,failedSuiteCount:0,totalFileCount:0,completedFileCount:0,passedFileCount:0,failedFileCount:0,skippedFileCount:0,notRunFileCount:0,durationMs:0,totalTaskDurationMs:0,suites:[],errors:[],errorDetails:[],infraFailed:!1,blockedByRuntime:!1};const t=[...e.suites].sort((a,d)=>a.orderIndex-d.orderIndex||a.name.localeCompare(d.name)).map(a=>P(a)),r=t.filter(a=>a.completedFileCount+a.skippedFileCount===a.fileCount).length,o=t.filter(a=>a.skippedFileCount===a.fileCount&&a.fileCount>0).length,n=t.filter(a=>a.failedFileCount>0).length,f=t.reduce((a,d)=>a+d.fileCount,0),s=t.reduce((a,d)=>a+d.completedFileCount,0),u=t.reduce((a,d)=>a+d.failedFileCount,0),D=t.reduce((a,d)=>a+d.passedFileCount,0),F=t.reduce((a,d)=>a+d.skippedFileCount,0),p=f-s-F,m=t.reduce((a,d)=>a+d.files.filter(c=>c.infraFailed===!0).length,0),C=t.reduce((a,d)=>a+d.files.filter(c=>c.blockedByRuntime===!0).length,0),B=t.reduce((a,d)=>a+d.files.filter(c=>c.status==="cancelled"||c.status==="interrupted").length,0),y=p>0&&B===p,R=p>0&&m===p,h=e.errorDetails,T=h.filter(a=>a.classification!=="infra").length,M=h.filter(a=>a.classification==="infra").length,I=m>0||M>0,S=C>0,A=e.firstTaskAt!==null&&e.lastTaskAt!==null?Math.max(0,e.lastTaskAt-e.firstTaskAt):Math.max(0,l-i);return{name:e.name,dbBackend:e.dbBackend,failed:n>0||T>0||r<e.suiteCount&&!y&&!R,infraFailed:I,blockedByRuntime:S,infraFailedFileCount:m,blockedByRuntimeFileCount:C,skipped:!1,suiteCount:e.suiteCount,completedSuiteCount:r,skippedSuiteCount:o,failedSuiteCount:n,totalFileCount:f,completedFileCount:s,passedFileCount:D,failedFileCount:u,skippedFileCount:F,notRunFileCount:p,durationMs:A,totalTaskDurationMs:e.totalTaskDurationMs,suites:t,errors:e.errors,errorDetails:h}}export function summarizeDbBackend(e){const i=[...new Set(e.map(l=>l.dbBackend).filter(l=>typeof l=="string"))];return i.length===0?null:i.length===1?i[0]:"mixed"}function P(e){const i=[...e.fileResultsByPath.values()].sort((s,u)=>s.path.localeCompare(u.path)).map(s=>({path:s.path,failed:s.failed,status:s.status,durationMs:s.durationMs,error:s.error,reason:s.reason,...s.infraFailed?{infraFailed:!0}:{},...s.blockedByRuntime?{blockedByRuntime:!0}:{},...s.failureDetails.length>0?{failureDetails:s.failureDetails}:{},...s.artifacts.length>0?{artifacts:s.artifacts}:{},...s.checkDetails.length>0?{checkDetails:s.checkDetails}:{},...s.runtimeIsolation?{runtimeIsolation:s.runtimeIsolation}:{},...s.runtimeBatch?{runtimeBatch:s.runtimeBatch}:{},...s.runtimeResets.length>0?{runtimeResets:s.runtimeResets}:{}})),l=i.length,t=i.filter(s=>s.status==="passed").length,r=i.filter(s=>s.status==="failed").length,o=i.filter(s=>s.status==="skipped").length,n=t+r,f=l-n-o;return{name:e.name,testType:e.testType,engineId:e.engineId,profileId:e.profileId,failed:r>0,fileCount:l,completedFileCount:n,passedFileCount:t,failedFileCount:r,skippedFileCount:o,notRunFileCount:f,failedFiles:e.failedFiles,durationMs:e.durationMs,error:e.error,files:i}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CommittedRunFinalization } from "../../evidence/finalization.js";
|
|
2
|
-
import { writeRunArtifact, type NormalizedArtifactArchiveConfig, type ReportedRunArtifact, type ReportingExecution, type ReportingMetadata, type ReportingOperationTimingInput, type ReportingRecord, type ReportingServiceLogReference, type ReportingSetupLogReference, type ReportingSetupOperation, type ReportingSuiteSelector, type RunServiceResult } from "../../evidence/index.js";
|
|
2
|
+
import { writeRunArtifact, type NormalizedArtifactArchiveConfig, type ReportedRunArtifact, type ReportingEnvironmentService, type ReportingExecution, type ReportingMetadata, type ReportingOperationTimingInput, type ReportingRecord, type ReportingServiceLogReference, type ReportingSetupLogReference, type ReportingSetupOperation, type ReportingSuiteSelector, type RunServiceResult } from "../../evidence/index.js";
|
|
3
3
|
interface RunSelection {
|
|
4
4
|
fileNames: readonly string[];
|
|
5
5
|
planning?: null | ReportingRecord;
|
|
@@ -7,6 +7,9 @@ interface RunSelection {
|
|
|
7
7
|
suiteSelectors: readonly ReportingSuiteSelector[];
|
|
8
8
|
typeValues: readonly string[];
|
|
9
9
|
}
|
|
10
|
+
interface RunEnvironmentRecord {
|
|
11
|
+
listServices(): readonly ReportingEnvironmentService[];
|
|
12
|
+
}
|
|
10
13
|
interface RunLogRegistry {
|
|
11
14
|
listServiceLogs(): readonly ReportingServiceLogReference[];
|
|
12
15
|
listSetupLogs(): readonly ReportingSetupLogReference[];
|
|
@@ -23,6 +26,7 @@ export interface RunFinalizationReporter<Result extends RunServiceResult = RunSe
|
|
|
23
26
|
export interface FinalizeRunArtifactsOptions<Result extends RunServiceResult = RunServiceResult> {
|
|
24
27
|
archiveConfig?: NormalizedArtifactArchiveConfig | null;
|
|
25
28
|
declaredEnvironmentKeys?: readonly string[];
|
|
29
|
+
environmentRecord: RunEnvironmentRecord;
|
|
26
30
|
execution: ReportingExecution;
|
|
27
31
|
finishedAt: number;
|
|
28
32
|
logRegistry: RunLogRegistry;
|
|
@@ -48,5 +52,5 @@ export interface FinalizedRunArtifacts {
|
|
|
48
52
|
shouldFailArchivePublication: boolean;
|
|
49
53
|
}
|
|
50
54
|
export type PublishRunFinalization = (artifactStatus: "failed" | "passed", publish: (finalization: Readonly<CommittedRunFinalization>) => FinalizedRunArtifacts) => Promise<FinalizedRunArtifacts>;
|
|
51
|
-
export declare function finalizeRunArtifacts<Result extends RunServiceResult = RunServiceResult>({ productDir, runId, results, startedAt, finishedAt, execution, workerCount, runtimeInstanceCount, runtimeStats, selection, archiveConfig, declaredEnvironmentKeys, metadata, logRegistry, setupRegistry, operationTimingRegistry, reporter, runInterruptReason, runStatus, publishRunFinalization, }: FinalizeRunArtifactsOptions<Result>): Promise<FinalizedRunArtifacts>;
|
|
55
|
+
export declare function finalizeRunArtifacts<Result extends RunServiceResult = RunServiceResult>({ productDir, runId, results, startedAt, finishedAt, execution, workerCount, runtimeInstanceCount, runtimeStats, selection, archiveConfig, declaredEnvironmentKeys, metadata, environmentRecord, logRegistry, setupRegistry, operationTimingRegistry, reporter, runInterruptReason, runStatus, publishRunFinalization, }: FinalizeRunArtifactsOptions<Result>): Promise<FinalizedRunArtifacts>;
|
|
52
56
|
export {};
|