@gfdelarue/testkit 0.6.6 → 0.6.7
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 +134 -0
- package/dist/app/bugs/ci-report.js +2 -2
- package/dist/app/campaigns/brief.js +1 -1
- package/dist/app/cleanup/index.js +1 -1
- package/dist/app/doctor/index.js +1 -1
- package/dist/app/environments/static-values.js +1 -1
- package/dist/app/environments/supplied.js +1 -1
- package/dist/app/environments/task-databases.js +2 -2
- package/dist/app/evidence/ci-status.js +1 -0
- package/dist/app/evidence/ci.js +1 -0
- package/dist/app/evidence/index.js +1 -0
- package/dist/app/evidence/inputs.js +1 -0
- package/dist/app/evidence/report.js +1 -0
- package/dist/app/lint/index.js +1 -1
- package/dist/app/run/engine-evidence-persistence.js +1 -1
- package/dist/app/run/engine-execution-failures.js +1 -1
- package/dist/app/run/engine-execution.js +1 -1
- package/dist/app/run/operation-progress.js +1 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/results.js +1 -1
- package/dist/app/run/run-events.js +1 -1
- package/dist/app/run/run-finalization.js +1 -1
- package/dist/app/run/run-guards.js +3 -3
- package/dist/app/run/scheduler/estimates.js +1 -1
- package/dist/app/run/scheduler/index.js +1 -1
- package/dist/app/run/task-batches.js +1 -1
- package/dist/app/run/worker-loop.js +1 -1
- package/dist/app/status/runs.js +1 -1
- package/dist/app/typecheck/index.js +4 -4
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/commands/evidence.js +2 -0
- package/dist/cli/renderers/destroy/text.js +1 -1
- package/dist/cli/renderers/evidence/text.js +1 -0
- package/dist/cli/renderers/run/failure.js +1 -1
- package/dist/cli/renderers/run/status.js +1 -1
- package/dist/cli/renderers/run/text-reporter.js +23 -23
- package/dist/config/loader/decode-discovery.js +1 -1
- package/dist/config/loader/discover-tests.js +1 -1
- package/dist/config/loader/discovery-result.js +1 -1
- package/dist/config/loader/discovery.js +2 -2
- package/dist/config/loader/env.js +1 -1
- package/dist/config/loader/file-metadata.js +1 -1
- package/dist/config/loader/groups.js +2 -2
- package/dist/config/loader/index.js +1 -1
- package/dist/config/loader/load.js +1 -1
- package/dist/config/loader/path-policy.js +1 -1
- package/dist/config/loader/runtime-isolation.js +1 -1
- package/dist/config/loader/runtime.js +1 -1
- package/dist/config/loader/test-selection.js +1 -1
- package/dist/config/loader/validation.js +1 -1
- package/dist/database/admin.js +4 -4
- package/dist/database/cleanup.js +1 -1
- package/dist/database/configured-steps.js +1 -1
- package/dist/database/docker.js +3 -5
- package/dist/database/index.js +2 -2
- package/dist/database/local-postgres.js +3 -4
- package/dist/database/postgres/index.js +2 -2
- package/dist/database/progress.js +1 -1
- package/dist/database/roles.js +2 -2
- package/dist/database/runtime-config.js +1 -1
- package/dist/database/schema-source.js +3 -3
- package/dist/database/source-url.js +1 -1
- package/dist/database/template-steps.js +3 -3
- package/dist/domain/operation-reason.js +1 -1
- package/dist/domain/suite-selection.js +1 -1
- package/dist/engines/adapters/evidence-files.js +1 -1
- package/dist/engines/adapters/evidence-identity.js +1 -1
- package/dist/engines/adapters/execution.js +1 -1
- package/dist/engines/adapters/node-startup-cache.js +1 -1
- package/dist/engines/adapters/workspace.js +1 -1
- package/dist/engines/contracts/fixture-manifest.js +1 -1
- package/dist/engines/installations/package-resolution.js +1 -1
- package/dist/engines/managed-module-resolution.js +2 -2
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/runtime/data/report.js +3 -3
- package/dist/engines/node-vitest/runtime/data/scenario.js +2 -2
- package/dist/engines/node-vitest/selection.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/config.js +3 -3
- package/dist/engines/playwright/adapter/index.js +1 -1
- package/dist/environment/lifecycle-controller.js +1 -1
- package/dist/environment/template.js +1 -1
- package/dist/evidence/artifacts.js +3 -3
- package/dist/evidence/bundles.js +2 -2
- package/dist/evidence/environment-record.js +1 -1
- package/dist/evidence/index.js +1 -1
- package/dist/evidence/logs.js +2 -2
- package/dist/evidence/operation-timings.js +1 -1
- package/dist/evidence/reading.js +1 -1
- package/dist/evidence/reporting.js +1 -1
- package/dist/evidence/run-evidence.js +1 -1
- package/dist/evidence/run-inputs.js +1 -0
- package/dist/evidence/run-model.js +1 -1
- package/dist/evidence/setup-progress.js +1 -1
- package/dist/evidence/storage-inventory.js +1 -1
- package/dist/evidence/storage.js +2 -2
- package/dist/process/capture-output.js +1 -1
- package/dist/process/execa-bundle.json +3 -3
- package/dist/process/host/platform.js +1 -1
- package/dist/process/locks.js +4 -4
- package/dist/process/process-group.js +1 -1
- package/dist/process/process.js +1 -1
- package/dist/process/ready-deadline.js +1 -1
- package/dist/process/runs/manifest.js +1 -1
- package/dist/process/runs/stop.js +1 -1
- package/dist/process/windows-native.js +1 -1
- package/dist/repo/content.js +4 -0
- package/dist/repo/fingerprint-policy.js +1 -1
- package/dist/repo/managed-git.js +1 -1
- package/dist/repo/metadata.js +1 -1
- package/dist/repo/state.js +4 -4
- package/dist/steps/configured-step-testkit-modules.js +1 -1
- package/dist/steps/configured-steps.js +1 -1
- package/dist/steps/environment.js +1 -1
- package/dist/steps/index.js +1 -1
- package/dist/steps/run.js +2 -2
- package/dist/toolchains/esbuild.js +1 -1
- package/dist/toolchains/index.js +3 -3
- package/dist/toolchains/node-integrity.js +1 -1
- package/dist/toolchains/node-provisioning.js +2 -2
- package/dist/toolchains/semver.js +1 -1
- package/dist/tracker/gitea.js +1 -1
- package/dist/tracker/github.js +1 -1
- package/dist/tracker/requests.js +1 -1
- package/dist/tracker/responses.js +1 -1
- package/docs/bug-records.md +18 -2
- package/docs/release-notes.md +27 -0
- package/package.json +1 -1
- package/dist/app/run/planning-types.js +0 -1
- package/dist/app/run/selection.js +0 -1
- package/dist/config/authoring/http-methods.js +0 -1
- package/dist/evidence/artifact-validation.js +0 -1
package/README.md
CHANGED
|
@@ -114,6 +114,140 @@ steps:
|
|
|
114
114
|
- A job that runs Testkit more than once passes `--context <name>` to each
|
|
115
115
|
report, so each run keeps its own status and its own comment.
|
|
116
116
|
|
|
117
|
+
## Check which results still hold
|
|
118
|
+
|
|
119
|
+
`testkit evidence` reports, for each test file, whether a recorded result
|
|
120
|
+
applies to the code as it is now. Run it before you run the suite: in a new
|
|
121
|
+
worktree, after an edit, or before you push. It runs no tests.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npx testkit evidence
|
|
125
|
+
npx testkit evidence --json
|
|
126
|
+
npx testkit evidence --offline # don't read CI commit statuses
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Every `testkit run` records what it ran against under `inputs` in `run.json`:
|
|
130
|
+
|
|
131
|
+
- the commit, and the Git object ID of every file in the repository that
|
|
132
|
+
differs from it, including untracked files that Git doesn't ignore. Files
|
|
133
|
+
outside the product directory count, so a change to a workspace package
|
|
134
|
+
shows up. A submodule that isn't checked out counts as absent. One whose
|
|
135
|
+
HEAD moved or whose files changed can't be identified, so the run can't
|
|
136
|
+
match;
|
|
137
|
+
- the SHA-256, or that the file is absent, of each dotenv file in the product
|
|
138
|
+
directory and in each service's `local.cwd`: `.env`, `.env.local`,
|
|
139
|
+
`.env.testkit`, and `.env.development`, `.env.test` and `.env.production`
|
|
140
|
+
with their `.local` variants, which Testkit or a framework such as Next.js
|
|
141
|
+
loads, of every `envFile` or `envFiles` entry, and of every other `.env*`
|
|
142
|
+
file that Git ignores in the repository or a checked-out submodule, outside
|
|
143
|
+
`node_modules`;
|
|
144
|
+
- the dependency install records found from the product directory up to the
|
|
145
|
+
filesystem root: `node_modules/.package-lock.json` (npm),
|
|
146
|
+
`node_modules/.pnpm/lock.yaml` (pnpm),
|
|
147
|
+
`node_modules/.yarn-state.yml` and `.yarn/install-state.gz` (Yarn), and
|
|
148
|
+
`node_modules/.yarn-integrity` (Yarn 1). A record inside the repository is
|
|
149
|
+
named relative to the product directory, and one above it by its path
|
|
150
|
+
under your home directory or its absolute path, so worktrees at different
|
|
151
|
+
depths agree;
|
|
152
|
+
- the platform, CPU architecture, Node version, Testkit version, Git
|
|
153
|
+
line-ending settings, and time zone, which `--timezone` or `TZ` sets;
|
|
154
|
+
- `--vitest-config`, `--vitest-project`, `--vitest-runner`, `--vitest-shuffle`,
|
|
155
|
+
`--file-timeout-seconds`, `--task-concurrency`, `--retries`,
|
|
156
|
+
`--ignore-skip-rules`, and `--skip-schema-source-verify`, and whether the run
|
|
157
|
+
used a supplied environment.
|
|
158
|
+
|
|
159
|
+
Testkit reads the files again when the run ends. When a file changed while the
|
|
160
|
+
run was in progress, the record names it, and `testkit evidence` doesn't count
|
|
161
|
+
that run as a match. Testkit compares files Git tracks or doesn't ignore,
|
|
162
|
+
ignored files whose names start with `.env`, install records, and configured env
|
|
163
|
+
files, so tests that write any of these cause this. Have them write elsewhere.
|
|
164
|
+
|
|
165
|
+
`testkit evidence` compares those records with the checkout. It reads the runs
|
|
166
|
+
in this checkout and in the same product directory of every other worktree of
|
|
167
|
+
the repository, so a new worktree sees the results of the main checkout. It
|
|
168
|
+
checks each run's `run.json` against the run's manifest first, and lists any
|
|
169
|
+
run it skips. Each discovered file gets one state:
|
|
170
|
+
|
|
171
|
+
| State | Meaning |
|
|
172
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
173
|
+
| `passed`, `failed`, `skipped` | Runs with identical inputs had this outcome. A failure outranks a newer skip. |
|
|
174
|
+
| `inconsistent` | Runs with identical inputs both passed and failed the file. |
|
|
175
|
+
| `stale` | The file's newest result came from different inputs. The report names that run and lists each changed file, environment field, and option. |
|
|
176
|
+
| `unrun` | No retained run has a result for the file. |
|
|
177
|
+
|
|
178
|
+
Inputs are identical when the files compare equal, every recorded environment
|
|
179
|
+
field and env file matches, the run used none of the options above, and no file
|
|
180
|
+
changed during the run. A run from before this release has no record, so its
|
|
181
|
+
files are `stale`. The report also shows whether each run covered the full
|
|
182
|
+
suite or selected files, and its status, duration and problems, such as a
|
|
183
|
+
failed cleanup. A run on identical inputs that failed before any file ran still
|
|
184
|
+
appears. The report ends with the files that need a run and a
|
|
185
|
+
`testkit run` command for them, with `--dir` when you ran it from another
|
|
186
|
+
directory. When a CI status covers the full suite on this code and
|
|
187
|
+
recorded environment, the report says so above that list, and it says the suite
|
|
188
|
+
passed only when the outcome is `N files passed`, not when CI allowed known
|
|
189
|
+
failures. `--json` gives the command as `rerun`, an array of arguments.
|
|
190
|
+
|
|
191
|
+
The report lists what it compared and what it didn't. It doesn't compare other
|
|
192
|
+
files Git ignores, such as build output, installed package files beyond their
|
|
193
|
+
install record, run options in CI statuses, line endings that Git's conversion
|
|
194
|
+
settings normalize, executable bits when Git's `core.fileMode` is false, as on
|
|
195
|
+
Windows, files edited and restored while a run was in progress,
|
|
196
|
+
environment variable values, the date and time, container images by tag,
|
|
197
|
+
services outside the repository, or files outside it other than install records
|
|
198
|
+
and env files, such as a linked package, or the superproject when the product
|
|
199
|
+
is in a submodule. Testkit never runs a filter driver from Git config, such as
|
|
200
|
+
Git LFS's clean filter, so a file that such a filter would change reads as
|
|
201
|
+
changed. When no dependency install record exists, it says so and doesn't
|
|
202
|
+
compare installed dependencies. When discovery fails, it names the error and
|
|
203
|
+
takes the files from the recorded runs.
|
|
204
|
+
|
|
205
|
+
### CI statuses
|
|
206
|
+
|
|
207
|
+
`testkit ci report` adds what the run covered to its commit status:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
412 files passed · full suite of this commit · linux-x64 Node v24.11.0 · Testkit 0.6.7
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- `full suite` means the run had no file, group, suite, type, or Vitest project
|
|
214
|
+
filter. Any other run says `selected files`.
|
|
215
|
+
- `this commit` means a clean checkout of the commit that holds the status. A
|
|
216
|
+
pull request job that tests the merge commit names the first 20 hex digits of
|
|
217
|
+
the merge tree instead, such as `tree 3f9a2c1b07de4a5b6c7d`. Other runs say
|
|
218
|
+
`a modified checkout` or `a supplied environment`.
|
|
219
|
+
- A product below the repository root adds its directory, such as
|
|
220
|
+
`full suite of this commit in apps/api`.
|
|
221
|
+
- GitHub caps a status description at 140 characters. When a long outcome or
|
|
222
|
+
product path leaves too little room for the facts, the status carries the
|
|
223
|
+
outcome alone and `testkit evidence` can't use it.
|
|
224
|
+
|
|
225
|
+
With a token, found as `ci report` finds one, `testkit evidence` reads the
|
|
226
|
+
commit statuses of HEAD and the nine commits before it on the first-parent
|
|
227
|
+
line. A commit that the server doesn't have counts as one without statuses. It
|
|
228
|
+
reports the Testkit statuses of the nearest commit that has any, with the files
|
|
229
|
+
that changed since that commit and the platform, architecture, Node, and
|
|
230
|
+
Testkit fields that differ. A status gets a file comparison when it covers this
|
|
231
|
+
product directory and either names this commit or names a tree that matches
|
|
232
|
+
that commit or HEAD. A status doesn't record dependency install records, env
|
|
233
|
+
files, line-ending settings, the time zone, or run options such as
|
|
234
|
+
`--file-timeout-seconds`. Without a token or an `origin` remote, the report
|
|
235
|
+
says why it didn't read CI.
|
|
236
|
+
|
|
237
|
+
Testkit reads Git with the repository's own config and the line-ending,
|
|
238
|
+
attributes, and excludes settings from yours. A checkout that Git trusts only
|
|
239
|
+
through a global `safe.directory` entry, such as one in a container job that
|
|
240
|
+
runs as root, records its content as unavailable, so its runs and statuses never
|
|
241
|
+
match.
|
|
242
|
+
|
|
243
|
+
`--json` prints the whole report, with `schemaVersion: 1`. `needsRun` lists
|
|
244
|
+
the files without a local result on this code, and `rerun` is the command for
|
|
245
|
+
them. The list is complete only when `discovery.errors` is empty. When every CI
|
|
246
|
+
status with `exact` true also has `passed` true, CI covers those files: it ran
|
|
247
|
+
the full suite on this code and recorded environment, and every file passed.
|
|
248
|
+
The text report says when nothing needs a run, and shows when each run finished
|
|
249
|
+
and when each CI status was posted.
|
|
250
|
+
|
|
117
251
|
## File a bug as an issue
|
|
118
252
|
|
|
119
253
|
Testkit files a confirmed bug as an issue in the repository's GitHub or Gitea.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`),
|
|
1
|
+
import R from"node:fs";import{publishableUrl as b}from"../../tracker/redaction.js";import q from"node:path";import{readBugPolicy as v}from"../../config/loader/bugs.js";import{loadTestkitConfig as h}from"../../config/loader/index.js";import{resolveCiProvenance as P}from"../../evidence/ci-provenance.js";import{loadLatestRun as U}from"../../evidence/reading.js";import{resolveTracker as S}from"../tracker/index.js";import{ciStatusFacts as D,describeCiStatus as _}from"../evidence/ci-status.js";import{readTree as I}from"../../repo/content.js";import{assessRun as M}from"./assessment.js";import{ciReportMarker as x,readCiContext as F,renderCiSummary as H}from"./ci-summary.js";import{COMMIT_STATUS_DESCRIPTION_MAX_LENGTH as A,describeRunOutcome as E}from"./known-failures.js";import{readRun as L}from"./run-reading.js";import{resolveBugCheckout as O}from"./store.js";const B="Testkit found no results for this commit";export async function reportCiRun(t){const r=F(t.context),e=await O(t),i=v((await h(e.productDir)).config),a={env:t.env,productDir:e.productDir};let l=null;const m=()=>(l??=S(a),l),{repository:c}=i,n=N(e),o=await M(n===null?{kind:"run-failed",reason:B}:L(n,e.productPath),c===null?m:()=>S({...a,repository:c})),p=E(o.outcome,i.knownFailures==="allow"),T=n===null?null:D(n,{...e,mergeTree:await G(e)}),s={...p,description:_(p.description,T,A)},u=e.pullRequestHeadCommit??e.headCommit,d=P(t.env).runUrl,f=H({...o,commit:u,context:r,knownFailuresAllowed:i.knownFailures==="allow",runUrl:d,status:s}),k=j(t.env,f),w=await m(),{creator:y}=await w.setCommitStatus({...s,context:r,sha:u,targetUrl:b(d)}),g=e.pullRequest===null?null:await w.upsertPullRequestComment({author:y,body:f,marker:x(r),pullRequest:e.pullRequest}),C=o.outcome.kind==="tests-failed"?o.outcome.classification:{known:[],new:[]};return{comment:g,commit:u,context:r,description:s.description,known:C.known,new:C.new,pullRequest:e.pullRequest,state:s.state,stepSummary:k,trackerProblem:o.trackerProblem,unreadableIssues:o.unreadableIssues}}function N(t){if(!R.existsSync(q.join(t.productDir,".testkit","results","latest.json")))return null;const r=U(t.productDir);return r.git.commitSha===t.headCommit?r:null}function j(t,r){const e=t.GITHUB_STEP_SUMMARY?.trim()??"";return e.length===0?null:(R.appendFileSync(e,`${r}
|
|
2
|
+
`),e)}async function G(t){return t.pullRequestHeadCommit===null||t.pullRequestHeadCommit===t.headCommit?null:I(t.productDir,t.headCommit)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{BUG_GUIDE as r,renderBugGuide as a}from"../bugs/guide.js";const o=50;export function formatShare(t){return`${(t*100).toFixed(1)}%`}export function formatCount(t,n){return`${String(t)} ${n}${t===1?"":"s"}`}export function campaignSummaryPath(t){return`.testkit/campaigns/${t}/summary.md`}export function renderCampaignBrief(t,n,e){return[`# Testkit campaign ${t.id}`,`Target: ${t.target}`,renderAreaMapReport(n),"## How to work",["- Write Testkit tests for the target.","- A failing test is a bug only when an authority says what the product should do: documentation, a specification, an API contract, or the code's own stated intent.","- Never skip a failing test, and never assert the broken behaviour to make a test pass.","- Keep credentials, tokens, and passwords out of records and tests.","- To check that your tests notice a fault in the target's code, write the fault as a mutant and run `testkit prove --mutants <file>`. `testkit prove --help` lists its options, and the README gives the mutant format. A mutant that survives points at a missing assertion."].join(`
|
|
1
|
+
import{BUG_GUIDE as r,renderBugGuide as a}from"../bugs/guide.js";const o=50;export function formatShare(t){return`${(t*100).toFixed(1)}%`}export function formatCount(t,n){return`${String(t)} ${n}${t===1?"":"s"}`}export function campaignSummaryPath(t){return`.testkit/campaigns/${t}/summary.md`}export function renderCampaignBrief(t,n,e){return[`# Testkit campaign ${t.id}`,`Target: ${t.target}`,renderAreaMapReport(n),"## How to work",["- Write Testkit tests for the target.","- Before you run tests, run `testkit evidence`. It shows which results already hold for this code.","- A failing test is a bug only when an authority says what the product should do: documentation, a specification, an API contract, or the code's own stated intent.","- Never skip a failing test, and never assert the broken behaviour to make a test pass.","- Keep credentials, tokens, and passwords out of records and tests.","- To check that your tests notice a fault in the target's code, write the fault as a mutant and run `testkit prove --mutants <file>`. `testkit prove --help` lists its options, and the README gives the mutant format. A mutant that survives points at a missing assertion."].join(`
|
|
2
2
|
`),"## For each confirmed bug",["1. Write the bug record as a JSON file. The sections below give its format.","2. Run `testkit bug record <file>`. It prints the draft id.","3. Commit and push the test.","4. Run `testkit bug file <id>`."].join(`
|
|
3
3
|
`),...e===""?[]:[`The product directory is \`${e}\`. Run these commands there, or pass \`--dir\`.`],a(r),"## How to end",`Run \`testkit campaign finish\`. It writes the summary for the pull request that adds the tests to \`${campaignSummaryPath(t.id)}\` and prints it.`].join(`
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{formatRunSummary as
|
|
1
|
+
import{formatRunSummary as l}from"../../process/runs/manifest.js";import d from"path";import{pruneEmptyDir as p}from"../../process/files/directory-status.js";import f from"fs";import{loadManagedConfigs as v}from"../../config/loader/managed-configs.js";import{collectBundleCleanupTargets as g}from"../../engines/bundle-cache.js";import{createEnvironmentMaintenance as h}from"../../environment/maintenance.js";import{runtimePlanning as y}from"../../environment/runtime-planning.js";import{resolveProductDir as R}from"../../process/files/product-dir.js";import{createRunEvidencePublisher as C}from"../../evidence/run-evidence.js";export async function cleanupEnvironment(e){const r=R(process.cwd(),e.dir),{allConfigs:t}=await v({dir:r,service:e.service}),n=await cleanup(r,{allConfigs:t,serviceName:e.service||null,dryRun:e.dryRun,cache:e.cache,resources:e.resources}),c={cleaned:a(n.cleaned),stoppedActive:a(n.stoppedActive),skippedActive:a(n.skippedActive)};return{productDir:r,result:n,runSummaries:c}}export async function cleanup(e,r={}){const t=r.allConfigs??[],n=r.serviceName||null,c=r.cache??[],{environment:s,otherCaches:u}=await h(e).maintain({kind:"cleanup",evidence:C(),desiredGraphs:y.collectDesiredRuntimeGraphs(t),serviceName:n,dryRun:!!r.dryRun,resources:!!r.resources},()=>{const i=c.includes("bundles")||c.includes("all")?g(e,{allConfigs:t,serviceName:n}):[],o=[];return{result:{targets:i,cleaned:o},prune(){for(const m of i)f.rmSync(m.path,{force:!0}),o.push(m);p(d.join(e,".testkit","_bundles"))}}});return{...s,targets:{runtime:s.targets,bundles:u.targets},bundleCleaned:u.cleaned}}function a(e){return e.map(r=>({runId:r.runId,summary:l(r)}))}
|
package/dist/app/doctor/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import d from"path";import{randomUUID as f}from"node:crypto";import{loadDiscoveryContext as g,loadConfigContext as p}from"../../config/loader/index.js";import{runLintForConfig as u}from"../lint/index.js";import{isUnknownRecord as h}from"../../kernel/unknown-values.js";export async function runDoctor(t={}){const e=[],s=t.dir?d.resolve(process.cwd(),t.dir):process.cwd(),o=await p({dir:s,configImportCacheSalt:f(),plain:!1});e.push({code:"config-load",level:"pass",message:"Loaded testkit config and service config"});const c=await g({dir:s,diagnostics:"report"},o),n=c.discovery.diagnostics.filter(i=>i.severity==="error");e.push({code:"discovery",level:n.length===0?"pass":"fail",message:n.length===0?`Discovered ${String(c.discovery.files.length)} testkit files without diagnostics`:`Discovery reported ${String(n.length)} error diagnostics`,details:n});const r=await u(o.config,{dir:s});if(e.push({code:"lint",level:r.ok?"pass":"fail",message:r.ok?`No Testkit lint violations across ${String(r.summary.testkitFiles)} suite file(s)`:`Found ${String(r.summary.violations)} Testkit lint violation(s)`,details:r.violations}),t.typecheck!==!1)try{const{runTestkitTypecheckForContext:i}=await import("../typecheck/index.js"),l=await i(o,c.discovery,{dir:s});e.push({code:"typecheck",level:"pass",message:`Typechecked ${String(l.results.length)} generated program(s)`})}catch(i){e.push({code:"typecheck",level:"fail",message:m(i)})}return{ok:!e.some(i=>i.level==="fail"),productDir:s,checks:e}}function m(t){const e=a(t,"result"),s=a(e,"stderr");if(typeof s=="string"&&s.length>0)return s;const o=a(e,"stdout");return typeof o=="string"&&o.length>0?o:t instanceof Error?t.message:String(t)}function a(t,e){return h(t)?Reflect.get(t,e):void 0}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{environmentTaskIdentities as p,PREVIEW_LEASE as h,renderSharedEnvironment as w}from"../run/environment.js";import{withEnvFileValues as
|
|
1
|
+
import{environmentTaskIdentities as p,PREVIEW_LEASE as h,renderSharedEnvironment as w}from"../run/environment.js";import{withEnvFileValues as E}from"../../config/loader/env.js";import{sensitiveKeyMatcher as g}from"../../kernel/redaction-targets.js";import{renderEnvironment as y}from"./config-driven.js";import{hasFileSetup as S}from"./file-setup.js";import{acquiredValueEnvironment as v}from"./task-databases.js";const A=new Set(["bin","lease","leaseDir","pathList","productDir","service","stateDir"]);export function createStaticValueEnvironments(n,o,i=null){const s=new Map,m=e=>{const t=s.get(e.name);if(t!==void 0)return t;const r=E(e);return T(r),s.set(e.name,r),r},l=(e,t,r,a)=>{const u=n.get(e);if(u===void 0)throw new Error(`No services are declared for group "${e}"`);const c={graphKey:t,leaseId:r.id,runtimeId:t,targetNames:[e],services:new Map(u.map(d=>[d.name,{baseUrl:null,database:null,config:m(d)}]))};return y(c,{name:e,uses:u.map(({name:d})=>d)},r,a,g(i?.redactKeys))};return{canAcquire:()=>!0,getGraph:()=>null,getGraphFailure:()=>null,hasSetup:e=>S(o,e),markGraphFailed:()=>{},preview(e){try{return l(e,"",h,()=>{})}catch{return null}},acquire(e,t){const r=i?.acquisition({source:"static",graphKey:e.graphKey,leaseId:t.id,tasks:p(e)});return Promise.resolve().then(()=>{const a=c=>i?.addRedactionTarget(c),u=w(e,c=>l(c,e.graphKey,t,a));return v({environment:u,lease:t,productDir:o,record:r,redact:a},!1)}).catch(a=>{throw r?.failed(a),a})}}}function T(n){const o=[...Object.entries(n.testkit.serviceEnv).map(([i,s])=>[`env.${i}`,s]),["ui",n.testkit.ui]];for(const[i,s]of o)for(const m of f(s))if(!A.has(m))throw new Error(`Service "${n.name}" uses {${m}} in ${i}, which needs a running service, and "${n.name}" starts none`)}function f(n){return typeof n=="string"?[...n.matchAll(/\{([a-zA-Z]+)(?::[^}]+)?\}/gu)].map(o=>o[1]):Array.isArray(n)?n.flatMap(f):n!==null&&typeof n=="object"?Object.values(n).flatMap(f):[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createEnvironment as
|
|
1
|
+
import{createEnvironment as w,environmentTaskIdentities as g,renderSharedEnvironment as y}from"../run/environment.js";import{httpCaptureRedactionTargets as O}from"../run/setup/runtime-node-setup-protocol.js";import{checkPostgresConnection as E,parsePostgresUrl as T}from"../../database/postgres/connection-check.js";import{errorMessage as l}from"../../kernel/error-serialization.js";import{envRedactionTargets as j,jsonRedactionTargets as m,sensitiveKeyMatcher as M,urlRedactionTargets as R}from"../../kernel/redaction-targets.js";import{parseHttpUrl as q,requireHttpResponse as S}from"../../process/readiness.js";import{hasFileSetup as D}from"./file-setup.js";import{acquiredValueEnvironment as $}from"./task-databases.js";const h=3e4;export function createSuppliedEnvironments(t,{productDir:e,record:a=null,signal:o}){const c=M(a?.redactKeys),n=r=>suppliedEnvironment(r,Object.hasOwn(t,r)?t[r]:{});return{canAcquire:()=>!0,checksServices(r){const{databases:d,endpoints:i}=n(r);return Object.keys({...d,...i}).length>0},getGraph:()=>null,getGraphFailure:()=>null,hasSetup:r=>!Object.hasOwn(t,r.targetName)&&D(e,r),markGraphFailed:()=>{},preview(r){try{return n(r)}catch{return null}},async acquire(r,d){const i=Object.hasOwn(t,r.targetName),p=a?.acquisition({source:i?"supplied":"none",graphKey:r.graphKey,leaseId:d.id,tasks:g(r)}),U=i?t[r.targetName]:{};for(const s of C(U,c))a?.addRedactionTarget(s);let f;try{f=y(r,n),await I(r.targetName,f,o)}catch(s){throw o?.aborted&&s===o.reason?p?.cancelled(s):p?.failed(s),s}return $({environment:f,lease:d,productDir:e,record:p,redact:s=>a?.addRedactionTarget(s)},i)}}}function C(t,e){return[...j(t.env??{},e),...[t.baseUrl,t.databaseUrl,...Object.values(t.databases??{}),...Object.values(t.endpoints??{})].flatMap(a=>a?R(a):[]),...m("handles",t.handles??{},e),...Object.values(t.handles??{}).filter(a=>a.kind==="http-capture").flatMap(O),...m("setupData",t.setupData??{},e),...m("ui",t.ui??{},e)]}async function I(t,e,a){const o=b(e.endpoints),c=b(e.databases);for(const[n,r]of o)await u(t,"endpoint",r,()=>q(n),a);for(const[n,r]of c)await u(t,"database",r,()=>{N(n)},a);for(const[n,r]of o)await u(t,"endpoint",r,()=>S(n,{signal:a,timeoutMs:h}),a);for(const[n,r]of c)await u(t,"database",r,()=>E(n,{signal:a,timeoutMs:h}),a)}function b(t){const e=new Map;for(const[a,o]of Object.entries(t))e.has(o)||e.set(o,a);return e}function N(t){try{T(t)}catch(e){throw new Error(`its URL ${l(e)}`,{cause:e})}}async function u(t,e,a,o,c){try{await o()}catch(n){throw c?.aborted?c.reason:new Error(`Supplied ${e} "${a}" for group "${t}" is not usable: ${l(n)}`,{cause:n})}}export function suppliedEnvironment(t,e){return{...w({baseUrl:e.baseUrl??null,databaseUrl:e.databaseUrl||null,databases:e.databases??{},endpoints:e.endpoints??{},env:e.env??{},group:t,...e.setupData===void 0?{}:{setupData:e.setupData},ui:e.ui??null}),...e.handles===void 0?{}:{handles:{...e.handles}}}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{TaskPreparationError as g}from"../run/environment.js";import{bindDatabaseUrl as
|
|
2
|
-
`);return[...e].filter(([
|
|
1
|
+
import{TaskPreparationError as g}from"../run/environment.js";import{bindDatabaseUrl as v}from"../../database/bindings.js";import{isEngineTaskDeadlineExceededReason as y}from"../../engines/api/index.js";import{toError as m}from"../../kernel/error-serialization.js";import{withDatabaseBindings as T}from"../../steps/index.js";import{prepareFileSetup as k}from"./file-setup.js";export function reachedDatabases(e,a){const t=[a.databaseUrl,...Object.values(a.databases),...Object.values(a.env),JSON.stringify(a.ui)].join(`
|
|
2
|
+
`);return[...e].filter(([n])=>t.includes(n))}export async function cloneTaskDatabases(e,a,t,n,o){const l=new Map;for(const r of e.services.values())r.database!==null&&r.config.testkit.database&&l.set(r.database.database,r);const u=reachedDatabases(l,a);if(u.length===0)throw h(t,!1);const d=new Map;try{const{cloneTaskDatabase:r,dropTaskDatabase:s}=await import("../../database/index.js");for(const[i,c]of u){const p={productDir:c.config.productDir,stateDir:c.config.stateDir},b=await r(p,c.config.name,`${n.id}:${String(t.id)}`,{signal:o.signal});d.set(i,{binding:v(b.url),drop:()=>s(p,b.databaseName)})}}catch(r){const s=y(o.signal.reason),i=o.signal.aborted?s?"deadline":"cancelled":"infra";throw new g(i,m(r),await w(d))}const f=r=>d.get(r.database)?.binding??r;let D=null;return{ready:{...e,services:new Map([...e.services].map(([r,s])=>[r,{...s,database:s.database===null?null:f(s.database),config:T(s.config,new Map([...s.config.testkit.templateContext?.databaseBindingsByService??new Map].map(([i,c])=>[i,f(c)])))}]))},release:()=>D??=w(d)}}async function w(e){const a=[];for(const t of e.values())try{await t.drop()}catch(n){a.push({error:m(n),owner:"testkit"})}return a}export function acquiredValueEnvironment(e,a){const{environment:t,record:n}=e;return n?.acquired({runtimeId:null,services:[]}),n?.rendered(t),{environment:t,prepare:async(o,l)=>{if(o.runtimeIsolation==="resetBefore")throw h(o,a);return a?null:k(o,l,e)},release:()=>(n?.released(),Promise.resolve())}}function h(e,a){const t=a?`group "${e.targetName}" runs on supplied values, and Testkit resets only a database it provisions`:`group "${e.targetName}" reaches no database Testkit provisions`;return new g("infra",new Error(`${e.file} sets runtimeIsolation "resetBefore", but ${t}`),[])}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TREE_PREFIX=20;const l=24,p=" \xB7 ",d=new RegExp("^(?<outcome>.+) \xB7 (?<scope>full suite|selected files) of (?:this commit|tree (?<tree>[0-9a-f]{20})|(?<modified>a modified checkout)|(?<supplied>a supplied environment))(?: in (?<product>[^\xB7]+))? \xB7 (?<platform>[a-z0-9]+)-(?<arch>[a-z0-9]+) Node (?<node>v\\d+\\.\\d+\\.\\d+\\S*) \xB7 Testkit (?<testkit>\\S+)$","u");export function ciStatusFacts(e,t){if(e.inputs.kind!=="recorded")return null;const{content:n,duringRun:o,environment:i,options:r}=e.inputs.inputs;return{arch:i.arch,content:r.suppliedEnvironment?{kind:"supplied"}:u(n,o,t),node:i.node,platform:i.platform,product:n.kind==="snapshot"?n.snapshot.product:"",scope:runScope(e),testkit:i.testkit}}export function runScope(e){const{fileNames:t,groupFilter:n,suiteSelectors:o}=e.run.selection,i=e.inputs.kind==="recorded"?e.inputs.inputs.options:null;return t.length===0&&o.length===0&&n===null&&e.run.types.length===1&&e.run.types[0]==="all"&&i?.vitestConfig===null&&i.vitestProject===null?"full suite":"selected files"}export function describeCiStatus(e,t,n){if(t===null)return truncateText(e,n);const o=t.product===""?"":` in ${t.product}`,i=` \xB7 ${t.scope} of ${describeContent(t.content)}${o} \xB7 ${t.platform}-${t.arch} Node ${t.node} \xB7 Testkit ${t.testkit}`;return n-i.length<Math.min(e.length,24)?truncateText(e,n):`${truncateText(e,n-i.length)}${i}`}export function describeContent(e){return e.kind==="commit"?"this commit":e.kind==="tree"?`tree ${e.tree}`:e.kind==="modified"?"a modified checkout":"a supplied environment"}export function parseCiStatus(e){const t=d.exec(e)?.groups;if(t===void 0)return null;const n=r=>t[r],o=n("tree"),i=o!==void 0?{kind:"tree",tree:o}:n("modified")!==void 0?{kind:"modified"}:n("supplied")!==void 0?{kind:"supplied"}:{kind:"commit"};return{facts:{arch:t.arch,content:i,node:t.node,platform:t.platform,product:n("product")??"",scope:t.scope==="full suite"?"full suite":"selected files",testkit:t.testkit},outcome:t.outcome}}function u(e,t,n){if(e.kind!=="snapshot"||t.kind!=="unchanged")return{kind:"modified"};const{snapshot:o}=e;return Object.keys(o.changes).length>0||o.unidentified.length>0?{kind:"modified"}:o.base!==n.headCommit?{kind:"modified"}:n.pullRequestHeadCommit===null||n.pullRequestHeadCommit===n.headCommit?{kind:"commit"}:n.mergeTree===null?{kind:"modified"}:{kind:"tree",tree:n.mergeTree.slice(0,20)}}export function truncateText(e,t){return e.length<=t?e:`${e.slice(0,Math.max(t-3,0))}...`}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{errorMessage as u}from"../../kernel/error-serialization.js";import{compareContent as f,listRecentCommits as m,readTree as l}from"../../repo/content.js";import{resolveTracker as p}from"../tracker/index.js";import{describeContent as h,parseCiStatus as k}from"./ci-status.js";import{environmentDifferences as C,isExact as x}from"./report.js";export const CI_COMMITS_CHECKED=10;const w=/^\d+ files? passed$/u;export const CI_NOT_RECORDED=["dependency install records","env files","line-ending settings","the time zone","run options such as --file-timeout-seconds"];export async function readCiEvidence(e){let o,t;try{o=await m(e.checkout.repoRoot,CI_COMMITS_CHECKED);const r=await p({env:e.env,productDir:e.checkout.productDir});t=await Promise.all(o.map(i=>r.listCommitStatuses(i)))}catch(r){return{kind:"unchecked",reason:u(r)}}const n=r=>newestTestkitStatuses(r).filter(({parsed:i})=>i.facts.product===e.current.product),c=t.findIndex(r=>n(r).length>0);if(c<0)return{commitsChecked:o.length,kind:"none"};const a=o[c],s=[];for(const{parsed:r,record:i}of n(t[c])){const d=await v(e,a,r.facts);s.push({context:i.context,createdAt:i.createdAt,differences:d,...statusVerdict(i.state,r,d),facts:r.facts,outcome:r.outcome,state:i.state,targetUrl:i.targetUrl})}return{commit:a,commitsBack:c,kind:"found",statuses:s}}export function statusVerdict(e,o,t){return{exact:o.facts.scope==="full suite"&&x(t),passed:e==="success"&&w.test(o.outcome)}}export function newestTestkitStatuses(e){const o=new Map;for(const t of e){const n=o.get(t.context);(n===void 0||g(t,n)>0)&&o.set(t.context,t)}return[...o.values()].flatMap(t=>{const n=k(t.description);return n===null?[]:[{parsed:n,record:t}]}).sort((t,n)=>t.record.context.localeCompare(n.record.context))}function g(e,o){return(e.createdAt??"").localeCompare(o.createdAt??"")||e.id-o.id}async function v(e,o,t){const n=[];t.scope!=="full suite"&&n.push("CI ran selected files, not the full suite");const c=C({arch:t.arch,node:t.node,platform:t.platform,testkit:t.testkit},e.environment),a=await D(e,o,t);if(a===null)return n.push(`CI tested ${h(t.content)}, which no commit here matches`),{content:[],environment:c,reasons:n,unidentified:[]};const s=await f(e.checkout.repoRoot,{base:a,changes:{},product:e.current.product,unidentified:[]},e.current,e.cache);return s.kind==="unknown"?{content:[],environment:c,reasons:[...n,s.reason],unidentified:[]}:s.kind==="same"?{content:[],environment:c,reasons:n,unidentified:[]}:{content:s.changes,environment:c,reasons:n,unidentified:s.unidentified}}async function D(e,o,t){if(t.content.kind==="commit")return o;if(t.content.kind!=="tree")return null;for(const n of[o,e.current.base])if((await l(e.checkout.repoRoot,n)).startsWith(t.content.tree))return n;return null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import y from"node:fs";import m from"node:path";import{resolveProductDir as M}from"../../config/loader/index.js";import{loadRunBundleModels as D}from"../../evidence/index.js";import{errorMessage as k}from"../../kernel/error-serialization.js";import{compareContent as C,createContentCache as F,listOtherWorktrees as I}from"../../repo/content.js";import{executeDiscoverOperation as P}from"../discover/index.js";import{readCiEvidence as S}from"./ci.js";import{runScope as E}from"./ci-status.js";import{DEPENDENCY_RECORDS as G,hashRecordedFile as N,readCheckoutInputs as O}from"./inputs.js";import{buildFileView as T,fileDifferences as j,fileOutcome as z,inputDifferences as x}from"./report.js";const A=100,_=8;export async function collectEvidence(n){const e=M(n.cwd,n.dir),o=await L(e,n.cwd),t=await O(e,[],n.env),i={cache:F(),limit:X(_),results:new Map},s=[],r=[];for(const c of await U(t,e,s)){const v=c.location??e;let p;try{p=D(c.productDir)}catch(u){s.push({location:v,reason:k(u)});continue}for(const{reason:u,runId:g}of p.rejected)s.push({location:v,reason:`run ${g}: ${u}`});for(const{run:u,runId:g}of p.models)r.push(V(u.inputs,t,e,i).then($=>({differences:$,durationMs:u.run.durationMs,files:u.groups.flatMap(w=>w.suites.flatMap(R=>R.files.map(b=>({group:w.name,outcome:z(b.status),path:b.path})))),finishedAt:u.run.finishedAt,id:g,location:c.location,problems:Y(u),scope:E(u),status:u.run.status})))}const[a,f,h]=await Promise.all([Promise.all(r),H(t,i.cache),Q(n,t,i.cache)]),l=T(o.files??Z(a),a),d=l.files.filter(c=>c.state==="stale"||c.state==="unrun").map(c=>c.path);return{checkout:f,ci:h,compared:q(t),discovery:{errors:o.errors},files:l.files,needsRun:d,notCompared:B(t,e),product:{directory:e,path:t.checkout?.product??null},rerun:d.length===0?null:K(d,e,n.cwd,o.errors.length>0),runs:l.runs,schemaVersion:1,summary:l.summary,unreadable:s}}async function L(n,e){try{const o=await P({dir:n,diagnostics:"report"},e),t=o.diagnostics.filter(i=>i.severity==="error").map(i=>`${i.code}: ${i.message}`);return{errors:t,files:t.length>0?null:o.files.map(i=>({group:i.group,path:i.path}))}}catch(o){return{errors:[k(o)],files:null}}}async function U(n,e,o){const t=[{location:null,productDir:e}];if(n.checkout===null)return t;const{product:i}=n.checkout;let s;try{s=await I(n.checkout)}catch(r){return o.push({location:n.checkout.repoRoot,reason:`Testkit couldn't list the other worktrees: ${k(r)}`}),t}for(const r of s){const a=m.join(r,i);y.existsSync(a)&&t.push({location:r,productDir:a})}return t}async function V(n,e,o,t){if(n.kind==="missing")return{content:[],environment:[],reasons:["Testkit didn't record what this run ran against"],unidentified:[]};if(n.kind==="unreadable")return{content:[],environment:[],reasons:[`its input record is unreadable: ${n.reason}`],unidentified:[]};const{inputs:i}=n,s=x(i,e.environment),r=[...s.environment,...j(i.environment.envFiles,W(i.environment.envFiles,e,o))],a=[...s.reasons];if(i.content.kind==="unavailable")return a.push(`Testkit couldn't read the files it ran against: ${i.content.reason}`),{content:[],environment:r,reasons:a,unidentified:[]};if(e.content.kind==="unavailable"||e.checkout===null){const c=e.content.kind==="unavailable"?e.content.reason:"no Git checkout";return a.push(`Testkit can't read this checkout's files: ${c}`),{content:[],environment:r,reasons:a,unidentified:[]}}const f=i.content.snapshot,h=JSON.stringify(f);let l=t.results.get(h);if(l===void 0){const{repoRoot:c}=e.checkout,v=e.content.snapshot;l=t.limit(()=>C(c,f,v,t.cache)).catch(p=>({kind:"unknown",reason:k(p)})),t.results.set(h,l)}const d=await l;return d.kind==="same"?{content:[],environment:r,reasons:a,unidentified:[]}:d.kind==="unknown"?{content:[],environment:r,reasons:[...a,d.reason],unidentified:[]}:{content:d.changes,environment:r,reasons:a,unidentified:d.unidentified}}function W(n,e,o){const t=n.map(s=>({path:s.path,sha256:N(o,s.path)})),i=new Set(t.map(s=>s.path));return[...t,...e.environment.envFiles.filter(s=>!i.has(s.path))]}function Y(n){return[...n.groups.flatMap(e=>e.errors.map(o=>`${e.name}: ${o}`)),...n.finalizationFailure===null?[]:[`finalization failed: ${n.finalizationFailure.message}`],...n.archive.cleanup?.status==="failed"?["cleanup failed"]:[],...n.archive.publication?.status==="failed"&&n.archive.publication.required?["publication failed"]:[]]}function q(n){const{dependencies:e}=n.environment;return["every file in the repository that Git tracks or doesn't ignore, as Git stores it","the env files Testkit loads for services, such as .env and .env.testkit, and every ignored .env file in the repository or a checked-out submodule outside node_modules, by SHA-256",...e.length===0?[]:[`dependency install records: ${e.map(o=>o.path).join(", ")}`],"Git line-ending settings","platform, CPU architecture, Node version, Testkit version and time zone","Vitest options, supplied environments, and --file-timeout-seconds, --task-concurrency, --retries, --ignore-skip-rules and --skip-schema-source-verify"]}function B(n,e){return[...n.environment.dependencies.length===0?[`installed dependencies, because no install record (${G.join(", ")}) exists from ${e} up`]:["installed package files beyond their install record"],"other files Git ignores, such as build output","line endings that Git's conversion settings normalize","executable bits, when Git's core.fileMode is false, as on Windows","files edited and restored while a run was in progress","environment variable values","the date and time; each run shows when it finished","container images by tag, such as the local PostgreSQL image, and system tools","services outside the repository, such as remote APIs","files outside the repository other than install records and env files, such as a linked package, or the superproject when the product is in a submodule"]}async function H(n,e){if(n.content.kind==="unavailable")return{kind:"unavailable",reason:n.content.reason};if(n.checkout===null)return{kind:"unavailable",reason:"the product isn't in a Git checkout"};const{snapshot:o}=n.content;try{const t=await J(n.checkout.repoRoot,o,e);return t.kind==="unknown"?{kind:"unavailable",reason:t.reason}:{base:o.base,changes:t.kind==="same"?[]:t.changes,kind:"snapshot",unidentified:o.unidentified}}catch(t){return{kind:"unavailable",reason:k(t)}}}function J(n,e,o){return C(n,{base:e.base,changes:{},product:e.product,unidentified:[]},e,o)}async function Q(n,e,o){return n.ci?e.content.kind==="unavailable"?{kind:"unchecked",reason:e.content.reason}:e.checkout===null?{kind:"unchecked",reason:"the product isn't in a Git checkout"}:S({cache:o,checkout:e.checkout,current:e.content.snapshot,env:n.env,environment:e.environment}):{kind:"unchecked",reason:"--offline skips CI"}}function K(n,e,o,t){const i=y.realpathSync.native(m.resolve(o)),s=y.realpathSync.native(e),r=h=>m.relative(i,h).split(m.sep).join("/"),a=r(s),f=n.length<=A&&!t?n.flatMap(h=>["--file",r(m.join(s,h))]):[];return["testkit","run",...a===""?[]:["--dir",a],...f]}function X(n){let e=0;const o=[];return async t=>{e>=n&&await new Promise(i=>o.push(i)),e+=1;try{return await t()}finally{e-=1,o.shift()?.()}}}function Z(n){const e=new Map;for(const o of n)for(const t of o.files)e.set(`${t.group}\0${t.path}`,{group:t.group,path:t.path});return[...e.values()].sort((o,t)=>o.path.localeCompare(t.path))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import y from"node:crypto";import d from"node:fs";import g from"node:os";import i from"node:path";import{RUN_INPUTS_VERSION as w}from"../../evidence/index.js";import{errorMessage as l}from"../../kernel/error-serialization.js";import{readPackageMetadata as p}from"../../process/host/metadata.js";import{compareContent as S,listIgnoredEnvFiles as F,locateGitCheckout as E,readContent as b}from"../../repo/content.js";export const DOTENV_FILES=[".env",".env.local",".env.testkit",".env.development",".env.development.local",".env.test",".env.test.local",".env.production",".env.production.local"],DEPENDENCY_RECORDS=["node_modules/.package-lock.json","node_modules/.pnpm/lock.yaml","node_modules/.yarn-state.yml","node_modules/.yarn-integrity",".yarn/install-state.gz"];export async function readCheckoutInputs(e,t=[],o=process.env){let n,r;try{n=await E(e,o),r=n===null?{kind:"unavailable",reason:"the product isn't in a Git checkout"}:await h(n)}catch(s){n=null,r={kind:"unavailable",reason:l(s)}}try{const s=n===null?[]:await f(n);return{checkout:n,content:r,environment:m(e,n,[...new Set([...t,...s])].sort())}}catch(s){return{checkout:n,content:{kind:"unavailable",reason:`Testkit couldn't read the environment: ${l(s)}`},environment:{...j(),dependencies:[],envFiles:[],lineEndings:{autocrlf:null,eol:null},timeZone:k()}}}}export async function startRunInputs(e,t){const o=await readCheckoutInputs(e,t.envFiles),n=t.vitest?.config??null,r={overrides:[...t.overrides],suppliedEnvironment:t.suppliedEnvironment,vitestConfig:n===null?null:i.relative(e,n).split(i.sep).join("/"),vitestProject:t.vitest?.project??null,vitestRunner:t.vitest?.runner??"auto"},s=a=>({content:o.content,duringRun:a,environment:o.environment,options:r,version:w});return{finish:async()=>s(await R(o)),unchecked:a=>s({kind:"unchecked",reason:a})}}export function runOverrides(e){return[...e.fileTimeoutSeconds==null?[]:[`--file-timeout-seconds ${String(e.fileTimeoutSeconds)}`],...e.taskConcurrency==null?[]:[`--task-concurrency ${String(e.taskConcurrency)}`],...e.retries===void 0?[]:[`--retries ${String(e.retries)}`],...e.vitest?.shuffle==null?[]:[`--vitest-shuffle ${String(e.vitest.shuffle)}`],...e.ignoreSkipRules===!0?["--ignore-skip-rules"]:[],...e.skipSchemaSourceVerify===!0?["--skip-schema-source-verify"]:[]]}export function envFileCandidates(e){const t=new Set(["."]),o=new Set;for(const{testkit:n}of e){for(const r of n.envFiles)o.add(r);n.local?.cwd!==void 0&&t.add(n.local.cwd)}for(const n of t)for(const r of DOTENV_FILES)o.add(i.posix.join(n,r));return[...o].map(n=>n.split(i.sep).join("/")).sort()}export function hashRecordedFile(e,t){const o=i.resolve(e,t);return d.statSync(o,{throwIfNoEntry:!1})?.isFile()===!0?y.createHash("sha256").update(d.readFileSync(o)).digest("hex"):null}async function h(e){try{const t=await b(e);return"snapshot"in t?{kind:"snapshot",snapshot:t.snapshot}:{kind:"unavailable",reason:t.unavailable}}catch(t){return{kind:"unavailable",reason:l(t)}}}async function R(e){if(e.content.kind==="unavailable")return{kind:"unchecked",reason:e.content.reason};if(e.checkout===null)return{kind:"unchecked",reason:"the product isn't in a Git checkout"};const t=await h(e.checkout);if(t.kind==="unavailable")return{kind:"unchecked",reason:t.reason};try{const o=await S(e.checkout.repoRoot,e.content.snapshot,t.snapshot);if(o.kind==="unknown")return{kind:"unchecked",reason:o.reason};const n=new Set(e.content.snapshot.unidentified),r=e.environment.envFiles.map(c=>c.path),s=[...new Set([...r,...await f(e.checkout)])].sort(),a=m(e.checkout.productDir,e.checkout,s),u=[...o.kind==="same"?[]:o.changes.map(c=>c.path),...t.snapshot.unidentified.filter(c=>!n.has(c)),...v(e.environment.envFiles,a.envFiles),...v(e.environment.dependencies,a.dependencies)];return u.length===0?{kind:"unchanged"}:{kind:"changed",paths:[...new Set(u)].sort()}}catch(o){return{kind:"unchecked",reason:l(o)}}}async function f(e){return(await F(e)).map(t=>i.relative(e.productDir,i.join(e.repoRoot,t)).split(i.sep).join("/"))}function v(e,t){const o=new Map(e.map(r=>[r.path,r.sha256])),n=new Map(t.map(r=>[r.path,r.sha256]));return[...new Set([...o.keys(),...n.keys()])].filter(r=>o.get(r)!==n.get(r))}function j(){return{arch:process.arch,node:process.version,platform:process.platform,testkit:p().version}}function m(e,t,o){const n=t?.productDir??i.resolve(e);return{arch:process.arch,dependencies:x(n,t?.repoRoot??n),envFiles:o.map(r=>({path:r,sha256:hashRecordedFile(n,r)})),lineEndings:{autocrlf:t?.settings.autocrlf??null,eol:t?.settings.eol??null},node:process.version,platform:process.platform,testkit:p().version,timeZone:k()}}function k(){return Intl.DateTimeFormat().resolvedOptions().timeZone}function x(e,t){const o=[];for(let n=e;;n=i.dirname(n)){for(const r of DEPENDENCY_RECORDS){const s=hashRecordedFile(n,r);s!==null&&o.push({path:C(e,t,i.join(n,r)),sha256:s})}if(i.dirname(n)===n)return o}}function C(e,t,o){const n=a=>a.split(i.sep).join("/"),r=i.relative(t,o);if(!r.startsWith("..")&&!i.isAbsolute(r))return n(i.relative(e,o));const s=i.relative(g.homedir(),o);return!s.startsWith("..")&&!i.isAbsolute(s)?`~/${n(s)}`:n(o)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const NO_DIFFERENCES={content:[],environment:[],reasons:[],unidentified:[]};const h="Testkit compares files Git tracks or doesn't ignore, ignored files whose names start with .env, install records, and configured env files; if the tests write any of these, have them write elsewhere.";export function isExact(e){return e.content.length===0&&e.environment.length===0&&e.reasons.length===0&&e.unidentified.length===0}export function fileOutcome(e){return e==="passed"||e==="failed"||e==="skipped"?e:null}export function buildFileView(e,s){const t=[...s].sort((n,c)=>(c.finishedAt??"").localeCompare(n.finishedAt??"")).map(n=>({outcomes:v(n),run:n})),i=e.map(n=>w(n,t)),r=new Set(i.flatMap(n=>n.runs)),o={failed:0,inconsistent:0,passed:0,skipped:0,stale:0,unrun:0};for(const n of i)o[n.state]+=1;return{files:i,runs:t.filter(({run:n})=>r.has(n.id)||isExact(n.differences)&&g(n)).map(({run:n})=>({differences:n.differences,durationMs:n.durationMs,exact:isExact(n.differences),finishedAt:n.finishedAt,id:n.id,location:n.location,problems:n.problems,scope:n.scope,status:n.status})),summary:o}}export function inputDifferences(e,s){const t=[],{duringRun:i,options:r}=e;i.kind==="changed"&&t.push(`files changed while it ran: ${i.paths.join(", ")}. ${h}`),i.kind==="unchecked"&&t.push(`Testkit didn't check its files after it ran: ${i.reason}`),r.suppliedEnvironment&&t.push("it ran against a supplied environment"),r.vitestConfig!==null&&t.push(`it ran with --vitest-config ${r.vitestConfig}`),r.vitestProject!==null&&t.push(`it ran with --vitest-project ${r.vitestProject}`),r.vitestRunner!=="auto"&&t.push(`it ran with --vitest-runner ${r.vitestRunner}`);for(const o of r.overrides)t.push(`it ran with ${o}`);return{environment:environmentDifferences(e.environment,s),reasons:t}}export function environmentDifferences(e,s){const t=[],i=(r,o,n)=>{o!==void 0&&o!==n&&t.push({field:r,here:n,there:o})};return i("platform",e.platform,s.platform),i("arch",e.arch,s.arch),i("node",e.node,s.node),i("testkit",e.testkit,s.testkit),i("time zone",e.timeZone,s.timeZone),e.dependencies!==void 0&&!$(e.dependencies,s.dependencies)&&t.push({field:"dependency install records",here:l(s.dependencies),there:l(e.dependencies)}),e.lineEndings!==void 0&&i("line endings",p(e.lineEndings),p(s.lineEndings)),t}export function fileDifferences(e,s){const t=new Map(s.map(o=>[o.path,o.sha256])),i=new Set(e.map(o=>o.path)),r=s.filter(o=>o.sha256!==null&&!i.has(o.path)).map(o=>({field:`env file ${o.path}`,here:"present",there:"absent"}));return[...m(e,t),...r]}function m(e,s){return e.flatMap(t=>{const i=s.get(t.path)??null;return i===t.sha256?[]:[{field:`env file ${t.path}`,here:i===null?"absent":t.sha256===null?"present":"different content",there:t.sha256===null?"absent":"present"}]})}function g(e){return e.status!=="passed"||e.problems.length>0}function v(e){const s=new Map;for(const t of e.files)t.outcome!==null&&s.set(f(t),t.outcome);return s}function w(e,s){const t=f(e),i=s.flatMap(({outcomes:a,run:d})=>{const u=a.get(t);return u===void 0?[]:[{outcome:u,run:d}]}),r=i.filter(a=>isExact(a.run.differences));if(r.length===0)return i.length===0?{...e,outcome:null,runs:[],state:"unrun"}:{...e,outcome:i[0].outcome,runs:[i[0].run.id],state:"stale"};const o=new Set(r.map(a=>a.outcome)),n=o.has("passed")&&o.has("failed")?"inconsistent":o.has("failed")?"failed":r[0].outcome,c=n==="inconsistent"?r:r.filter(a=>a.outcome===n);return{...e,outcome:n==="inconsistent"?null:n,runs:[...new Set(c.map(a=>a.run.id))],state:n}}function f(e){return`${e.group}\0${e.path}`}function $(e,s){return e.length===s.length&&e.every((t,i)=>t.path===s[i].path&&t.sha256===s[i].sha256)}function l(e){return e.length===0?"none":e.map(s=>`${s.path} ${s.sha256?.slice(0,12)??"absent"}`).join(", ")}function p(e){return`core.autocrlf=${e.autocrlf??"unset"} core.eol=${e.eol??"unset"}`}
|
package/dist/app/lint/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{loadTestkitConfig as u,resolveProductDir as m}from"../../config/loader/index.js";export async function executeLintOperation(t={}){const
|
|
1
|
+
import{loadTestkitConfig as u,resolveProductDir as m}from"../../config/loader/index.js";export async function executeLintOperation(t={}){const n=m(process.cwd(),t.dir),{config:i}=await u(n);return runLintForConfig(i,{dir:n})}import c from"node:fs";import r from"node:path";import{normalizeLintOptions as p,normalizeLintConfig as l}from"./options.js";import{rejectRetiredTestFile as d}from"../../domain/retired-test-types.js";import{collectCurrentFiles as g,collectTrackedFiles as w,isTestkitSuitePath as y}from"./shared.js";export async function runLint(t={}){const{dir:n,...i}=t,o=r.resolve(process.cwd(),n||".");return a(o,p(i))}export async function runLintForConfig(t,n={}){const i=r.resolve(process.cwd(),n.dir||".");return a(i,l(t))}async function a(t,n){const i=await w(t),o=await g(t,i!==null);for(const f of o)d(f);const e=o.length===0?[]:await(await import("./native.js")).lintProject({productDir:t,files:o,options:n,trackedFiles:i}),s={ok:e.length===0,productDir:t,summary:{files:o.length,testkitFiles:o.filter(y).length,violations:e.length},violations:e};return h(t,s),s}export function normalizeLintConfig(t={}){return l(t)}function h(t,n){try{const i=r.join(t,".testkit","lint");c.mkdirSync(i,{recursive:!0}),c.writeFileSync(r.join(i,"latest.json"),`${JSON.stringify(n,null,2)}
|
|
2
2
|
`,"utf8")}catch{}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{persistFileEvidence as
|
|
1
|
+
import{persistFileEvidence as g,fileEvidenceBudgetError as y}from"../../evidence/file-evidence.js";import{MAX_PERSISTED_FILE_ARTIFACT_BYTES as m,persistTaskArtifacts as p,persistTaskOutputArtifacts as l}from"../../evidence/index.js";import{toError as v}from"../../kernel/error-serialization.js";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=g({artifactRoot:e,artifacts:t.artifacts,productDir:i,task:o,fileEvidenceBudget:a}),f=l(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,...p(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=E(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=m){return{countsByCategory:{artifacts:new Map,logs:new Map,metrics:new Map,native:new Map},fileEvidence:{maximumBytes:Math.min(e,m),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}: ${v(t).message}`,{cause:t})}function E(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 y(e.fileEvidence.maximumBytes);e.fileEvidence.persistedBytes=i},maximumBytes(){return e.fileEvidence.maximumBytes},remainingBytes(){return e.fileEvidence.maximumBytes-e.fileEvidence.persistedBytes}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isEngineTaskDeadlineExceededReason as k}from"../../engines/api/index.js";import{TaskPreparationError as g}from"./environment.js";import{toError as m}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;state;cause;constructor(r,n,
|
|
1
|
+
import{isEngineTaskDeadlineExceededReason as k}from"../../engines/api/index.js";import{TaskPreparationError as g}from"./environment.js";import{toError as m}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;state;cause;constructor(r,n,i,t,a,c,s){super(r,{cause:n}),this.name="EngineExecutionBridgeError",this.cause=n,this.disposition=Object.freeze({...i}),this.failingTaskId=c,this.artifactReferencesByTaskId=t,this.completedOutcomesByTaskId=a,this.state=s}}export class EngineExecutionReporterCallbackError extends Error{cancelled;cause;constructor(r,n){const i=m(n);super(`Engine reporter ${r} callback failed: ${i.message}`,{cause:n}),this.name="EngineExecutionReporterCallbackError",this.cancelled=R(i)===ENGINE_REPORTER_CALLBACK_CANCELLED_CODE,this.cause=n}}export function buildBridgeError(e,r,n,i,t,a){const{operationErrors:c}=e,s=r.map(u=>u.error),l=[...c,...s],d=c.map(u=>u.message).join("; "),f=s.map(u=>u.message).join("; "),p=c.length===0?`Engine node setup cleanup failed: ${f}`:s.length===0?d:`${d}; node setup cleanup also failed: ${f}`,o=l.length===1?l[0]:new AggregateError(l,"Engine execution bridge failed");return new EngineExecutionBridgeError(p,o,n,i,t,r[0]?.taskId??a,e)}export function preparationFailure(e,r){const n=e instanceof g?e.cause:e;return E(n,y(e),r)}export function taskStartedFailure(e,r){return E(e,reporterBridgeDisposition(),r)}function E(e,r,n){const i={activeDisposition:r,observedInfraOperationFailure:r.classification==="infra",operationErrors:[m(e)]};return buildBridgeError(i,[],resolveBridgeDisposition(i,[]),new Map,new Map,n)}export async function settlePreparedTask(e,r,n,i){const t=r.map(o=>({...o,taskId:n})),a="error"in e?e.error:null,c="outcomes"in e?e.outcomes:[];let s=a instanceof EngineExecutionBridgeError?a.state:a!==null?{activeDisposition:infraBridgeDisposition(),observedInfraOperationFailure:!0,operationErrors:[m(a)]}:{activeDisposition:infraBridgeDisposition(c.some(o=>o.infraFailed&&o.blockedByRuntime)?"fail-graph":"none"),observedInfraOperationFailure:!1,operationErrors:[]};const l=new Map(a instanceof EngineExecutionBridgeError?a.completedOutcomesByTaskId:c.map(o=>[String(o.task.id),o])),d=a instanceof EngineExecutionBridgeError?a.artifactReferencesByTaskId:new Map(c.map(o=>[String(o.task.id),o.artifacts]));h(l,t);const f=l.get(n);let p=!1;if(f!==void 0)try{await i(f)}catch(o){p=!0;const u=a===null&&t.length===0;s={activeDisposition:u?reporterBridgeDisposition():s.activeDisposition,observedInfraOperationFailure:u||s.observedInfraOperationFailure,operationErrors:[...s.operationErrors,m(o)]}}return t.length===0&&!p?null:buildBridgeError(s,t,resolveBridgeDisposition(s,t),d,l,n)}function h(e,r){const n=new Map;for(const i of r){if(i.owner!=="consumer")continue;const t=n.get(i.taskId)??[];t.push(i.error),n.set(i.taskId,t)}for(const[i,t]of n){const a=e.get(i);if(a===void 0)continue;const c=`Runtime node setup consumer cleanup failed: ${t.map(s=>s.message).join("; ")}`;e.set(i,{...a,error:a.error===null?c:`${a.error}; ${c}`,failureDetails:[...a.failureDetails,...t.map(I)],reason:null,status:"failed"})}}function I(e){return{actual:{owner:"consumer"},classification:"product",count:1,key:"runtime_node_setup_consumer_cleanup_failed",kind:"runtime-node-setup-cleanup",message:e.message,...e.stack===void 0?{}:{stack:e.stack},title:"Runtime node setup consumer cleanup failed"}}export function resolveBridgeDisposition({activeDisposition:e,observedInfraOperationFailure:r,operationErrors:n},i){return i.length>0?e.classification==="infra"&&e.runtimeImpact==="fail-graph"?infraBridgeDisposition("fail-graph"):i.some(t=>t.owner==="testkit")?infraBridgeDisposition("invalidate-lease"):n.length>0&&n.every(t=>t instanceof EngineExecutionReporterCallbackError)?n.every(t=>t instanceof EngineExecutionReporterCallbackError&&t.cancelled)?productBridgeDisposition("invalidate-lease"):reporterBridgeDisposition("invalidate-lease"):r||n.length>0&&e.classification==="infra"?infraBridgeDisposition(e.runtimeImpact==="fail-graph"?"fail-graph":"invalidate-lease"):productBridgeDisposition("invalidate-lease"):n.length>0&&n.every(t=>t instanceof EngineExecutionReporterCallbackError)?n.every(t=>t instanceof EngineExecutionReporterCallbackError&&t.cancelled)?cancelledBridgeDisposition():reporterBridgeDisposition(e.runtimeImpact):r&&e.classification!=="infra"?infraBridgeDisposition("none"):{...e}}export function managerFailureBridgeDisposition(e,r,n){return!r.aborted||n!==r.reason?infraBridgeDisposition("none"):e.aborted&&n===e.reason?cancelledBridgeDisposition():k(r.reason)?productBridgeDisposition():infraBridgeDisposition("none")}function y(e){return e instanceof g?e.kind==="cancelled"?cancelledBridgeDisposition():e.kind==="infra"?infraBridgeDisposition("invalidate-lease"):productBridgeDisposition(e.cleanupFailures.length>0?"invalidate-lease":"none"):infraBridgeDisposition("invalidate-lease")}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 R(e){return e.code}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import q from"node:fs";import G from"node:path";import{EngineTaskDeadlineExceededError as ie}from"../../engines/api/index.js";import"../../domain/identifiers.js";import"../../domain/test-outcomes.js";import{resolveExecutionProfile as oe}from"../../engines/catalog.js";import{engineSupportInputs as se,defaultEnvironmentValues as ae,engineInputEnvironment as ce}from"./engine-orchestration.js";import{persistTaskArtifacts as de}from"../../evidence/index.js";import{createEvidenceLedger as K,persistTrackedEngineEvidence as H,requireTaskArtifacts as Q,requireTaskEvidenceLedger as ue}from"./engine-evidence-persistence.js";import{buildBridgeError as fe,infraBridgeDisposition as A,managerFailureBridgeDisposition as le,productBridgeDisposition as X,reporterBridgeDisposition as Y,resolveBridgeDisposition as pe}from"./engine-execution-failures.js";import{DEFAULT_ENGINE_REPORTER_CALLBACK_TIMEOUT_MS as me,invokeEngineReporterCallback as Z}from"./engine-execution-reporting.js";import{createEngineExecutionObserver as ge}from"./engine-execution-observer.js";import{toError as B}from"../../kernel/error-serialization.js";export{EngineExecutionBridgeError}from"./engine-execution-failures.js";import{collectFailureDetailsFromRuntimeArtifacts as Ee}from"./failure-details.js";import{buildRuntimeFixtureManifest as he}from"./runtime-fixtures.js";import{readEnvironmentValueMissingFailure as ke}from"../../engines/contracts/environment-values.js";export async function executeEngineTask(e,{environment:t,lease:n,request:o,started:r,task:i},c,k=null){const T={signal:c.signal,timeoutMs:me},m="batchTasks"in i?i.batchTasks:[i],M=m[0],O={name:M.targetName},y=o.productDir,R=oe(M),te=normalizeTimeoutMs(o.fileTimeoutSeconds),v=ye(n,y),I=new Map(m.map(a=>[String(a.id),[]])),L=new Map(m.map(a=>[String(a.id),K()])),D=new Map,d=[];let g=X(),b=!1;const J=[];let F=null;const C=new Set,$=async(a,h)=>{const u=String(a.id);u===r?.taskId&&r.prepared!==null||C.has(u)||(C.add(u),await Z("taskFinished",()=>k?.taskFinished?.(a,h),T))};try{g=A();const a=m.find(u=>String(u.id)===r?.taskId);if(r!==null&&r.prepared!==null&&a!==void 0){F=r.taskId;for(const u of r.prepared.artifacts){const f=de(y,a,[u]);I.get(r.taskId)?.push(...f)}}q.mkdirSync(v.temporaryDirectory,{recursive:!0}),q.mkdirSync(v.artifactDirectory,{recursive:!0});const h=R.taskBatching==="selection"?[m]:m.map(u=>[u]);for(const[u,f]of h.entries()){const V=te*f.length,j=[];for(const s of f){g=Y();const l=String(s.id);F=l;const E=r?.taskId===l?r:null;E===null&&await Z("taskStarted",()=>k?.taskStarted?.(s,O),T),g=X();const p={environment:t,request:o,runtimeEnvironment:ae(t),task:s},{configuration:w,supportModules:S}=se(p);j.push({id:l,file:s.file,serviceName:s.serviceName,suiteName:s.suiteName,...s.testType===void 0?{}:{testType:s.testType},timeoutMs:V,inputs:{...w===void 0?{}:{configuration:w},environment:ce(p),fixtures:Me(he(y,s.file),`fixture manifest for ${s.file}`),setupData:ee(E!==null&&E.prepared!==null?E.prepared.setupData:t.setupData,`node setup data for ${s.file}`),supportModules:S},orchestration:{lease:{directory:n.directory,id:n.id},mode:"test"}})}const P=r?.deadlineMs??buildTaskDeadline(Date.now(),V);g=A();const ne=d.length,z=K(),U=f.length===1?u:void 0,re=ge({artifactReferencesByTaskId:I,completedOutcomesByTaskId:D,errors:d,executableTask:i,invocationEvidenceLedger:z,productDir:y,taskBatchIndex:U,taskEvidenceLedgers:L,tasks:f,tasksById:new Map(f.map(s=>[String(s.id),s])),workspaceArtifactDirectory:v.artifactDirectory},mapEngineOutcome),N=createDeadlineSignal(c.signal,P);try{const s={invocationId:[c.runId,n.id,we(f[0])].join(":"),observer:re,profile:R,signal:N.signal,target:{baseUrl:t.baseUrl,databases:t.databases,endpoints:t.endpoints,handles:{...t.handles,...r===null||r.prepared===null?{}:r.prepared.handles},inputs:ve()},tasks:j.map(p=>({...p,deadlineMs:P})),workspace:v};let l;try{l=await e.execute(s);for(const p of l.runErrors??[])c.recordRunError?.(p)}catch(p){throw g=le(c.signal,N.signal,p),p}for(const{outcome:p,task:w}of mapEngineOutcomes(f,l)){const S=String(w.id),W=Q(I,S);H(p.evidence,{artifactRoot:v.artifactDirectory,errors:d,ledger:ue(L,S),mode:"reconcile",productDir:y,references:W,scope:"task",task:w});const x=mapEngineOutcome(p,w,W,i,U);D.set(S,x),x.infraFailed&&x.blockedByRuntime&&(g=A("fail-graph"))}const E=Se(l);Te(E)&&H(E,{artifactRoot:v.artifactDirectory,errors:d,ledger:z,mode:"reconcile",productDir:y,references:Q(I,String(f[0].id)),scope:"invocation",task:f[0]})}finally{N.dispose()}if(d.length>ne&&(b=!0),d.length>0)break;for(const s of f){const l=D.get(String(s.id));if(l===void 0){d.push(new Error(`Engine task ${String(s.id)} completed without a mapped outcome`)),b=!0;break}J.push(l),g=Y();try{await $(s,l)}catch(E){d.push(B(E)),b=!0;break}}if(d.length>0)break}}catch(a){const h=d.length;d.push(B(a)),(h>0||g.classification==="infra")&&(b=!0)}for(const a of m){const h=D.get(String(a.id));if(h!==void 0)try{await $(a,h)}catch(u){d.push(B(u))}}if(d.length>0){const a={activeDisposition:g,observedInfraOperationFailure:b,operationErrors:d};throw fe(a,[],pe(a,[]),I,D,F)}return{outcomes:J}}export function mapEngineOutcomes(e,t){const n=new Map(e.map(i=>[String(i.id),i])),o=new Map;for(const i of t.outcomes){const c=n.get(i.taskId);if(!c)throw new Error(`Engine manager returned an outcome for unknown task ${i.taskId}`);if(o.has(i.taskId))throw new Error(`Engine manager returned duplicate outcomes for task ${i.taskId}`);o.set(i.taskId,{task:c,outcome:i})}const r=e.map(i=>String(i.id)).filter(i=>!o.has(i));if(r.length>0)throw new Error(`Engine manager did not return outcomes for task(s): ${r.join(", ")}`);return e.map(i=>{const c=o.get(String(i.id));if(!c)throw new Error("Engine outcome mapping invariant failed");return c})}function ye(e,t){return{rootDirectory:t,cacheDirectory:e.engineCacheDirectory,temporaryDirectory:G.join(e.engineWorkspaceDirectory,"temporary"),artifactDirectory:G.join(e.engineWorkspaceDirectory,"artifacts")}}function ve(){return{environment:{},fixtures:{},supportModules:{}}}function we(e){return"batchId"in e?e.batchId:String(e.id)}export function normalizeTimeoutMs(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}export function buildTaskDeadline(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}export function createDeadlineSignal(e,t){const n=new AbortController;let o=null;const r=()=>{n.abort(e.reason)},i=()=>{const c=t-Date.now();if(c<=0){n.abort(new ie(t));return}o=setTimeout(i,Math.min(c,2147483647)),o.unref()};return e.aborted?r():(e.addEventListener("abort",r,{once:!0}),i()),{signal:n.signal,dispose(){o!==null&&clearTimeout(o),e.removeEventListener("abort",r)}}}export function mapEngineOutcome(e,t,n,o,r){const i=Ee(e.evidence.artifacts),c=t.engineId==="node-vitest"&&i.some(O=>O.kind==="vitest-test")&&e.failure!==null&&De(e.failure),k=ke(e.failure),T=[...e.failure&&!c?[be(e.failure,k===null)]:[],...i],m=e.status==="not_run",M=m||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:M,blockedByRuntime:m,durationMs:e.durationMs,startedAt:e.startedAtMs,finishedAt:e.finishedAtMs,artifacts:n,failureDetails:T,...k===null?{}:{environmentValuesMissing:k},...Ie(o,t,r)}}function Ie(e,t,n){if(!("batchTasks"in e))return{};const o=n??e.batchTasks.findIndex(r=>r.id===t.id&&r.file===t.file);if(o<0)throw new Error(`Task ${String(t.id)} is missing from its engine batch`);return{runtimeBatch:{id:e.batchId,index:o,fileCount:e.batchTasks.length,files:e.batchTasks.map(r=>r.file)}}}function De(e){return e.code==="NODE_VITEST_REPORT_FAILED"?!0:e.code==="NODE_VITEST_PROCESS_FAILED"&&e.details?.exitCode===1}function be(e,t){const n=e.code??"ENGINE_TASK_FAILED",o={code:n,details:!t||e.details===void 0?null:Oe(e.details)};return{kind:"engine-failure",key:n,title:n,...e.classification===void 0?{}:{classification:e.classification},count:1,message:e.message,actual:o,...e.stack===void 0?{}:{stack:e.stack}}}function Se(e){return{artifacts:_(e.evidence.artifacts,e.outcomes.flatMap(t=>t.evidence.artifacts)),logs:_(e.evidence.logs,e.outcomes.flatMap(t=>t.evidence.logs)),metrics:_(e.evidence.metrics,e.outcomes.flatMap(t=>t.evidence.metrics)),...e.evidence.native===void 0?{}:{native:e.evidence.native}}}function _(e,t){const n=new Map;for(const o of t){const r=JSON.stringify(o);n.set(r,(n.get(r)??0)+1)}return e.filter(o=>{const r=JSON.stringify(o),i=n.get(r)??0;return i===0?!0:(n.set(r,i-1),!1)})}function Te(e){return e.artifacts.length>0||e.logs.length>0||e.metrics.length>0||e.native!==void 0}function Me(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 Oe(e){return JSON.parse(JSON.stringify(e))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function createOperationReporterBridge(t=null){const n=new Map;return function(a=[]){if(t)for(const s of a){const e=n.get(s.id)||null,i=operationProgressSignature(s);if(!e){n.set(s.id,{status:s.status,signature:i}),s.status==="running"?t.operationStarted?.(s):t.operationFinished?.(s);continue}e.signature!==i&&s.status==="running"&&t.operationProgress?.(s),e.status==="running"&&s.status!=="running"&&t.operationFinished?.(s),n.set(s.id,{status:s.status,signature:i})}}}export function operationProgressSignature(t={}){
|
|
1
|
+
export function createOperationReporterBridge(t=null){const n=new Map;return function(a=[]){if(t)for(const s of a){const e=n.get(s.id)||null,i=operationProgressSignature(s);if(!e){n.set(s.id,{status:s.status,signature:i}),s.status==="running"?t.operationStarted?.(s):t.operationFinished?.(s);continue}e.signature!==i&&s.status==="running"&&t.operationProgress?.(s),e.status==="running"&&s.status!=="running"&&t.operationFinished?.(s),n.set(s.id,{status:s.status,signature:i})}}}export function operationProgressSignature(t={}){return JSON.stringify({status:t.status||"",summary:t.summary||"",message:t.message||"",error:t.error||"",metadata:t.metadata||{},logRefs:t.logRefs||[]})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveRuntimeClosure as Ee}from"../../config/loader/dependencies.js";import{claimNextTask as Ke,collectSuites as Te}from"./planning.js";import{buildRunPlanningMetadata as Ae,buildScheduledQueue as be}from"./scheduler/index.js";import{addTrackerError as Me,buildServiceTrackers as xe,finalizeServiceResult as ge,recordGraphError as Ie,recordTaskOutcome as Pe,resolveRunGateFailure as qe}from"./results.js";import{loadTimings as ze,saveTimings as Oe}from"../../evidence/index.js";import{writeRunArtifact as Ne}from"../../evidence/artifacts.js";import{stableJson as $e}from"../../evidence/storage.js";import{createRunEnvironmentRecord as Le}from"../../evidence/environment-record.js";import{createRunLogRegistry as je}from"../../evidence/logs.js";import{createOperationTimingRegistry as De,timeOperation as J}from"../../evidence/operation-timings.js";import{createRunReporterFromListener as Ge}from"./run-events.js";import{createCallerRedaction as Ve}from"./caller-redaction.js";import{createSetupOperationRegistry as Be}from"../../evidence/setup-progress.js";import{createLocalRunEnvironment as We}from"../../environment/run-environment.js";import{writeRunMaintenance as Ue}from"../../evidence/maintenance.js";import{createConfigDrivenEnvironments as _e,planConfiguredCapacity as He}from"../environments/config-driven.js";import{declaresProvisioning as Je,declaresValues as he,holdBeforeRelease as Qe,routeEnvironments as Xe}from"../environments/routing.js";import{createStaticValueEnvironments as Ye}from"../environments/static-values.js";import{createSuppliedEnvironments as Ze}from"../environments/supplied.js";import{environmentTaskIdentity as et}from"./environment.js";import{createTaskLeases as tt}from"./task-leases.js";import{createTaskLocks as rt}from"./scheduler/locks.js";import{readPackageMetadata as nt,safeHostname as it,safeUsername as at}from"../../process/host/metadata.js";import{collectGitMetadata as st}from"../../repo/metadata.js";import{resolveExecutionConfig as ot}from"../../config/loader/decode-execution.js";import{projectGroupTestPolicy as ct}from"../../config/loader/discover-tests.js";import{resolveTaskPolicy as ut}from"../../config/loader/test-selection.js";import"../../environment/run-environment.js";import{createEngineManager as lt}from"../../engines/factory.js";import{createWorker as mt,runWorker as ft}from"./worker-loop.js";import{ensureRequestedFilesMatch as dt}from"./run-guards.js";import{createLiveSnapshotWriter as pt}from"./live-run.js";import{finalizeRunArtifacts as gt}from"./run-finalization.js";import{createRunFinisher as ht}from"./run-finisher.js";import{createRunEvidencePublisher as yt}from"../../evidence/run-evidence.js";import{buildRunArtifact as vt}from"../../evidence/reporting.js";import{createOperationReporterBridge as kt}from"./operation-progress.js";import{formatError as z,serializeError as Z}from"../../kernel/error-serialization.js";export async function runAll(e,a,o,n,m=[]){const g=n.fileNames||[];dt(e,a,o,g);const{productDir:c}=n,p=We(c),y=lt(c),v=new Map(m.map(r=>[r.name,r])),t=Date.now(),S=e[0]?.artifacts??null,E=[...new Set(e.flatMap(r=>r.uses.flatMap(l=>Object.keys(v.get(l)?.testkit.serviceEnv??{}))))].sort(),f={git:await st(c),host:{hostname:it(),username:at()},testkitVersion:nt().version},C=e[0]?.redact??[],R=Ve(process.env,C),ee=n.onEvent;let te=()=>{};const k=ee?Ge(r=>{r.type==="run.task_started"&&te(),ee(R.redact(r))},{debug:n.debug}):null,M=je(c),B={workerCount:0},O=resolveRunExecutionConfig(e[0],n),W=collectServicePlans(e,v,a,o,n,O,k);k?.setServicePlans?.(W);const N=xe(W,t),$=(r,l="product")=>{for(const s of N.values())s.skipped||Me(s,r,{classification:l});k?.runError?.(r)},ve=o.map(r=>({...r})),Q={typeValues:a,suiteSelectors:ve,fileNames:g,groupFilter:n.serviceFilter||null,planning:null};let w=()=>{};const ke=kt(k),b=De({onChange:r=>{ke(r),w()}}),re=Be({logRegistry:M,onChange:()=>{w()}}),d=Le({logRegistry:M,redactKeys:C,setupRegistry:re,onPublishedChange:()=>{w()}});R.useSourceTargets(()=>d.redactionTargets()),d.recordGroups(W.map(r=>({dbBackend:r.dbBackend,name:r.config.name})));const X=W.filter(r=>!r.skipped),ne=n.environments?.supplied??{},{plans:x,provisionedGraphKeys:ie,staticGraphKeys:ae}=assignRuntimeGraphs(X,new Set(Object.keys(ne)));let h;const u=await p.start({evidence:yt({redactKeys:C,redactionTargets:()=>d.redactionTargets(),resetResults:!0}),keepAlive:!0}),L=ht(u),U=pt({productDir:c,runId:u.runId,configs:e,trackers:N,startedAt:t,execution:O,workerState:B,selection:Q,archiveConfig:S,declaredEnvironmentKeys:E,metadata:f,environmentRecord:d,operationTimingRegistry:b,reporter:k});w=()=>{U.update()},te=()=>{U.flush()};const j={artifacts:null},D={result:null},se=r=>{const l=R.redact(r);try{return Ue(c,u.runId,l),l}catch(s){const F=`Could not write the maintenance record: ${R.redact(z(s))}`;return{...l,status:"failed",error:l.error===null?F:`${l.error}; ${F}`}}},oe=r=>(D.result=r.maintenance,r.maintenance.status==="failed"&&k?.warning?.(`Post-run maintenance failed: ${r.maintenance.error??"unknown error"}`),r.sealed),Re=async(r,l)=>(u.beginFinalization(),oe(await L.finish(r,s=>{const F=l(s);return j.artifacts={runArtifact:F.runArtifact},F},"normal",se)));let K,I;const ce=(r,l,s)=>({productDir:c,runId:u.runId,results:r,startedAt:t,finishedAt:l,execution:O,workerCount:B.workerCount,selection:Q,archiveConfig:S,declaredEnvironmentKeys:E,metadata:f,environmentRecord:d,operationTimingRegistry:b,reporter:k,runInterruptReason:u.getInterruptReason(),runStatus:s,publishRunFinalization:Re});w();let T=null,ue=Symbol("no release failure");try{try{if(X.length>0){const s=ze(c),F=_e(u,new Map(x.map(i=>[i.config.name,i.config.uses])),c,d),we=Ye(new Map(x.filter(i=>ae.has(i.runtimeKey)).map(i=>[i.config.name,i.uses])),c,d),Se=Ze(ne,{productDir:c,record:d,signal:u.signal}),le=i=>ie.has(i)?F:ae.has(i)?we:Se,me=Xe(new Map(x.map(({runtimeKey:i})=>[i,le(i)]))),{capacity:_,queue:G}=await J(b,{name:"testkit.plan",kind:"planning",summary:"plan test execution",scope:{serviceCount:X.length}},()=>({capacity:He(x.filter(i=>ie.has(i.runtimeKey))),queue:be(x.map(i=>prepareSchedulerPlan(i,A=>me.hasSetup({file:A,graphKey:i.runtimeKey,targetName:i.config.name}))),{timings:s})}));Q.planning=Ae(G),d.recordCapacity(_),k?.setTotalFileCount?.(G.length),B.workerCount=Math.max(1,Math.min(O.taskConcurrency,G.length));const fe=Array.from({length:B.workerCount},(i,A)=>mt(A+1,c));_.graphs.length>0&&u.provision(_.graphs,{reporter:k,environmentRecord:d,logRegistry:M,setupRegistry:re,operationTimingRegistry:b,skipSchemaSourceVerify:n.skipSchemaSourceVerify});const de=tt(c),Ce={recordRunError:$,runId:u.runId,signal:u.signal,isStopRequested:()=>u.isStopRequested(),environments:Ft(me,n.environments?.hold),leases:St(de,d),locks:rt(),request:Et(c,e,O,n.vitest,n.retries),environmentKeys:new Map(x.flatMap(({config:i,runtimeKey:A})=>{const P=le(A),q=P.preview?.(i.name)??null;return q===null?[]:[[i.name,$e([q,P.checksServices?.(i.name)??!1])]]}))},pe=[];try{const A=(await J(b,{name:"testkit.execute",kind:"execution",summary:"execute local worker queue",scope:{workerCount:fe.length,taskCount:G.length}},async()=>Promise.allSettled(fe.map(P=>ft(P,G,Ce,y,N,pe,Ke,(q,H,V,Y)=>{Pe(q,H,V,Y),V.environmentValuesMissing!==void 0&&d.recordMissingValues(et(H),V.environmentValuesMissing),w()},(q,H,V,Y,Fe)=>{Ie(q,H,V,Y,Fe),w()},k))))).flatMap(workerResultErrorsForOrchestrator);for(const P of A)$(P);A.length>0&&w(),d.recordRuntimeStats(u.getStats())}finally{_.graphs.length>0&&await J(b,{name:"testkit.cleanup",kind:"cleanup",summary:"stop managed runtimes and services"},()=>Ct(d,()=>u.cleanupAll()).catch(i=>{throw ue=i,$(Z(i).message||z(i),"infra"),i})),de.close()}Oe(c,s,pe)}}catch(s){T=Z(s),s!==ue&&$(T.message||z(s)),w()}U.finish(),h=T===null?0:u.isStopRequested()?130:1,I=Date.now(),K=e.map(s=>ge(N.get(s.name),t,I));const r=qe(K,n.allowEmpty===!0);await J(b,{name:"testkit.collect",kind:"collection",summary:"close and collect run logs"},()=>M.closeAll());const l=await gt(ce(K,I,u.isStopRequested()?"interrupted":T===null&&r===null?null:"failed"));return T!==null&&(h=1),K.some(s=>s.failed||s.infraFailed)&&(h=1),r!==null&&(h=1),l.shouldFailArchivePublication&&(h=1),l.shouldFailArchiveCleanup&&(h=1),(l.runArtifact.run.status==="interrupted"||u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted")&&(h=Math.max(h,130)),{runArtifact:l.runArtifact,results:R.redact(K),exitCode:h,...T===null?{}:{failure:R.redact(T)},...D.result===null?{}:{maintenance:R.redact(D.result)}}}catch(r){const l=Z(r);$(l.message||z(r),"infra"),w(),U.finish(),u.beginFinalization(),h=u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted"?130:1,I=Date.now(),K=e.map(F=>ge(N.get(F.name),t,I)),await ye(M);let s=j.artifacts;if(s===null)try{s=await Rt(ce(K,I,u.isStopRequested()?"interrupted":"failed"),L,j,l,se,oe)}catch{if(j.artifacts===null)throw r;s=j.artifacts}return(s.runArtifact.run.status==="interrupted"||u.isStopRequested()||L.committedFinalization?.artifactStatus==="interrupted")&&(h=Math.max(h,130)),{runArtifact:s.runArtifact,results:R.redact(K),exitCode:h,failure:R.redact(T??l),...D.result===null?{}:{maintenance:R.redact(D.result)}}}finally{u.dispose(),await ye(M),wt(d,n.environments?.observeRecord,k)}}async function Rt(e,a,o,n,m,g){const c=await a.finish("failed",p=>{const y=p.artifactStatus==="interrupted"?p:{artifactStatus:"failed",interruptReason:null},v=e.declaredEnvironmentKeys??[],t=vt({productDir:e.productDir,runId:e.runId,results:e.results,startedAt:e.startedAt,finishedAt:e.finishedAt,execution:e.execution,workerCount:e.workerCount,typeValues:e.selection.typeValues,suiteSelectors:e.selection.suiteSelectors,fileNames:e.selection.fileNames,groupFilter:e.selection.groupFilter,planning:e.selection.planning??null,archiveConfig:e.archiveConfig,declaredEnvironmentKeys:v,metadata:e.metadata,environment:e.environmentRecord.snapshot(),operationTimings:e.operationTimingRegistry?.listOperations()??[],runInterruptReason:y.interruptReason,runStatus:y.artifactStatus});t.finalizationFailure={...n};const E={runArtifact:Ne(e.productDir,t,{archiveConfig:e.archiveConfig,declaredEnvironmentKeys:v,redactKeys:e.environmentRecord.redactKeys,redactionTargets:e.environmentRecord.redactionTargets()})};return o.artifacts=E,E},"recovery",m);return g(c)}function wt(e,a,o){if(a!==void 0)try{a(e.snapshot())}catch(n){try{o?.warning?.(`Environment record observer failed: ${z(n)}`)}catch{}}}function St(e,a){return{...e,remove(o){const n=a.releaseFor(o.id);try{e.remove(o)}catch(m){throw n?.releaseFailed(m),m}n?.released()}}}async function Ct(e,a){try{await a()}catch(o){throw e.recordCleanup(o),o}e.recordCleanup(null)}async function ye(e){try{await e.closeAll()}catch{}}export function workerResultErrorsForOrchestrator(e){return e.status==="rejected"?[z(e.reason)]:[...new Set(e.value.unpersistedErrors)]}function Ft(e,a){return a===void 0?e:Qe(e,a)}function Et(e,a,o,n,m){return{vitest:n,...m===void 0?{}:{retries:m},productDir:e,fileTimeoutSeconds:o.fileTimeoutSeconds,ownerRoots:Object.fromEntries(a.map(g=>[g.name,g.roots]))}}export function resolveRunExecutionConfig(e,a){return ot({cli:{fileTimeoutSeconds:a.fileTimeoutSeconds,taskConcurrency:a.taskConcurrency},repo:e?{fileTimeoutSeconds:e.testkit.execution.fileTimeoutSeconds,taskConcurrency:e.testkit.execution.taskConcurrency}:void 0})}export function assignRuntimeGraphs(e,a){const o=e.filter(t=>t.suites.some(S=>S.files.length>0)),n=new Set(o.filter(t=>!a.has(t.config.name)&&Je(t.runtimeConfigs))),m=new Set([...n].map(t=>t.runtimeKey)),g=t=>!n.has(t)&&!a.has(t.config.name)&&he(t.runtimeConfigs),c=new Set(o.filter(t=>g(t)&&t.runtimeKey!=="").map(t=>t.runtimeKey)),p=new Set(o.map(t=>t.runtimeKey)),y=t=>t.runtimeKey!==""&&!m.has(t.runtimeKey)&&(g(t)||!c.has(t.runtimeKey))?t.runtimeKey:p.has(t.config.name)?`group:${t.config.name}`:t.config.name,v=o.map(t=>n.has(t)?t:{...t,runtimeKey:y(t)});return{plans:v,provisionedGraphKeys:m,staticGraphKeys:new Set(v.filter(t=>!m.has(t.runtimeKey)&&!a.has(t.config.name)&&he(t.runtimeConfigs)).map(t=>t.runtimeKey))}}export function prepareSchedulerPlan(e,a){const o=ct(e.config.testkit);return{graphKey:e.runtimeKey,owner:e.config.name,suites:e.suites.map(n=>({...n,tasks:n.files.map(m=>({file:m,hasSetup:a(m),...ut(o,n,m)}))}))}}export function collectServicePlans(e,a,o,n,m,g,c=null){return e.map(p=>{const y=p.uses.map(f=>{const C=a.get(f);if(C===void 0)throw new Error(`Group "${p.name}" uses unknown service "${f}"`);return C}),v=y.some(f=>f.testkit.database!==void 0)?"local":null,t=Te(p,o,n,m.fileNames||[],m);if(t.length===0)return c?.serviceSkipped?.(p,`no matching files (types=${o.join(",")||"all"} suites=${n.map(f=>f.raw).join(",")||"all"} files=${(m.fileNames||[]).join(",")||"all"})`),{config:p,dbBackend:v,execution:g,skipped:!0,suites:[],runtimeConfigs:[],runtimeNames:[],runtimeKey:null,uses:y};for(const f of t)for(const C of f.skippedFiles)c?.plannedSkip?.({serviceName:p.name,testType:f.testType,engineId:f.engineId,profileId:f.profileId,file:C.path,reason:C.reason});const S=Ee(y,a),E=S.map(f=>f.name).sort();return{config:p,dbBackend:v,execution:g,skipped:!1,suites:t,runtimeConfigs:S,runtimeNames:E,runtimeKey:E.join("|"),uses:y}})}
|
|
1
|
+
import{resolveRuntimeClosure as xe}from"../../config/loader/dependencies.js";import{claimNextTask as Ie,collectSuites as Pe}from"./planning.js";import{buildRunPlanningMetadata as Oe,buildScheduledQueue as ze}from"./scheduler/index.js";import{addTrackerError as qe,buildServiceTrackers as $e,finalizeServiceResult as we,recordGraphError as je,recordTaskOutcome as Le,resolveRunGateFailure as Ne}from"./results.js";import{loadTimings as Ge,saveTimings as Be}from"../../evidence/index.js";import{stableJson as De}from"../../evidence/storage.js";import{createRunEnvironmentRecord as Ve}from"../../evidence/environment-record.js";import{createRunLogRegistry as We}from"../../evidence/logs.js";import{createOperationTimingRegistry as Ue,timeOperation as _}from"../../evidence/operation-timings.js";import{createRunReporterFromListener as _e}from"./run-events.js";import{createCallerRedaction as He}from"./caller-redaction.js";import{createSetupOperationRegistry as Je}from"../../evidence/setup-progress.js";import{createLocalRunEnvironment as Qe}from"../../environment/run-environment.js";import{writeRunMaintenance as Xe}from"../../evidence/maintenance.js";import{createConfigDrivenEnvironments as Ye,planConfiguredCapacity as Ze}from"../environments/config-driven.js";import{declaresProvisioning as et,declaresValues as Re,holdBeforeRelease as tt,routeEnvironments as nt}from"../environments/routing.js";import{createStaticValueEnvironments as rt}from"../environments/static-values.js";import{createSuppliedEnvironments as it}from"../environments/supplied.js";import{environmentTaskIdentity as at}from"./environment.js";import{createTaskLeases as ot}from"./task-leases.js";import{createTaskLocks as st}from"./scheduler/locks.js";import{readPackageMetadata as ct,safeHostname as ut,safeUsername as lt}from"../../process/host/metadata.js";import{collectGitMetadata as mt}from"../../repo/metadata.js";import{envFileCandidates as ft,runOverrides as dt,startRunInputs as pt}from"../evidence/inputs.js";import{resolveExecutionConfig as ht}from"../../config/loader/decode-execution.js";import{projectGroupTestPolicy as gt}from"../../config/loader/discover-tests.js";import{resolveTaskPolicy as vt}from"../../config/loader/test-selection.js";import"../../environment/run-environment.js";import{createEngineManager as yt}from"../../engines/factory.js";import{createWorker as kt,runWorker as wt}from"./worker-loop.js";import{ensureRequestedFilesMatch as Rt}from"./run-guards.js";import{createLiveSnapshotWriter as St}from"./live-run.js";import{buildFinalizationRunArtifact as Ct,finalizeRunArtifacts as Ft,sealFinalizedRunArtifacts as Et}from"./run-finalization.js";import{createRunFinisher as bt}from"./run-finisher.js";import{createRunEvidencePublisher as Tt}from"../../evidence/run-evidence.js";import{createOperationReporterBridge as Kt}from"./operation-progress.js";import{formatError as O,serializeError as ee}from"../../kernel/error-serialization.js";export async function runAll(t,a,o,r,c=[]){const h=r.fileNames||[];Rt(t,a,o,h);const{productDir:u}=r,p=Qe(u),g=yt(u),v=new Map(c.map(n=>[n.name,n])),e=Date.now(),S=t[0]?.artifacts??null,z=[...new Set(t.flatMap(n=>n.uses.flatMap(s=>Object.keys(v.get(s)?.testkit.serviceEnv??{}))))].sort(),[f,C]=await Promise.all([mt(u),pt(u,{envFiles:ft(c),overrides:dt(r),suppliedEnvironment:Object.keys(r.environments?.supplied??{}).length>0,vitest:r.vitest})]);let te=C.unchecked("the run ended before Testkit read the files again");const ne={git:f,host:{hostname:ut(),username:lt()},testkitVersion:ct().version},H=t[0]?.redact??[],E=He(process.env,H),re=r.onEvent;let ie=()=>{};const y=re?_e(n=>{n.type==="run.task_started"&&ie(),re(E.redact(n))},{debug:r.debug}):null,T=We(u),B={workerCount:0},q=resolveRunExecutionConfig(t[0],r),D=collectServicePlans(t,v,a,o,r,q,y);y?.setServicePlans?.(D);const $=$e(D,e),j=(n,s="product")=>{for(const m of $.values())m.skipped||qe(m,n,{classification:s});y?.runError?.(n)},Ce=o.map(n=>({...n})),J={typeValues:a,suiteSelectors:Ce,fileNames:h,groupFilter:r.serviceFilter||null,planning:null};let k=()=>{};const Fe=Kt(y),b=Ue({onChange:n=>{Fe(n),k()}}),ae=Je({logRegistry:T,onChange:()=>{k()}}),d=Ve({logRegistry:T,redactKeys:H,setupRegistry:ae,onPublishedChange:()=>{k()}});E.useSourceTargets(()=>d.redactionTargets()),d.recordGroups(D.map(n=>({dbBackend:n.dbBackend,name:n.config.name})));const Q=D.filter(n=>!n.skipped),oe=r.environments?.supplied??{},{plans:K,provisionedGraphKeys:se,staticGraphKeys:ce}=assignRuntimeGraphs(Q,new Set(Object.keys(oe))),l=await p.start({evidence:Tt({redactKeys:H,redactionTargets:()=>d.redactionTargets(),resetResults:!0}),keepAlive:!0}),X=bt(l),V=St({productDir:u,runId:l.runId,configs:t,trackers:$,startedAt:e,execution:q,workerState:B,selection:J,archiveConfig:S,declaredEnvironmentKeys:z,metadata:ne,environmentRecord:d,operationTimingRegistry:b,reporter:y});k=()=>{V.update()},ie=()=>{V.flush()};const L={artifacts:null},Y={result:null},ue=n=>{const s=E.redact(n);try{return Xe(u,l.runId,s),s}catch(m){const R=`Could not write the maintenance record: ${E.redact(O(m))}`;return{...s,status:"failed",error:s.error===null?R:`${s.error}; ${R}`}}},le=n=>(Y.result=n.maintenance,n.maintenance.status==="failed"&&y?.warning?.(`Post-run maintenance failed: ${n.maintenance.error??"unknown error"}`),n.sealed),Ee=async(n,s)=>(l.beginFinalization(),le(await X.finish(n,m=>{const R=s(m);return L.artifacts={runArtifact:R.runArtifact},R},"normal",ue)));let A,M;const me=(n,s,m)=>({productDir:u,runId:l.runId,results:n,startedAt:e,finishedAt:s,execution:q,workerCount:B.workerCount,selection:J,archiveConfig:S,declaredEnvironmentKeys:z,metadata:{...ne,inputs:te},environmentRecord:d,operationTimingRegistry:b,reporter:y,runInterruptReason:l.getInterruptReason(),runStatus:m,publishRunFinalization:Ee}),fe=n=>n.run.status==="interrupted"||l.isStopRequested()||X.committedFinalization?.artifactStatus==="interrupted",de=(n,s,m)=>({runArtifact:n,results:E.redact(A),exitCode:s,...m===null?{}:{failure:E.redact(m)},...Y.result===null?{}:{maintenance:E.redact(Y.result)}});k();let x=null,pe=Symbol("no release failure");try{try{if(Q.length>0){const w=Ge(u),be=Ye(l,new Map(K.map(i=>[i.config.name,i.config.uses])),u,d),Te=rt(new Map(K.filter(i=>ce.has(i.runtimeKey)).map(i=>[i.config.name,i.uses])),u,d),Ke=it(oe,{productDir:u,record:d,signal:l.signal}),he=i=>se.has(i)?be:ce.has(i)?Te:Ke,ge=nt(new Map(K.map(({runtimeKey:i})=>[i,he(i)]))),{capacity:W,queue:N}=await _(b,{name:"testkit.plan",kind:"planning",summary:"plan test execution",scope:{serviceCount:Q.length}},()=>({capacity:Ze(K.filter(i=>se.has(i.runtimeKey))),queue:ze(K.map(i=>prepareSchedulerPlan(i,F=>ge.hasSetup({file:F,graphKey:i.runtimeKey,targetName:i.config.name}))),{timings:w})}));J.planning=Oe(N),d.recordCapacity(W),y?.setTotalFileCount?.(N.length),B.workerCount=Math.max(1,Math.min(q.taskConcurrency,N.length));const ve=Array.from({length:B.workerCount},(i,F)=>kt(F+1));W.graphs.length>0&&l.provision(W.graphs,{reporter:y,environmentRecord:d,logRegistry:T,setupRegistry:ae,operationTimingRegistry:b,skipSchemaSourceVerify:r.skipSchemaSourceVerify});const ye=ot(u),Ae={recordRunError:j,runId:l.runId,signal:l.signal,isStopRequested:()=>l.isStopRequested(),environments:Pt(ge,r.environments?.hold),leases:xt(ye,d),locks:st(),request:Ot(u,t,q,r.vitest,r.retries),environmentKeys:new Map(K.flatMap(({config:i,runtimeKey:F})=>{const I=he(F),P=I.preview?.(i.name)??null;return P===null?[]:[[i.name,De([P,I.checksServices?.(i.name)??!1])]]}))},ke=[];try{const F=(await _(b,{name:"testkit.execute",kind:"execution",summary:"execute local worker queue",scope:{workerCount:ve.length,taskCount:N.length}},async()=>Promise.allSettled(ve.map(I=>wt(I,N,Ae,g,$,ke,Ie,(P,U,G,Z)=>{Le(P,U,G,Z),G.environmentValuesMissing!==void 0&&d.recordMissingValues(at(U),G.environmentValuesMissing),k()},(P,U,G,Z,Me)=>{je(P,U,G,Z,Me),k()},y))))).flatMap(workerResultErrorsForOrchestrator);for(const I of F)j(I);F.length>0&&k(),d.recordRuntimeStats(l.getStats())}finally{W.graphs.length>0&&await _(b,{name:"testkit.cleanup",kind:"cleanup",summary:"stop managed runtimes and services"},()=>It(d,()=>l.cleanupAll()).catch(i=>{throw pe=i,j(ee(i).message||O(i),"infra"),i})),ye.close()}Be(u,w,ke)}}catch(w){x=ee(w),w!==pe&&j(x.message||O(w)),k()}V.finish(),M=Date.now(),A=t.map(w=>we($.get(w.name),e,M));const n=Ne(A,r.allowEmpty===!0);await _(b,{name:"testkit.collect",kind:"collection",summary:"close and collect run logs"},()=>T.closeAll()),te=await C.finish();const s=await Ft(me(A,M,l.isStopRequested()?"interrupted":x===null&&n===null?null:"failed")),m=x!==null||n!==null||s.shouldFailArchivePublication||s.shouldFailArchiveCleanup,R=fe(s.runArtifact)?130:m?1:0;return de(s.runArtifact,R,x)}catch(n){const s=ee(n);j(s.message||O(n),"infra"),k(),V.finish(),l.beginFinalization(),M=Date.now(),A=t.map(w=>we($.get(w.name),e,M)),await Se(T);let m=L.artifacts;if(m===null)try{m=await At(me(A,M,l.isStopRequested()?"interrupted":"failed"),X,L,s,ue,le)}catch{if(L.artifacts===null)throw n;m=L.artifacts}const R=fe(m.runArtifact)?130:1;return de(m.runArtifact,R,x??s)}finally{l.dispose(),await Se(T),Mt(d,r.environments?.observeRecord,y)}}async function At(t,a,o,r,c,h){const u=await a.finish("failed",p=>{const g=p.artifactStatus==="interrupted"?p:{artifactStatus:"failed",interruptReason:null},v=Ct({...t,runInterruptReason:g.interruptReason,runStatus:g.artifactStatus});v.finalizationFailure={...r};const e={runArtifact:Et(t,v).runArtifact};return o.artifacts=e,e},"recovery",c);return h(u)}function Mt(t,a,o){if(a!==void 0)try{a(t.snapshot())}catch(r){try{o?.warning?.(`Environment record observer failed: ${O(r)}`)}catch{}}}function xt(t,a){return{...t,remove(o){const r=a.releaseFor(o.id);try{t.remove(o)}catch(c){throw r?.releaseFailed(c),c}r?.released()}}}async function It(t,a){try{await a()}catch(o){throw t.recordCleanup(o),o}t.recordCleanup(null)}async function Se(t){try{await t.closeAll()}catch{}}export function workerResultErrorsForOrchestrator(t){return t.status==="rejected"?[O(t.reason)]:[...new Set(t.value.unpersistedErrors)]}function Pt(t,a){return a===void 0?t:tt(t,a)}function Ot(t,a,o,r,c){return{vitest:r,...c===void 0?{}:{retries:c},productDir:t,fileTimeoutSeconds:o.fileTimeoutSeconds,ownerRoots:Object.fromEntries(a.map(h=>[h.name,h.roots]))}}export function resolveRunExecutionConfig(t,a){return ht({cli:{fileTimeoutSeconds:a.fileTimeoutSeconds,taskConcurrency:a.taskConcurrency},repo:t?{fileTimeoutSeconds:t.testkit.execution.fileTimeoutSeconds,taskConcurrency:t.testkit.execution.taskConcurrency}:void 0})}export function assignRuntimeGraphs(t,a){const o=t.filter(e=>e.suites.some(S=>S.files.length>0)),r=new Set(o.filter(e=>!a.has(e.config.name)&&et(e.runtimeConfigs))),c=new Set([...r].map(e=>e.runtimeKey)),h=e=>!r.has(e)&&!a.has(e.config.name)&&Re(e.runtimeConfigs),u=new Set(o.filter(e=>h(e)&&e.runtimeKey!=="").map(e=>e.runtimeKey)),p=new Set(o.map(e=>e.runtimeKey)),g=e=>e.runtimeKey!==""&&!c.has(e.runtimeKey)&&(h(e)||!u.has(e.runtimeKey))?e.runtimeKey:p.has(e.config.name)?`group:${e.config.name}`:e.config.name,v=o.map(e=>r.has(e)?e:{...e,runtimeKey:g(e)});return{plans:v,provisionedGraphKeys:c,staticGraphKeys:new Set(v.filter(e=>!c.has(e.runtimeKey)&&!a.has(e.config.name)&&Re(e.runtimeConfigs)).map(e=>e.runtimeKey))}}export function prepareSchedulerPlan(t,a){const o=gt(t.config.testkit);return{graphKey:t.runtimeKey,owner:t.config.name,suites:t.suites.map(r=>({...r,tasks:r.files.map(c=>({file:c,hasSetup:a(c),...vt(o,r,c)}))}))}}export function collectServicePlans(t,a,o,r,c,h,u=null){return t.map(p=>{const g=p.uses.map(f=>{const C=a.get(f);if(C===void 0)throw new Error(`Group "${p.name}" uses unknown service "${f}"`);return C}),v=g.some(f=>f.testkit.database!==void 0)?"local":null,e=Pe(p,o,r,c.fileNames||[],c);if(e.length===0)return u?.serviceSkipped?.(p,`no matching files (types=${o.join(",")||"all"} suites=${r.map(f=>f.raw).join(",")||"all"} files=${(c.fileNames||[]).join(",")||"all"})`),{config:p,dbBackend:v,execution:h,skipped:!0,suites:[],runtimeConfigs:[],runtimeNames:[],runtimeKey:null,uses:g};for(const f of e)for(const C of f.skippedFiles)u?.plannedSkip?.({serviceName:p.name,testType:f.testType,engineId:f.engineId,profileId:f.profileId,file:C.path,reason:C.reason});const S=xe(g,a),z=S.map(f=>f.name).sort();return{config:p,dbBackend:v,execution:h,skipped:!1,suites:e,runtimeConfigs:S,runtimeNames:z,runtimeKey:z.join("|"),uses:g}})}
|
package/dist/app/run/results.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{suiteKey as x}from"../../domain/suite-selection.js";import{mergeFailureDetails as b}from"./failure-details.js";import{posixPath as
|
|
1
|
+
import{suiteKey as x}from"../../domain/suite-selection.js";import{mergeFailureDetails as b}from"./failure-details.js";import{posixPath as k}from"../../process/files/paths.js";export function buildServiceTrackers(e,i){const a=new Map;for(const t of e){const r=t.skipped?[]:t.suites.map(w),l={name:t.config.name,skipped:t.skipped===!0,suiteCount:r.length,suites:r,suitesByKey:new Map(r.map(o=>[o.key,o])),errors:[],errorSet:new Set,errorDetails:[],errorDetailSet:new Set,startedAt:i,firstTaskAt:null,lastTaskAt:null,totalTaskDurationMs:0};a.set(t.config.name,l)}return a}function w(e){const i=new Map;for(const a of e.files){const t=k(a);i.set(t,g(t,"not_run",null))}for(const a of e.skippedFiles??[])i.set(a.path,g(a.path,"skipped",a.reason));return{key:x(e),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 g(e,i,a){return{path:e,failed:i==="failed",durationMs:0,error:null,reason:a,status:i,artifacts:[],infraFailed:!1,blockedByRuntime:!1,failureDetails:[],runtimeIsolation:null,runtimeBatch:null}}export function recordTaskOutcome(e,i,a,t=Date.now()){const r=e.get(i.serviceName);if(!r||r.skipped)return;const l=r.suitesByKey.get(i.suiteKey);if(!l)return;const o=z(a,i,t);r.firstTaskAt=r.firstTaskAt===null?o.startedAt:Math.min(r.firstTaskAt,o.startedAt),r.lastTaskAt=r.lastTaskAt===null?o.finishedAt:Math.max(r.lastTaskAt,o.finishedAt),r.totalTaskDurationMs+=o.durationMs,l.durationMs+=o.durationMs;const d=k(i.file);l.fileResultsByPath.set(d,{...o,path:d,failed:o.status==="failed"}),o.status==="failed"&&!l.failedFileSet.has(i.file)&&(l.failedFileSet.add(i.file),l.failedFiles.push(i.file)),o.error&&!l.error&&(l.error=o.error)}function z(e,i,a){const t=e.durationMs??0,r=e.finishedAt??a;return{artifacts:e.artifacts??[],blockedByRuntime:e.blockedByRuntime??!1,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,startedAt:e.startedAt??Math.max(0,r-t),status:e.status}}export function recordGraphError(e,i,a,t=Date.now(),r={}){const l=i?.targetNames||[];for(const o of l){const d=e.get(o);d&&!d.skipped&&(addTrackerError(d,a,r),d.lastTaskAt=t)}}export function addTrackerError(e,i,a={}){const t=i.trim();if(!t)return;const r=a.classification??"product";e.errorSet.has(t)||(e.errorSet.add(t),e.errors.push(t));const l=`${r}\0${t}`;e.errorDetailSet.has(l)||(e.errorDetailSet.add(l),e.errorDetails.push({message:t,classification:r}))}export function finalizeServiceResult(e,i,a){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((n,u)=>n.orderIndex-u.orderIndex||n.name.localeCompare(u.name)).map(n=>P(n)),r=t.filter(n=>n.completedFileCount+n.skippedFileCount===n.fileCount).length,l=t.filter(n=>n.skippedFileCount===n.fileCount&&n.fileCount>0).length,o=t.filter(n=>n.failedFileCount>0).length,d=t.reduce((n,u)=>n+u.fileCount,0),s=t.reduce((n,u)=>n+u.completedFileCount,0),p=t.reduce((n,u)=>n+u.failedFileCount,0),T=t.reduce((n,u)=>n+u.passedFileCount,0),C=t.reduce((n,u)=>n+u.skippedFileCount,0),c=d-s-C,m=t.reduce((n,u)=>n+u.files.filter(f=>f.infraFailed===!0).length,0),h=t.reduce((n,u)=>n+u.files.filter(f=>f.blockedByRuntime===!0).length,0),y=t.reduce((n,u)=>n+u.files.filter(f=>f.status==="cancelled"||f.status==="interrupted").length,0),M=c>0&&y===c,B=c>0&&m===c,F=e.errorDetails,D=F.filter(n=>n.classification!=="infra").length,S=F.filter(n=>n.classification==="infra").length,A=m>0||S>0,I=h>0,R=e.firstTaskAt!==null&&e.lastTaskAt!==null?Math.max(0,e.lastTaskAt-e.firstTaskAt):Math.max(0,a-i);return{name:e.name,failed:o>0||D>0||r<e.suiteCount&&!M&&!B,infraFailed:A,blockedByRuntime:I,infraFailedFileCount:m,blockedByRuntimeFileCount:h,skipped:!1,suiteCount:e.suiteCount,completedSuiteCount:r,skippedSuiteCount:l,failedSuiteCount:o,totalFileCount:d,completedFileCount:s,passedFileCount:T,failedFileCount:p,skippedFileCount:C,notRunFileCount:c,durationMs:R,totalTaskDurationMs:e.totalTaskDurationMs,suites:t,errors:e.errors,errorDetails:F}}function P(e){const i=[...e.fileResultsByPath.values()].sort((s,p)=>s.path.localeCompare(p.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.runtimeIsolation?{runtimeIsolation:s.runtimeIsolation}:{},...s.runtimeBatch?{runtimeBatch:s.runtimeBatch}:{}})),a=i.length,t=i.filter(s=>s.status==="passed").length,r=i.filter(s=>s.status==="failed").length,l=i.filter(s=>s.status==="skipped").length,o=t+r,d=a-o-l;return{name:e.name,...e.testType===void 0?{}:{testType:e.testType},engineId:e.engineId,profileId:e.profileId,failed:r>0,fileCount:a,completedFileCount:o,passedFileCount:t,failedFileCount:r,skippedFileCount:l,notRunFileCount:d,failedFiles:e.failedFiles,durationMs:e.durationMs,error:e.error,files:i}}export function resolveRunGateFailure(e,i=!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,r)=>t+r.completedFileCount,0)===0&&!i?"no runnable test files executed":null}
|