@hominis/fireforge 0.49.2 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -1
- package/README.md +1 -1
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/build-prelock.d.ts +20 -0
- package/dist/src/commands/build-prelock.js +50 -0
- package/dist/src/commands/build.d.ts +3 -1
- package/dist/src/commands/build.js +13 -6
- package/dist/src/commands/check-report.d.ts +28 -0
- package/dist/src/commands/check-report.js +78 -0
- package/dist/src/commands/check-run.d.ts +28 -0
- package/dist/src/commands/check-run.js +104 -0
- package/dist/src/commands/check-steps.d.ts +67 -0
- package/dist/src/commands/check-steps.js +175 -0
- package/dist/src/commands/check.d.ts +32 -0
- package/dist/src/commands/check.js +160 -0
- package/dist/src/commands/config.js +5 -6
- package/dist/src/commands/doctor-engine-eslint.js +6 -1
- package/dist/src/commands/doctor-source-pin.js +4 -1
- package/dist/src/commands/export-adopt-check.d.ts +38 -0
- package/dist/src/commands/export-adopt-check.js +98 -0
- package/dist/src/commands/export-flow.js +29 -14
- package/dist/src/commands/export-placement-gate.d.ts +6 -2
- package/dist/src/commands/export-shared.d.ts +2 -1
- package/dist/src/commands/export-staged-registration.d.ts +46 -0
- package/dist/src/commands/export-staged-registration.js +91 -0
- package/dist/src/commands/forward-import-remedy.d.ts +32 -0
- package/dist/src/commands/forward-import-remedy.js +63 -0
- package/dist/src/commands/furnace/apply.js +3 -3
- package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
- package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc.js +2 -2
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
- package/dist/src/commands/furnace/deploy-named.js +107 -0
- package/dist/src/commands/furnace/deploy.d.ts +3 -2
- package/dist/src/commands/furnace/deploy.js +50 -86
- package/dist/src/commands/furnace/diff.js +26 -3
- package/dist/src/commands/furnace/index.js +66 -41
- package/dist/src/commands/furnace/init.js +13 -6
- package/dist/src/commands/furnace/override.js +4 -2
- package/dist/src/commands/furnace/preview.js +4 -3
- package/dist/src/commands/furnace/refresh.js +2 -2
- package/dist/src/commands/furnace/remove.js +4 -3
- package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
- package/dist/src/commands/furnace/rename-dry-run.js +127 -0
- package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
- package/dist/src/commands/furnace/rename-helpers.js +15 -1
- package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
- package/dist/src/commands/furnace/rename-registrations.js +93 -0
- package/dist/src/commands/furnace/rename.d.ts +2 -1
- package/dist/src/commands/furnace/rename.js +28 -66
- package/dist/src/commands/furnace/scan.d.ts +9 -4
- package/dist/src/commands/furnace/scan.js +11 -9
- package/dist/src/commands/furnace/sync.js +3 -2
- package/dist/src/commands/furnace/validate-report.d.ts +45 -0
- package/dist/src/commands/furnace/validate-report.js +91 -0
- package/dist/src/commands/furnace/validate.d.ts +1 -1
- package/dist/src/commands/furnace/validate.js +127 -114
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +31 -60
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +15 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/delete-gap-notice.js +3 -1
- package/dist/src/commands/patch/delete.d.ts +1 -1
- package/dist/src/commands/patch/delete.js +13 -98
- package/dist/src/commands/patch/index.d.ts +1 -1
- package/dist/src/commands/patch/index.js +4 -2
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +65 -28
- package/dist/src/commands/patch/move-files-create.js +26 -17
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
- package/dist/src/commands/patch/removal-dependents.js +133 -0
- package/dist/src/commands/patch/rename.js +7 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +128 -0
- package/dist/src/commands/patch/split-plan.d.ts +7 -7
- package/dist/src/commands/patch/split-plan.js +45 -28
- package/dist/src/commands/patch/split-staged.d.ts +108 -0
- package/dist/src/commands/patch/split-staged.js +202 -0
- package/dist/src/commands/patch/split.d.ts +5 -0
- package/dist/src/commands/patch/split.js +51 -15
- package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
- package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
- package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
- package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
- package/dist/src/commands/patch/staged-dependency-from.js +108 -0
- package/dist/src/commands/patch/staged-dependency.js +14 -41
- package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
- package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
- package/dist/src/commands/re-export-adjacent.d.ts +45 -2
- package/dist/src/commands/re-export-adjacent.js +156 -37
- package/dist/src/commands/re-export-drift.d.ts +36 -8
- package/dist/src/commands/re-export-drift.js +73 -49
- package/dist/src/commands/re-export-files.js +11 -8
- package/dist/src/commands/re-export-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +45 -4
- package/dist/src/commands/re-export-preflight.d.ts +23 -0
- package/dist/src/commands/re-export-preflight.js +160 -0
- package/dist/src/commands/re-export-register.js +42 -17
- package/dist/src/commands/re-export-removed.d.ts +12 -1
- package/dist/src/commands/re-export-removed.js +41 -11
- package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
- package/dist/src/commands/re-export-scan-owner.js +116 -0
- package/dist/src/commands/re-export-scan.d.ts +17 -9
- package/dist/src/commands/re-export-scan.js +56 -23
- package/dist/src/commands/re-export-single.js +22 -6
- package/dist/src/commands/re-export-summary.d.ts +28 -0
- package/dist/src/commands/re-export-summary.js +36 -0
- package/dist/src/commands/re-export.js +37 -24
- package/dist/src/commands/rebase/index.js +17 -17
- package/dist/src/commands/run.js +16 -2
- package/dist/src/commands/status-check.d.ts +55 -16
- package/dist/src/commands/status-check.js +92 -37
- package/dist/src/commands/status-json.d.ts +22 -9
- package/dist/src/commands/status-json.js +47 -32
- package/dist/src/commands/status-output.js +3 -1
- package/dist/src/commands/status-scope.d.ts +37 -0
- package/dist/src/commands/status-scope.js +54 -0
- package/dist/src/commands/status.js +66 -23
- package/dist/src/commands/test-browser-preflight.js +19 -3
- package/dist/src/commands/test-build-furnace.d.ts +21 -0
- package/dist/src/commands/test-build-furnace.js +48 -0
- package/dist/src/commands/test-build-phase.js +47 -41
- package/dist/src/commands/test-checkjs.js +7 -0
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-modes.d.ts +16 -1
- package/dist/src/commands/test-modes.js +24 -1
- package/dist/src/commands/test-multi-verdict.d.ts +61 -0
- package/dist/src/commands/test-multi-verdict.js +111 -0
- package/dist/src/commands/test-orchestrate.d.ts +43 -0
- package/dist/src/commands/test-orchestrate.js +222 -0
- package/dist/src/commands/test-perf-scope.d.ts +29 -0
- package/dist/src/commands/test-perf-scope.js +83 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +177 -0
- package/dist/src/commands/test-register.js +54 -10
- package/dist/src/commands/test-run.js +4 -1
- package/dist/src/commands/test-stale-gate.js +57 -27
- package/dist/src/commands/test-verdict.d.ts +10 -1
- package/dist/src/commands/test-verdict.js +28 -1
- package/dist/src/commands/test.d.ts +6 -1
- package/dist/src/commands/test.js +66 -60
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +111 -0
- package/dist/src/commands/token.js +10 -0
- package/dist/src/commands/verify-scope.d.ts +28 -0
- package/dist/src/commands/verify-scope.js +67 -0
- package/dist/src/commands/verify.d.ts +10 -2
- package/dist/src/commands/verify.js +59 -35
- package/dist/src/core/build-baseline-types.d.ts +46 -0
- package/dist/src/core/build-baseline-validate.js +19 -0
- package/dist/src/core/build-baseline.d.ts +5 -1
- package/dist/src/core/build-baseline.js +3 -1
- package/dist/src/core/build-overwrite-guard.d.ts +50 -0
- package/dist/src/core/build-overwrite-guard.js +84 -4
- package/dist/src/core/build-prepare.d.ts +7 -0
- package/dist/src/core/build-prepare.js +14 -8
- package/dist/src/core/build-run-record.d.ts +74 -0
- package/dist/src/core/build-run-record.js +136 -0
- package/dist/src/core/checkjs-support-heads.d.ts +41 -0
- package/dist/src/core/checkjs-support-heads.js +104 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +8 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +143 -0
- package/dist/src/core/config-validate-file-size.d.ts +9 -6
- package/dist/src/core/config-validate-file-size.js +91 -2
- package/dist/src/core/config-validate-history.d.ts +11 -0
- package/dist/src/core/config-validate-history.js +38 -0
- package/dist/src/core/config-validate-patch-policy.js +100 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/config-validate-token-scales.d.ts +6 -0
- package/dist/src/core/config-validate-token-scales.js +50 -0
- package/dist/src/core/config-validate.js +14 -5
- package/dist/src/core/config.d.ts +5 -0
- package/dist/src/core/config.js +16 -0
- package/dist/src/core/coverage-dispatch.d.ts +24 -0
- package/dist/src/core/coverage-dispatch.js +44 -0
- package/dist/src/core/coverage-extend.d.ts +70 -45
- package/dist/src/core/coverage-extend.js +199 -144
- package/dist/src/core/coverage-records.d.ts +97 -0
- package/dist/src/core/coverage-records.js +350 -0
- package/dist/src/core/destructive.d.ts +7 -4
- package/dist/src/core/destructive.js +10 -10
- package/dist/src/core/engine-session-lock.d.ts +10 -1
- package/dist/src/core/engine-session-lock.js +28 -8
- package/dist/src/core/export-moved-code-shape.d.ts +49 -11
- package/dist/src/core/export-moved-code-shape.js +135 -44
- package/dist/src/core/file-lock-queue.d.ts +67 -0
- package/dist/src/core/file-lock-queue.js +221 -0
- package/dist/src/core/file-lock.d.ts +4 -25
- package/dist/src/core/file-lock.js +105 -138
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +31 -1
- package/dist/src/core/furnace-apply.js +12 -2
- package/dist/src/core/furnace-composed-source.d.ts +76 -0
- package/dist/src/core/furnace-composed-source.js +167 -0
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
- package/dist/src/core/furnace-config-validate.js +8 -0
- package/dist/src/core/furnace-css-fragments.d.ts +10 -0
- package/dist/src/core/furnace-css-fragments.js +26 -0
- package/dist/src/core/furnace-operation.d.ts +3 -3
- package/dist/src/core/furnace-registration-remove.d.ts +22 -1
- package/dist/src/core/furnace-registration-remove.js +53 -17
- package/dist/src/core/furnace-registration-rename.d.ts +26 -0
- package/dist/src/core/furnace-registration-rename.js +77 -0
- package/dist/src/core/furnace-registration.d.ts +14 -4
- package/dist/src/core/furnace-registration.js +44 -10
- package/dist/src/core/furnace-staleness.d.ts +7 -0
- package/dist/src/core/furnace-staleness.js +34 -24
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +10 -1
- package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
- package/dist/src/core/furnace-validate-exceptions.js +43 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +49 -0
- package/dist/src/core/furnace-validate.js +1 -1
- package/dist/src/core/harness-port-fallback.d.ts +60 -0
- package/dist/src/core/harness-port-fallback.js +96 -0
- package/dist/src/core/history-log-file.d.ts +61 -0
- package/dist/src/core/history-log-file.js +157 -0
- package/dist/src/core/history-log.d.ts +2 -1
- package/dist/src/core/history-log.js +2 -1
- package/dist/src/core/json-preserving-edit.d.ts +22 -0
- package/dist/src/core/json-preserving-edit.js +199 -0
- package/dist/src/core/mach.js +8 -1
- package/dist/src/core/negative-control-diff.d.ts +39 -0
- package/dist/src/core/negative-control-diff.js +136 -0
- package/dist/src/core/negative-control.d.ts +138 -0
- package/dist/src/core/negative-control.js +356 -0
- package/dist/src/core/objdir-install-manifests.d.ts +73 -0
- package/dist/src/core/objdir-install-manifests.js +226 -0
- package/dist/src/core/patch-export-update.d.ts +2 -0
- package/dist/src/core/patch-export-update.js +1 -0
- package/dist/src/core/patch-export.js +3 -4
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -11
- package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
- package/dist/src/core/patch-lint-code-lines.js +105 -0
- package/dist/src/core/patch-lint-cross.js +12 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +10 -4
- package/dist/src/core/patch-lint-file-size.d.ts +12 -5
- package/dist/src/core/patch-lint-file-size.js +22 -11
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +152 -0
- package/dist/src/core/patch-lint-ignore.js +299 -0
- package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
- package/dist/src/core/patch-lint-size-thresholds.js +110 -0
- package/dist/src/core/patch-lint-size.d.ts +117 -0
- package/dist/src/core/patch-lint-size.js +323 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
- package/dist/src/core/patch-lint-staged-registration.js +20 -0
- package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
- package/dist/src/core/patch-lint-token-scale.js +109 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +64 -286
- package/dist/src/core/patch-lock.js +7 -6
- package/dist/src/core/patch-manifest-consistency.js +7 -6
- package/dist/src/core/patch-manifest-io.d.ts +6 -3
- package/dist/src/core/patch-manifest-io.js +19 -8
- package/dist/src/core/patch-manifest-validate.js +34 -2
- package/dist/src/core/patch-order-legal.d.ts +25 -0
- package/dist/src/core/patch-order-legal.js +83 -0
- package/dist/src/core/patch-policy-content.d.ts +40 -0
- package/dist/src/core/patch-policy-content.js +216 -0
- package/dist/src/core/patch-policy.d.ts +16 -3
- package/dist/src/core/patch-policy.js +16 -9
- package/dist/src/core/patch-retired.d.ts +35 -0
- package/dist/src/core/patch-retired.js +54 -0
- package/dist/src/core/patch-subset.d.ts +16 -0
- package/dist/src/core/patch-subset.js +66 -0
- package/dist/src/core/preflight-wait.d.ts +20 -0
- package/dist/src/core/preflight-wait.js +38 -0
- package/dist/src/core/rebase-from-version.d.ts +46 -0
- package/dist/src/core/rebase-from-version.js +71 -0
- package/dist/src/core/run-log.d.ts +20 -0
- package/dist/src/core/run-log.js +89 -3
- package/dist/src/core/run-profile.d.ts +7 -0
- package/dist/src/core/run-profile.js +7 -1
- package/dist/src/core/smoke-patterns.d.ts +5 -0
- package/dist/src/core/smoke-patterns.js +13 -0
- package/dist/src/core/test-harness-crash.d.ts +18 -1
- package/dist/src/core/test-harness-crash.js +6 -4
- package/dist/src/core/test-harness-signature.d.ts +17 -0
- package/dist/src/core/test-harness-signature.js +4 -0
- package/dist/src/core/test-run-completeness.d.ts +57 -0
- package/dist/src/core/test-run-completeness.js +144 -0
- package/dist/src/core/test-stale-check.d.ts +27 -1
- package/dist/src/core/test-stale-check.js +39 -49
- package/dist/src/core/test-stale-exempt.d.ts +40 -0
- package/dist/src/core/test-stale-exempt.js +70 -0
- package/dist/src/core/token-lint.d.ts +50 -0
- package/dist/src/core/token-lint.js +126 -0
- package/dist/src/core/tree-guard.js +3 -0
- package/dist/src/core/typecheck-shim.d.ts +1 -1
- package/dist/src/core/typecheck-shim.js +90 -2
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +148 -21
- package/dist/src/types/commands/patches.d.ts +75 -1
- package/dist/src/types/config.d.ts +192 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +43 -1
- package/dist/src/types/furnace.js +7 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +34 -6
- package/dist/src/utils/options.js +86 -5
- package/dist/src/utils/path-glob.d.ts +15 -0
- package/dist/src/utils/path-glob.js +47 -0
- package/dist/src/utils/regex.d.ts +15 -3
- package/dist/src/utils/regex.js +37 -16
- package/docs/configuration.md +282 -9
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +97 -3
- package/docs/lifecycle-invariants.md +52 -19
- package/docs/machine-output.md +74 -12
- package/docs/patch-workflow.md +297 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +276 -20
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-run lines and the aggregate verdict of a multi-run `fireforge test`
|
|
3
|
+
* (`--repeat`, mixed harnesses, `--negative-control`). Pure: the
|
|
4
|
+
* orchestration in `test-orchestrate.ts` feeds it the verdict line each
|
|
5
|
+
* sub-run would have printed, captured instead of written.
|
|
6
|
+
*/
|
|
7
|
+
/** One finished sub-run. */
|
|
8
|
+
export interface SubRunResult {
|
|
9
|
+
/** 1-based repetition this run belongs to. */
|
|
10
|
+
repetition: number;
|
|
11
|
+
/** The harness half of a mixed invocation. */
|
|
12
|
+
harness?: 'xpcshell' | 'mochitest';
|
|
13
|
+
/** The captured verdict, without the `FIREFORGE-VERDICT: ` prefix. */
|
|
14
|
+
body: string;
|
|
15
|
+
passed: boolean;
|
|
16
|
+
/** `reason=` of a failed run. */
|
|
17
|
+
reason?: string;
|
|
18
|
+
/** `note=` of a failed run, when it carried one. */
|
|
19
|
+
note?: string;
|
|
20
|
+
/** What the sub-run threw, if anything. */
|
|
21
|
+
error?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* How a negative control came out: `caught` when every repetition went red
|
|
25
|
+
* on test failures, `missed` when a repetition stayed green (the tests did
|
|
26
|
+
* not detect the mutation), `inconclusive` when any run failed for another
|
|
27
|
+
* reason or the invocation stopped early.
|
|
28
|
+
*/
|
|
29
|
+
export type ControlOutcome = 'caught' | 'missed' | 'inconclusive';
|
|
30
|
+
/**
|
|
31
|
+
* Parses a captured verdict line into a sub-run result.
|
|
32
|
+
*
|
|
33
|
+
* @param line - The captured line, or undefined when the sub-run wrote none
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseCapturedVerdict(line: string | undefined): Pick<SubRunResult, 'body' | 'passed' | 'reason' | 'note'>;
|
|
36
|
+
/**
|
|
37
|
+
* The `FIREFORGE-RUN:` line for one sub-run: the verdict it would have
|
|
38
|
+
* printed alone, plus `run=<i>/<n>` and, for a mixed invocation,
|
|
39
|
+
* `harness=`. Never `FIREFORGE-VERDICT:`, so the invocation keeps exactly
|
|
40
|
+
* one verdict line.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatRunLine(result: SubRunResult, index: number, total: number): string;
|
|
43
|
+
/** The failed run with the most structural reason, or undefined when all passed. */
|
|
44
|
+
export declare function worstRun(results: readonly SubRunResult[]): SubRunResult | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Derives a negative control's outcome.
|
|
47
|
+
*
|
|
48
|
+
* @param results - Every finished sub-run
|
|
49
|
+
* @param repetitions - How many repetitions were planned
|
|
50
|
+
* @param complete - False when the invocation stopped before its last run
|
|
51
|
+
*/
|
|
52
|
+
export declare function deriveControlOutcome(results: readonly SubRunResult[], repetitions: number, complete: boolean): ControlOutcome;
|
|
53
|
+
/**
|
|
54
|
+
* The aggregate verdict body. A single planned run (a lone control) keeps
|
|
55
|
+
* its own line, counts included; several aggregate to `PASS runs=<n>/<n>`
|
|
56
|
+
* or `FAIL reason=<most structural> [note=] runs=<passed>/<planned>`.
|
|
57
|
+
* A control appends `control=<outcome>`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function formatAggregateBody(results: readonly SubRunResult[], plannedTotal: number, control?: ControlOutcome): string;
|
|
60
|
+
/** One-line human summary of a control's outcome. */
|
|
61
|
+
export declare function describeControlOutcome(outcome: ControlOutcome): string;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Per-run lines and the aggregate verdict of a multi-run `fireforge test`
|
|
4
|
+
* (`--repeat`, mixed harnesses, `--negative-control`). Pure: the
|
|
5
|
+
* orchestration in `test-orchestrate.ts` feeds it the verdict line each
|
|
6
|
+
* sub-run would have printed, captured instead of written.
|
|
7
|
+
*/
|
|
8
|
+
/** The prefix every verdict line starts with. */
|
|
9
|
+
const VERDICT_PREFIX = 'FIREFORGE-VERDICT: ';
|
|
10
|
+
/**
|
|
11
|
+
* Parses a captured verdict line into a sub-run result.
|
|
12
|
+
*
|
|
13
|
+
* @param line - The captured line, or undefined when the sub-run wrote none
|
|
14
|
+
*/
|
|
15
|
+
export function parseCapturedVerdict(line) {
|
|
16
|
+
const body = (line ?? `${VERDICT_PREFIX}FAIL reason=preflight`).slice(VERDICT_PREFIX.length);
|
|
17
|
+
const passed = body.startsWith('PASS');
|
|
18
|
+
const reason = /(?:^| )reason=(\S+)/.exec(body)?.[1];
|
|
19
|
+
const note = /(?:^| )note=(\S+)/.exec(body)?.[1];
|
|
20
|
+
return {
|
|
21
|
+
body,
|
|
22
|
+
passed,
|
|
23
|
+
...(reason === undefined ? {} : { reason }),
|
|
24
|
+
...(note === undefined ? {} : { note }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The `FIREFORGE-RUN:` line for one sub-run: the verdict it would have
|
|
29
|
+
* printed alone, plus `run=<i>/<n>` and, for a mixed invocation,
|
|
30
|
+
* `harness=`. Never `FIREFORGE-VERDICT:`, so the invocation keeps exactly
|
|
31
|
+
* one verdict line.
|
|
32
|
+
*/
|
|
33
|
+
export function formatRunLine(result, index, total) {
|
|
34
|
+
const harness = result.harness === undefined ? '' : ` harness=${result.harness}`;
|
|
35
|
+
return `FIREFORGE-RUN: ${result.body} run=${index + 1}/${total}${harness}`;
|
|
36
|
+
}
|
|
37
|
+
/** Most structural first: the reason an aggregate failure reports. */
|
|
38
|
+
const REASON_RANK = [
|
|
39
|
+
'inconclusive',
|
|
40
|
+
'preflight',
|
|
41
|
+
'crash',
|
|
42
|
+
'harness-arguments',
|
|
43
|
+
'no-tests',
|
|
44
|
+
'test-failures',
|
|
45
|
+
];
|
|
46
|
+
function rankOf(reason) {
|
|
47
|
+
const rank = REASON_RANK.indexOf(reason ?? '');
|
|
48
|
+
return rank === -1 ? REASON_RANK.length : rank;
|
|
49
|
+
}
|
|
50
|
+
/** The failed run with the most structural reason, or undefined when all passed. */
|
|
51
|
+
export function worstRun(results) {
|
|
52
|
+
let worst;
|
|
53
|
+
for (const result of results) {
|
|
54
|
+
if (result.passed)
|
|
55
|
+
continue;
|
|
56
|
+
if (worst === undefined || rankOf(result.reason) < rankOf(worst.reason))
|
|
57
|
+
worst = result;
|
|
58
|
+
}
|
|
59
|
+
return worst;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Derives a negative control's outcome.
|
|
63
|
+
*
|
|
64
|
+
* @param results - Every finished sub-run
|
|
65
|
+
* @param repetitions - How many repetitions were planned
|
|
66
|
+
* @param complete - False when the invocation stopped before its last run
|
|
67
|
+
*/
|
|
68
|
+
export function deriveControlOutcome(results, repetitions, complete) {
|
|
69
|
+
if (!complete)
|
|
70
|
+
return 'inconclusive';
|
|
71
|
+
if (results.some((result) => !result.passed && result.reason !== 'test-failures')) {
|
|
72
|
+
return 'inconclusive';
|
|
73
|
+
}
|
|
74
|
+
for (let repetition = 1; repetition <= repetitions; repetition += 1) {
|
|
75
|
+
const red = results.some((result) => result.repetition === repetition && result.reason === 'test-failures');
|
|
76
|
+
if (!red)
|
|
77
|
+
return 'missed';
|
|
78
|
+
}
|
|
79
|
+
return 'caught';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The aggregate verdict body. A single planned run (a lone control) keeps
|
|
83
|
+
* its own line, counts included; several aggregate to `PASS runs=<n>/<n>`
|
|
84
|
+
* or `FAIL reason=<most structural> [note=] runs=<passed>/<planned>`.
|
|
85
|
+
* A control appends `control=<outcome>`.
|
|
86
|
+
*/
|
|
87
|
+
export function formatAggregateBody(results, plannedTotal, control) {
|
|
88
|
+
const controlKey = control === undefined ? '' : ` control=${control}`;
|
|
89
|
+
const only = results[0];
|
|
90
|
+
if (plannedTotal === 1 && only !== undefined)
|
|
91
|
+
return `${only.body}${controlKey}`;
|
|
92
|
+
const passed = results.filter((result) => result.passed).length;
|
|
93
|
+
const runs = ` runs=${passed}/${plannedTotal}`;
|
|
94
|
+
const worst = worstRun(results);
|
|
95
|
+
if (worst === undefined && results.length === plannedTotal)
|
|
96
|
+
return `PASS${runs}${controlKey}`;
|
|
97
|
+
const reason = worst?.reason ?? 'preflight';
|
|
98
|
+
const note = worst?.note === undefined ? '' : ` note=${worst.note}`;
|
|
99
|
+
return `FAIL reason=${reason}${note}${runs}${controlKey}`;
|
|
100
|
+
}
|
|
101
|
+
/** One-line human summary of a control's outcome. */
|
|
102
|
+
export function describeControlOutcome(outcome) {
|
|
103
|
+
switch (outcome) {
|
|
104
|
+
case 'caught':
|
|
105
|
+
return 'Negative control: the tests went red on the mutation, as a control should (control=caught).';
|
|
106
|
+
case 'missed':
|
|
107
|
+
return 'Negative control: the tests STAYED GREEN with the mutation applied, so they do not detect it (control=missed).';
|
|
108
|
+
case 'inconclusive':
|
|
109
|
+
return 'Negative control: inconclusive, a run failed for a reason other than test failures or did not run (control=inconclusive).';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-run orchestration for `fireforge test`, all under the one
|
|
3
|
+
* engine-session lock hold the command already has:
|
|
4
|
+
*
|
|
5
|
+
* - mixed harness paths run as two consecutive mach invocations, xpcshell
|
|
6
|
+
* first, with one pre-test build (when `--build`) packaging both halves;
|
|
7
|
+
* - `--repeat <n>` runs the same selection n times, building once;
|
|
8
|
+
* - `--negative-control <diff>` applies a reviewed mutation after the
|
|
9
|
+
* build and right before each dispatch, and restores it after the run.
|
|
10
|
+
*
|
|
11
|
+
* Each sub-run is the ordinary single-run body. Its verdict line is
|
|
12
|
+
* captured rather than printed (see `setVerdictCapture`) and echoed as a
|
|
13
|
+
* `FIREFORGE-RUN:` line, and the invocation ends with one aggregate
|
|
14
|
+
* `FIREFORGE-VERDICT:` line. A request that needs none of this runs the
|
|
15
|
+
* body exactly as before.
|
|
16
|
+
*/
|
|
17
|
+
import type { TestOptions } from '../types/commands/index.js';
|
|
18
|
+
/** What the orchestrator asks of one sub-run beyond its paths and options. */
|
|
19
|
+
export interface TestRunHooks {
|
|
20
|
+
/**
|
|
21
|
+
* Paths the pre-test build packages and records as coverage when they
|
|
22
|
+
* are wider than this run's own: the union of a mixed invocation's
|
|
23
|
+
* halves, so the second half runs build-less against the same build.
|
|
24
|
+
*/
|
|
25
|
+
coveragePaths?: string[];
|
|
26
|
+
/** Runs after every preflight gate and the pre-test build, right before dispatch. */
|
|
27
|
+
beforeDispatch?: () => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/** The single-run body (`runTestCommandBody`), injected to avoid an import cycle. */
|
|
30
|
+
export type TestRunBody = (paths: string[], options: TestOptions, hooks: TestRunHooks) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Runs `fireforge test` as one or several sub-runs. Always recovers a
|
|
33
|
+
* negative control a killed earlier run left applied, first.
|
|
34
|
+
*
|
|
35
|
+
* @param projectRoot - Project root
|
|
36
|
+
* @param testPaths - The positional test paths
|
|
37
|
+
* @param options - Parsed test options
|
|
38
|
+
* @param body - The single-run body
|
|
39
|
+
* @param onRefusal - Renders a sub-run's preflight refusal and emits its
|
|
40
|
+
* `FAIL reason=preflight` verdict (captured), as `testCommand` does for a
|
|
41
|
+
* single run
|
|
42
|
+
*/
|
|
43
|
+
export declare function orchestrateTestRuns(projectRoot: string, testPaths: string[], options: TestOptions, body: TestRunBody, onRefusal: (error: unknown) => void): Promise<void>;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Multi-run orchestration for `fireforge test`, all under the one
|
|
4
|
+
* engine-session lock hold the command already has:
|
|
5
|
+
*
|
|
6
|
+
* - mixed harness paths run as two consecutive mach invocations, xpcshell
|
|
7
|
+
* first, with one pre-test build (when `--build`) packaging both halves;
|
|
8
|
+
* - `--repeat <n>` runs the same selection n times, building once;
|
|
9
|
+
* - `--negative-control <diff>` applies a reviewed mutation after the
|
|
10
|
+
* build and right before each dispatch, and restores it after the run.
|
|
11
|
+
*
|
|
12
|
+
* Each sub-run is the ordinary single-run body. Its verdict line is
|
|
13
|
+
* captured rather than printed (see `setVerdictCapture`) and echoed as a
|
|
14
|
+
* `FIREFORGE-RUN:` line, and the invocation ends with one aggregate
|
|
15
|
+
* `FIREFORGE-VERDICT:` line. A request that needs none of this runs the
|
|
16
|
+
* body exactly as before.
|
|
17
|
+
*/
|
|
18
|
+
import { getProjectPaths } from '../core/config.js';
|
|
19
|
+
import { applyNegativeControl, planNegativeControl, recoverPendingNegativeControl, restoreNegativeControl, } from '../core/negative-control.js';
|
|
20
|
+
import { getActiveRunLogPath, writeToActiveRunLog } from '../core/run-log.js';
|
|
21
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
22
|
+
import { TestFailureError } from '../errors/build.js';
|
|
23
|
+
import { toError } from '../utils/errors.js';
|
|
24
|
+
import { info, notice, success } from '../utils/logger.js';
|
|
25
|
+
import { stripEnginePrefix } from '../utils/paths.js';
|
|
26
|
+
import { classifyTestHarnesses } from './test-modes.js';
|
|
27
|
+
import { deriveControlOutcome, describeControlOutcome, formatAggregateBody, formatRunLine, parseCapturedVerdict, worstRun, } from './test-multi-verdict.js';
|
|
28
|
+
import { emitAggregateVerdict, emitFailVerdict, setVerdictCapture, verdictEmitted, } from './test-verdict.js';
|
|
29
|
+
function assertMultiRunUsage(testPaths, options) {
|
|
30
|
+
const flag = options.negativeControl !== undefined ? '--negative-control' : '--repeat';
|
|
31
|
+
if (testPaths.length === 0) {
|
|
32
|
+
throw new InvalidArgumentError(`${flag} needs explicit test paths.`, flag);
|
|
33
|
+
}
|
|
34
|
+
if (options.buildOnly === true || options.canary !== undefined || options.auto === true) {
|
|
35
|
+
throw new InvalidArgumentError(`${flag} runs tests, so it cannot be combined with --build-only, --canary or --auto.`, flag);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function splitHarnesses(engineDir, testPaths, options) {
|
|
39
|
+
// `--build-only` packages a mixed union itself and dispatches nothing.
|
|
40
|
+
if (testPaths.length === 0 || options.buildOnly === true || options.canary !== undefined) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const normalized = testPaths.map((path) => stripEnginePrefix(path).trim());
|
|
44
|
+
const classification = await classifyTestHarnesses(engineDir, normalized);
|
|
45
|
+
if (classification.xpcshell.length === 0 || classification.nonXpcshell.length === 0) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
notice(`Mixed harnesses: running ${classification.xpcshell.length} xpcshell path(s), then ` +
|
|
49
|
+
`${classification.nonXpcshell.length} browser/mochitest path(s), as two mach invocations ` +
|
|
50
|
+
'under this lock hold' +
|
|
51
|
+
(options.build === true ? ', after one pre-test build that packages both.' : '.'));
|
|
52
|
+
return [
|
|
53
|
+
{ harness: 'xpcshell', paths: classification.xpcshell },
|
|
54
|
+
{ harness: 'mochitest', paths: classification.nonXpcshell },
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
/** Options for every run after the first: the build and doctor run once. */
|
|
58
|
+
function laterRunOptions(options) {
|
|
59
|
+
const later = { ...options };
|
|
60
|
+
delete later.build;
|
|
61
|
+
delete later.extendCoverage;
|
|
62
|
+
delete later.refuseUnexportedDrift;
|
|
63
|
+
delete later.doctor;
|
|
64
|
+
return later;
|
|
65
|
+
}
|
|
66
|
+
async function planRuns(engineDir, testPaths, options) {
|
|
67
|
+
const repeat = options.repeat ?? 1;
|
|
68
|
+
const control = options.negativeControl !== undefined;
|
|
69
|
+
if (repeat > 1 || control)
|
|
70
|
+
assertMultiRunUsage(testPaths, options);
|
|
71
|
+
const halves = await splitHarnesses(engineDir, testPaths, options);
|
|
72
|
+
if (halves === undefined && repeat === 1 && !control)
|
|
73
|
+
return undefined;
|
|
74
|
+
const groups = halves ?? [
|
|
75
|
+
{ paths: testPaths },
|
|
76
|
+
];
|
|
77
|
+
const union = halves?.flatMap((half) => half.paths);
|
|
78
|
+
const runs = [];
|
|
79
|
+
for (let repetition = 1; repetition <= repeat; repetition += 1) {
|
|
80
|
+
for (const group of groups) {
|
|
81
|
+
const first = runs.length === 0;
|
|
82
|
+
runs.push({
|
|
83
|
+
repetition,
|
|
84
|
+
...(group.harness === undefined ? {} : { harness: group.harness }),
|
|
85
|
+
paths: group.paths,
|
|
86
|
+
options: first ? options : laterRunOptions(options),
|
|
87
|
+
hooks: first && union !== undefined ? { coveragePaths: union } : {},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return runs;
|
|
92
|
+
}
|
|
93
|
+
function writeRunLine(line) {
|
|
94
|
+
process.stdout.write(`${line}\n`);
|
|
95
|
+
writeToActiveRunLog(`${line}\n`);
|
|
96
|
+
}
|
|
97
|
+
function controlHook(paths, plan, slot) {
|
|
98
|
+
return async () => {
|
|
99
|
+
slot.applied = await applyNegativeControl(paths, plan);
|
|
100
|
+
notice(`Negative control applied to ${plan.targets.map((target) => `engine/${target.enginePath}`).join(', ')}; ` +
|
|
101
|
+
'it stays in place until this run has finished.');
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Throws the invocation's error after the aggregate verdict, or returns
|
|
106
|
+
* when the invocation succeeded (all runs green, or a control caught).
|
|
107
|
+
*/
|
|
108
|
+
function finishInvocation(results, plannedTotal, control) {
|
|
109
|
+
if (control !== undefined)
|
|
110
|
+
info(describeControlOutcome(control));
|
|
111
|
+
if (control === 'caught') {
|
|
112
|
+
success('Negative control caught.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (control === 'missed') {
|
|
116
|
+
throw new TestFailureError('Negative control stayed green: no run went red with the mutation applied, so the selected tests do not detect it.', 'mach test', undefined, getActiveRunLogPath());
|
|
117
|
+
}
|
|
118
|
+
const worst = worstRun(results);
|
|
119
|
+
if (worst === undefined)
|
|
120
|
+
return;
|
|
121
|
+
if (worst.reason === 'preflight' || worst.reason === 'inconclusive') {
|
|
122
|
+
throw worst.error instanceof Error
|
|
123
|
+
? worst.error
|
|
124
|
+
: new GeneralError(`Run ${results.indexOf(worst) + 1} was refused.`);
|
|
125
|
+
}
|
|
126
|
+
const failing = results
|
|
127
|
+
.map((result, index) => ({ result, index }))
|
|
128
|
+
.filter(({ result }) => !result.passed)
|
|
129
|
+
.map(({ result, index }) => `run ${index + 1} (${result.reason ?? 'failed'})`);
|
|
130
|
+
throw new TestFailureError(`${failing.length} of ${plannedTotal} run(s) did not pass: ${failing.join(', ')}. See each FIREFORGE-RUN line above.`, 'mach test', undefined, getActiveRunLogPath());
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Runs `fireforge test` as one or several sub-runs. Always recovers a
|
|
134
|
+
* negative control a killed earlier run left applied, first.
|
|
135
|
+
*
|
|
136
|
+
* @param projectRoot - Project root
|
|
137
|
+
* @param testPaths - The positional test paths
|
|
138
|
+
* @param options - Parsed test options
|
|
139
|
+
* @param body - The single-run body
|
|
140
|
+
* @param onRefusal - Renders a sub-run's preflight refusal and emits its
|
|
141
|
+
* `FAIL reason=preflight` verdict (captured), as `testCommand` does for a
|
|
142
|
+
* single run
|
|
143
|
+
*/
|
|
144
|
+
export async function orchestrateTestRuns(projectRoot, testPaths, options, body, onRefusal) {
|
|
145
|
+
const projectPaths = getProjectPaths(projectRoot);
|
|
146
|
+
const controlPaths = {
|
|
147
|
+
engineDir: projectPaths.engine,
|
|
148
|
+
fireforgeDir: projectPaths.fireforgeDir,
|
|
149
|
+
};
|
|
150
|
+
await recoverPendingNegativeControl(controlPaths);
|
|
151
|
+
const runs = await planRuns(projectPaths.engine, testPaths, options);
|
|
152
|
+
if (runs === undefined) {
|
|
153
|
+
await body(testPaths, options, {});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const plan = options.negativeControl === undefined
|
|
157
|
+
? undefined
|
|
158
|
+
: await planNegativeControl(projectRoot, projectPaths.engine, options.negativeControl, {
|
|
159
|
+
deferApplyCheck: options.build === true,
|
|
160
|
+
});
|
|
161
|
+
const results = [];
|
|
162
|
+
let restoreError;
|
|
163
|
+
for (const [index, run] of runs.entries()) {
|
|
164
|
+
notice(`Run ${index + 1}/${runs.length}${run.harness === undefined ? '' : ` (${run.harness})`}` +
|
|
165
|
+
`${plan === undefined ? '' : ', negative control'}: ${run.paths.join(' ')}`);
|
|
166
|
+
const capture = {};
|
|
167
|
+
const slot = {};
|
|
168
|
+
let error;
|
|
169
|
+
setVerdictCapture(capture);
|
|
170
|
+
try {
|
|
171
|
+
await body(run.paths, run.options, {
|
|
172
|
+
...run.hooks,
|
|
173
|
+
...(plan === undefined ? {} : { beforeDispatch: controlHook(controlPaths, plan, slot) }),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (caught) {
|
|
177
|
+
error = caught;
|
|
178
|
+
if (capture.line === undefined)
|
|
179
|
+
onRefusal(caught);
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
setVerdictCapture(undefined);
|
|
183
|
+
}
|
|
184
|
+
// Only a kill writes a real verdict while a capture is installed (see
|
|
185
|
+
// emitKilledVerdict). The invocation is over: restore, start nothing
|
|
186
|
+
// more, print nothing after that line.
|
|
187
|
+
const killed = verdictEmitted();
|
|
188
|
+
if (slot.applied !== undefined) {
|
|
189
|
+
try {
|
|
190
|
+
const restored = await restoreNegativeControl(slot.applied);
|
|
191
|
+
info(`Negative control restored byte-exact: ${restored.restored.length} file(s).`);
|
|
192
|
+
}
|
|
193
|
+
catch (caught) {
|
|
194
|
+
restoreError = caught;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (killed)
|
|
198
|
+
throw toError(restoreError ?? error ?? new GeneralError('The run was killed.'));
|
|
199
|
+
const result = {
|
|
200
|
+
repetition: run.repetition,
|
|
201
|
+
...(run.harness === undefined ? {} : { harness: run.harness }),
|
|
202
|
+
...parseCapturedVerdict(capture.line),
|
|
203
|
+
...(error === undefined ? {} : { error }),
|
|
204
|
+
};
|
|
205
|
+
results.push(result);
|
|
206
|
+
writeRunLine(formatRunLine(result, index, runs.length));
|
|
207
|
+
if (restoreError !== undefined)
|
|
208
|
+
break;
|
|
209
|
+
if (result.reason === 'preflight' || result.reason === 'inconclusive')
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
if (restoreError !== undefined) {
|
|
213
|
+
emitFailVerdict('inconclusive', 'negative-control-restore');
|
|
214
|
+
throw toError(restoreError);
|
|
215
|
+
}
|
|
216
|
+
const repetitions = options.repeat ?? 1;
|
|
217
|
+
const control = plan === undefined
|
|
218
|
+
? undefined
|
|
219
|
+
: deriveControlOutcome(results, repetitions, results.length === runs.length);
|
|
220
|
+
emitAggregateVerdict(formatAggregateBody(results, runs.length, control));
|
|
221
|
+
finishInvocation(results, runs.length, control);
|
|
222
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope of the evidence a `--perf-samples` run reports and invalidates on.
|
|
3
|
+
*
|
|
4
|
+
* - A stale-build refusal on a perf run says which stale packaged files are
|
|
5
|
+
* staging inputs of the sampled test and which lie elsewhere, so a sample
|
|
6
|
+
* whose fixture is unaffected can proceed knowingly under
|
|
7
|
+
* `--allow-stale-build`.
|
|
8
|
+
* - An engine edit that lands while a sample runs makes the verdict
|
|
9
|
+
* `inconclusive` only when the edited path is something the running build
|
|
10
|
+
* packages (an install-manifest source, or a test file). An edit to a
|
|
11
|
+
* compiled source, a build file or anything else no manifest installs
|
|
12
|
+
* cannot reach the running binary, so the sample stands with a warning.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The perf-impact line of a stale-build refusal.
|
|
16
|
+
*
|
|
17
|
+
* @param engineDir - Absolute engine directory
|
|
18
|
+
* @param stalePaths - Stale packaged paths the refusal names
|
|
19
|
+
* @param requested - Normalized request paths of the run
|
|
20
|
+
*/
|
|
21
|
+
export declare function describePerfStaleImpact(engineDir: string, stalePaths: readonly string[], requested: readonly string[]): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Tolerance for `assertEngineGenerationUnchanged` on a `--perf-samples` run:
|
|
24
|
+
* true (with a warning naming the edits) when HEAD did not move and no
|
|
25
|
+
* changed path is packaged by the running build.
|
|
26
|
+
*
|
|
27
|
+
* @param engineDir - Absolute engine directory
|
|
28
|
+
*/
|
|
29
|
+
export declare function perfGenerationTolerance(engineDir: string): (before: string, after: string) => Promise<boolean>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Scope of the evidence a `--perf-samples` run reports and invalidates on.
|
|
4
|
+
*
|
|
5
|
+
* - A stale-build refusal on a perf run says which stale packaged files are
|
|
6
|
+
* staging inputs of the sampled test and which lie elsewhere, so a sample
|
|
7
|
+
* whose fixture is unaffected can proceed knowingly under
|
|
8
|
+
* `--allow-stale-build`.
|
|
9
|
+
* - An engine edit that lands while a sample runs makes the verdict
|
|
10
|
+
* `inconclusive` only when the edited path is something the running build
|
|
11
|
+
* packages (an install-manifest source, or a test file). An edit to a
|
|
12
|
+
* compiled source, a build file or anything else no manifest installs
|
|
13
|
+
* cannot reach the running binary, so the sample stands with a warning.
|
|
14
|
+
*/
|
|
15
|
+
import { isTestPath } from '../core/build-audit-resolve.js';
|
|
16
|
+
import { collectStagingInputs, isUnderPath } from '../core/coverage-records.js';
|
|
17
|
+
import { generationDeltaPaths } from '../core/engine-session-lock.js';
|
|
18
|
+
import { classifyRuntimeDelivery, installsFromDirectory, } from '../core/objdir-install-manifests.js';
|
|
19
|
+
import { loadEngineInstallIndex } from '../core/test-stale-exempt.js';
|
|
20
|
+
import { warn } from '../utils/logger.js';
|
|
21
|
+
/** How many paths an impact line names per group. */
|
|
22
|
+
const IMPACT_LIST_LIMIT = 5;
|
|
23
|
+
function capList(paths) {
|
|
24
|
+
const head = paths.slice(0, IMPACT_LIST_LIMIT).join(', ');
|
|
25
|
+
const extra = paths.length - Math.min(paths.length, IMPACT_LIST_LIMIT);
|
|
26
|
+
return extra > 0 ? `${head}, … (+${extra} more)` : head;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The perf-impact line of a stale-build refusal.
|
|
30
|
+
*
|
|
31
|
+
* @param engineDir - Absolute engine directory
|
|
32
|
+
* @param stalePaths - Stale packaged paths the refusal names
|
|
33
|
+
* @param requested - Normalized request paths of the run
|
|
34
|
+
*/
|
|
35
|
+
export async function describePerfStaleImpact(engineDir, stalePaths, requested) {
|
|
36
|
+
const inputs = [];
|
|
37
|
+
for (const path of requested)
|
|
38
|
+
inputs.push(...(await collectStagingInputs(engineDir, path)));
|
|
39
|
+
const fixture = stalePaths.filter((path) => inputs.some((input) => isUnderPath(path, input)));
|
|
40
|
+
const elsewhere = stalePaths.filter((path) => !fixture.includes(path));
|
|
41
|
+
const fixturePart = fixture.length > 0
|
|
42
|
+
? `${fixture.length} stale file(s) are staging inputs of the sampled test (${capList(fixture)}), so the sample would measure the old content`
|
|
43
|
+
: requested.length > 0
|
|
44
|
+
? "the sampled test's own staging inputs are unaffected"
|
|
45
|
+
: 'no test paths were named, so every stale file may be measured';
|
|
46
|
+
const elsewherePart = elsewhere.length > 0 && requested.length > 0
|
|
47
|
+
? `; ${elsewhere.length} stale packaged file(s) lie outside them (${capList(elsewhere)}) and affect the sample only if the measured path loads them`
|
|
48
|
+
: '';
|
|
49
|
+
return (`Perf sample impact: ${fixturePart}${elsewherePart}. Repackage first, or pass ` +
|
|
50
|
+
'--allow-stale-build to take the sample knowingly.');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Tolerance for `assertEngineGenerationUnchanged` on a `--perf-samples` run:
|
|
54
|
+
* true (with a warning naming the edits) when HEAD did not move and no
|
|
55
|
+
* changed path is packaged by the running build.
|
|
56
|
+
*
|
|
57
|
+
* @param engineDir - Absolute engine directory
|
|
58
|
+
*/
|
|
59
|
+
export function perfGenerationTolerance(engineDir) {
|
|
60
|
+
return async (before, after) => {
|
|
61
|
+
const delta = generationDeltaPaths(before, after);
|
|
62
|
+
if (delta.headMoved || delta.paths.length === 0)
|
|
63
|
+
return false;
|
|
64
|
+
const index = await loadEngineInstallIndex(engineDir);
|
|
65
|
+
if (index === undefined)
|
|
66
|
+
return false;
|
|
67
|
+
for (const path of delta.paths) {
|
|
68
|
+
if (isTestPath(path))
|
|
69
|
+
return false;
|
|
70
|
+
if (path.endsWith('/')) {
|
|
71
|
+
if (installsFromDirectory(index, path))
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
else if ((await classifyRuntimeDelivery(index, engineDir, path)) !== 'unlisted') {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
warn(`engine/ changed while this perf sample ran, but only in files the running build does not ` +
|
|
79
|
+
`package (${capList(delta.paths)}); the sample stands. Rebuild before the next sample if ` +
|
|
80
|
+
'they belong in it.');
|
|
81
|
+
return true;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `fireforge test` checks that run BEFORE the engine session lock.
|
|
3
|
+
*
|
|
4
|
+
* Everything here reads, never writes, so none of it needs the lock, and
|
|
5
|
+
* each check used to cost a full place in the lock queue before it could
|
|
6
|
+
* refuse:
|
|
7
|
+
*
|
|
8
|
+
* - a missing launchable binary (a peer's interrupted build) refused only
|
|
9
|
+
* after the wait, in every session, with nothing naming the cause;
|
|
10
|
+
* - the named-file checkJs pass refused an `Element | null` after an
|
|
11
|
+
* eighteen-minute queue;
|
|
12
|
+
* - `--wait-browser` and `--wait-port` spent their budget holding the
|
|
13
|
+
* lock, so every peer queued behind a run that was doing nothing.
|
|
14
|
+
*
|
|
15
|
+
* The in-lock ladder in `test.ts` still runs in full afterwards. That is
|
|
16
|
+
* the "re-check once under the lock": the waits this pass spent are
|
|
17
|
+
* stripped from the options it returns, so the in-lock browser and port
|
|
18
|
+
* checks probe once instead of waiting again.
|
|
19
|
+
*/
|
|
20
|
+
import type { TestOptions } from '../types/commands/index.js';
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the launchable binary a test run needs, or refuses naming why
|
|
23
|
+
* it is missing. The cause comes from the build run record, which any
|
|
24
|
+
* session's build writes, so a session that never ran the build can still
|
|
25
|
+
* say "a peer's build was interrupted" rather than only "the binary is
|
|
26
|
+
* missing".
|
|
27
|
+
*
|
|
28
|
+
* @param projectRoot - Project root
|
|
29
|
+
* @param engineDir - Absolute engine directory
|
|
30
|
+
* @param binaryName - Configured binary name
|
|
31
|
+
* @param objDir - Active objdir, when one exists
|
|
32
|
+
* @returns The engine-relative launchable path, or undefined without an objdir
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveLaunchablePathForTests(projectRoot: string, engineDir: string, binaryName: string, objDir: string | undefined): Promise<string | undefined>;
|
|
35
|
+
/** Build-artifact preflight wording for `fireforge test`. */
|
|
36
|
+
export declare const TEST_BUILD_PREFLIGHT: {
|
|
37
|
+
readonly label: "Tests";
|
|
38
|
+
readonly requirement: "Tests require a completed build.";
|
|
39
|
+
readonly remediation: "Run 'fireforge build' first, then run 'fireforge test'.";
|
|
40
|
+
readonly requireExisting: true;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Runs the lock-free checks and waits, and returns the options the in-lock
|
|
44
|
+
* run should use: with `waitBrowser`/`waitPort` removed when their budget
|
|
45
|
+
* was already spent here.
|
|
46
|
+
*
|
|
47
|
+
* @param projectRoot - Project root
|
|
48
|
+
* @param testPaths - Requested test paths
|
|
49
|
+
* @param options - Parsed test options
|
|
50
|
+
*/
|
|
51
|
+
export declare function runTestPreLockPreflight(projectRoot: string, testPaths: string[], options: TestOptions): Promise<TestOptions>;
|