@hominis/fireforge 0.49.2 → 0.51.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 +87 -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 +41 -0
- package/dist/src/commands/check-report.js +91 -0
- package/dist/src/commands/check-run.d.ts +33 -0
- package/dist/src/commands/check-run.js +112 -0
- package/dist/src/commands/check-steps.d.ts +75 -0
- package/dist/src/commands/check-steps.js +200 -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/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- 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-placement-gate.js +8 -0
- 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 +98 -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 +151 -113
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +36 -61
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +20 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +4 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -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 +24 -104
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +10 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +78 -31
- package/dist/src/commands/patch/move-files-create.js +34 -18
- package/dist/src/commands/patch/move-files-into.js +15 -7
- package/dist/src/commands/patch/move-files.js +6 -2
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- 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 +20 -1
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +133 -0
- package/dist/src/commands/patch/split-plan.d.ts +17 -8
- package/dist/src/commands/patch/split-plan.js +60 -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 +62 -17
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- 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-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +54 -139
- 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 +50 -2
- package/dist/src/commands/re-export-adjacent.js +160 -40
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- 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-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- 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 +74 -6
- 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-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +56 -17
- package/dist/src/commands/re-export-removed.d.ts +13 -2
- package/dist/src/commands/re-export-removed.js +57 -12
- 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 +19 -9
- package/dist/src/commands/re-export-scan.js +60 -24
- package/dist/src/commands/re-export-single.js +122 -46
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- 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 +71 -57
- 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 +22 -0
- package/dist/src/commands/test-build-furnace.js +54 -0
- package/dist/src/commands/test-build-phase.js +64 -41
- package/dist/src/commands/test-checkjs.js +10 -2
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +211 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -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 +52 -0
- package/dist/src/commands/test-orchestrate.js +240 -0
- package/dist/src/commands/test-perf-scope.d.ts +50 -0
- package/dist/src/commands/test-perf-scope.js +138 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +273 -0
- package/dist/src/commands/test-register.js +69 -10
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +74 -99
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +60 -28
- package/dist/src/commands/test-verdict.d.ts +11 -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 +120 -124
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +151 -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 +67 -36
- 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 +13 -0
- package/dist/src/core/build-prepare.js +42 -10
- 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 +10 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +219 -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 +21 -0
- package/dist/src/core/config-validate-token-scales.d.ts +9 -0
- package/dist/src/core/config-validate-token-scales.js +92 -0
- package/dist/src/core/config-validate.js +20 -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 +108 -0
- package/dist/src/core/coverage-records.js +384 -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 +16 -1
- package/dist/src/core/engine-session-lock.js +38 -10
- 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-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +13 -25
- package/dist/src/core/file-lock.js +163 -140
- 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 +22 -12
- 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 +16 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
- package/dist/src/core/furnace-config-validate.js +14 -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-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- 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 +8 -4
- package/dist/src/core/furnace-validate-accessibility.js +71 -28
- package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
- package/dist/src/core/furnace-validate-exceptions.js +80 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +78 -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 +164 -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/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +32 -10
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +27 -0
- package/dist/src/core/negative-control-compiled.js +73 -0
- package/dist/src/core/negative-control-diff.d.ts +51 -0
- package/dist/src/core/negative-control-diff.js +179 -0
- package/dist/src/core/negative-control.d.ts +150 -0
- package/dist/src/core/negative-control.js +399 -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/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +6 -0
- package/dist/src/core/patch-export-update.js +13 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +22 -16
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +20 -13
- 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.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +17 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +24 -14
- 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 +23 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +196 -0
- package/dist/src/core/patch-lint-ignore.js +429 -0
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -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 +331 -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 +57 -0
- package/dist/src/core/patch-lint-token-scale.js +286 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +66 -288
- 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 +27 -9
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +61 -3
- package/dist/src/core/patch-order-legal.d.ts +26 -0
- package/dist/src/core/patch-order-legal.js +88 -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 +22 -5
- package/dist/src/core/patch-policy.js +26 -12
- package/dist/src/core/patch-retired.d.ts +57 -0
- package/dist/src/core/patch-retired.js +92 -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 +26 -0
- package/dist/src/core/run-log.js +106 -4
- 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 +19 -1
- package/dist/src/core/test-harness-crash.js +8 -9
- package/dist/src/core/test-harness-signature.d.ts +33 -0
- package/dist/src/core/test-harness-signature.js +23 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +90 -0
- package/dist/src/core/test-run-completeness.js +184 -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/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +53 -0
- package/dist/src/core/token-lint.js +131 -0
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +5 -0
- package/dist/src/core/typecheck-shim.d.ts +6 -2
- package/dist/src/core/typecheck-shim.js +205 -4
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +14 -3
- package/dist/src/index.js +6 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +212 -24
- package/dist/src/types/commands/patches.d.ts +118 -1
- package/dist/src/types/config.d.ts +264 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +63 -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 +36 -7
- package/dist/src/utils/options.js +94 -8
- 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 +369 -11
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +139 -3
- package/docs/lifecycle-invariants.md +65 -30
- package/docs/machine-output.md +120 -12
- package/docs/patch-workflow.md +458 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +346 -21
- package/package.json +3 -3
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* The `fireforge test` checks that run BEFORE the engine session lock.
|
|
4
|
+
*
|
|
5
|
+
* Everything here reads, never writes, so none of it needs the lock, and
|
|
6
|
+
* each check used to cost a full place in the lock queue before it could
|
|
7
|
+
* refuse:
|
|
8
|
+
*
|
|
9
|
+
* - a missing launchable binary (a peer's interrupted build) refused only
|
|
10
|
+
* after the wait, in every session, with nothing naming the cause;
|
|
11
|
+
* - the named-file checkJs pass refused an `Element | null` after an
|
|
12
|
+
* eighteen-minute queue;
|
|
13
|
+
* - `--wait-browser` and `--wait-port` spent their budget holding the
|
|
14
|
+
* lock, so every peer queued behind a run that was doing nothing.
|
|
15
|
+
*
|
|
16
|
+
* The in-lock ladder in `test.ts` still runs in full afterwards. That is
|
|
17
|
+
* the "re-check once under the lock": the waits this pass spent are
|
|
18
|
+
* stripped from the options it returns, so the in-lock browser and port
|
|
19
|
+
* checks probe once instead of waiting again.
|
|
20
|
+
*/
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { describeBuildRunState, readBuildRunState } from '../core/build-run-record.js';
|
|
23
|
+
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
24
|
+
import { assertEngineExists } from '../core/engine-precondition.js';
|
|
25
|
+
import { autoPortsRequested } from '../core/harness-port-fallback.js';
|
|
26
|
+
import { hasBuildArtifacts, hasRunnableBundle } from '../core/mach.js';
|
|
27
|
+
import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
|
|
28
|
+
import { assertMarionettePortAvailable, ensureLaunchableBrowserNotRunning, extractForwardedMarionettePort, } from '../core/marionette-port.js';
|
|
29
|
+
import { ensureMochitestServerPortAvailable } from '../core/mochitest-server-port.js';
|
|
30
|
+
import { planNegativeControl } from '../core/negative-control.js';
|
|
31
|
+
import { formatCompiledControlRefusal } from '../core/negative-control-compiled.js';
|
|
32
|
+
import { waitBeforeLock } from '../core/preflight-wait.js';
|
|
33
|
+
import { PreflightRefusalError } from '../errors/base.js';
|
|
34
|
+
import { info } from '../utils/logger.js';
|
|
35
|
+
import { resolveWaitLockSeconds } from '../utils/options.js';
|
|
36
|
+
import { stripEnginePrefix } from '../utils/paths.js';
|
|
37
|
+
import { checkRequestedTestTypes } from './test-checkjs.js';
|
|
38
|
+
import { assertPathlessTestMode, classifyBeforeDispatch, resolveCanaryPath } from './test-modes.js';
|
|
39
|
+
import { enforceStaleBuildGate } from './test-stale-gate.js';
|
|
40
|
+
/**
|
|
41
|
+
* Resolves the launchable binary a test run needs, or refuses naming why
|
|
42
|
+
* it is missing. The cause comes from the build run record, which any
|
|
43
|
+
* session's build writes, so a session that never ran the build can still
|
|
44
|
+
* say "a peer's build was interrupted" rather than only "the binary is
|
|
45
|
+
* missing".
|
|
46
|
+
*
|
|
47
|
+
* @param projectRoot - Project root
|
|
48
|
+
* @param engineDir - Absolute engine directory
|
|
49
|
+
* @param binaryName - Configured binary name
|
|
50
|
+
* @param objDir - Active objdir, when one exists
|
|
51
|
+
* @returns The engine-relative launchable path, or undefined without an objdir
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveLaunchablePathForTests(projectRoot, engineDir, binaryName, objDir) {
|
|
54
|
+
if (!objDir)
|
|
55
|
+
return undefined;
|
|
56
|
+
const bundleCheck = await hasRunnableBundle(engineDir, binaryName, objDir);
|
|
57
|
+
if (bundleCheck.runnable)
|
|
58
|
+
return bundleCheck.expectedPath;
|
|
59
|
+
const expectedSuffix = bundleCheck.expectedPath
|
|
60
|
+
? ` (expected at engine/${bundleCheck.expectedPath})`
|
|
61
|
+
: '';
|
|
62
|
+
const cause = describeBuildRunState(await readBuildRunState(projectRoot));
|
|
63
|
+
throw new PreflightRefusalError(`Tests require a complete launchable build${expectedSuffix}. ` +
|
|
64
|
+
'The obj-*/dist/ tree exists but the launchable binary is missing' +
|
|
65
|
+
(cause !== undefined
|
|
66
|
+
? `.\n\n${cause}\n\n`
|
|
67
|
+
: ' — typically the result of an interrupted or partially failed `fireforge build`.\n\n') +
|
|
68
|
+
'Run "fireforge build" again and let it finish before retrying "fireforge test".', 'no-launchable-binary');
|
|
69
|
+
}
|
|
70
|
+
/** Build-artifact preflight wording for `fireforge test`. */
|
|
71
|
+
export const TEST_BUILD_PREFLIGHT = {
|
|
72
|
+
label: 'Tests',
|
|
73
|
+
requirement: 'Tests require a completed build.',
|
|
74
|
+
remediation: "Run 'fireforge build' first, then run 'fireforge test'.",
|
|
75
|
+
requireExisting: true,
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The head of the in-lock ladder, run early: argument shape, engine, build
|
|
79
|
+
* artifacts and config. Each refuses with exactly the message it gives
|
|
80
|
+
* under the lock. Returns undefined when there is no objdir to check
|
|
81
|
+
* further.
|
|
82
|
+
*/
|
|
83
|
+
async function loadPreLockContext(projectRoot, testPaths, options) {
|
|
84
|
+
assertPathlessTestMode(testPaths, options);
|
|
85
|
+
const paths = getProjectPaths(projectRoot);
|
|
86
|
+
await assertEngineExists(paths.engine);
|
|
87
|
+
const buildCheck = await hasBuildArtifacts(paths.engine);
|
|
88
|
+
assertBuildArtifacts(paths.engine, buildCheck, TEST_BUILD_PREFLIGHT);
|
|
89
|
+
const config = await loadConfig(projectRoot);
|
|
90
|
+
return buildCheck.objDir === undefined ? undefined : { paths, config, objDir: buildCheck.objDir };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Runs the lock-free checks and waits, and returns the options the in-lock
|
|
94
|
+
* run should use: with `waitBrowser`/`waitPort` removed when their budget
|
|
95
|
+
* was already spent here.
|
|
96
|
+
*
|
|
97
|
+
* @param projectRoot - Project root
|
|
98
|
+
* @param testPaths - Requested test paths
|
|
99
|
+
* @param options - Parsed test options
|
|
100
|
+
*/
|
|
101
|
+
export async function runTestPreLockPreflight(projectRoot, testPaths, options) {
|
|
102
|
+
const loaded = await loadPreLockContext(projectRoot, testPaths, options);
|
|
103
|
+
if (loaded === undefined)
|
|
104
|
+
return withLockMode(options, 'exclusive');
|
|
105
|
+
const { paths, config, objDir } = loaded;
|
|
106
|
+
options = withLockMode(options, resolveTestLockMode(options, config));
|
|
107
|
+
let launchablePath;
|
|
108
|
+
try {
|
|
109
|
+
launchablePath = await resolveLaunchablePathForTests(projectRoot, paths.engine, config.binaryName, objDir);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// A build in progress holds the engine lock and relinks the bundle
|
|
113
|
+
// before it lets go, so this run queues behind it rather than refusing.
|
|
114
|
+
if ((await readBuildRunState(projectRoot)).kind !== 'running')
|
|
115
|
+
throw error;
|
|
116
|
+
info('The launchable binary is missing while a build runs; queueing behind it.');
|
|
117
|
+
}
|
|
118
|
+
const canaryPath = resolveCanaryPath(options, config);
|
|
119
|
+
const normalizedPaths = (canaryPath !== undefined ? [canaryPath] : testPaths).map((path) => stripEnginePrefix(path).trim());
|
|
120
|
+
if (options.build === true || options.buildOnly === true) {
|
|
121
|
+
await checkRequestedTestTypes(paths, config, normalizedPaths);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await refuseCompiledControlBeforeLock(projectRoot, paths.engine, options);
|
|
125
|
+
await refuseStalePackagingBeforeLock(projectRoot, paths.engine, options, normalizedPaths);
|
|
126
|
+
}
|
|
127
|
+
return waitForResourcesBeforeLock(paths.engine, config, options, {
|
|
128
|
+
launchablePath,
|
|
129
|
+
normalizedPaths,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** `options` with `sharedLock` set to the resolved decision. */
|
|
133
|
+
function withLockMode(options, mode) {
|
|
134
|
+
if (mode === 'exclusive' && options.sharedLock === undefined)
|
|
135
|
+
return options;
|
|
136
|
+
const rest = { ...options };
|
|
137
|
+
delete rest.sharedLock;
|
|
138
|
+
return mode === 'shared' ? { ...rest, sharedLock: true } : rest;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* How this run holds the engine session lock. Shared only on request
|
|
142
|
+
* (`--shared-lock` or `test.lockMode: "shared"`), and only for a run that
|
|
143
|
+
* writes nothing into engine/ or the objdir and can move its ports: no
|
|
144
|
+
* build, no negative control, auto ports. Every other run stays exclusive,
|
|
145
|
+
* so a shared holder can never overlap a build or a re-export.
|
|
146
|
+
*/
|
|
147
|
+
function resolveTestLockMode(options, config) {
|
|
148
|
+
const requested = options.sharedLock === true || config.test?.lockMode === 'shared';
|
|
149
|
+
if (!requested)
|
|
150
|
+
return 'exclusive';
|
|
151
|
+
const writes = options.build === true ||
|
|
152
|
+
options.buildOnly === true ||
|
|
153
|
+
options.negativeControl !== undefined ||
|
|
154
|
+
options.canary !== undefined ||
|
|
155
|
+
options.doctor === true;
|
|
156
|
+
if (writes || !autoPortsRequested(options, config)) {
|
|
157
|
+
info('Shared test lock requested, but this run ' +
|
|
158
|
+
(writes
|
|
159
|
+
? 'builds, applies a negative control, or runs a canary/doctor preflight'
|
|
160
|
+
: 'does not use auto ports (--auto-ports or test.mochitestPorts: "auto")') +
|
|
161
|
+
', so it holds the engine lock exclusively.');
|
|
162
|
+
return 'exclusive';
|
|
163
|
+
}
|
|
164
|
+
return 'shared';
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Refuses a build-less `--negative-control` on compiled source before the
|
|
168
|
+
* lock: applied after the build it is never compiled, so the run could only
|
|
169
|
+
* report `control=missed` against the unmutated binary.
|
|
170
|
+
*/
|
|
171
|
+
async function refuseCompiledControlBeforeLock(projectRoot, engineDir, options) {
|
|
172
|
+
if (options.negativeControl === undefined)
|
|
173
|
+
return;
|
|
174
|
+
let plan;
|
|
175
|
+
try {
|
|
176
|
+
plan = await planNegativeControl(projectRoot, engineDir, options.negativeControl);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Any other refusal (including a control a killed run left applied,
|
|
180
|
+
// which only the in-lock recovery may undo) is the in-lock pass's.
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (plan.compiled === undefined)
|
|
184
|
+
return;
|
|
185
|
+
throw new PreflightRefusalError(formatCompiledControlRefusal(plan.compiled), 'negative-control');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Runs the build-less run's packaging gates (coverage, stale content,
|
|
189
|
+
* StaticComponents) before the lock, so a refusal surfaces before the run
|
|
190
|
+
* queues instead of after it reached the head of the queue. The same gate
|
|
191
|
+
* runs again under the lock, where a peer's build may since have changed
|
|
192
|
+
* the answer; this pass only refuses early what would refuse anyway.
|
|
193
|
+
*
|
|
194
|
+
* Skipped while a build runs (it will rewrite the record this reads) and
|
|
195
|
+
* under `--allow-stale-build`/`--allow-stale-components`, whose warnings
|
|
196
|
+
* belong to the in-lock pass alone. A `--negative-control` refusal says to
|
|
197
|
+
* add `--build`: without it the control has nothing current to run on, and
|
|
198
|
+
* the invocation used to end `control=inconclusive` after a full queue.
|
|
199
|
+
*/
|
|
200
|
+
async function refuseStalePackagingBeforeLock(projectRoot, engineDir, options, normalizedPaths) {
|
|
201
|
+
if (options.allowStaleBuild === true || options.allowStaleComponents === true)
|
|
202
|
+
return;
|
|
203
|
+
if ((await readBuildRunState(projectRoot)).kind === 'running')
|
|
204
|
+
return;
|
|
205
|
+
try {
|
|
206
|
+
await enforceStaleBuildGate(projectRoot, engineDir, options, normalizedPaths, {
|
|
207
|
+
quiet: true,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (options.negativeControl === undefined || !(error instanceof PreflightRefusalError)) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
throw new PreflightRefusalError(`${error.message}\n\nThis --negative-control run has no --build, so its control would ` +
|
|
215
|
+
'run on this packaging and end inconclusive. Add --build: the control is then planned ' +
|
|
216
|
+
'against a fresh build of the requested paths.', error.note);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** `options` without one wait budget, which this pass already spent. */
|
|
220
|
+
function withoutOption(options, key) {
|
|
221
|
+
const rest = { ...options };
|
|
222
|
+
if (key === 'waitBrowser')
|
|
223
|
+
delete rest.waitBrowser;
|
|
224
|
+
else
|
|
225
|
+
delete rest.waitPort;
|
|
226
|
+
return rest;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Spends `--wait-browser` and `--wait-port` before the lock, mirroring
|
|
230
|
+
* which in-lock checks would apply to this run, and strips each budget it
|
|
231
|
+
* spent from the returned options.
|
|
232
|
+
*/
|
|
233
|
+
async function waitForResourcesBeforeLock(engineDir, config, options, request) {
|
|
234
|
+
if (options.waitBrowser === undefined && options.waitPort === undefined)
|
|
235
|
+
return options;
|
|
236
|
+
let xpcshellOnly;
|
|
237
|
+
try {
|
|
238
|
+
({ xpcshellOnly } = await classifyBeforeDispatch(engineDir, request.normalizedPaths, {
|
|
239
|
+
allowMixed: true,
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return options;
|
|
244
|
+
}
|
|
245
|
+
const kill = options.killStaleMarionette === true;
|
|
246
|
+
let result = options;
|
|
247
|
+
const building = options.build === true || options.buildOnly === true;
|
|
248
|
+
if (options.waitBrowser !== undefined &&
|
|
249
|
+
request.launchablePath !== undefined &&
|
|
250
|
+
(building || !xpcshellOnly)) {
|
|
251
|
+
const binary = join(engineDir, request.launchablePath);
|
|
252
|
+
await waitBeforeLock(() => ensureLaunchableBrowserNotRunning(binary), resolveWaitLockSeconds(options.waitBrowser),
|
|
253
|
+
// A stale browser the run may reap is not worth waiting out; the
|
|
254
|
+
// in-lock check terminates it.
|
|
255
|
+
kill ? ['stale-browser'] : []);
|
|
256
|
+
result = withoutOption(result, 'waitBrowser');
|
|
257
|
+
}
|
|
258
|
+
// Port waits stay under the lock with --kill-stale-marionette: a stale
|
|
259
|
+
// listener reads as busy until the in-lock pass reaps it, so waiting it
|
|
260
|
+
// out here would only burn the budget.
|
|
261
|
+
if (options.waitPort !== undefined && options.buildOnly !== true && !kill) {
|
|
262
|
+
const seconds = resolveWaitLockSeconds(options.waitPort);
|
|
263
|
+
if (!xpcshellOnly) {
|
|
264
|
+
await waitBeforeLock(() => ensureMochitestServerPortAvailable(undefined, { engineDir }), seconds);
|
|
265
|
+
}
|
|
266
|
+
if (!xpcshellOnly || options.doctor === true) {
|
|
267
|
+
const port = options.marionettePort ?? extractForwardedMarionettePort(options.machArg ?? []);
|
|
268
|
+
await waitBeforeLock(() => assertMarionettePortAvailable(port, { binaryName: config.binaryName }), seconds);
|
|
269
|
+
}
|
|
270
|
+
result = withoutOption(result, 'waitPort');
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
@@ -26,12 +26,33 @@ const TEST_HELP_TEXT = [
|
|
|
26
26
|
'not exercise cross-argument state; --no-shard restores the',
|
|
27
27
|
'combined single-instance invocation.',
|
|
28
28
|
'',
|
|
29
|
+
'Mixed xpcshell and browser/mochitest paths run as two consecutive',
|
|
30
|
+
'mach invocations (xpcshell first) under one lock hold and one',
|
|
31
|
+
'verdict; with --build, one pre-test build packages both halves.',
|
|
32
|
+
'--repeat <n> and --negative-control <diff> run under one lock hold',
|
|
33
|
+
'too. Each sub-run prints a FIREFORGE-RUN: line (its own verdict',
|
|
34
|
+
'plus run=<i>/<n>) before the single aggregate verdict.',
|
|
35
|
+
'',
|
|
36
|
+
'A dev objdir symlinks chrome JS/CSS and widget files into engine/,',
|
|
37
|
+
'so the browser reads engine/ sources when it loads them, after the',
|
|
38
|
+
'build step: an edit made while a suite runs reaches that suite, and',
|
|
39
|
+
'a hand-made neuter must stay until the verdict line, not until',
|
|
40
|
+
'"Build complete", since one restored early is gone for later',
|
|
41
|
+
'shards. --negative-control applies its diff after the build and',
|
|
42
|
+
'restores it byte-exact only after the run, before the lock is',
|
|
43
|
+
'released.',
|
|
44
|
+
'',
|
|
29
45
|
'Machine-readable verdict: every run (single, sharded, --canary,',
|
|
30
46
|
'--doctor) ends with exactly one raw stdout line',
|
|
31
47
|
' FIREFORGE-VERDICT: PASS|FAIL [reason=crash|harness-arguments|test-failures|',
|
|
32
48
|
' no-tests|preflight|inconclusive|lock-timeout|killed] [checks=<n>]',
|
|
33
|
-
' [unexpected=<n>] [
|
|
34
|
-
' [
|
|
49
|
+
' [unexpected=<n>] [started=<n>/<m> first-unstarted=<path>]',
|
|
50
|
+
' [shards=<p>/<t>] [(<note>)] [shuffle=<seed>]',
|
|
51
|
+
' [runs=<p>/<n>] [control=caught|missed|inconclusive]',
|
|
52
|
+
' [furnace-applied=<names>|furnace-apply=skipped] [log=<path>]',
|
|
53
|
+
"control= is a negative control's outcome: caught (went red on test",
|
|
54
|
+
'failures, as a control should; exit 0), missed (stayed green; exit',
|
|
55
|
+
'5) or inconclusive.',
|
|
35
56
|
'shuffle=<seed> is the seed a --shuffle run exported to the harness.',
|
|
36
57
|
"log=<path> names this run's complete output, written to",
|
|
37
58
|
'.fireforge/logs/ as the run streams, so a piped or truncated run',
|
|
@@ -39,7 +60,11 @@ const TEST_HELP_TEXT = [
|
|
|
39
60
|
'The status follows the harness classifier, not the raw exit code:',
|
|
40
61
|
'a crash-classified run says FAIL reason=crash even at exit 0, and',
|
|
41
62
|
'a green-summary-override pass says PASS despite a non-zero mach',
|
|
42
|
-
'exit.
|
|
63
|
+
'exit. An exit-0 run is still FAIL reason=test-failures when its',
|
|
64
|
+
'summary reports unexpected results, the browser shut down mid-test,',
|
|
65
|
+
'or a requested file never started; started=/first-unstarted= then',
|
|
66
|
+
'name how far it got and where a re-run has to pick up.',
|
|
67
|
+
'reason=preflight covers any failure before the harness ran',
|
|
43
68
|
'(missing/stale build, invalid paths, port conflicts, config',
|
|
44
69
|
'errors); reason=inconclusive means engine/ or perf power source',
|
|
45
70
|
'changed while tests ran, so the result cannot be trusted;',
|
|
@@ -56,13 +81,47 @@ const TEST_HELP_TEXT = [
|
|
|
56
81
|
'"(headed run stalled with the display asleep)" marks a no-output',
|
|
57
82
|
'stall whose display was measured asleep. Key on this line',
|
|
58
83
|
'instead of mach internals.',
|
|
84
|
+
'',
|
|
85
|
+
'Lock-free checks run before the engine lock is requested: a missing',
|
|
86
|
+
'launchable binary (with the build that left it missing, when one is',
|
|
87
|
+
'recorded), the named-file checkJs pass of --build, and the',
|
|
88
|
+
'--wait-browser/--wait-port budgets. Each is re-checked once under the',
|
|
89
|
+
'lock.',
|
|
59
90
|
].join('\n');
|
|
91
|
+
function registerTestMultiRunOptions(test) {
|
|
92
|
+
return test
|
|
93
|
+
.option('--repeat <n>', 'Run the same paths n times under one engine-lock hold (a --build builds once); prints each run and aggregates runs=<passed>/<n> on the verdict', commanderArgParser((raw) => parsePositiveIntegerFlag('--repeat', raw)))
|
|
94
|
+
.option('--negative-control <diff>', 'Apply this reviewed git diff (engine/ or components/ sources; project-relative path) after the pre-test build and right before dispatch, restore it byte-exact after the run (also on failure, SIGINT and SIGTERM) before the lock is released, and report control=caught|missed|inconclusive on the verdict')
|
|
95
|
+
.option('--auto-ports', "Let a browser run proceed when the mochitest server or Marionette port is held by something that is not this checkout's own: the engine's harness falls back to a free port itself. Same as test.mochitestPorts: \"auto\". This checkout's own leftovers are still refused.");
|
|
96
|
+
}
|
|
60
97
|
function registerTestSharingOptions(test) {
|
|
61
98
|
return test
|
|
62
99
|
.option('--wait-browser [seconds]', 'Wait for this objdir browser before any deployment/build (default 60)', commanderArgParser((raw) => resolveWaitLockSeconds(raw)))
|
|
63
100
|
.option('--wait-port [seconds]', 'Wait for the mochitest and Marionette ports (default 60)', commanderArgParser((raw) => resolveWaitLockSeconds(raw)))
|
|
101
|
+
.option('--restart-between-tests', "Forward mochitest's --restart-between-tests: restart the browser between test files to expose undocumented cross-file dependencies (the shape a manifest's standalone key asks for). Mochitest dispatch only.")
|
|
102
|
+
.option('--keep-log <path>', "Also write this run's log to <path> (a directory receives it under its own name), outside .fireforge/logs/ retention. FIREFORGE_LOG_KEEP sets a session default.")
|
|
64
103
|
.option('--profile-file <source=destination>', 'Merge a file into the harness profile, e.g. sheet.css=chrome/userChrome.css. Repeatable.', ...stringListOption());
|
|
65
104
|
}
|
|
105
|
+
/** Pre-test build options that shape the coverage claim and the Furnace step. */
|
|
106
|
+
function registerTestBuildOptions(test) {
|
|
107
|
+
return test
|
|
108
|
+
.option('--extend-coverage', 'With --build/--build-only and explicit paths, refuse instead of dropping any earlier packaging claim. Scoped builds keep each earlier path by default while its own staging inputs (manifest directory and the fixtures its manifest names) and engine/mozconfig are unchanged; this flag turns a drop into a refusal before the build.')
|
|
109
|
+
.option('--furnace-apply <mode>', 'With --build/--build-only: all (default; deploy every component with undeployed edits), reached (only components a requested test names; the rest are reported as furnace-skipped=<names>), or none. Same as test.furnaceApply.', commanderArgParser((raw) => {
|
|
110
|
+
if (raw !== 'all' && raw !== 'reached' && raw !== 'none') {
|
|
111
|
+
throw new GeneralError(`--furnace-apply must be all, reached or none (got "${raw}")`);
|
|
112
|
+
}
|
|
113
|
+
return raw;
|
|
114
|
+
}))
|
|
115
|
+
.option('--no-furnace-apply', 'With --build/--build-only, skip the Furnace apply so the build packages engine/ as it stands; names the components whose undeployed components/ edits it leaves out (same as --furnace-apply none)');
|
|
116
|
+
}
|
|
117
|
+
/** Options that run a request on several harness instances, or judge it per file. */
|
|
118
|
+
function registerTestParallelOptions(test) {
|
|
119
|
+
return test
|
|
120
|
+
.option('--jobs <n>', 'Run shards on n harness instances at once inside one lock hold, each with its own profile, Marionette port and (via auto ports) server ports. With --no-shard, or one directory argument, the files are split in order into n contiguous chunks. Prints a FIREFORGE-SHARD line per shard and one aggregate verdict. Requires --auto-ports or test.mochitestPorts: "auto".', commanderArgParser((raw) => parsePositiveIntegerFlag('--jobs', raw)))
|
|
121
|
+
.option('--shard-size <n>', 'Dispatch n files per mach invocation (mochitest restarts the browser between files) and judge each file from the structured log: shards= then counts files and failed-shards= names failed files. Combines with --jobs.', commanderArgParser((raw) => parsePositiveIntegerFlag('--shard-size', raw)))
|
|
122
|
+
.option('--results <path>', 'Write a JSON record per test file and --repeat run (status, expected, duration, run-log line) from the mozlog structured log, and name it on the verdict as results=<path>')
|
|
123
|
+
.option('--shared-lock', 'Hold the engine session lock shared when this run is build-less, applies no negative control and uses auto ports, so peers\' build-less runs on this objdir overlap; builds and re-exports still wait. Same as test.lockMode: "shared".');
|
|
124
|
+
}
|
|
66
125
|
/** Registers the test command on the CLI program. */
|
|
67
126
|
export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
68
127
|
const test = program
|
|
@@ -72,7 +131,6 @@ export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
|
72
131
|
.option('--full-output', "Pass mozbuild's full verbosity through for the TEST phase. mozbuild quiets terminal output to warnings and errors when it detects a coding agent, which removes TEST_START and console INFO — the lines a hang or stall diagnosis needs. This unsets that detection for test dispatches only; the build phase stays quiet.")
|
|
73
132
|
.option('--build', 'Run incremental UI build before testing')
|
|
74
133
|
.option('--build-only', 'Run the scoped pre-test build (packaging exactly [paths...], mixed harnesses allowed) and exit without dispatching tests — one build covers both harness halves, then run each half without --build')
|
|
75
|
-
.option('--extend-coverage', 'With --build/--build-only and explicit paths, union those paths into the recorded test-packaging coverage instead of replacing it, so an earlier scoped build stays covered. Refused when the build anchor moved (engine HEAD, mozconfig, or a previously fingerprinted packageable file changed).')
|
|
76
134
|
.option('--refuse-unexported-drift', 'With --build/--build-only, refuse (instead of warning) when the pre-test build would overwrite engine content recorded in neither a patch body nor the pristine baseline — the same belt as "fireforge build --refuse-unexported-drift", armable from the scripted "test --build" shape the hazard actually shows up in')
|
|
77
135
|
.option('--auto', 'Forward mach test --auto. Valid only when no explicit paths are provided.')
|
|
78
136
|
.option('--allow-stale-build', 'Allow tests to run even when packageable engine files changed since the last successful FireForge build')
|
|
@@ -83,7 +141,7 @@ export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
|
83
141
|
.option('--canary [path]', 'Run one short browser-chrome harness canary. Uses test.canaryPath from fireforge.json when no path is supplied.')
|
|
84
142
|
.option('--doctor', 'Run a marionette handshake preflight before tests (exit 1 on FAIL). With no paths, runs the preflight only.')
|
|
85
143
|
.option('--mach-arg <arg>', 'Forward this argument verbatim to `mach test` (repeatable). Escape valve for upstream xpcshell/mochitest flags FireForge does not model.', ...stringListOption())
|
|
86
|
-
.option('--shuffle [seed]', 'Shuffle isolated mochitest path arguments in a repeatable seeded order (fresh seed when omitted). Native mach --shuffle within each invocation remains unseeded. Export FIREFORGE_SHUFFLE_SEED for custom task code and stamp shuffle=<seed> on the verdict. --no-shard disables the seeded argument permutation.
|
|
144
|
+
.option('--shuffle [seed]', 'Shuffle isolated mochitest path arguments in a repeatable seeded order (fresh seed when omitted). Native mach --shuffle within each invocation remains unseeded. Export FIREFORGE_SHUFFLE_SEED for custom task code and stamp shuffle=<seed> on the verdict. --no-shard disables the seeded argument permutation. A path-shaped argument right after the flag (browser_x.js, dir/) is a test path, not a seed.', commanderArgParser((raw) => parsePositiveIntegerFlag('--shuffle', raw)))
|
|
87
145
|
.option('--harness-retries <n>', `Retry budget for recognized harness crashes (resource-monitor tracebacks, pre-test hangs, post-green shutdown re-entry). 0 disables retries. Default: ${DEFAULT_HARNESS_RETRIES}.`, commanderArgParser((raw) => {
|
|
88
146
|
const n = Number.parseInt(raw, 10);
|
|
89
147
|
if (!Number.isFinite(n) || n < 0 || n > 10) {
|
|
@@ -102,14 +160,15 @@ export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
|
102
160
|
return n;
|
|
103
161
|
}))
|
|
104
162
|
.addHelpText('after', TEST_HELP_TEXT);
|
|
105
|
-
addWaitLockOption(registerTestSharingOptions(test)).action(withErrorHandling(async (paths, options) => {
|
|
163
|
+
addWaitLockOption(registerTestParallelOptions(registerTestBuildOptions(registerTestMultiRunOptions(registerTestSharingOptions(test))))).action(withErrorHandling(async (paths, options) => {
|
|
106
164
|
const projectRoot = getProjectRoot();
|
|
107
|
-
//
|
|
108
|
-
// exactly-one-verdict guarantee, so a lock failure must emit here or
|
|
109
|
-
// the run ends with no FIREFORGE-VERDICT line at all.
|
|
165
|
+
// Fallback for a verdict testCommand did not emit (it takes the lock).
|
|
110
166
|
resetVerdictEmission();
|
|
111
167
|
try {
|
|
112
|
-
await
|
|
168
|
+
await testCommand(projectRoot, paths, pickDefined(options), (body, mode) => withEngineSessionLock(projectRoot, 'test', body, {
|
|
169
|
+
waitLockSeconds: resolveWaitLockSeconds(options.waitLock),
|
|
170
|
+
...(mode === 'shared' ? { mode } : {}),
|
|
171
|
+
}));
|
|
113
172
|
}
|
|
114
173
|
catch (error) {
|
|
115
174
|
if (!verdictEmitted()) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireforge test --results <path>`: one record per test file and per
|
|
3
|
+
* `--repeat` run, from the mozlog raw log each dispatch writes.
|
|
4
|
+
*
|
|
5
|
+
* Gates and retry wrappers downstream named a run's failing files by
|
|
6
|
+
* reading `TEST_START`/`TEST_END` lines and the prose of the "sharded test
|
|
7
|
+
* run(s) did not pass" error out of the human log, which nothing promised
|
|
8
|
+
* to keep stable. The results file is the stable form: JSON with a
|
|
9
|
+
* `schemaVersion`, written once the invocation has finished, and named on
|
|
10
|
+
* the verdict line as `results=<path>`.
|
|
11
|
+
*/
|
|
12
|
+
import type { RawTestResult } from '../core/test-raw-log.js';
|
|
13
|
+
/** One file's result in one dispatch. */
|
|
14
|
+
export interface TestFileResult extends RawTestResult {
|
|
15
|
+
/** `--repeat` run (1-based); 1 for a single run. */
|
|
16
|
+
run: number;
|
|
17
|
+
/** The shard (path argument or `--shard-size` chunk) the file ran in. */
|
|
18
|
+
shard: string;
|
|
19
|
+
/** Line of the run log where this file's dispatch began (1-based). */
|
|
20
|
+
logLine: number;
|
|
21
|
+
}
|
|
22
|
+
/** The document `--results` writes. */
|
|
23
|
+
export interface TestResultsDocument {
|
|
24
|
+
schemaVersion: 1;
|
|
25
|
+
/** Files in dispatch-completion order. */
|
|
26
|
+
files: TestFileResult[];
|
|
27
|
+
/** Started files that never ended, per dispatch. */
|
|
28
|
+
unfinished: Array<{
|
|
29
|
+
test: string;
|
|
30
|
+
run: number;
|
|
31
|
+
shard: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
/** Clears the collector at the start of an invocation. */
|
|
35
|
+
export declare function resetTestResults(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Adds one dispatch's parsed raw log to the invocation's results.
|
|
38
|
+
*
|
|
39
|
+
* @param results - The dispatch's finished files
|
|
40
|
+
* @param unfinished - Its started-but-unfinished files
|
|
41
|
+
* @param context - Run, shard and run-log line of the dispatch
|
|
42
|
+
*/
|
|
43
|
+
export declare function recordTestResults(results: readonly RawTestResult[], unfinished: readonly string[], context: {
|
|
44
|
+
run: number;
|
|
45
|
+
shard: string;
|
|
46
|
+
logLine: number;
|
|
47
|
+
}): void;
|
|
48
|
+
/** The results collected so far (for tests and the shard summary). */
|
|
49
|
+
export declare function collectedTestResults(): TestResultsDocument;
|
|
50
|
+
/**
|
|
51
|
+
* Writes the collected results.
|
|
52
|
+
*
|
|
53
|
+
* @param path - Destination of `--results`
|
|
54
|
+
*/
|
|
55
|
+
export declare function writeTestResults(path: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Creates the scratch directory the dispatches' raw logs go to, runs
|
|
58
|
+
* `body`, and removes it afterwards.
|
|
59
|
+
*
|
|
60
|
+
* @param body - Receives the directory
|
|
61
|
+
*/
|
|
62
|
+
export declare function withRawLogDir<T>(body: (dir: string) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge test --results <path>`: one record per test file and per
|
|
4
|
+
* `--repeat` run, from the mozlog raw log each dispatch writes.
|
|
5
|
+
*
|
|
6
|
+
* Gates and retry wrappers downstream named a run's failing files by
|
|
7
|
+
* reading `TEST_START`/`TEST_END` lines and the prose of the "sharded test
|
|
8
|
+
* run(s) did not pass" error out of the human log, which nothing promised
|
|
9
|
+
* to keep stable. The results file is the stable form: JSON with a
|
|
10
|
+
* `schemaVersion`, written once the invocation has finished, and named on
|
|
11
|
+
* the verdict line as `results=<path>`.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { writeJson } from '../utils/fs.js';
|
|
17
|
+
let collected = { schemaVersion: 1, files: [], unfinished: [] };
|
|
18
|
+
/** Clears the collector at the start of an invocation. */
|
|
19
|
+
export function resetTestResults() {
|
|
20
|
+
collected = { schemaVersion: 1, files: [], unfinished: [] };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Adds one dispatch's parsed raw log to the invocation's results.
|
|
24
|
+
*
|
|
25
|
+
* @param results - The dispatch's finished files
|
|
26
|
+
* @param unfinished - Its started-but-unfinished files
|
|
27
|
+
* @param context - Run, shard and run-log line of the dispatch
|
|
28
|
+
*/
|
|
29
|
+
export function recordTestResults(results, unfinished, context) {
|
|
30
|
+
for (const result of results)
|
|
31
|
+
collected.files.push({ ...result, ...context });
|
|
32
|
+
for (const test of unfinished) {
|
|
33
|
+
collected.unfinished.push({ test, run: context.run, shard: context.shard });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The results collected so far (for tests and the shard summary). */
|
|
37
|
+
export function collectedTestResults() {
|
|
38
|
+
return collected;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Writes the collected results.
|
|
42
|
+
*
|
|
43
|
+
* @param path - Destination of `--results`
|
|
44
|
+
*/
|
|
45
|
+
export async function writeTestResults(path) {
|
|
46
|
+
await writeJson(path, collectedTestResults());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates the scratch directory the dispatches' raw logs go to, runs
|
|
50
|
+
* `body`, and removes it afterwards.
|
|
51
|
+
*
|
|
52
|
+
* @param body - Receives the directory
|
|
53
|
+
*/
|
|
54
|
+
export async function withRawLogDir(body) {
|
|
55
|
+
const dir = await mkdtemp(join(tmpdir(), 'fireforge-raw-log-'));
|
|
56
|
+
try {
|
|
57
|
+
return await body(dir);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await rm(dir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { type DisplaySleepState } from '../core/display-state.js';
|
|
13
13
|
import { type MachCommandResult } from '../core/mach.js';
|
|
14
14
|
import { type HarnessRunVerdict } from '../core/test-harness-crash.js';
|
|
15
|
+
import { type RawTestResult } from '../core/test-raw-log.js';
|
|
15
16
|
/** Default bounded retry budget for recognized harness crashes. */
|
|
16
17
|
export declare const DEFAULT_HARNESS_RETRIES = 2;
|
|
17
18
|
/**
|
|
@@ -49,6 +50,15 @@ export interface TestRunContext {
|
|
|
49
50
|
fullOutput?: boolean;
|
|
50
51
|
/** `--pgid-file`: where each dispatch publishes its harness process group id. */
|
|
51
52
|
pgidFile?: string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Where each dispatch writes its mozlog raw log (`--results`,
|
|
55
|
+
* `--shard-size`). Unset: no `--log-raw` is added and nothing is parsed.
|
|
56
|
+
*/
|
|
57
|
+
rawLogDir?: string | undefined;
|
|
58
|
+
/** `--repeat` run this dispatch belongs to (1-based), for `--results`. */
|
|
59
|
+
repetition?: number | undefined;
|
|
60
|
+
/** Prefix for every echoed and logged output line (`--jobs` instances). */
|
|
61
|
+
outputPrefix?: string | undefined;
|
|
52
62
|
}
|
|
53
63
|
/** Outcome of one (possibly retried) harness invocation. */
|
|
54
64
|
export interface TestRunOutcome {
|
|
@@ -62,64 +72,12 @@ export interface TestRunOutcome {
|
|
|
62
72
|
* when the shape did not apply and no probe ran.
|
|
63
73
|
*/
|
|
64
74
|
displayState?: DisplaySleepState | undefined;
|
|
75
|
+
/** Per-file results from the raw log, when the run wrote one. */
|
|
76
|
+
fileResults?: RawTestResult[] | undefined;
|
|
65
77
|
}
|
|
66
78
|
/**
|
|
67
79
|
* Runs one mach test invocation for `paths`, retrying recognized harness
|
|
68
80
|
* crashes up to the configured budget. Every attempt goes through the
|
|
69
81
|
* stale-xpcshell-symlink repair path the single-run flow already used.
|
|
70
82
|
*/
|
|
71
|
-
export declare function runTestsWithRetries(ctx: TestRunContext, paths: string[]): Promise<TestRunOutcome>;
|
|
72
|
-
/**
|
|
73
|
-
* One sequential harness invocation of a sharded run: a requested path
|
|
74
|
-
* argument and the mach paths dispatched for it. A file argument is a group
|
|
75
|
-
* of one. A directory argument groups its enumerated test files so the whole
|
|
76
|
-
* directory still runs in ONE browser instance, keeping cross-file state
|
|
77
|
-
* behaviour within a directory run.
|
|
78
|
-
*/
|
|
79
|
-
export interface ShardGroup {
|
|
80
|
-
/** The path argument as the operator passed it (display label). */
|
|
81
|
-
label: string;
|
|
82
|
-
/** The mach dispatch paths for this argument. */
|
|
83
|
-
paths: string[];
|
|
84
|
-
}
|
|
85
|
-
/** Per-shard summary entry. */
|
|
86
|
-
export interface ShardOutcome {
|
|
87
|
-
label: string;
|
|
88
|
-
outcome: TestRunOutcome;
|
|
89
|
-
}
|
|
90
|
-
/** Aggregate result of a sharded run, verdict emission deferred to the caller. */
|
|
91
|
-
export interface ShardedRunSummary {
|
|
92
|
-
shards: ShardOutcome[];
|
|
93
|
-
passed: number;
|
|
94
|
-
total: number;
|
|
95
|
-
/** Aggregate classifier verdict. See {@link deriveAggregateShardVerdict}. */
|
|
96
|
-
aggregate: HarnessRunVerdict;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Derives the aggregate classification for a sharded run: all-green is
|
|
100
|
-
* `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
|
|
101
|
-
* shard makes the whole run `harness-crash`, then `no-tests`, then plain
|
|
102
|
-
* `test-failures`, so the aggregate `reason=` keeps the classifier-driven
|
|
103
|
-
* contract instead of collapsing every failure to `test-failures`.
|
|
104
|
-
*/
|
|
105
|
-
export declare function deriveAggregateShardVerdict(shards: ShardOutcome[]): HarnessRunVerdict;
|
|
106
|
-
/**
|
|
107
|
-
* Runs each requested path argument as its own sequential harness
|
|
108
|
-
* invocation (a directory argument's enumerated files stay together in
|
|
109
|
-
* one invocation, see {@link ShardGroup}) and prints an aggregate
|
|
110
|
-
* report. Per-shard failures are diagnosed via `diagnoseShardFailure`
|
|
111
|
-
* (which receives the throwing diagnosis chain from the command layer)
|
|
112
|
-
* but downgraded to warnings so every shard runs. The aggregate verdict
|
|
113
|
-
* line and the aggregate error are not produced here: the caller runs
|
|
114
|
-
* the engine-generation integrity check first and then calls
|
|
115
|
-
* {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
|
|
116
|
-
* `engine/` mutation can never print `PASS` before the check fails.
|
|
117
|
-
*/
|
|
118
|
-
export declare function runShardedTests(ctx: TestRunContext, groups: ShardGroup[], diagnoseShardFailure: (outcome: TestRunOutcome, label: string) => string | undefined): Promise<ShardedRunSummary>;
|
|
119
|
-
/**
|
|
120
|
-
* Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
|
|
121
|
-
* throws the aggregate error when any shard did not pass, the same
|
|
122
|
-
* emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
|
|
123
|
-
* the engine-generation check succeeded.
|
|
124
|
-
*/
|
|
125
|
-
export declare function finalizeShardedOutcome(summary: ShardedRunSummary): void;
|
|
83
|
+
export declare function runTestsWithRetries(ctx: TestRunContext, paths: string[], label?: string): Promise<TestRunOutcome>;
|