@hominis/fireforge 0.44.4 → 0.46.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 +120 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +9 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -60
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -37
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -11
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +32 -2
- package/dist/src/commands/re-export-adjacent.js +51 -10
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +65 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -32
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +8 -8
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +44 -27
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +33 -8
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +6 -3
- package/dist/src/commands/test-run.js +29 -21
- package/dist/src/commands/test-stale-gate.js +8 -5
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +51 -7
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +154 -12
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +128 -0
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +5 -2
- package/dist/src/core/build-prepare.js +34 -6
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +20 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +187 -0
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
- package/dist/src/core/mach-known-noise-filter.js +31 -10
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +46 -53
- package/dist/src/core/mach.js +31 -83
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +55 -32
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +43 -25
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +27 -27
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +21 -8
- package/dist/src/core/patch-transform.js +38 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +18 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -8
- package/dist/src/core/test-stale-check.js +77 -4
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +51 -10
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +96 -123
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +75 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +11 -14
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +136 -1
- package/docs/exit-codes.md +34 -20
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +70 -11
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* smoke run into noise for operators and every CI run into flake.
|
|
21
21
|
*/
|
|
22
22
|
import { toError } from '../utils/errors.js';
|
|
23
|
-
|
|
23
|
+
const SMOKE_ERROR_PATTERNS = [
|
|
24
24
|
// Firefox chrome error lines — `JavaScript error: chrome://…, line N: TypeError: …`.
|
|
25
25
|
/^\s*JavaScript error:/i,
|
|
26
26
|
// Some log paths prefix browser-console `console.error(...)` with the literal label below.
|
|
@@ -29,6 +29,18 @@ export const SMOKE_ERROR_PATTERNS = [
|
|
|
29
29
|
/^\s*\[JavaScript (Error|Warning)\]/i,
|
|
30
30
|
// IPC-layer fatal assertions — Firefox prints `###!!! [Parent] Error: …` on content-process crashes.
|
|
31
31
|
/^\s*###!!! \[Parent\]/,
|
|
32
|
+
// A chrome:// or resource:// URL that resolves to nothing. Outside
|
|
33
|
+
// automation Gecko only PRINTS this (printf_stderr in
|
|
34
|
+
// `CheckForBrokenChromeURL`, netwerk/base/nsNetUtil.cpp); under
|
|
35
|
+
// `xpc::IsInAutomation()` the same condition is a
|
|
36
|
+
// `MOZ_CRASH_UNSAFE_PRINTF` in whichever process opened the channel. So
|
|
37
|
+
// the smoke probe — which runs OUTSIDE automation — sees the printed
|
|
38
|
+
// line for the same defect that hard-crashes every harness run, and a
|
|
39
|
+
// probe that exits 0 on it reports "startup is healthy" for a build that
|
|
40
|
+
// hangs at "Waiting for browser…". Both spellings are matched: the
|
|
41
|
+
// non-automation singular `URL:` and the automation plural `URLs:`.
|
|
42
|
+
// Allowlistable through `--console-allow` like any other pattern.
|
|
43
|
+
/^\s*Missing chrome or resource URLs?:/,
|
|
32
44
|
];
|
|
33
45
|
/**
|
|
34
46
|
* Returns `true` when `line` matches any pattern in
|
|
@@ -51,12 +63,7 @@ export function matchesSmokeError(line) {
|
|
|
51
63
|
* allowlist (always returns -1).
|
|
52
64
|
*/
|
|
53
65
|
export function matchAllowlist(line, allow) {
|
|
54
|
-
|
|
55
|
-
if (allow[i]?.pattern.test(line)) {
|
|
56
|
-
return i;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return -1;
|
|
66
|
+
return allow.findIndex((entry) => entry.pattern.test(line));
|
|
60
67
|
}
|
|
61
68
|
/**
|
|
62
69
|
* Parses a newline-delimited allowlist file body. Lines are trimmed; blank
|
|
@@ -76,12 +83,12 @@ export function compileAllowlistFromFile(body, sourcePath) {
|
|
|
76
83
|
compiled.push({
|
|
77
84
|
pattern: new RegExp(line),
|
|
78
85
|
source: line,
|
|
79
|
-
origin: `${sourcePath}:${
|
|
86
|
+
origin: `${sourcePath}:${index + 1}`,
|
|
80
87
|
});
|
|
81
88
|
}
|
|
82
89
|
catch (error) {
|
|
83
90
|
const message = toError(error).message;
|
|
84
|
-
throw new Error(`Invalid allowlist regex at ${sourcePath}:${
|
|
91
|
+
throw new Error(`Invalid allowlist regex at ${sourcePath}:${index + 1}: ${message}`, {
|
|
85
92
|
cause: error,
|
|
86
93
|
});
|
|
87
94
|
}
|
|
@@ -99,12 +106,12 @@ export function compileAllowlistFromStrings(sources) {
|
|
|
99
106
|
compiled.push({
|
|
100
107
|
pattern: new RegExp(source),
|
|
101
108
|
source,
|
|
102
|
-
origin: `--console-allow #${
|
|
109
|
+
origin: `--console-allow #${index + 1}`,
|
|
103
110
|
});
|
|
104
111
|
}
|
|
105
112
|
catch (error) {
|
|
106
113
|
const message = toError(error).message;
|
|
107
|
-
throw new Error(`Invalid --console-allow regex at position ${
|
|
114
|
+
throw new Error(`Invalid --console-allow regex at position ${index + 1} ("${source}"): ${message}`, { cause: error });
|
|
108
115
|
}
|
|
109
116
|
});
|
|
110
117
|
return compiled;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
/** Runs an operation while holding the sidecar lock for a FireForge state file. */
|
|
2
2
|
export declare function withStateFileLock<T>(statePath: string, operation: () => Promise<T>): Promise<T>;
|
|
3
|
+
/**
|
|
4
|
+
* A filesystem-safe, sortable timestamp: `2026-08-28T14-32-05-123Z`.
|
|
5
|
+
*
|
|
6
|
+
* Colons are not portable in filenames (Windows rejects them outright), so
|
|
7
|
+
* the ISO form is punctuated with dashes only. Lexical order still equals
|
|
8
|
+
* chronological order, which is what run-log pruning relies on.
|
|
9
|
+
*
|
|
10
|
+
* @param now - Instant to render
|
|
11
|
+
* @returns The ISO-8601 form with `:` and `.` replaced by `-`
|
|
12
|
+
*/
|
|
13
|
+
export declare function fileSafeTimestamp(now: Date): string;
|
|
3
14
|
/** Renames a state file out of the way while preserving it for later inspection. */
|
|
4
15
|
export declare function quarantineStateFile(statePath: string, reason?: string): Promise<string | undefined>;
|
|
5
16
|
/**
|
|
@@ -15,7 +26,7 @@ export declare function quarantineStateFile(statePath: string, reason?: string):
|
|
|
15
26
|
* inside that tree.
|
|
16
27
|
*
|
|
17
28
|
* `furnace.json` is the pattern the rest should follow: it refuses a newer
|
|
18
|
-
* version by name and tells the operator to upgrade (`
|
|
29
|
+
* version by name and tells the operator to upgrade (`validateConfigVersion`
|
|
19
30
|
* in `furnace-config.ts`).
|
|
20
31
|
*/
|
|
21
32
|
export type DocumentVersionCheck = {
|
|
@@ -12,12 +12,25 @@ export async function withStateFileLock(statePath, operation) {
|
|
|
12
12
|
`(age: ${Math.round(ageMs / 1000)}s). A previous fireforge process may have crashed.`,
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A filesystem-safe, sortable timestamp: `2026-08-28T14-32-05-123Z`.
|
|
17
|
+
*
|
|
18
|
+
* Colons are not portable in filenames (Windows rejects them outright), so
|
|
19
|
+
* the ISO form is punctuated with dashes only. Lexical order still equals
|
|
20
|
+
* chronological order, which is what run-log pruning relies on.
|
|
21
|
+
*
|
|
22
|
+
* @param now - Instant to render
|
|
23
|
+
* @returns The ISO-8601 form with `:` and `.` replaced by `-`
|
|
24
|
+
*/
|
|
25
|
+
export function fileSafeTimestamp(now) {
|
|
26
|
+
return now.toISOString().replace(/[:.]/g, '-');
|
|
27
|
+
}
|
|
15
28
|
/** Renames a state file out of the way while preserving it for later inspection. */
|
|
16
29
|
export async function quarantineStateFile(statePath, reason = 'corrupt') {
|
|
17
30
|
if (!(await pathExists(statePath))) {
|
|
18
31
|
return undefined;
|
|
19
32
|
}
|
|
20
|
-
const timestamp = new Date()
|
|
33
|
+
const timestamp = fileSafeTimestamp(new Date());
|
|
21
34
|
const quarantinedPath = `${statePath}.${reason}-${timestamp}`;
|
|
22
35
|
await rename(statePath, quarantinedPath);
|
|
23
36
|
return basename(quarantinedPath);
|
|
@@ -11,6 +11,7 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
12
|
import { readText } from '../utils/fs.js';
|
|
13
13
|
import { verbose } from '../utils/logger.js';
|
|
14
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
14
15
|
import { isBrandingManagedPath } from './branding.js';
|
|
15
16
|
import { createPatchedContentContext } from './patch-apply.js';
|
|
16
17
|
import { classifyBinaryOwnedFile } from './status-binary.js';
|
|
@@ -56,7 +57,7 @@ export function getPrimaryStatusCode(status) {
|
|
|
56
57
|
* visible as unmanaged (the Assets.car precedent).
|
|
57
58
|
*/
|
|
58
59
|
export function isGeneratedBrandingPath(file, binaryName) {
|
|
59
|
-
const normalized = file
|
|
60
|
+
const normalized = normalizePathSlashes(file);
|
|
60
61
|
const brandingRoot = `browser/branding/${binaryName}`;
|
|
61
62
|
return (normalized === 'browser/moz.configure' ||
|
|
62
63
|
normalized === `${brandingRoot}/configure.sh` ||
|
|
@@ -46,6 +46,30 @@ const CHANGED_PREF_BLOCK_PATTERN = /pref(?:erence)?s?\b/i;
|
|
|
46
46
|
* @returns Operator-facing explanation, or undefined
|
|
47
47
|
*/
|
|
48
48
|
export function describeChangedPrefNoise(output) {
|
|
49
|
+
const namesSeen = matchedTimeDrivenPrefNames(output);
|
|
50
|
+
if (namesSeen === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
return (`Every unexpected result in this run is a changed-preference check on ${[...namesSeen].join(' / ')}. ` +
|
|
53
|
+
'Those preferences are TIME-DRIVEN: they move once a run is long enough to cross the ' +
|
|
54
|
+
'cold-startup check interval, so they correlate with run LENGTH rather than with the ' +
|
|
55
|
+
'change under test or with the build. The first probe is to halve the chunk size ' +
|
|
56
|
+
'(`--chunk 8` where a 40-row chunk tripped it) and see whether the same work passes; ' +
|
|
57
|
+
'if it does, this was harness noise, not a regression. FireForge cannot forgive the ' +
|
|
58
|
+
'check — it belongs to the harness — only tell you which kind of failure you are ' +
|
|
59
|
+
'looking at.');
|
|
60
|
+
}
|
|
61
|
+
/** Failure blocks inspected before the changed-pref shape is abandoned. */
|
|
62
|
+
const CHANGED_PREF_BLOCK_LIMIT = 20;
|
|
63
|
+
/**
|
|
64
|
+
* The RECOGNIZER half of the shape, split from the formatting so a caller
|
|
65
|
+
* that only needs the yes/no answer does not build the ~500-character
|
|
66
|
+
* explanation to throw it away.
|
|
67
|
+
*
|
|
68
|
+
* @param output - Combined harness stdout+stderr
|
|
69
|
+
* @returns The time-driven pref names seen, or undefined when the shape does
|
|
70
|
+
* not apply
|
|
71
|
+
*/
|
|
72
|
+
function matchedTimeDrivenPrefNames(output) {
|
|
49
73
|
const blocks = collectUnexpectedFailureBlocks(output, CHANGED_PREF_BLOCK_LIMIT).filter((block) => !block.startsWith('…'));
|
|
50
74
|
if (blocks.length === 0)
|
|
51
75
|
return undefined;
|
|
@@ -59,20 +83,11 @@ export function describeChangedPrefNoise(output) {
|
|
|
59
83
|
for (const name of matched)
|
|
60
84
|
namesSeen.add(name);
|
|
61
85
|
}
|
|
62
|
-
return
|
|
63
|
-
'Those preferences are TIME-DRIVEN: they move once a run is long enough to cross the ' +
|
|
64
|
-
'cold-startup check interval, so they correlate with run LENGTH rather than with the ' +
|
|
65
|
-
'change under test or with the build. The first probe is to halve the chunk size ' +
|
|
66
|
-
'(`--chunk 8` where a 40-row chunk tripped it) and see whether the same work passes; ' +
|
|
67
|
-
'if it does, this was harness noise, not a regression. FireForge cannot forgive the ' +
|
|
68
|
-
'check — it belongs to the harness — only tell you which kind of failure you are ' +
|
|
69
|
-
'looking at.');
|
|
86
|
+
return namesSeen;
|
|
70
87
|
}
|
|
71
|
-
/** Failure blocks inspected before the changed-pref shape is abandoned. */
|
|
72
|
-
const CHANGED_PREF_BLOCK_LIMIT = 20;
|
|
73
88
|
/** Short verdict-line note for the changed-preference-noise shape. */
|
|
74
89
|
export function changedPrefNoiseVerdictNote(output) {
|
|
75
|
-
return
|
|
90
|
+
return matchedTimeDrivenPrefNames(output) === undefined
|
|
76
91
|
? undefined
|
|
77
92
|
: 'all unexpected results are time-driven changed-pref checks';
|
|
78
93
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* reporting phantom test failures (or phantom passes).
|
|
24
24
|
*/
|
|
25
25
|
import { hasKnownTeardownNoise } from './mach-known-noise-filter.js';
|
|
26
|
+
import { truncateEvidence, unmarkedFailureEvidenceNote, } from './test-harness-verdict-notes.js';
|
|
26
27
|
// The no-output-stall triage text lives in `test-stall-triage.ts` (it is
|
|
27
28
|
// operator advice, not classification) and is re-exported here so the
|
|
28
29
|
// command layer keeps importing its harness diagnostics from one module.
|
|
@@ -487,12 +488,22 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
487
488
|
// shutdown-re-entry shape, or a requested file that never started keeps
|
|
488
489
|
// the run failing. Only the missing shutdown marker is forgiven, and only
|
|
489
490
|
// when the recognized teardown traceback explains it.
|
|
490
|
-
|
|
491
|
+
const teardownOverride = evaluateGreenTeardownOverride(output, counts, realFailures, requestedPaths);
|
|
492
|
+
if (teardownOverride.accepted) {
|
|
491
493
|
return { kind: 'tests-ran-ok', note: 'harness teardown noise ignored', ...counts };
|
|
492
494
|
}
|
|
495
|
+
// A run that carried the recognized teardown noise and still failed says
|
|
496
|
+
// WHICH condition rejected the override, on the one greppable line.
|
|
497
|
+
const teardownNote = teardownOverride.rejectedBy !== undefined
|
|
498
|
+
? `green-teardown override rejected: ${teardownOverride.rejectedBy}`
|
|
499
|
+
: undefined;
|
|
500
|
+
const withNotes = (verdict, extra) => {
|
|
501
|
+
const notes = [teardownNote, extra].filter((n) => n !== undefined);
|
|
502
|
+
return notes.length > 0 ? { ...verdict, note: notes.join('; ') } : verdict;
|
|
503
|
+
};
|
|
493
504
|
const signature = detectHarnessCrashSignature(output);
|
|
494
505
|
if (signature) {
|
|
495
|
-
return { kind: 'harness-crash', signature, ...counts };
|
|
506
|
+
return withNotes({ kind: 'harness-crash', signature, ...counts });
|
|
496
507
|
}
|
|
497
508
|
const ranTests = hasExecutionSignal(output);
|
|
498
509
|
if (!ranTests && requestedPaths.length > 0) {
|
|
@@ -518,7 +529,7 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
518
529
|
if (crashLine !== undefined ||
|
|
519
530
|
completeness.neverStarted.length > 0 ||
|
|
520
531
|
completeness.neverEnded.length > 0) {
|
|
521
|
-
return {
|
|
532
|
+
return withNotes({
|
|
522
533
|
kind: 'test-failures',
|
|
523
534
|
...counts,
|
|
524
535
|
...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
|
|
@@ -529,43 +540,18 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
529
540
|
neverStarted: completeness.neverStarted,
|
|
530
541
|
neverEnded: completeness.neverEnded,
|
|
531
542
|
},
|
|
532
|
-
};
|
|
543
|
+
});
|
|
533
544
|
}
|
|
534
545
|
return { kind: 'tests-ran-ok', greenSummaryOverride: true, ...counts };
|
|
535
546
|
}
|
|
536
547
|
const wordMatchNote = unmarkedFailureEvidenceNote(counts, realFailures);
|
|
537
|
-
return {
|
|
548
|
+
return withNotes({
|
|
538
549
|
kind: 'test-failures',
|
|
539
550
|
...counts,
|
|
540
551
|
...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
|
|
541
552
|
...(failureBlocks.length > 0 ? { realFailureBlocks: failureBlocks } : {}),
|
|
542
553
|
...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
|
|
543
|
-
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Belt for the residual case the tightened {@link FAIL_LINE_PATTERN} /
|
|
548
|
-
* {@link ASSERTION_LINE_PATTERN} cannot reach: the summary reported
|
|
549
|
-
* `Unexpected results: 0`, yet the run is still classified `test-failures`
|
|
550
|
-
* on evidence lines that carry NO `TEST-UNEXPECTED-*` marker.
|
|
551
|
-
*
|
|
552
|
-
* `unexpected=0` printed beside `reason=test-failures` is the tell that the
|
|
553
|
-
* verdict rests on pattern matching rather than on a harness result, and a
|
|
554
|
-
* reader who cannot see that spends hours attributing a red run to the
|
|
555
|
-
* change under test. The note says so on the verdict line itself.
|
|
556
|
-
*
|
|
557
|
-
* Deliberately a NOTE and not a reclassification: a non-zero exit with no
|
|
558
|
-
* green-shaped summary is still a failing run, and inventing a new `reason`
|
|
559
|
-
* would break the `FIREFORGE-VERDICT:` contract its consumers parse.
|
|
560
|
-
*/
|
|
561
|
-
function unmarkedFailureEvidenceNote(counts, realFailures) {
|
|
562
|
-
if (counts.unexpected !== 0)
|
|
563
|
-
return undefined;
|
|
564
|
-
if (realFailures.length === 0)
|
|
565
|
-
return undefined;
|
|
566
|
-
if (realFailures.some((line) => UNEXPECTED_LINE_SINGLE.test(line)))
|
|
567
|
-
return undefined;
|
|
568
|
-
return 'summary reported 0 unexpected; no TEST-UNEXPECTED marker in the matched evidence';
|
|
554
|
+
}, wordMatchNote);
|
|
569
555
|
}
|
|
570
556
|
/**
|
|
571
557
|
* True when the capture shows a substantively GREEN suite whose only
|
|
@@ -577,29 +563,56 @@ function unmarkedFailureEvidenceNote(counts, realFailures) {
|
|
|
577
563
|
* unexpected count anywhere; no real `TEST-UNEXPECTED-*`/assertion line;
|
|
578
564
|
* no crash marker; every requested test file both started and ended; and
|
|
579
565
|
* the recognized teardown traceback is present. Anything short of that
|
|
580
|
-
* falls through to the normal (failing) chain
|
|
566
|
+
* falls through to the normal (failing) chain — with the failing condition
|
|
567
|
+
* NAMED, so the next occurrence is a filed bug rather than another
|
|
568
|
+
* undiagnosable re-run.
|
|
569
|
+
*
|
|
570
|
+
* The conditions are evaluated in a fixed order and the FIRST failing one
|
|
571
|
+
* is reported; a run can violate several, and reporting one deterministic
|
|
572
|
+
* condition beats an unordered list of everything that happened to be true.
|
|
581
573
|
*/
|
|
582
|
-
function
|
|
574
|
+
function evaluateGreenTeardownOverride(output, counts, realFailures, requestedPaths) {
|
|
583
575
|
if (!hasKnownTeardownNoise(output))
|
|
584
|
-
return false;
|
|
576
|
+
return { accepted: false };
|
|
577
|
+
const reject = (rejectedBy) => ({ accepted: false, rejectedBy });
|
|
585
578
|
if (!hasExecutionSignal(output))
|
|
586
|
-
return
|
|
587
|
-
if (counts.checks === undefined
|
|
588
|
-
return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (
|
|
596
|
-
return
|
|
579
|
+
return reject('no execution signal (no TEST-START/TEST_START)');
|
|
580
|
+
if (counts.checks === undefined)
|
|
581
|
+
return reject('summary printed no "Ran N checks" line');
|
|
582
|
+
// `counts.unexpected === 0` IS the explicit-`Unexpected results: 0` test
|
|
583
|
+
// the previous revision spelled a second time with
|
|
584
|
+
// GREEN_UNEXPECTED_SUMMARY_PATTERN: the count is parsed from the same
|
|
585
|
+
// line and an absent line leaves it undefined. The two are kept apart in
|
|
586
|
+
// the message because "printed no such line" and "printed a non-zero one"
|
|
587
|
+
// are different findings for the reader.
|
|
588
|
+
if (counts.unexpected === undefined) {
|
|
589
|
+
return reject('summary printed no "Unexpected results:" line');
|
|
590
|
+
}
|
|
591
|
+
if (counts.unexpected !== 0) {
|
|
592
|
+
return reject(`summary reported unexpected=${counts.unexpected}`);
|
|
593
|
+
}
|
|
594
|
+
if (NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output)) {
|
|
595
|
+
return reject('a non-zero "Unexpected results:" line is present somewhere in the output');
|
|
596
|
+
}
|
|
597
|
+
if (realFailures.length > 0) {
|
|
598
|
+
return reject(`${realFailures.length} matched failure line(s), first: ${truncateEvidence(realFailures[0] ?? '')}`);
|
|
599
|
+
}
|
|
600
|
+
const crashLine = findCrashMarkerLine(output);
|
|
601
|
+
if (crashLine !== undefined)
|
|
602
|
+
return reject(`crash marker present: ${truncateEvidence(crashLine)}`);
|
|
597
603
|
// The post-green shutdown re-entry shape is deliberately a crash verdict
|
|
598
604
|
// on an otherwise-green log; it must not be swept up here.
|
|
599
|
-
if (SHUTDOWN_REENTRY_PATTERN.test(output))
|
|
600
|
-
return
|
|
605
|
+
if (SHUTDOWN_REENTRY_PATTERN.test(output)) {
|
|
606
|
+
return reject('post-green shutdown re-entry shape present');
|
|
607
|
+
}
|
|
601
608
|
const completeness = analyzeTestCompleteness(output, requestedPaths);
|
|
602
|
-
|
|
609
|
+
if (completeness.neverStarted.length > 0) {
|
|
610
|
+
return reject(`requested file(s) never started: ${completeness.neverStarted.join(', ')}`);
|
|
611
|
+
}
|
|
612
|
+
if (completeness.neverEnded.length > 0) {
|
|
613
|
+
return reject(`requested file(s) never ended: ${completeness.neverEnded.join(', ')}`);
|
|
614
|
+
}
|
|
615
|
+
return { accepted: true };
|
|
603
616
|
}
|
|
604
617
|
/**
|
|
605
618
|
* Parses the numeric counts from the harness's embedded result summary
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verdict-note helpers for the harness classifier.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `test-harness-crash.ts` (at its per-file line budget). These
|
|
5
|
+
* decide the ADVISORY parenthetical on the `FIREFORGE-VERDICT:` line; none
|
|
6
|
+
* of them changes a status or a reason key, which the verdict contract's
|
|
7
|
+
* consumers parse.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The only part of the classifier's summary counts these notes read.
|
|
11
|
+
* Structural rather than an import of `HarnessSummaryCounts`: a type edge
|
|
12
|
+
* back into `test-harness-crash.ts` is still an edge, and the cycle gate
|
|
13
|
+
* rejects it.
|
|
14
|
+
*/
|
|
15
|
+
interface SummaryCountsView {
|
|
16
|
+
unexpected?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Belt for the residual case the tightened {@link FAIL_LINE_PATTERN} /
|
|
20
|
+
* {@link ASSERTION_LINE_PATTERN} cannot reach: the summary reported
|
|
21
|
+
* `Unexpected results: 0`, yet the run is still classified `test-failures`
|
|
22
|
+
* on evidence lines that carry NO `TEST-UNEXPECTED-*` marker.
|
|
23
|
+
*
|
|
24
|
+
* `unexpected=0` printed beside `reason=test-failures` is the tell that the
|
|
25
|
+
* verdict rests on pattern matching rather than on a harness result, and a
|
|
26
|
+
* reader who cannot see that spends hours attributing a red run to the
|
|
27
|
+
* change under test. The note says so on the verdict line itself.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately a NOTE and not a reclassification: a non-zero exit with no
|
|
30
|
+
* green-shaped summary is still a failing run, and inventing a new `reason`
|
|
31
|
+
* would break the `FIREFORGE-VERDICT:` contract its consumers parse.
|
|
32
|
+
*/
|
|
33
|
+
export declare function unmarkedFailureEvidenceNote(counts: SummaryCountsView, realFailures: readonly string[]): string | undefined;
|
|
34
|
+
/** Caps an evidence line so the one-line verdict note stays one line. */
|
|
35
|
+
export declare function truncateEvidence(line: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Outcome of the green-teardown belt.
|
|
38
|
+
*
|
|
39
|
+
* `accepted` is the pass. A rejection carries `rejectedBy` ONLY when the
|
|
40
|
+
* recognized teardown noise was present — i.e. when the belt was the thing
|
|
41
|
+
* that could have applied and did not. That distinction is the whole point
|
|
42
|
+
* of the field: a run with no teardown traceback was never a candidate and
|
|
43
|
+
* naming a "rejecting condition" for it would be noise, whereas a run that
|
|
44
|
+
* DOES carry the traceback and still fails is the shape an operator
|
|
45
|
+
* otherwise cannot diagnose from outside — every condition is
|
|
46
|
+
* all-or-nothing, so the failing verdict looks identical whichever one
|
|
47
|
+
* rejected it, and a downstream report spent a re-run and a filed-bug's
|
|
48
|
+
* worth of effort guessing between them.
|
|
49
|
+
*/
|
|
50
|
+
export interface GreenTeardownOverride {
|
|
51
|
+
accepted: boolean;
|
|
52
|
+
/** Which single condition rejected, when the teardown noise was present. */
|
|
53
|
+
rejectedBy?: string;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Verdict-note helpers for the harness classifier.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `test-harness-crash.ts` (at its per-file line budget). These
|
|
6
|
+
* decide the ADVISORY parenthetical on the `FIREFORGE-VERDICT:` line; none
|
|
7
|
+
* of them changes a status or a reason key, which the verdict contract's
|
|
8
|
+
* consumers parse.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A single `TEST-UNEXPECTED-*` marker. Local copy of the classifier's line
|
|
12
|
+
* pattern: importing the value would draw a runtime edge back into
|
|
13
|
+
* `test-harness-crash.ts` and make the split circular.
|
|
14
|
+
*/
|
|
15
|
+
const UNEXPECTED_MARKER = /TEST-UNEXPECTED-\w+/;
|
|
16
|
+
/**
|
|
17
|
+
* Belt for the residual case the tightened {@link FAIL_LINE_PATTERN} /
|
|
18
|
+
* {@link ASSERTION_LINE_PATTERN} cannot reach: the summary reported
|
|
19
|
+
* `Unexpected results: 0`, yet the run is still classified `test-failures`
|
|
20
|
+
* on evidence lines that carry NO `TEST-UNEXPECTED-*` marker.
|
|
21
|
+
*
|
|
22
|
+
* `unexpected=0` printed beside `reason=test-failures` is the tell that the
|
|
23
|
+
* verdict rests on pattern matching rather than on a harness result, and a
|
|
24
|
+
* reader who cannot see that spends hours attributing a red run to the
|
|
25
|
+
* change under test. The note says so on the verdict line itself.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately a NOTE and not a reclassification: a non-zero exit with no
|
|
28
|
+
* green-shaped summary is still a failing run, and inventing a new `reason`
|
|
29
|
+
* would break the `FIREFORGE-VERDICT:` contract its consumers parse.
|
|
30
|
+
*/
|
|
31
|
+
export function unmarkedFailureEvidenceNote(counts, realFailures) {
|
|
32
|
+
if (counts.unexpected !== 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (realFailures.length === 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (realFailures.some((line) => UNEXPECTED_MARKER.test(line)))
|
|
37
|
+
return undefined;
|
|
38
|
+
return 'summary reported 0 unexpected; no TEST-UNEXPECTED marker in the matched evidence';
|
|
39
|
+
}
|
|
40
|
+
/** Caps an evidence line so the one-line verdict note stays one line. */
|
|
41
|
+
export function truncateEvidence(line) {
|
|
42
|
+
const flat = line.trim().replace(/\s+/g, ' ');
|
|
43
|
+
return flat.length <= GREEN_TEARDOWN_EVIDENCE_LIMIT
|
|
44
|
+
? flat
|
|
45
|
+
: `${flat.slice(0, GREEN_TEARDOWN_EVIDENCE_LIMIT)}…`;
|
|
46
|
+
}
|
|
47
|
+
/** Character budget for an evidence excerpt inside the verdict note. */
|
|
48
|
+
const GREEN_TEARDOWN_EVIDENCE_LIMIT = 80;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { isXpcomManifestPath } from './build-audit.js';
|
|
2
1
|
import type { BuildBaseline, TestPackagingCoverage } from './build-baseline-types.js';
|
|
3
|
-
export { isXpcomManifestPath };
|
|
4
2
|
/** Result of the stale-build preflight probe. */
|
|
5
3
|
export interface StaleBuildResult {
|
|
6
4
|
/** True when at least one packageable engine file changed since the baseline. */
|
|
@@ -66,13 +64,30 @@ export declare const FULL_SUITE_REQUEST = "(entire suite)";
|
|
|
66
64
|
*/
|
|
67
65
|
export declare function findUncoveredRequestPaths(coverage: TestPackagingCoverage | undefined, requestedPaths: readonly string[]): string[];
|
|
68
66
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
67
|
+
* Test manifests changed since the recorded build that sit in (or above)
|
|
68
|
+
* the directory of an uncovered request path — i.e. the manifests that
|
|
69
|
+
* EXPLAIN why this run needs coverage the build never claimed.
|
|
70
|
+
*
|
|
71
|
+
* Best-effort: a probe failure returns an empty list, so the refusal
|
|
72
|
+
* degrades to exactly its previous text rather than failing differently.
|
|
73
|
+
*
|
|
74
|
+
* @param engineDir - Absolute engine directory
|
|
75
|
+
* @param baseline - Last successful build's baseline
|
|
76
|
+
* @param uncovered - Uncovered request paths from
|
|
77
|
+
* {@link findUncoveredRequestPaths}
|
|
78
|
+
* @returns Engine-relative manifest paths, capped at
|
|
79
|
+
* {@link STALE_PATHS_LIMIT}
|
|
80
|
+
*/
|
|
81
|
+
export declare function findChangedTestManifestsForPaths(engineDir: string, baseline: BuildBaseline | undefined, uncovered: readonly string[]): Promise<string[]>;
|
|
82
|
+
/**
|
|
83
|
+
* @param uncovered - Request paths the recorded packaging does not cover
|
|
84
|
+
* @param coverage - The recorded scoped coverage claim
|
|
85
|
+
* @param changedManifests - Test manifests changed since the recorded
|
|
86
|
+
* build that explain an uncovered path (from
|
|
87
|
+
* {@link findChangedTestManifestsForPaths}); omitted or empty renders
|
|
88
|
+
* nothing
|
|
74
89
|
*/
|
|
75
|
-
export declare function formatTestCoverageRefusal(uncovered: string[], coverage: string[]): string;
|
|
90
|
+
export declare function formatTestCoverageRefusal(uncovered: string[], coverage: string[], changedManifests?: readonly string[]): string;
|
|
76
91
|
/** Result of the compiled-StaticComponents staleness probe. */
|
|
77
92
|
export interface StaticComponentsStaleResult {
|
|
78
93
|
/** True when at least one `components.conf` genuinely diverged from the anchor. */
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { toError } from '../utils/errors.js';
|
|
26
26
|
import { verbose, warn } from '../utils/logger.js';
|
|
27
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
27
28
|
import { isPackageablePath, isXpcomManifestPath } from './build-audit.js';
|
|
28
29
|
import { readBuildBaseline } from './build-baseline.js';
|
|
29
30
|
import { hashEngineFile } from './coverage-extend.js';
|
|
30
31
|
import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
|
|
31
|
-
export { isXpcomManifestPath };
|
|
32
32
|
/** Cap on the number of changed paths rendered inline. */
|
|
33
33
|
const STALE_PATHS_LIMIT = 10;
|
|
34
34
|
/**
|
|
@@ -138,7 +138,7 @@ function toManifestGranule(path) {
|
|
|
138
138
|
}
|
|
139
139
|
/** Normalizes a path for coverage comparison: forward slashes, no trailing slash. */
|
|
140
140
|
function normalizeCoveragePath(path) {
|
|
141
|
-
return path.trim()
|
|
141
|
+
return normalizePathSlashes(path.trim()).replace(/\/+$/, '');
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
144
|
* Formats the refusal shown when a non-`--build` run requests paths the
|
|
@@ -147,7 +147,74 @@ function normalizeCoveragePath(path) {
|
|
|
147
147
|
* separate from the matcher so tests can pin structure and copy
|
|
148
148
|
* independently (same split as {@link formatStaleBuildWarning}).
|
|
149
149
|
*/
|
|
150
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Basenames of the test manifests whose entries decide what a packaged test
|
|
152
|
+
* runtime stages. A manifest that gained an entry is the single most common
|
|
153
|
+
* reason a run needs coverage the last build never claimed — and it is a
|
|
154
|
+
* fact FireForge already holds but never said, leaving the operator to
|
|
155
|
+
* rediscover it after a correct refusal.
|
|
156
|
+
*/
|
|
157
|
+
const TEST_MANIFEST_BASENAMES = new Set([
|
|
158
|
+
'xpcshell.toml',
|
|
159
|
+
'xpcshell.ini',
|
|
160
|
+
'browser.toml',
|
|
161
|
+
'browser.ini',
|
|
162
|
+
'mochitest.toml',
|
|
163
|
+
'mochitest.ini',
|
|
164
|
+
'chrome.toml',
|
|
165
|
+
'chrome.ini',
|
|
166
|
+
'a11y.toml',
|
|
167
|
+
'a11y.ini',
|
|
168
|
+
]);
|
|
169
|
+
/** True for a test manifest path (by basename). */
|
|
170
|
+
function isTestManifestPath(path) {
|
|
171
|
+
const base = path.slice(path.lastIndexOf('/') + 1);
|
|
172
|
+
return TEST_MANIFEST_BASENAMES.has(base);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Test manifests changed since the recorded build that sit in (or above)
|
|
176
|
+
* the directory of an uncovered request path — i.e. the manifests that
|
|
177
|
+
* EXPLAIN why this run needs coverage the build never claimed.
|
|
178
|
+
*
|
|
179
|
+
* Best-effort: a probe failure returns an empty list, so the refusal
|
|
180
|
+
* degrades to exactly its previous text rather than failing differently.
|
|
181
|
+
*
|
|
182
|
+
* @param engineDir - Absolute engine directory
|
|
183
|
+
* @param baseline - Last successful build's baseline
|
|
184
|
+
* @param uncovered - Uncovered request paths from
|
|
185
|
+
* {@link findUncoveredRequestPaths}
|
|
186
|
+
* @returns Engine-relative manifest paths, capped at
|
|
187
|
+
* {@link STALE_PATHS_LIMIT}
|
|
188
|
+
*/
|
|
189
|
+
export async function findChangedTestManifestsForPaths(engineDir, baseline, uncovered) {
|
|
190
|
+
let changed;
|
|
191
|
+
try {
|
|
192
|
+
changed = await collectChangedEnginePaths(engineDir, baseline, 'Coverage refusal');
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
verbose(`Coverage refusal: manifest probe failed — ${toError(error).message}`);
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const requestDirs = uncovered
|
|
199
|
+
.filter((p) => p !== FULL_SUITE_REQUEST)
|
|
200
|
+
.map((p) => toManifestGranule(normalizeCoveragePath(p)));
|
|
201
|
+
const hits = changed.filter((path) => {
|
|
202
|
+
if (!isTestManifestPath(path))
|
|
203
|
+
return false;
|
|
204
|
+
const dir = path.slice(0, Math.max(0, path.lastIndexOf('/')));
|
|
205
|
+
return requestDirs.some((req) => req === dir || req.startsWith(`${dir}/`));
|
|
206
|
+
});
|
|
207
|
+
return hits.slice(0, STALE_PATHS_LIMIT);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* @param uncovered - Request paths the recorded packaging does not cover
|
|
211
|
+
* @param coverage - The recorded scoped coverage claim
|
|
212
|
+
* @param changedManifests - Test manifests changed since the recorded
|
|
213
|
+
* build that explain an uncovered path (from
|
|
214
|
+
* {@link findChangedTestManifestsForPaths}); omitted or empty renders
|
|
215
|
+
* nothing
|
|
216
|
+
*/
|
|
217
|
+
export function formatTestCoverageRefusal(uncovered, coverage, changedManifests = []) {
|
|
151
218
|
const cap = (paths) => {
|
|
152
219
|
const head = paths.slice(0, STALE_PATHS_LIMIT);
|
|
153
220
|
const truncated = paths.length - head.length;
|
|
@@ -166,10 +233,16 @@ export function formatTestCoverageRefusal(uncovered, coverage) {
|
|
|
166
233
|
const rebuildHint = rebuildTargets.length > 0
|
|
167
234
|
? `Rerun "fireforge test --build ${rebuildTargets.slice(0, STALE_PATHS_LIMIT).join(' ')}" to package them, or run "fireforge build" for full coverage.${extendHint}`
|
|
168
235
|
: 'Run "fireforge build" (or a path-less "fireforge test --build") for full coverage first.';
|
|
236
|
+
// Why THIS run needs new coverage, when a changed manifest explains it: a
|
|
237
|
+
// manifest that gained an entry is not covered by a build claim recorded
|
|
238
|
+
// for other paths, and a browser-chrome claim never covers xpcshell.
|
|
239
|
+
const manifestLine = changedManifests.length > 0
|
|
240
|
+
? ` This run needs coverage the recorded build never claimed because a test manifest changed since it: ${changedManifests.map((m) => `engine/${m}`).join(', ')} — a manifest that gained an entry stages new support fixtures, and a claim recorded for other paths (or another harness) does not cover them.`
|
|
241
|
+
: '';
|
|
169
242
|
return (`The packaged test runtime was produced by a scoped "fireforge test --build" covering only: ${cap(coverage)}.\n` +
|
|
170
243
|
`The requested run needs ${cap(uncovered)}, which that packaging does not cover — support ` +
|
|
171
244
|
'fixtures for those manifests may be missing from obj-*/_tests/, and the run can hang ' +
|
|
172
|
-
`rather than fail
|
|
245
|
+
`rather than fail.${manifestLine} ${rebuildHint} ` +
|
|
173
246
|
'(--allow-stale-build does not bypass this check — it accepts stale content, not missing coverage.)');
|
|
174
247
|
}
|
|
175
248
|
/**
|