@hominis/fireforge 0.35.0 → 0.36.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 +29 -0
- package/README.md +34 -9
- package/dist/bin/fireforge.js +21 -6
- package/dist/src/cli.js +22 -5
- package/dist/src/commands/bootstrap-checks.js +0 -1
- package/dist/src/commands/bootstrap.d.ts +2 -1
- package/dist/src/commands/bootstrap.js +16 -7
- package/dist/src/commands/build.js +4 -3
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +18 -3
- package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
- package/dist/src/commands/doctor-check-core.js +0 -1
- package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
- package/dist/src/commands/doctor-external-toolchains.js +54 -0
- package/dist/src/commands/doctor-furnace-jar.js +0 -1
- package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
- package/dist/src/commands/doctor-furnace.js +2 -2
- package/dist/src/commands/doctor-working-tree.js +0 -1
- package/dist/src/commands/doctor.js +4 -1
- package/dist/src/commands/download.js +28 -13
- package/dist/src/commands/export-all.js +5 -3
- package/dist/src/commands/export-flow.js +12 -7
- package/dist/src/commands/export-placement-gate.js +0 -1
- package/dist/src/commands/export-placement-policy.js +0 -1
- package/dist/src/commands/export-shared.d.ts +5 -1
- package/dist/src/commands/export-shared.js +18 -6
- package/dist/src/commands/export.js +15 -10
- package/dist/src/commands/furnace/apply.js +22 -6
- package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
- package/dist/src/commands/furnace/chrome-doc.js +0 -1
- package/dist/src/commands/furnace/create-browser-test.js +0 -1
- package/dist/src/commands/furnace/create-dry-run.js +0 -1
- package/dist/src/commands/furnace/create-features.js +0 -1
- package/dist/src/commands/furnace/create-mochikit.js +0 -1
- package/dist/src/commands/furnace/create-readback.js +0 -1
- package/dist/src/commands/furnace/create-templates.js +0 -1
- package/dist/src/commands/furnace/create-validation.js +0 -1
- package/dist/src/commands/furnace/create-xpcshell.js +0 -1
- package/dist/src/commands/furnace/create.js +0 -1
- package/dist/src/commands/furnace/deploy.js +7 -83
- package/dist/src/commands/furnace/diff.js +0 -1
- package/dist/src/commands/furnace/index.js +59 -25
- package/dist/src/commands/furnace/init.js +0 -1
- package/dist/src/commands/furnace/list.js +0 -1
- package/dist/src/commands/furnace/override.js +0 -1
- package/dist/src/commands/furnace/preview.js +3 -3
- package/dist/src/commands/furnace/refresh.js +0 -1
- package/dist/src/commands/furnace/remove-state.d.ts +15 -0
- package/dist/src/commands/furnace/remove-state.js +47 -1
- package/dist/src/commands/furnace/remove.js +3 -9
- package/dist/src/commands/furnace/rename-browser-test.js +1 -5
- package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
- package/dist/src/commands/furnace/rename-helpers.js +30 -1
- package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
- package/dist/src/commands/furnace/rename.js +15 -36
- package/dist/src/commands/furnace/scan.js +0 -1
- package/dist/src/commands/furnace/status.js +0 -1
- package/dist/src/commands/furnace/sync.js +76 -4
- package/dist/src/commands/furnace/validate.js +0 -1
- package/dist/src/commands/furnace/validation-output.js +0 -1
- package/dist/src/commands/import.js +35 -9
- package/dist/src/commands/lint-per-patch.js +19 -7
- package/dist/src/commands/lint.js +13 -3
- package/dist/src/commands/manifest.js +1 -2
- package/dist/src/commands/package.js +2 -1
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +1 -1
- package/dist/src/commands/patch/index.js +1 -1
- package/dist/src/commands/patch/lint-ignore.js +21 -6
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/patch-context.js +0 -1
- package/dist/src/commands/patch/rename.js +4 -4
- package/dist/src/commands/patch/reorder.js +3 -4
- package/dist/src/commands/patch/split-plan.js +2 -1
- package/dist/src/commands/patch/split.js +4 -4
- package/dist/src/commands/patch/staged-dependency.js +1 -1
- package/dist/src/commands/patch/tier.js +18 -5
- package/dist/src/commands/re-export-bulk-scan.js +0 -1
- package/dist/src/commands/re-export-files.js +1 -1
- package/dist/src/commands/re-export-options.js +0 -1
- package/dist/src/commands/re-export-scan.js +0 -1
- package/dist/src/commands/re-export.js +6 -4
- package/dist/src/commands/rebase/abort.js +2 -9
- package/dist/src/commands/rebase/confirm.js +0 -1
- package/dist/src/commands/rebase/conflict-summary.js +0 -1
- package/dist/src/commands/rebase/continue.js +0 -1
- package/dist/src/commands/rebase/index.d.ts +1 -1
- package/dist/src/commands/rebase/index.js +10 -15
- package/dist/src/commands/rebase/patch-loop.js +38 -7
- package/dist/src/commands/rebase/summary.js +2 -3
- package/dist/src/commands/rebase.js +0 -1
- package/dist/src/commands/register.js +1 -1
- package/dist/src/commands/reset.js +16 -9
- package/dist/src/commands/resolve.js +2 -2
- package/dist/src/commands/run.js +4 -4
- package/dist/src/commands/setup-support.js +0 -1
- package/dist/src/commands/setup.js +1 -2
- package/dist/src/commands/source.js +1 -2
- package/dist/src/commands/status-output.js +1 -12
- package/dist/src/commands/status.js +147 -133
- package/dist/src/commands/test-appdir.js +0 -1
- package/dist/src/commands/test-diagnose.js +18 -2
- package/dist/src/commands/test-modes.d.ts +15 -0
- package/dist/src/commands/test-modes.js +61 -0
- package/dist/src/commands/test-register.d.ts +4 -0
- package/dist/src/commands/test-register.js +64 -0
- package/dist/src/commands/test-run.d.ts +23 -7
- package/dist/src/commands/test-run.js +15 -14
- package/dist/src/commands/test.d.ts +0 -4
- package/dist/src/commands/test.js +85 -86
- package/dist/src/commands/token-coverage.js +0 -1
- package/dist/src/commands/token.js +1 -2
- package/dist/src/commands/typecheck.js +1 -1
- package/dist/src/commands/verify.js +3 -9
- package/dist/src/commands/watch.js +5 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.js +0 -1
- package/dist/src/core/brand-validation.js +0 -1
- package/dist/src/core/branding.js +0 -1
- package/dist/src/core/browser-wire.js +0 -1
- package/dist/src/core/build-audit-platform.js +0 -1
- package/dist/src/core/build-audit-registration.js +0 -1
- package/dist/src/core/build-audit-resolve.js +0 -1
- package/dist/src/core/build-audit-transforms.js +0 -1
- package/dist/src/core/build-audit.js +0 -1
- package/dist/src/core/build-baseline-types.js +0 -1
- package/dist/src/core/build-baseline.js +0 -1
- package/dist/src/core/build-prepare.js +2 -2
- package/dist/src/core/config-mutate.js +0 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +6 -1
- package/dist/src/core/config-state.js +0 -1
- package/dist/src/core/config-validate-patch-policy.js +0 -1
- package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
- package/dist/src/core/config-validate-test-toolchains.js +105 -0
- package/dist/src/core/config-validate.js +3 -1
- package/dist/src/core/config.js +0 -1
- package/dist/src/core/destructive.js +0 -1
- package/dist/src/core/diff-hunks.js +13 -7
- package/dist/src/core/engine-changes.js +0 -1
- package/dist/src/core/engine-session-lock.d.ts +12 -0
- package/dist/src/core/engine-session-lock.js +45 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +157 -48
- package/dist/src/core/firefox-archive.d.ts +4 -0
- package/dist/src/core/firefox-archive.js +6 -1
- package/dist/src/core/firefox-cache.js +60 -4
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.js +75 -16
- package/dist/src/core/firefox-ignorefile.js +0 -1
- package/dist/src/core/firefox.d.ts +12 -0
- package/dist/src/core/firefox.js +64 -2
- package/dist/src/core/furnace-apply-dry-run.js +0 -1
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
- package/dist/src/core/furnace-apply-ftl.js +10 -5
- package/dist/src/core/furnace-apply-helpers.js +0 -1
- package/dist/src/core/furnace-apply-output.js +2 -2
- package/dist/src/core/furnace-apply.d.ts +7 -1
- package/dist/src/core/furnace-apply.js +47 -13
- package/dist/src/core/furnace-checksum-utils.js +0 -1
- package/dist/src/core/furnace-config-array-utils.js +0 -1
- package/dist/src/core/furnace-config-custom.js +0 -1
- package/dist/src/core/furnace-config-order.js +0 -1
- package/dist/src/core/furnace-config-tokens.js +0 -1
- package/dist/src/core/furnace-config-validate.js +0 -1
- package/dist/src/core/furnace-config.d.ts +11 -0
- package/dist/src/core/furnace-config.js +37 -1
- package/dist/src/core/furnace-constants.js +0 -1
- package/dist/src/core/furnace-css-fragments.js +0 -1
- package/dist/src/core/furnace-graph-utils.js +0 -1
- package/dist/src/core/furnace-jsconfig.d.ts +2 -3
- package/dist/src/core/furnace-jsconfig.js +25 -18
- package/dist/src/core/furnace-marker.js +0 -1
- package/dist/src/core/furnace-operation.js +0 -1
- package/dist/src/core/furnace-refresh.js +0 -1
- package/dist/src/core/furnace-registration-ast.d.ts +14 -0
- package/dist/src/core/furnace-registration-ast.js +1 -2
- package/dist/src/core/furnace-registration-remove.js +0 -1
- package/dist/src/core/furnace-registration-validate.js +1 -5
- package/dist/src/core/furnace-registration.js +0 -1
- package/dist/src/core/furnace-rollback.js +0 -1
- package/dist/src/core/furnace-scanner.js +0 -1
- package/dist/src/core/furnace-staleness.js +0 -1
- package/dist/src/core/furnace-state-persist.d.ts +72 -0
- package/dist/src/core/furnace-state-persist.js +118 -0
- package/dist/src/core/furnace-step-errors.d.ts +23 -0
- package/dist/src/core/furnace-step-errors.js +25 -0
- package/dist/src/core/furnace-stories.js +0 -1
- package/dist/src/core/furnace-validate-accessibility.js +0 -1
- package/dist/src/core/furnace-validate-checks.js +0 -1
- package/dist/src/core/furnace-validate-compatibility.js +0 -1
- package/dist/src/core/furnace-validate-helpers.js +0 -1
- package/dist/src/core/furnace-validate-registration.js +7 -7
- package/dist/src/core/furnace-validate-structure.d.ts +8 -0
- package/dist/src/core/furnace-validate-structure.js +11 -2
- package/dist/src/core/furnace-validate.js +0 -1
- package/dist/src/core/furnace-version-drift.d.ts +6 -3
- package/dist/src/core/furnace-version-drift.js +6 -4
- package/dist/src/core/git-base.js +0 -1
- package/dist/src/core/git-diff.js +0 -1
- package/dist/src/core/git-file-ops.js +0 -1
- package/dist/src/core/git-status.d.ts +13 -0
- package/dist/src/core/git-status.js +32 -2
- package/dist/src/core/git.d.ts +10 -1
- package/dist/src/core/git.js +63 -6
- package/dist/src/core/license-headers.js +0 -1
- package/dist/src/core/lint-cache.js +0 -1
- package/dist/src/core/lint-projection.js +0 -1
- package/dist/src/core/mach-build-artifacts.js +0 -1
- package/dist/src/core/mach-error-hints.js +0 -1
- package/dist/src/core/mach-mozconfig.js +0 -1
- package/dist/src/core/mach-python.js +0 -1
- package/dist/src/core/mach-resource-shim.js +0 -1
- package/dist/src/core/mach.js +0 -1
- package/dist/src/core/manifest-helpers.js +0 -1
- package/dist/src/core/manifest-register.js +0 -1
- package/dist/src/core/manifest-rules.js +0 -1
- package/dist/src/core/manifest-tokenizers.js +0 -1
- package/dist/src/core/marionette-port.d.ts +8 -0
- package/dist/src/core/marionette-port.js +34 -3
- package/dist/src/core/marionette-preflight.js +0 -1
- package/dist/src/core/markdown-table.js +0 -1
- package/dist/src/core/ownership-table.js +2 -9
- package/dist/src/core/parser-fallback.js +0 -1
- package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
- package/dist/src/core/patch-apply-fuzz.js +71 -34
- package/dist/src/core/patch-apply.d.ts +30 -0
- package/dist/src/core/patch-apply.js +101 -18
- package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
- package/dist/src/core/patch-artifact-normalize.js +19 -15
- package/dist/src/core/patch-export-coverage.js +0 -1
- package/dist/src/core/patch-export-metadata.js +0 -1
- package/dist/src/core/patch-export-update.js +2 -3
- package/dist/src/core/patch-export.d.ts +2 -6
- package/dist/src/core/patch-export.js +24 -48
- package/dist/src/core/patch-files.js +0 -1
- package/dist/src/core/patch-identifier-suggest.js +0 -1
- package/dist/src/core/patch-lint-checkjs.js +0 -1
- package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-cross.js +0 -1
- package/dist/src/core/patch-lint-css.js +0 -1
- package/dist/src/core/patch-lint-diff-tag.js +0 -1
- package/dist/src/core/patch-lint-diff.d.ts +2 -1
- package/dist/src/core/patch-lint-diff.js +17 -32
- package/dist/src/core/patch-lint-imports.js +0 -1
- package/dist/src/core/patch-lint-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-observer.js +0 -1
- package/dist/src/core/patch-lint-ownership.js +0 -1
- package/dist/src/core/patch-lint-reexports.js +0 -1
- package/dist/src/core/patch-lint.d.ts +37 -2
- package/dist/src/core/patch-lint.js +47 -34
- package/dist/src/core/patch-lock.js +0 -1
- package/dist/src/core/patch-manifest-consistency.js +9 -1
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +30 -5
- package/dist/src/core/patch-manifest-query.js +18 -10
- package/dist/src/core/patch-manifest-resolve.js +0 -1
- package/dist/src/core/patch-manifest-validate.js +0 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -2
- package/dist/src/core/patch-parse.d.ts +59 -0
- package/dist/src/core/patch-parse.js +209 -92
- package/dist/src/core/patch-policy.js +0 -1
- package/dist/src/core/patch-registration-refs.js +16 -26
- package/dist/src/core/patch-source-metadata.js +0 -1
- package/dist/src/core/patch-transform.d.ts +4 -0
- package/dist/src/core/patch-transform.js +30 -38
- package/dist/src/core/rebase-session.js +0 -1
- package/dist/src/core/register-browser-content.js +0 -1
- package/dist/src/core/register-module.js +0 -1
- package/dist/src/core/register-result.js +0 -1
- package/dist/src/core/register-scaffold.js +0 -1
- package/dist/src/core/register-shared-css.js +0 -1
- package/dist/src/core/register-test-manifest.js +0 -1
- package/dist/src/core/register-xpcshell-test.js +0 -1
- package/dist/src/core/shared-ftl.js +0 -1
- package/dist/src/core/signal-critical.js +0 -1
- package/dist/src/core/smoke-patterns.js +0 -1
- package/dist/src/core/state-file.js +0 -1
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +24 -16
- package/dist/src/core/test-harness-crash.d.ts +64 -7
- package/dist/src/core/test-harness-crash.js +196 -15
- package/dist/src/core/test-harness-output.js +0 -1
- package/dist/src/core/test-path-scope.d.ts +21 -13
- package/dist/src/core/test-path-scope.js +43 -28
- package/dist/src/core/test-stale-check.js +0 -1
- package/dist/src/core/test-stale-symlink.js +0 -1
- package/dist/src/core/test-xpcshell-retry.js +0 -1
- package/dist/src/core/token-coverage.js +0 -1
- package/dist/src/core/token-dark-mode.js +0 -1
- package/dist/src/core/token-docs.js +0 -1
- package/dist/src/core/token-manager.js +0 -1
- package/dist/src/core/token-scaffold.js +0 -1
- package/dist/src/core/token-variant.js +0 -1
- package/dist/src/core/toolchain-preflight.d.ts +31 -10
- package/dist/src/core/toolchain-preflight.js +97 -36
- package/dist/src/core/typecheck-shim.js +13 -1
- package/dist/src/core/typecheck.js +0 -1
- package/dist/src/core/wire-destroy.js +0 -1
- package/dist/src/core/wire-dom-fragment.js +0 -1
- package/dist/src/core/wire-init.js +0 -1
- package/dist/src/core/wire-subscript.js +0 -1
- package/dist/src/core/wire-targets.js +0 -1
- package/dist/src/core/wire-utils.js +0 -1
- package/dist/src/core/xpcshell-appdir.js +1 -4
- package/dist/src/errors/base.d.ts +29 -0
- package/dist/src/errors/base.js +35 -1
- package/dist/src/errors/build.js +0 -1
- package/dist/src/errors/codes.d.ts +6 -0
- package/dist/src/errors/codes.js +6 -1
- package/dist/src/errors/config.js +0 -1
- package/dist/src/errors/download.js +0 -1
- package/dist/src/errors/furnace.d.ts +11 -1
- package/dist/src/errors/furnace.js +6 -5
- package/dist/src/errors/git.js +0 -1
- package/dist/src/errors/patch.d.ts +17 -0
- package/dist/src/errors/patch.js +29 -1
- package/dist/src/errors/rebase.js +0 -1
- package/dist/src/errors/run.js +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/types/cli.js +0 -1
- package/dist/src/types/commands/index.js +0 -1
- package/dist/src/types/commands/options.d.ts +13 -5
- package/dist/src/types/commands/options.js +0 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/commands/patches.js +0 -1
- package/dist/src/types/commands/project.d.ts +11 -2
- package/dist/src/types/commands/project.js +0 -1
- package/dist/src/types/config.d.ts +32 -0
- package/dist/src/types/config.js +0 -1
- package/dist/src/types/furnace.d.ts +10 -0
- package/dist/src/types/furnace.js +0 -1
- package/dist/src/types/index.js +0 -1
- package/dist/src/types/typecheck.js +0 -1
- package/dist/src/utils/elapsed.js +0 -1
- package/dist/src/utils/errors.js +0 -1
- package/dist/src/utils/fs.js +37 -5
- package/dist/src/utils/logger.d.ts +7 -0
- package/dist/src/utils/logger.js +85 -3
- package/dist/src/utils/options.d.ts +15 -0
- package/dist/src/utils/options.js +25 -1
- package/dist/src/utils/package-root.js +11 -4
- package/dist/src/utils/parse.js +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/platform.js +0 -1
- package/dist/src/utils/process.d.ts +33 -4
- package/dist/src/utils/process.js +183 -28
- package/dist/src/utils/regex.d.ts +13 -3
- package/dist/src/utils/regex.js +53 -4
- package/dist/src/utils/validation.d.ts +0 -12
- package/dist/src/utils/validation.js +13 -26
- package/package.json +8 -7
|
@@ -13,7 +13,29 @@ import { verbose } from '../utils/logger.js';
|
|
|
13
13
|
import { isBrandingManagedPath } from './branding.js';
|
|
14
14
|
import { computePatchedContent } from './patch-apply.js';
|
|
15
15
|
import { loadPatchesManifest } from './patch-manifest.js';
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Builds the file → owning-patch-filenames multimap from manifest rows.
|
|
18
|
+
* Single source of truth for cross-patch claim detection — status
|
|
19
|
+
* classification, verify's cross-claim check, and the ownership table all
|
|
20
|
+
* consume this builder (they used to each rebuild it, one drift away from
|
|
21
|
+
* disagreeing about what "claimed" means).
|
|
22
|
+
*/
|
|
23
|
+
export function buildPatchClaims(manifestPatches) {
|
|
24
|
+
const ownersByPath = new Map();
|
|
25
|
+
for (const patch of manifestPatches) {
|
|
26
|
+
for (const file of patch.filesAffected) {
|
|
27
|
+
const existing = ownersByPath.get(file) ?? [];
|
|
28
|
+
existing.push(patch.filename);
|
|
29
|
+
ownersByPath.set(file, existing);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return ownersByPath;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reduces a two-character porcelain XY status to its primary code.
|
|
36
|
+
* Single source of truth — status-output.ts renders from the same logic.
|
|
37
|
+
*/
|
|
38
|
+
export function getPrimaryStatusCode(status) {
|
|
17
39
|
if (status.includes('?'))
|
|
18
40
|
return '?';
|
|
19
41
|
if (status.includes('!'))
|
|
@@ -55,20 +77,7 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
|
|
|
55
77
|
// branch where the expected-vs-actual content comparison then routed
|
|
56
78
|
// them into `unmanaged` when the content didn't match either owner's
|
|
57
79
|
// expectation.
|
|
58
|
-
const patchClaims = new Map();
|
|
59
|
-
if (manifest) {
|
|
60
|
-
for (const patch of manifest.patches) {
|
|
61
|
-
for (const f of patch.filesAffected) {
|
|
62
|
-
const owners = patchClaims.get(f);
|
|
63
|
-
if (owners) {
|
|
64
|
-
owners.push(patch.filename);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
patchClaims.set(f, [patch.filename]);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
80
|
+
const patchClaims = manifest ? buildPatchClaims(manifest.patches) : new Map();
|
|
72
81
|
const results = [];
|
|
73
82
|
for (const entry of files) {
|
|
74
83
|
const owners = patchClaims.get(entry.file);
|
|
@@ -145,4 +154,3 @@ export async function classifyFiles(files, engineDir, patchesDir, binaryName, fu
|
|
|
145
154
|
}
|
|
146
155
|
return results;
|
|
147
156
|
}
|
|
148
|
-
//# sourceMappingURL=status-classify.js.map
|
|
@@ -32,12 +32,34 @@ export interface HarnessCrashSignature {
|
|
|
32
32
|
export interface HarnessRunVerdict {
|
|
33
33
|
kind: HarnessRunClassification;
|
|
34
34
|
signature?: HarnessCrashSignature;
|
|
35
|
+
/** First concrete test-failure evidence line, when available. */
|
|
36
|
+
realFailureLine?: string;
|
|
37
|
+
/** Harness noise seen in the same output as a real test failure. */
|
|
38
|
+
secondaryHarnessSignature?: HarnessCrashSignature;
|
|
35
39
|
/**
|
|
36
40
|
* Set when a non-zero mach exit code was overridden because the output
|
|
37
41
|
* embeds a completed green summary — the exit code followed harness
|
|
38
42
|
* noise, not a test result. Callers should surface a note.
|
|
39
43
|
*/
|
|
40
44
|
greenSummaryOverride?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Set when the output embeds a green-LOOKING summary that was REJECTED:
|
|
47
|
+
* crash or truncation evidence proves the suite never completed, so the
|
|
48
|
+
* "green" counts only cover the files that ran before the run died
|
|
49
|
+
* (field report: a SIGSEGV at the second of eight files left a
|
|
50
|
+
* `Passed: 2 / Failed: 0` summary that 0.35.0 green-washed into a pass).
|
|
51
|
+
* Callers must fail the run and surface the evidence.
|
|
52
|
+
*/
|
|
53
|
+
greenSummaryRejected?: GreenSummaryRejection;
|
|
54
|
+
}
|
|
55
|
+
/** Why a green-looking embedded summary was not trusted. */
|
|
56
|
+
export interface GreenSummaryRejection {
|
|
57
|
+
/** Crash-marker evidence line (mozcrash / signal kill), if any. */
|
|
58
|
+
crashLine?: string;
|
|
59
|
+
/** Requested test files with no `TEST_START`/`TEST-START` line at all. */
|
|
60
|
+
neverStarted: string[];
|
|
61
|
+
/** Files whose `TEST_START` has no matching end marker. */
|
|
62
|
+
neverEnded: string[];
|
|
41
63
|
}
|
|
42
64
|
/**
|
|
43
65
|
* Strips non-signal noise from captured output before crash-signature
|
|
@@ -47,17 +69,41 @@ export interface HarnessRunVerdict {
|
|
|
47
69
|
* classification of test results still reads the full output.
|
|
48
70
|
*/
|
|
49
71
|
export declare function stripNonSignalNoise(output: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Finds the first crash-marker line in the RAW output (see
|
|
74
|
+
* {@link CRASH_MARKER_PATTERNS}), or undefined when the run shows no
|
|
75
|
+
* crash evidence. Exported for direct unit testing.
|
|
76
|
+
*/
|
|
77
|
+
export declare function findCrashMarkerLine(output: string): string | undefined;
|
|
50
78
|
/**
|
|
51
79
|
* True when the output embeds a COMPLETED, GREEN suite summary: an
|
|
52
80
|
* execution signal, `Unexpected results: 0` (and no non-zero unexpected
|
|
53
|
-
* count), a `SUITE_END` marker,
|
|
54
|
-
* Such a run finished its suite; any startup-traceback-
|
|
55
|
-
* same output is by definition non-fatal, so this
|
|
56
|
-
* crash classification (field report: fully green
|
|
57
|
-
* `CRASH (N attempts)` because degradation warnings
|
|
58
|
-
* signals).
|
|
81
|
+
* count), a `SUITE_END` marker, no real `TEST-UNEXPECTED-*` lines — and
|
|
82
|
+
* NO crash marker. Such a run finished its suite; any startup-traceback-
|
|
83
|
+
* shaped noise in the same output is by definition non-fatal, so this
|
|
84
|
+
* vetoes signature-based crash classification (field report: fully green
|
|
85
|
+
* sharded runs reported `CRASH (N attempts)` because degradation warnings
|
|
86
|
+
* matched the psutil signals). A summary printed after a crash marker is
|
|
87
|
+
* NOT "completed" — it only covers the files that ran before the crash
|
|
88
|
+
* (0.35.0 green-wash field report). Exported for direct unit testing.
|
|
59
89
|
*/
|
|
60
90
|
export declare function hasCompletedGreenSummary(output: string): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Cross-checks run completeness against the requested test files: every
|
|
93
|
+
* requested file must produce a start marker, and every started test must
|
|
94
|
+
* produce an end marker (the harness emits both even for failing files).
|
|
95
|
+
*
|
|
96
|
+
* Ends are paired with starts POSITIONALLY (last open start is closed by
|
|
97
|
+
* the next end), not by filename — the xpcshell dialect's `TEST_END: Test
|
|
98
|
+
* PASS` lines do not name the file. Requested paths that are not
|
|
99
|
+
* test-file-shaped (directories, manifests) are skipped for the
|
|
100
|
+
* never-started check; the start/end pairing scan covers them regardless.
|
|
101
|
+
* Exported for direct unit testing.
|
|
102
|
+
*/
|
|
103
|
+
export declare function analyzeTestCompleteness(output: string, requestedPaths: readonly string[]): {
|
|
104
|
+
neverStarted: string[];
|
|
105
|
+
neverEnded: string[];
|
|
106
|
+
};
|
|
61
107
|
/**
|
|
62
108
|
* Detects the known harness-crash shapes in captured mach output.
|
|
63
109
|
* Returns undefined for anything that looks like a genuine test result.
|
|
@@ -76,9 +122,20 @@ export declare function detectHarnessCrashSignature(output: string): HarnessCras
|
|
|
76
122
|
* dispatch prints no `TEST-START`). Bare `Passed:`/`Failed:` summary
|
|
77
123
|
* lines are still not trusted as evidence of execution.
|
|
78
124
|
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
79
|
-
* test failure for the regular diagnosis chain
|
|
125
|
+
* test failure for the regular diagnosis chain — except a non-zero
|
|
126
|
+
* exit whose embedded summary completed green AND shows no crash
|
|
127
|
+
* marker and no truncated/never-started requested file, which is a
|
|
128
|
+
* pass with a `greenSummaryOverride` note (harness noise owns the
|
|
129
|
+
* exit code). A green-shaped summary WITH such evidence is rejected
|
|
130
|
+
* (`greenSummaryRejected`) and fails.
|
|
80
131
|
*/
|
|
81
132
|
export declare function classifyHarnessRun(exitCode: number, output: string, requestedPaths: readonly string[]): HarnessRunVerdict;
|
|
133
|
+
/**
|
|
134
|
+
* Builds the operator-facing failure message for a green-looking summary
|
|
135
|
+
* that was rejected on crash or truncation evidence (never trusted as a
|
|
136
|
+
* pass, regardless of how green the embedded counts look).
|
|
137
|
+
*/
|
|
138
|
+
export declare function buildGreenSummaryRejectedMessage(rejection: GreenSummaryRejection, exitCode: number): string;
|
|
82
139
|
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
83
140
|
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
|
|
84
141
|
/**
|
|
@@ -47,10 +47,30 @@ const NONZERO_UNEXPECTED_SUMMARY_PATTERN = /\bUnexpected results:\s*[1-9]/;
|
|
|
47
47
|
*/
|
|
48
48
|
const XPCSHELL_RESULT_SUMMARY_PATTERN = /\bTEST_END\b|\bRan \d+ checks?\b|\bResult summary:/i;
|
|
49
49
|
const UNEXPECTED_LINE_PATTERN = /^.*\bTEST-UNEXPECTED-[A-Z-]+\b.*$/gm;
|
|
50
|
+
const FAIL_LINE_PATTERN = /^.*\bFAIL\b.*$/gm;
|
|
51
|
+
const ASSERTION_LINE_PATTERN = /^.*\b(?:Assertion failure|MOZ_ASSERT|ASSERTION)\b.*$/gim;
|
|
50
52
|
const SHUTDOWN_REENTRY_PATTERN = /Application shut down \(without crashing\) in the middle of a test/i;
|
|
51
53
|
const FOCUS_STALL_PATTERN = /must wait for focus/i;
|
|
52
54
|
const TRACEBACK_PATTERN = /Traceback \(most recent call last\)/;
|
|
53
55
|
const NO_OUTPUT_TIMEOUT_PATTERN = /timed out after \d+ seconds with no output/i;
|
|
56
|
+
/**
|
|
57
|
+
* Crash-marker lines mozcrash / the mochitest harness print when the
|
|
58
|
+
* browser process itself died. Matched against the RAW output (never
|
|
59
|
+
* noise-stripped): any of these proves the run was truncated by a crash,
|
|
60
|
+
* so an embedded green summary is under-reporting, not a result (field
|
|
61
|
+
* report: `Main app process: killed by SIGSEGV` at the second of eight
|
|
62
|
+
* files, summary `Passed: 2 / Failed: 0`, mach exit 1 — 0.35.0 accepted
|
|
63
|
+
* it as passed and masked a real regression for a whole drill).
|
|
64
|
+
*/
|
|
65
|
+
const CRASH_MARKER_PATTERNS = [
|
|
66
|
+
/Main app process: killed by SIG\w+/,
|
|
67
|
+
/\bPROCESS-CRASH\b/,
|
|
68
|
+
// The minidump-processing header shapes mozcrash emits when it walks a
|
|
69
|
+
// crash dump after the run.
|
|
70
|
+
/mozcrash.*minidump/i,
|
|
71
|
+
/Crash dump filename/i,
|
|
72
|
+
/Thread \d+ \(crashed\)/,
|
|
73
|
+
];
|
|
54
74
|
/**
|
|
55
75
|
* Startup-traceback fingerprints from the mozlog resource monitor / psutil
|
|
56
76
|
* on macOS. Each is matched per-line so the evidence line in the report is
|
|
@@ -165,22 +185,89 @@ function hasExecutionSignal(output) {
|
|
|
165
185
|
hasXpcshellResultSummary(output));
|
|
166
186
|
}
|
|
167
187
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* Such a run finished its suite; any startup-traceback-shaped noise in the
|
|
172
|
-
* same output is by definition non-fatal, so this vetoes signature-based
|
|
173
|
-
* crash classification (field report: fully green sharded runs reported
|
|
174
|
-
* `CRASH (N attempts)` because degradation warnings matched the psutil
|
|
175
|
-
* signals). Exported for direct unit testing.
|
|
188
|
+
* Finds the first crash-marker line in the RAW output (see
|
|
189
|
+
* {@link CRASH_MARKER_PATTERNS}), or undefined when the run shows no
|
|
190
|
+
* crash evidence. Exported for direct unit testing.
|
|
176
191
|
*/
|
|
177
|
-
export function
|
|
192
|
+
export function findCrashMarkerLine(output) {
|
|
193
|
+
return findLine(output, CRASH_MARKER_PATTERNS);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* True when the summary lines LOOK green: execution signal, `Unexpected
|
|
197
|
+
* results: 0` with no non-zero count, `SUITE_END`, no real
|
|
198
|
+
* `TEST-UNEXPECTED-*` lines. Deliberately blind to crash/truncation
|
|
199
|
+
* evidence — {@link hasCompletedGreenSummary} layers that on top, and
|
|
200
|
+
* {@link classifyHarnessRun} needs the distinction to report WHY a
|
|
201
|
+
* green-shaped summary was rejected.
|
|
202
|
+
*/
|
|
203
|
+
function hasGreenShapedSummary(output) {
|
|
178
204
|
return (hasExecutionSignal(output) &&
|
|
179
205
|
SUITE_END_PATTERN.test(output) &&
|
|
180
206
|
GREEN_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
|
|
181
207
|
!NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
|
|
182
208
|
realUnexpectedFailureLines(output).length === 0);
|
|
183
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* True when the output embeds a COMPLETED, GREEN suite summary: an
|
|
212
|
+
* execution signal, `Unexpected results: 0` (and no non-zero unexpected
|
|
213
|
+
* count), a `SUITE_END` marker, no real `TEST-UNEXPECTED-*` lines — and
|
|
214
|
+
* NO crash marker. Such a run finished its suite; any startup-traceback-
|
|
215
|
+
* shaped noise in the same output is by definition non-fatal, so this
|
|
216
|
+
* vetoes signature-based crash classification (field report: fully green
|
|
217
|
+
* sharded runs reported `CRASH (N attempts)` because degradation warnings
|
|
218
|
+
* matched the psutil signals). A summary printed after a crash marker is
|
|
219
|
+
* NOT "completed" — it only covers the files that ran before the crash
|
|
220
|
+
* (0.35.0 green-wash field report). Exported for direct unit testing.
|
|
221
|
+
*/
|
|
222
|
+
export function hasCompletedGreenSummary(output) {
|
|
223
|
+
return hasGreenShapedSummary(output) && findCrashMarkerLine(output) === undefined;
|
|
224
|
+
}
|
|
225
|
+
/** Basename shape of a mozbuild-manifest test implementation file. */
|
|
226
|
+
const TEST_FILE_BASENAME_PATTERN = /^(?:browser|test)_.*\.m?js$/;
|
|
227
|
+
/**
|
|
228
|
+
* Start/end execution markers, both log dialects: the structured mozlog
|
|
229
|
+
* `TEST_START`/`TEST_END` pair and the human browser-chrome
|
|
230
|
+
* `TEST-START`/`TEST-OK` pair. `TEST-UNEXPECTED-*` end shapes are
|
|
231
|
+
* irrelevant here — any such line already fails the green-summary check
|
|
232
|
+
* before completeness is consulted.
|
|
233
|
+
*/
|
|
234
|
+
const START_MARKER_PATTERN = /\bTEST[-_]START\b[:| ]*\s*(\S+)?/;
|
|
235
|
+
const END_MARKER_PATTERN = /\bTEST[-_]END\b|\bTEST-OK\b/;
|
|
236
|
+
/**
|
|
237
|
+
* Cross-checks run completeness against the requested test files: every
|
|
238
|
+
* requested file must produce a start marker, and every started test must
|
|
239
|
+
* produce an end marker (the harness emits both even for failing files).
|
|
240
|
+
*
|
|
241
|
+
* Ends are paired with starts POSITIONALLY (last open start is closed by
|
|
242
|
+
* the next end), not by filename — the xpcshell dialect's `TEST_END: Test
|
|
243
|
+
* PASS` lines do not name the file. Requested paths that are not
|
|
244
|
+
* test-file-shaped (directories, manifests) are skipped for the
|
|
245
|
+
* never-started check; the start/end pairing scan covers them regardless.
|
|
246
|
+
* Exported for direct unit testing.
|
|
247
|
+
*/
|
|
248
|
+
export function analyzeTestCompleteness(output, requestedPaths) {
|
|
249
|
+
const startedNames = [];
|
|
250
|
+
const openTests = [];
|
|
251
|
+
for (const line of output.split(/\r?\n/)) {
|
|
252
|
+
const startMatch = START_MARKER_PATTERN.exec(line);
|
|
253
|
+
if (startMatch) {
|
|
254
|
+
const name = startMatch[1] ?? '(unnamed test)';
|
|
255
|
+
startedNames.push(name);
|
|
256
|
+
openTests.push(name);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (END_MARKER_PATTERN.test(line)) {
|
|
260
|
+
openTests.pop();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const neverStarted = requestedPaths.filter((path) => {
|
|
264
|
+
const base = path.slice(path.lastIndexOf('/') + 1);
|
|
265
|
+
if (!TEST_FILE_BASENAME_PATTERN.test(base))
|
|
266
|
+
return false;
|
|
267
|
+
return !startedNames.some((name) => name.includes(base));
|
|
268
|
+
});
|
|
269
|
+
return { neverStarted, neverEnded: openTests };
|
|
270
|
+
}
|
|
184
271
|
/**
|
|
185
272
|
* True when the captured output carries the suite-specific xpcshell
|
|
186
273
|
* result-summary block, which proves tests executed even though the
|
|
@@ -193,14 +280,49 @@ function hasXpcshellResultSummary(output) {
|
|
|
193
280
|
}
|
|
194
281
|
/** Unexpected-failure lines that are NOT the shutdown re-entry artifact. */
|
|
195
282
|
function realUnexpectedFailureLines(output) {
|
|
196
|
-
const matches =
|
|
197
|
-
|
|
283
|
+
const matches = [
|
|
284
|
+
...(output.match(UNEXPECTED_LINE_PATTERN) ?? []),
|
|
285
|
+
...(output.match(FAIL_LINE_PATTERN) ?? []),
|
|
286
|
+
...(output.match(ASSERTION_LINE_PATTERN) ?? []),
|
|
287
|
+
];
|
|
288
|
+
if (NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output)) {
|
|
289
|
+
const summary = output
|
|
290
|
+
.split(/\r?\n/)
|
|
291
|
+
.find((line) => NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(line));
|
|
292
|
+
if (summary)
|
|
293
|
+
matches.push(summary);
|
|
294
|
+
}
|
|
295
|
+
return [...new Set(matches.map((line) => line.trim()))].filter((line) => !SHUTDOWN_REENTRY_PATTERN.test(line));
|
|
296
|
+
}
|
|
297
|
+
function detectSecondaryHarnessNoise(output) {
|
|
298
|
+
const evidence = stripNonSignalNoise(output);
|
|
299
|
+
if (TRACEBACK_PATTERN.test(evidence)) {
|
|
300
|
+
const signalLine = findLine(evidence, STARTUP_TRACEBACK_SIGNALS);
|
|
301
|
+
if (signalLine) {
|
|
302
|
+
return {
|
|
303
|
+
reason: 'harness traceback also present (resource monitor/psutil)',
|
|
304
|
+
line: signalLine,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const timeoutLine = findLine(evidence, [NO_OUTPUT_TIMEOUT_PATTERN]);
|
|
309
|
+
if (timeoutLine) {
|
|
310
|
+
return { reason: 'harness no-output timeout also present', line: timeoutLine };
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
198
313
|
}
|
|
199
314
|
/**
|
|
200
315
|
* Detects the known harness-crash shapes in captured mach output.
|
|
201
316
|
* Returns undefined for anything that looks like a genuine test result.
|
|
202
317
|
*/
|
|
203
318
|
export function detectHarnessCrashSignature(output) {
|
|
319
|
+
// Browser/app process crashes are deterministic crash evidence, not
|
|
320
|
+
// retriable environmental harness noise. Leave them to
|
|
321
|
+
// `classifyHarnessRun` so it can reject green-looking truncated summaries
|
|
322
|
+
// with the crash line plus never-started/never-ended file evidence.
|
|
323
|
+
if (findCrashMarkerLine(output) !== undefined) {
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
204
326
|
const hasTestStart = hasExecutionSignal(output);
|
|
205
327
|
const realFailures = realUnexpectedFailureLines(output);
|
|
206
328
|
// A completed green embedded summary vetoes signature-based crash
|
|
@@ -265,9 +387,17 @@ export function detectHarnessCrashSignature(output) {
|
|
|
265
387
|
* dispatch prints no `TEST-START`). Bare `Passed:`/`Failed:` summary
|
|
266
388
|
* lines are still not trusted as evidence of execution.
|
|
267
389
|
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
268
|
-
* test failure for the regular diagnosis chain
|
|
390
|
+
* test failure for the regular diagnosis chain — except a non-zero
|
|
391
|
+
* exit whose embedded summary completed green AND shows no crash
|
|
392
|
+
* marker and no truncated/never-started requested file, which is a
|
|
393
|
+
* pass with a `greenSummaryOverride` note (harness noise owns the
|
|
394
|
+
* exit code). A green-shaped summary WITH such evidence is rejected
|
|
395
|
+
* (`greenSummaryRejected`) and fails.
|
|
269
396
|
*/
|
|
270
397
|
export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
398
|
+
const realFailures = realUnexpectedFailureLines(output);
|
|
399
|
+
const firstRealFailure = realFailures[0];
|
|
400
|
+
const secondaryHarnessSignature = firstRealFailure !== undefined ? detectSecondaryHarnessNoise(output) : undefined;
|
|
271
401
|
const signature = detectHarnessCrashSignature(output);
|
|
272
402
|
if (signature) {
|
|
273
403
|
return { kind: 'harness-crash', signature };
|
|
@@ -284,10 +414,62 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
284
414
|
// non-zero on harness noise (field report: a fully green --no-shard run
|
|
285
415
|
// exited 1). Real failures always carry a non-zero unexpected count or
|
|
286
416
|
// TEST-UNEXPECTED lines, both of which fail the green-summary check.
|
|
287
|
-
|
|
417
|
+
//
|
|
418
|
+
// The override must never win over crash or truncation evidence
|
|
419
|
+
// (0.35.0 green-wash field report: a SIGSEGV at the second of eight
|
|
420
|
+
// files produced a "green" `Passed: 2 / Failed: 0` summary and mach
|
|
421
|
+
// exit 1 — the summary was green only because the crash prevented the
|
|
422
|
+
// other six files from producing any results). A crash marker or an
|
|
423
|
+
// unpaired/never-started requested file rejects the override with the
|
|
424
|
+
// evidence attached, so the caller can say why.
|
|
425
|
+
if (hasGreenShapedSummary(output)) {
|
|
426
|
+
const crashLine = findCrashMarkerLine(output);
|
|
427
|
+
const completeness = analyzeTestCompleteness(output, requestedPaths);
|
|
428
|
+
if (crashLine !== undefined ||
|
|
429
|
+
completeness.neverStarted.length > 0 ||
|
|
430
|
+
completeness.neverEnded.length > 0) {
|
|
431
|
+
return {
|
|
432
|
+
kind: 'test-failures',
|
|
433
|
+
...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
|
|
434
|
+
...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
|
|
435
|
+
greenSummaryRejected: {
|
|
436
|
+
...(crashLine !== undefined ? { crashLine } : {}),
|
|
437
|
+
neverStarted: completeness.neverStarted,
|
|
438
|
+
neverEnded: completeness.neverEnded,
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
288
442
|
return { kind: 'tests-ran-ok', greenSummaryOverride: true };
|
|
289
443
|
}
|
|
290
|
-
return {
|
|
444
|
+
return {
|
|
445
|
+
kind: 'test-failures',
|
|
446
|
+
...(firstRealFailure !== undefined ? { realFailureLine: firstRealFailure } : {}),
|
|
447
|
+
...(secondaryHarnessSignature !== undefined ? { secondaryHarnessSignature } : {}),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Builds the operator-facing failure message for a green-looking summary
|
|
452
|
+
* that was rejected on crash or truncation evidence (never trusted as a
|
|
453
|
+
* pass, regardless of how green the embedded counts look).
|
|
454
|
+
*/
|
|
455
|
+
export function buildGreenSummaryRejectedMessage(rejection, exitCode) {
|
|
456
|
+
const evidence = [];
|
|
457
|
+
if (rejection.crashLine !== undefined) {
|
|
458
|
+
evidence.push(` - crash evidence: ${rejection.crashLine}`);
|
|
459
|
+
}
|
|
460
|
+
if (rejection.neverStarted.length > 0) {
|
|
461
|
+
evidence.push(` - requested test file(s) that never started: ${rejection.neverStarted.join(', ')}`);
|
|
462
|
+
}
|
|
463
|
+
if (rejection.neverEnded.length > 0) {
|
|
464
|
+
evidence.push(` - test file(s) that started but never finished: ${rejection.neverEnded.join(', ')}`);
|
|
465
|
+
}
|
|
466
|
+
return (`mach exited ${exitCode} and the embedded suite summary looks green, but FireForge did NOT ` +
|
|
467
|
+
'treat the run as passed:\n\n' +
|
|
468
|
+
`${evidence.join('\n')}\n\n` +
|
|
469
|
+
'A crash- or truncation-shortened run under-reports: the "green" counts only cover the ' +
|
|
470
|
+
'files that ran before the run died, so the summary is not a suite result. Treat this as a ' +
|
|
471
|
+
'FAILED run — check the crash/truncation point in the output above, then re-run the ' +
|
|
472
|
+
'remaining files.');
|
|
291
473
|
}
|
|
292
474
|
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
293
475
|
export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'mach test') {
|
|
@@ -315,4 +497,3 @@ export function buildNoTestsRanMessage(exitCode, requestedPaths) {
|
|
|
315
497
|
'Check that the paths are registered in their test manifest (browser.toml / xpcshell.toml) ' +
|
|
316
498
|
'and that the manifest is reachable from moz.build, then retry.');
|
|
317
499
|
}
|
|
318
|
-
//# sourceMappingURL=test-harness-crash.js.map
|
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
* so `fireforge test browser/base/content/test/hominis` silently also
|
|
6
6
|
* ran the sibling directory `browser/base/content/test/hominis-tiles`
|
|
7
7
|
* (152.0b7 → 153.0b8 source-refresh drill: 1224 tests instead of ~200,
|
|
8
|
-
* with no indication the scope widened).
|
|
9
|
-
* prefix match exact — `hominis/` cannot prefix `hominis-tiles/…`.
|
|
8
|
+
* with no indication the scope widened).
|
|
10
9
|
*
|
|
11
10
|
* FireForge therefore treats a directory argument as meaning EXACTLY
|
|
12
|
-
* that directory: {@link analyzeTestPathScopes}
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
11
|
+
* that directory: {@link analyzeTestPathScopes} enumerates the test
|
|
12
|
+
* files of exactly that directory and dispatches THAT explicit file
|
|
13
|
+
* list to mach — a file list cannot prefix-match anything. (0.35.0
|
|
14
|
+
* instead normalized the directory with a trailing `/`, assuming the
|
|
15
|
+
* separator makes the prefix match exact; field verification on Firefox
|
|
16
|
+
* 153 showed mach still swept in the prefix-named sibling, so the
|
|
17
|
+
* trailing-slash mechanism was cosmetic and its exclusion echo was
|
|
18
|
+
* wrong.) The prefix-matching sibling directories are still reported so
|
|
19
|
+
* the command layer can tell the operator what was excluded. Note
|
|
20
|
+
* FireForge already rejects non-existent paths up front, so raw
|
|
21
|
+
* prefix-widening was never something an operator could invoke
|
|
22
|
+
* deliberately — it only ever happened by accident.
|
|
19
23
|
*/
|
|
20
24
|
/** A sibling directory the raw mach prefix match would have included. */
|
|
21
25
|
export interface SiblingPrefixMatch {
|
|
@@ -29,13 +33,17 @@ export interface TestPathScope {
|
|
|
29
33
|
/** The path as the operator passed it (engine-relative, normalized). */
|
|
30
34
|
requestedPath: string;
|
|
31
35
|
/**
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
36
|
+
* The paths to hand to mach for this argument: the explicit test-file
|
|
37
|
+
* list for a directory (mach cannot prefix-widen an explicit file
|
|
38
|
+
* list), or the argument unchanged for files. A directory containing
|
|
39
|
+
* no enumerable test files falls back to the trailing-`/` directory
|
|
40
|
+
* form — there is nothing to list, and mach owns the "no tests found"
|
|
41
|
+
* failure.
|
|
34
42
|
*/
|
|
35
|
-
|
|
43
|
+
dispatchPaths: string[];
|
|
36
44
|
/** True when the path resolved to a directory under engine/. */
|
|
37
45
|
isDirectory: boolean;
|
|
38
|
-
/**
|
|
46
|
+
/** Test files enumerated inside the requested directory (empty for files). */
|
|
39
47
|
testFileCount: number;
|
|
40
48
|
/** Sibling directories a raw prefix match would also have selected. */
|
|
41
49
|
siblingPrefixMatches: SiblingPrefixMatch[];
|
|
@@ -6,45 +6,50 @@
|
|
|
6
6
|
* so `fireforge test browser/base/content/test/hominis` silently also
|
|
7
7
|
* ran the sibling directory `browser/base/content/test/hominis-tiles`
|
|
8
8
|
* (152.0b7 → 153.0b8 source-refresh drill: 1224 tests instead of ~200,
|
|
9
|
-
* with no indication the scope widened).
|
|
10
|
-
* prefix match exact — `hominis/` cannot prefix `hominis-tiles/…`.
|
|
9
|
+
* with no indication the scope widened).
|
|
11
10
|
*
|
|
12
11
|
* FireForge therefore treats a directory argument as meaning EXACTLY
|
|
13
|
-
* that directory: {@link analyzeTestPathScopes}
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* that directory: {@link analyzeTestPathScopes} enumerates the test
|
|
13
|
+
* files of exactly that directory and dispatches THAT explicit file
|
|
14
|
+
* list to mach — a file list cannot prefix-match anything. (0.35.0
|
|
15
|
+
* instead normalized the directory with a trailing `/`, assuming the
|
|
16
|
+
* separator makes the prefix match exact; field verification on Firefox
|
|
17
|
+
* 153 showed mach still swept in the prefix-named sibling, so the
|
|
18
|
+
* trailing-slash mechanism was cosmetic and its exclusion echo was
|
|
19
|
+
* wrong.) The prefix-matching sibling directories are still reported so
|
|
20
|
+
* the command layer can tell the operator what was excluded. Note
|
|
21
|
+
* FireForge already rejects non-existent paths up front, so raw
|
|
22
|
+
* prefix-widening was never something an operator could invoke
|
|
23
|
+
* deliberately — it only ever happened by accident.
|
|
20
24
|
*/
|
|
21
25
|
import { readdir, stat } from 'node:fs/promises';
|
|
22
26
|
import { basename, dirname, join } from 'node:path';
|
|
23
27
|
/** Test implementation files as mozbuild manifests name them. */
|
|
24
|
-
const TEST_FILE_PATTERN = /^(?:browser|test)_.*\.m?js$/;
|
|
28
|
+
const TEST_FILE_PATTERN = /^(?:browser|test)_.*\.(?:m?js|x?html)$/;
|
|
25
29
|
/**
|
|
26
|
-
* Recursively
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* Recursively enumerates test-implementation files under a directory,
|
|
31
|
+
* returned engine-relative and sorted for deterministic dispatch order.
|
|
32
|
+
* Returns [] when the directory cannot be read — the caller then falls
|
|
33
|
+
* back to the directory form and mach owns the failure.
|
|
29
34
|
*/
|
|
30
|
-
async function
|
|
31
|
-
|
|
35
|
+
async function collectTestFiles(dir, relPrefix) {
|
|
36
|
+
const files = [];
|
|
32
37
|
let entries;
|
|
33
38
|
try {
|
|
34
39
|
entries = await readdir(dir, { withFileTypes: true });
|
|
35
40
|
}
|
|
36
41
|
catch {
|
|
37
|
-
return
|
|
42
|
+
return [];
|
|
38
43
|
}
|
|
39
44
|
for (const entry of entries) {
|
|
40
45
|
if (entry.isDirectory()) {
|
|
41
|
-
|
|
46
|
+
files.push(...(await collectTestFiles(join(dir, entry.name), `${relPrefix}${entry.name}/`)));
|
|
42
47
|
}
|
|
43
48
|
else if (entry.isFile() && TEST_FILE_PATTERN.test(entry.name)) {
|
|
44
|
-
|
|
49
|
+
files.push(`${relPrefix}${entry.name}`);
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
|
-
return
|
|
52
|
+
return files.sort();
|
|
48
53
|
}
|
|
49
54
|
/** Probes whether `path` is a directory, without throwing. */
|
|
50
55
|
async function isDirectorySafe(path) {
|
|
@@ -65,7 +70,7 @@ async function analyzeTestPathScope(engineDir, requestedPath) {
|
|
|
65
70
|
if (!(await isDirectorySafe(absolute))) {
|
|
66
71
|
return {
|
|
67
72
|
requestedPath,
|
|
68
|
-
|
|
73
|
+
dispatchPaths: [requestedPath],
|
|
69
74
|
isDirectory: false,
|
|
70
75
|
testFileCount: 0,
|
|
71
76
|
siblingPrefixMatches: [],
|
|
@@ -82,7 +87,7 @@ async function analyzeTestPathScope(engineDir, requestedPath) {
|
|
|
82
87
|
if (entry.name === base || !entry.name.startsWith(base))
|
|
83
88
|
continue;
|
|
84
89
|
const siblingRel = parentRel === '.' ? entry.name : `${parentRel}/${entry.name}`;
|
|
85
|
-
const testFileCount = await
|
|
90
|
+
const testFileCount = (await collectTestFiles(join(dirname(absolute), entry.name), `${siblingRel}/`)).length;
|
|
86
91
|
// A prefix-matching sibling with no test files never surfaces in a
|
|
87
92
|
// mach run, so listing it would only add noise.
|
|
88
93
|
if (testFileCount > 0) {
|
|
@@ -92,13 +97,16 @@ async function analyzeTestPathScope(engineDir, requestedPath) {
|
|
|
92
97
|
}
|
|
93
98
|
catch {
|
|
94
99
|
// Unreadable parent: skip the sibling probe; exactness via the
|
|
95
|
-
//
|
|
100
|
+
// explicit file list is preserved regardless.
|
|
96
101
|
}
|
|
102
|
+
const testFiles = await collectTestFiles(absolute, `${stripped}/`);
|
|
97
103
|
return {
|
|
98
104
|
requestedPath,
|
|
99
|
-
|
|
105
|
+
// The explicit file list is what makes the selection exact; an empty
|
|
106
|
+
// enumeration falls back to the directory form.
|
|
107
|
+
dispatchPaths: testFiles.length > 0 ? testFiles : [`${stripped}/`],
|
|
100
108
|
isDirectory: true,
|
|
101
|
-
testFileCount:
|
|
109
|
+
testFileCount: testFiles.length,
|
|
102
110
|
siblingPrefixMatches,
|
|
103
111
|
};
|
|
104
112
|
}
|
|
@@ -122,12 +130,19 @@ export async function analyzeTestPathScopes(engineDir, requestedPaths) {
|
|
|
122
130
|
export function formatScopeNotice(scope) {
|
|
123
131
|
if (!scope.isDirectory || scope.siblingPrefixMatches.length === 0)
|
|
124
132
|
return undefined;
|
|
133
|
+
const requestedDir = scope.requestedPath.replace(/\/+$/, '');
|
|
125
134
|
const siblings = scope.siblingPrefixMatches
|
|
126
135
|
.map((s) => `${s.path}/ (${s.testFileCount} test file${s.testFileCount === 1 ? '' : 's'})`)
|
|
127
136
|
.join(', ');
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
if (scope.testFileCount === 0) {
|
|
138
|
+
// Fallback dispatch: with nothing to enumerate, the raw directory
|
|
139
|
+
// form goes to mach, whose prefix matching CAN sweep the siblings in.
|
|
140
|
+
// Claiming exclusion here would repeat the 0.35.0 mistake.
|
|
141
|
+
return (`${requestedDir}/ contains no enumerable test files, so the directory is passed to mach ` +
|
|
142
|
+
`as-is — mach resolves paths by string prefix, which may also select: ${siblings}.`);
|
|
143
|
+
}
|
|
144
|
+
return (`Selected exactly ${requestedDir}/ by passing its ${scope.testFileCount} test file${scope.testFileCount === 1 ? '' : 's'} ` +
|
|
145
|
+
'explicitly to mach (an explicit file list cannot prefix-match a sibling). ' +
|
|
146
|
+
`Excluded ${scope.siblingPrefixMatches.length} prefix-named sibling director${scope.siblingPrefixMatches.length === 1 ? 'y' : 'ies'}: ${siblings}. ` +
|
|
131
147
|
'Pass them as separate paths to include them.');
|
|
132
148
|
}
|
|
133
|
-
//# sourceMappingURL=test-path-scope.js.map
|
|
@@ -37,4 +37,3 @@ export async function tryRepairStaleXpcshellTestSymlink(engineDir, objDir, outpu
|
|
|
37
37
|
warn(`Removed stale xpcshell harness symlink under ${objDir}/_tests and retrying mach test once: ${relative(resolve(engineDir), resolvedDestination)}`);
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
|
-
//# sourceMappingURL=test-stale-symlink.js.map
|