@hominis/fireforge 0.51.0 → 0.53.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 +46 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/bootstrap.js +2 -3
- package/dist/src/commands/build-prelock.d.ts +3 -3
- package/dist/src/commands/build-prelock.js +3 -3
- package/dist/src/commands/build.js +4 -0
- package/dist/src/commands/declarations-report.d.ts +9 -3
- package/dist/src/commands/declarations-report.js +18 -11
- package/dist/src/commands/discard.js +3 -3
- package/dist/src/commands/doctor-furnace-config-sync.js +4 -4
- package/dist/src/commands/doctor-furnace.js +5 -5
- package/dist/src/commands/export-adopt-check.d.ts +5 -6
- package/dist/src/commands/export-adopt-check.js +5 -6
- package/dist/src/commands/export-flow.js +6 -9
- package/dist/src/commands/export-staged-registration.d.ts +4 -4
- package/dist/src/commands/export-staged-registration.js +4 -4
- package/dist/src/commands/forward-import-remedy.d.ts +2 -3
- package/dist/src/commands/forward-import-remedy.js +2 -3
- package/dist/src/commands/furnace/create-dry-run.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +2 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.js +2 -2
- package/dist/src/commands/furnace/index.js +2 -2
- package/dist/src/commands/lint.js +4 -4
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/lint-ignore-from.d.ts +3 -4
- package/dist/src/commands/patch/lint-ignore-from.js +3 -4
- package/dist/src/commands/patch/move-files-adopt.d.ts +33 -0
- package/dist/src/commands/patch/move-files-adopt.js +65 -0
- package/dist/src/commands/patch/move-files-create.js +41 -20
- package/dist/src/commands/patch/move-files-into.d.ts +18 -0
- package/dist/src/commands/patch/move-files-into.js +125 -60
- package/dist/src/commands/patch/move-files-retire.d.ts +39 -0
- package/dist/src/commands/patch/move-files-retire.js +91 -0
- package/dist/src/commands/patch/move-files.d.ts +3 -0
- package/dist/src/commands/patch/move-files.js +42 -12
- package/dist/src/commands/patch/move-size-tier.d.ts +27 -0
- package/dist/src/commands/patch/move-size-tier.js +66 -0
- package/dist/src/commands/patch/rename.js +1 -1
- package/dist/src/commands/patch/reorder-projection.d.ts +17 -0
- package/dist/src/commands/patch/reorder-projection.js +49 -0
- package/dist/src/commands/patch/reorder-shift.d.ts +30 -0
- package/dist/src/commands/patch/reorder-shift.js +307 -0
- package/dist/src/commands/patch/reorder.d.ts +9 -15
- package/dist/src/commands/patch/reorder.js +52 -50
- package/dist/src/commands/patch/split-plan.d.ts +31 -3
- package/dist/src/commands/patch/split-plan.js +36 -12
- package/dist/src/commands/patch/split-staged.d.ts +2 -2
- package/dist/src/commands/patch/split-staged.js +2 -2
- package/dist/src/commands/patch/split.js +18 -5
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +9 -1
- package/dist/src/commands/patch/staged-dependency-entries.js +32 -4
- package/dist/src/commands/patch/staged-dependency-from.js +15 -9
- package/dist/src/commands/patch/staged-dependency.js +13 -9
- package/dist/src/commands/patch/tier.js +2 -2
- package/dist/src/commands/patch-rename-map.d.ts +14 -3
- package/dist/src/commands/patch-rename-map.js +21 -3
- package/dist/src/commands/re-export-adjacent.d.ts +4 -5
- package/dist/src/commands/re-export-lint.d.ts +4 -3
- package/dist/src/commands/re-export-lint.js +4 -3
- package/dist/src/commands/re-export-preflight.js +1 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +1 -1
- package/dist/src/commands/re-export-purity-scope.js +13 -7
- package/dist/src/commands/re-export-retired-declarations.d.ts +22 -0
- package/dist/src/commands/re-export-retired-declarations.js +113 -0
- package/dist/src/commands/re-export-single.js +2 -2
- package/dist/src/commands/re-export-stale-registrations.d.ts +27 -6
- package/dist/src/commands/re-export-stale-registrations.js +65 -21
- package/dist/src/commands/re-export.js +4 -0
- package/dist/src/commands/rebase/continue.js +3 -3
- package/dist/src/commands/register.js +5 -4
- package/dist/src/commands/status-output.js +5 -1
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/test-build-furnace.d.ts +1 -2
- package/dist/src/commands/test-build-furnace.js +1 -2
- package/dist/src/commands/test-build-phase.d.ts +10 -0
- package/dist/src/commands/test-build-phase.js +51 -16
- package/dist/src/commands/test-control-restore.d.ts +40 -0
- package/dist/src/commands/test-control-restore.js +86 -0
- package/dist/src/commands/test-dispatch.js +83 -13
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +11 -0
- package/dist/src/commands/test-orchestrate.js +33 -2
- package/dist/src/commands/test-perf-scope.d.ts +1 -1
- package/dist/src/commands/test-perf-scope.js +1 -1
- package/dist/src/commands/test-prelock.d.ts +7 -8
- package/dist/src/commands/test-prelock.js +15 -12
- package/dist/src/commands/test-register.js +2 -2
- package/dist/src/commands/test-results.d.ts +10 -1
- package/dist/src/commands/test-results.js +20 -3
- package/dist/src/commands/test-run.d.ts +6 -0
- package/dist/src/commands/test-run.js +23 -4
- package/dist/src/commands/test-shards.d.ts +7 -3
- package/dist/src/commands/test-shards.js +24 -7
- package/dist/src/commands/test-stale-gate.d.ts +6 -1
- package/dist/src/commands/test-stale-gate.js +47 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +14 -1
- package/dist/src/commands/token.js +2 -2
- package/dist/src/commands/verify.js +1 -1
- package/dist/src/commands/watch.js +1 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/build-audit-resolve.d.ts +4 -5
- package/dist/src/core/build-audit-resolve.js +4 -5
- package/dist/src/core/build-audit-skip.d.ts +2 -2
- package/dist/src/core/build-audit.d.ts +3 -4
- package/dist/src/core/build-baseline-types.d.ts +37 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +134 -33
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +32 -16
- package/dist/src/core/build-jar-escalation.d.ts +4 -4
- package/dist/src/core/build-jar-escalation.js +4 -4
- package/dist/src/core/build-prepare.js +4 -4
- package/dist/src/core/config-validate.js +4 -4
- package/dist/src/core/coverage-extend.d.ts +5 -3
- package/dist/src/core/coverage-extend.js +8 -3
- package/dist/src/core/coverage-records.d.ts +4 -5
- package/dist/src/core/coverage-records.js +4 -5
- package/dist/src/core/destructive.d.ts +2 -2
- package/dist/src/core/destructive.js +2 -2
- package/dist/src/core/discard-baseline.d.ts +2 -2
- package/dist/src/core/discard-baseline.js +3 -3
- package/dist/src/core/engine-session-lock.d.ts +1 -1
- package/dist/src/core/engine-session-lock.js +1 -1
- package/dist/src/core/file-lock-owner.d.ts +5 -5
- package/dist/src/core/file-lock-owner.js +5 -5
- package/dist/src/core/file-lock-queue.js +6 -8
- package/dist/src/core/file-lock-shared.d.ts +4 -4
- package/dist/src/core/file-lock-shared.js +4 -4
- package/dist/src/core/file-lock.js +7 -10
- package/dist/src/core/firefox.js +2 -2
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +1 -1
- package/dist/src/core/furnace-apply-overwrite-warn.js +1 -1
- package/dist/src/core/furnace-apply.js +3 -3
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +4 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +26 -6
- package/dist/src/core/furnace-operation.js +5 -5
- package/dist/src/core/furnace-registration-jar-inplace.js +5 -6
- package/dist/src/core/furnace-registration-rename.d.ts +5 -5
- package/dist/src/core/furnace-registration-rename.js +5 -5
- package/dist/src/core/furnace-registration-validate.d.ts +2 -1
- package/dist/src/core/furnace-registration-validate.js +2 -1
- package/dist/src/core/furnace-registration.js +2 -2
- package/dist/src/core/furnace-validate-accessibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-accessibility.js +22 -11
- package/dist/src/core/furnace-validate-exceptions.d.ts +2 -1
- package/dist/src/core/furnace-validate-exceptions.js +31 -13
- package/dist/src/core/git-base.js +4 -3
- package/dist/src/core/git-diff.js +1 -1
- package/dist/src/core/git.js +1 -1
- package/dist/src/core/harness-orphans.d.ts +6 -6
- package/dist/src/core/harness-orphans.js +6 -6
- package/dist/src/core/harness-port-reservation.d.ts +60 -0
- package/dist/src/core/harness-port-reservation.js +122 -0
- package/dist/src/core/history-log-file.d.ts +9 -9
- package/dist/src/core/history-log-file.js +15 -15
- package/dist/src/core/history-log.d.ts +3 -3
- package/dist/src/core/history-log.js +3 -3
- package/dist/src/core/launchable-build.d.ts +39 -0
- package/dist/src/core/launchable-build.js +85 -0
- package/dist/src/core/marionette-port.js +1 -3
- package/dist/src/core/moz-manifest-rules.js +3 -2
- package/dist/src/core/negative-control-compiled.d.ts +22 -3
- package/dist/src/core/negative-control-compiled.js +41 -5
- package/dist/src/core/parser-fallback.d.ts +5 -5
- package/dist/src/core/parser-fallback.js +5 -5
- package/dist/src/core/patch-identifier-suggest.d.ts +6 -0
- package/dist/src/core/patch-identifier-suggest.js +11 -4
- package/dist/src/core/patch-lint-checkjs.d.ts +1 -1
- package/dist/src/core/patch-lint-checkjs.js +1 -1
- package/dist/src/core/patch-lint-css.js +2 -2
- package/dist/src/core/patch-lint-file-size.d.ts +5 -7
- package/dist/src/core/patch-lint-file-size.js +5 -7
- package/dist/src/core/patch-lint-ignore.d.ts +3 -3
- package/dist/src/core/patch-lint-ignore.js +3 -3
- package/dist/src/core/patch-lint-prettier.d.ts +4 -6
- package/dist/src/core/patch-lint-prettier.js +4 -6
- package/dist/src/core/patch-lint-projection.d.ts +1 -1
- package/dist/src/core/patch-lint-projection.js +1 -1
- package/dist/src/core/patch-lint-queue-types.d.ts +2 -2
- package/dist/src/core/patch-lint-queue-types.js +2 -2
- package/dist/src/core/patch-manifest-consistency.d.ts +2 -2
- package/dist/src/core/patch-manifest-consistency.js +2 -2
- package/dist/src/core/patch-manifest-io.d.ts +5 -1
- package/dist/src/core/patch-manifest-io.js +12 -10
- package/dist/src/core/patch-manifest-key-order.d.ts +3 -4
- package/dist/src/core/patch-manifest-key-order.js +3 -4
- package/dist/src/core/patch-parse.d.ts +3 -3
- package/dist/src/core/patch-parse.js +3 -3
- package/dist/src/core/patch-policy.d.ts +1 -1
- package/dist/src/core/patch-policy.js +1 -1
- package/dist/src/core/patch-source-metadata.d.ts +1 -1
- package/dist/src/core/patch-source-metadata.js +1 -1
- package/dist/src/core/patch-subset.d.ts +5 -3
- package/dist/src/core/patch-subset.js +8 -7
- package/dist/src/core/patch-transform.d.ts +2 -2
- package/dist/src/core/patch-transform.js +2 -2
- package/dist/src/core/rebase-session.js +4 -6
- package/dist/src/core/register-xpcshell-test.d.ts +28 -7
- package/dist/src/core/register-xpcshell-test.js +76 -27
- package/dist/src/core/run-log.d.ts +8 -8
- package/dist/src/core/run-log.js +8 -8
- package/dist/src/core/status-classify.d.ts +1 -2
- package/dist/src/core/status-classify.js +1 -2
- package/dist/src/core/test-harness-crash.d.ts +5 -3
- package/dist/src/core/test-harness-crash.js +13 -14
- package/dist/src/core/test-harness-infra.d.ts +32 -0
- package/dist/src/core/test-harness-infra.js +69 -0
- package/dist/src/core/test-harness-signature.d.ts +6 -0
- package/dist/src/core/test-run-completeness.d.ts +12 -5
- package/dist/src/core/test-run-completeness.js +15 -8
- package/dist/src/core/test-stale-check.d.ts +48 -1
- package/dist/src/core/test-stale-check.js +97 -2
- package/dist/src/core/test-stall-triage.js +5 -7
- package/dist/src/core/token-category.js +2 -2
- package/dist/src/core/token-inventory.js +3 -3
- package/dist/src/core/token-manager.d.ts +2 -2
- package/dist/src/core/token-manager.js +5 -7
- package/dist/src/core/token-variant.d.ts +6 -6
- package/dist/src/core/token-variant.js +7 -7
- package/dist/src/core/typecheck-shim.d.ts +2 -1
- package/dist/src/core/typecheck-shim.js +29 -5
- package/dist/src/core/wire-destroy.d.ts +1 -1
- package/dist/src/core/wire-destroy.js +1 -1
- package/dist/src/core/wire-dom-fragment.js +1 -1
- package/dist/src/core/wire-init.d.ts +1 -1
- package/dist/src/core/wire-init.js +1 -1
- package/dist/src/core/wire-subscript.d.ts +1 -1
- package/dist/src/core/wire-subscript.js +1 -1
- package/dist/src/core/wire-utils.d.ts +2 -2
- package/dist/src/core/wire-utils.js +3 -3
- package/dist/src/errors/base.d.ts +1 -1
- package/dist/src/errors/base.js +1 -1
- package/dist/src/errors/rebase.d.ts +1 -1
- package/dist/src/errors/rebase.js +1 -1
- package/dist/src/types/commands/options.d.ts +26 -3
- package/dist/src/types/config.d.ts +5 -7
- package/dist/src/types/furnace.d.ts +9 -0
- package/dist/src/utils/options.js +2 -2
- package/dist/src/utils/process.js +2 -2
- package/dist/src/utils/regex.d.ts +2 -2
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/sleep.d.ts +5 -6
- package/dist/src/utils/sleep.js +5 -6
- package/dist/src/utils/validation.js +2 -2
- package/docs/configuration.md +40 -26
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +21 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +43 -22
- package/docs/patch-workflow.md +129 -24
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +118 -44
- package/package.json +1 -1
package/dist/src/core/run-log.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Per-run output logs under `.fireforge/logs/`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* through `tail`/`grep`, the ergonomic default when output is long,
|
|
7
|
-
* the summary and discards the `TEST-UNEXPECTED-FAIL` lines that say
|
|
8
|
-
* broke, and it launders the exit code besides.
|
|
9
|
-
* pipe a run")
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* Without a log, a run's diagnosis exists in exactly one place: the terminal.
|
|
6
|
+
* Piping through `tail`/`grep`, the ergonomic default when output is long,
|
|
7
|
+
* keeps the summary and discards the `TEST-UNEXPECTED-FAIL` lines that say
|
|
8
|
+
* what broke, and it launders the exit code besides. An operator rule
|
|
9
|
+
* ("never pipe a run") does not hold in practice, so this is not an operator
|
|
10
|
+
* problem: the tool is the only party that can make the log survive the
|
|
11
|
+
* mistake.
|
|
12
12
|
*
|
|
13
13
|
* So `test` and `build` write their own complete copy as they stream, and the
|
|
14
14
|
* `FIREFORGE-VERDICT:` line names the path. A piped, truncated, or
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* Everything here is best-effort. A log is a diagnostic aid, and a run must
|
|
18
18
|
* never fail because one could not be opened, written, or pruned.
|
|
19
19
|
* Every entry point swallows its errors and degrades to "no log", which is
|
|
20
|
-
* exactly the behaviour
|
|
20
|
+
* exactly the behaviour of a run without this module.
|
|
21
21
|
*/
|
|
22
22
|
import { createWriteStream } from 'node:fs';
|
|
23
23
|
import { mkdir, readdir, stat, unlink } from 'node:fs/promises';
|
|
@@ -63,8 +63,7 @@ export interface ClassifiedFile extends StatusFile {
|
|
|
63
63
|
* Builds the file → owning-patch-filenames multimap from manifest rows.
|
|
64
64
|
* Single source of truth for cross-patch claim detection: status
|
|
65
65
|
* classification, verify's cross-claim check, and the ownership table all
|
|
66
|
-
* consume this builder
|
|
67
|
-
* disagreeing about what "claimed" means).
|
|
66
|
+
* consume this builder, so they cannot disagree about what "claimed" means.
|
|
68
67
|
*/
|
|
69
68
|
export declare function buildPatchClaims(manifestPatches: ReadonlyArray<{
|
|
70
69
|
filename: string;
|
|
@@ -20,8 +20,7 @@ import { classifyBinaryOwnedFile } from './status-binary.js';
|
|
|
20
20
|
* Builds the file → owning-patch-filenames multimap from manifest rows.
|
|
21
21
|
* Single source of truth for cross-patch claim detection: status
|
|
22
22
|
* classification, verify's cross-claim check, and the ownership table all
|
|
23
|
-
* consume this builder
|
|
24
|
-
* disagreeing about what "claimed" means).
|
|
23
|
+
* consume this builder, so they cannot disagree about what "claimed" means.
|
|
25
24
|
*/
|
|
26
25
|
export function buildPatchClaims(manifestPatches) {
|
|
27
26
|
const ownersByPath = new Map();
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
export type { HarnessCrashSignature } from './test-harness-signature.js';
|
|
25
25
|
import { type HarnessCrashSignature, type IncompleteRunEvidence } from './test-harness-signature.js';
|
|
26
|
+
export { buildHarnessCrashMessage } from './test-harness-infra.js';
|
|
26
27
|
export { headedDisplayAsleepVerdictNote, headedNoOutputTimeoutHint } from './test-stall-triage.js';
|
|
27
28
|
/** How a completed harness run should be interpreted. */
|
|
28
29
|
export type HarnessRunClassification = 'tests-ran-ok' | 'test-failures' | 'harness-crash' | 'harness-arguments' | 'no-tests';
|
|
@@ -160,7 +161,10 @@ export declare function detectHarnessCrashSignature(output: string): HarnessCras
|
|
|
160
161
|
* 1. A recognized crash signature wins regardless of exit code (the
|
|
161
162
|
* shutdown re-entry shape exits non-zero on an otherwise green run,
|
|
162
163
|
* and the hang shape can even exit zero with a `Passed: 0` summary).
|
|
163
|
-
* 2. No execution signal with explicit paths requested means no test ran
|
|
164
|
+
* 2. No execution signal with explicit paths requested means no test ran.
|
|
165
|
+
* A mochitest server that failed to start explains that, and is a
|
|
166
|
+
* retriable infrastructure failure (`harness-crash` with
|
|
167
|
+
* `reason=server-startup` on the verdict line). Otherwise it is
|
|
164
168
|
* `no-tests`, even when the exit code is zero. The execution signal is
|
|
165
169
|
* a `TEST-START` line (generic `mach test` / browser-chrome dispatch)
|
|
166
170
|
* or the suite-specific xpcshell result-summary block (the xpcshell
|
|
@@ -206,8 +210,6 @@ export declare function formatFireforgeVerdictLine(verdict: HarnessRunVerdict, s
|
|
|
206
210
|
* pass, regardless of how green the embedded counts look).
|
|
207
211
|
*/
|
|
208
212
|
export declare function buildGreenSummaryRejectedMessage(rejection: GreenSummaryRejection, exitCode: number): string;
|
|
209
|
-
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
210
|
-
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
|
|
211
213
|
/**
|
|
212
214
|
* True when a harness run died on SIGSEGV having produced no test evidence
|
|
213
215
|
* whatsoever.
|
|
@@ -23,9 +23,13 @@
|
|
|
23
23
|
* reporting phantom test failures (or phantom passes).
|
|
24
24
|
*/
|
|
25
25
|
import { hasKnownTeardownNoise } from './mach-known-noise-filter.js';
|
|
26
|
+
import { detectServerStartupFailure } from './test-harness-infra.js';
|
|
26
27
|
import { truncateEvidence, unmarkedFailureEvidenceNote, } from './test-harness-verdict-notes.js';
|
|
27
28
|
import { classifyProfileArgumentFailure } from './test-profile-files.js';
|
|
28
29
|
import { formatIncompleteSuffix, formatShardsSuffix, } from './test-harness-signature.js';
|
|
30
|
+
// The operator message after exhausted retries lives beside the
|
|
31
|
+
// server-startup shape in `test-harness-infra.ts`.
|
|
32
|
+
export { buildHarnessCrashMessage } from './test-harness-infra.js';
|
|
29
33
|
// The no-output-stall triage text lives in `test-stall-triage.ts` (it is
|
|
30
34
|
// operator advice, not classification) and is re-exported here so the
|
|
31
35
|
// command layer keeps importing its harness diagnostics from one module.
|
|
@@ -458,7 +462,10 @@ export function detectHarnessCrashSignature(output) {
|
|
|
458
462
|
* 1. A recognized crash signature wins regardless of exit code (the
|
|
459
463
|
* shutdown re-entry shape exits non-zero on an otherwise green run,
|
|
460
464
|
* and the hang shape can even exit zero with a `Passed: 0` summary).
|
|
461
|
-
* 2. No execution signal with explicit paths requested means no test ran
|
|
465
|
+
* 2. No execution signal with explicit paths requested means no test ran.
|
|
466
|
+
* A mochitest server that failed to start explains that, and is a
|
|
467
|
+
* retriable infrastructure failure (`harness-crash` with
|
|
468
|
+
* `reason=server-startup` on the verdict line). Otherwise it is
|
|
462
469
|
* `no-tests`, even when the exit code is zero. The execution signal is
|
|
463
470
|
* a `TEST-START` line (generic `mach test` / browser-chrome dispatch)
|
|
464
471
|
* or the suite-specific xpcshell result-summary block (the xpcshell
|
|
@@ -512,7 +519,10 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
512
519
|
}
|
|
513
520
|
const ranTests = hasExecutionSignal(output);
|
|
514
521
|
if (!ranTests && requestedPaths.length > 0) {
|
|
515
|
-
|
|
522
|
+
const serverStartup = detectServerStartupFailure(output);
|
|
523
|
+
return serverStartup === undefined
|
|
524
|
+
? { kind: 'no-tests', ...counts }
|
|
525
|
+
: { kind: 'harness-crash', signature: serverStartup, ...counts };
|
|
516
526
|
}
|
|
517
527
|
if (exitCode === 0) {
|
|
518
528
|
return { kind: 'tests-ran-ok', ...counts };
|
|
@@ -663,7 +673,7 @@ export function formatFireforgeVerdictLine(verdict, shards) {
|
|
|
663
673
|
const tail = `${formatShardsSuffix(shards)}${verdict.note !== undefined ? ` (${verdict.note})` : ''}`;
|
|
664
674
|
if (verdict.kind === 'tests-ran-ok')
|
|
665
675
|
return `FIREFORGE-VERDICT: PASS${counts}${tail}`;
|
|
666
|
-
const reason = verdict.kind === 'harness-crash' ? 'crash' : verdict.kind;
|
|
676
|
+
const reason = verdict.kind === 'harness-crash' ? (verdict.signature?.verdictReason ?? 'crash') : verdict.kind;
|
|
667
677
|
return `FIREFORGE-VERDICT: FAIL reason=${reason}${counts}${formatIncompleteSuffix(verdict.incomplete)}${tail}`;
|
|
668
678
|
}
|
|
669
679
|
/**
|
|
@@ -690,17 +700,6 @@ export function buildGreenSummaryRejectedMessage(rejection, exitCode) {
|
|
|
690
700
|
'FAILED run — check the crash/truncation point in the output above, then re-run the ' +
|
|
691
701
|
'remaining files.');
|
|
692
702
|
}
|
|
693
|
-
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
694
|
-
export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'mach test') {
|
|
695
|
-
return (`${commandLabel} crashed in the harness itself (not in your tests) on all ${attempts} attempt(s).\n\n` +
|
|
696
|
-
`Detected shape: ${signature.reason}\n` +
|
|
697
|
-
`Evidence line: ${signature.line}\n\n` +
|
|
698
|
-
'This failure mode is environmental (mozlog resource monitor / psutil on macOS, focus-stall ' +
|
|
699
|
-
'shutdown re-entry, or a pre-test hang) rather than a test regression. Re-run the command, ' +
|
|
700
|
-
'raise the retry budget with --harness-retries <n>, or run the file in isolation. ' +
|
|
701
|
-
'If it persists across many runs, inspect the mach virtualenv (mach resyncs psutil on its own; ' +
|
|
702
|
-
'patching it manually does not stick).');
|
|
703
|
-
}
|
|
704
703
|
/**
|
|
705
704
|
* Exit codes that mean "the child died on SIGSEGV": Node reports a
|
|
706
705
|
* signal-killed child as the negated signal number, while a shell layer
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness failures that are the environment's, not the tests': the
|
|
3
|
+
* operator message for a recognized harness crash after its retries ran
|
|
4
|
+
* out, and the mochitest server that never came up.
|
|
5
|
+
*
|
|
6
|
+
* A server never comes up when the harness's httpd cannot bind its port,
|
|
7
|
+
* typically because a parallel `--jobs` instance, a sibling checkout or a
|
|
8
|
+
* leftover httpd held it (see `harness-port-reservation.ts`). Without this
|
|
9
|
+
* shape the run read as `no-tests`, a finding about the
|
|
10
|
+
* request, and was not retried. It is an infrastructure failure instead:
|
|
11
|
+
* `classifyHarnessRun` reports it as a `harness-crash` carrying
|
|
12
|
+
* `verdictReason: 'server-startup'`, so the harness-crash retry loop
|
|
13
|
+
* (`--harness-retries`) reruns the shard, and a run that exhausts the
|
|
14
|
+
* budget says `reason=server-startup` on its verdict line.
|
|
15
|
+
*/
|
|
16
|
+
import type { HarnessCrashSignature } from './test-harness-signature.js';
|
|
17
|
+
/**
|
|
18
|
+
* Detects a mochitest server that failed to start. The caller checks that
|
|
19
|
+
* no test ran first: both lines precede any test.
|
|
20
|
+
*
|
|
21
|
+
* @param output - Captured mach stdout and stderr
|
|
22
|
+
* @returns The infrastructure signature, or undefined
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectServerStartupFailure(output: string): HarnessCrashSignature | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Builds the operator-facing failure message after retries are exhausted.
|
|
27
|
+
*
|
|
28
|
+
* @param signature - The recognized shape
|
|
29
|
+
* @param attempts - Attempts made
|
|
30
|
+
* @param commandLabel - The mach command, for the message
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number, commandLabel?: string): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Harness failures that are the environment's, not the tests': the
|
|
4
|
+
* operator message for a recognized harness crash after its retries ran
|
|
5
|
+
* out, and the mochitest server that never came up.
|
|
6
|
+
*
|
|
7
|
+
* A server never comes up when the harness's httpd cannot bind its port,
|
|
8
|
+
* typically because a parallel `--jobs` instance, a sibling checkout or a
|
|
9
|
+
* leftover httpd held it (see `harness-port-reservation.ts`). Without this
|
|
10
|
+
* shape the run read as `no-tests`, a finding about the
|
|
11
|
+
* request, and was not retried. It is an infrastructure failure instead:
|
|
12
|
+
* `classifyHarnessRun` reports it as a `harness-crash` carrying
|
|
13
|
+
* `verdictReason: 'server-startup'`, so the harness-crash retry loop
|
|
14
|
+
* (`--harness-retries`) reruns the shard, and a run that exhausts the
|
|
15
|
+
* budget says `reason=server-startup` on its verdict line.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The harness's own lines for the shape: the httpd's bind failure
|
|
19
|
+
* (`!!! could not start server on port 8888`) and `runtests.py`'s wait for
|
|
20
|
+
* it (`Timed out while waiting for server startup`).
|
|
21
|
+
*/
|
|
22
|
+
const SERVER_STARTUP_FAILURE_PATTERNS = [
|
|
23
|
+
/could not start server on port \d+/,
|
|
24
|
+
/Timed out while waiting for server startup/,
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Detects a mochitest server that failed to start. The caller checks that
|
|
28
|
+
* no test ran first: both lines precede any test.
|
|
29
|
+
*
|
|
30
|
+
* @param output - Captured mach stdout and stderr
|
|
31
|
+
* @returns The infrastructure signature, or undefined
|
|
32
|
+
*/
|
|
33
|
+
export function detectServerStartupFailure(output) {
|
|
34
|
+
for (const line of output.split(/\r?\n/)) {
|
|
35
|
+
if (SERVER_STARTUP_FAILURE_PATTERNS.some((pattern) => pattern.test(line))) {
|
|
36
|
+
return {
|
|
37
|
+
reason: 'mochitest server failed to start (server-startup)',
|
|
38
|
+
line: line.trim(),
|
|
39
|
+
verdictReason: 'server-startup',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Builds the operator-facing failure message after retries are exhausted.
|
|
47
|
+
*
|
|
48
|
+
* @param signature - The recognized shape
|
|
49
|
+
* @param attempts - Attempts made
|
|
50
|
+
* @param commandLabel - The mach command, for the message
|
|
51
|
+
*/
|
|
52
|
+
export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'mach test') {
|
|
53
|
+
if (signature.verdictReason !== 'server-startup') {
|
|
54
|
+
return (`${commandLabel} crashed in the harness itself (not in your tests) on all ${attempts} attempt(s).\n\n` +
|
|
55
|
+
`Detected shape: ${signature.reason}\n` +
|
|
56
|
+
`Evidence line: ${signature.line}\n\n` +
|
|
57
|
+
'This failure mode is environmental (mozlog resource monitor / psutil on macOS, focus-stall ' +
|
|
58
|
+
'shutdown re-entry, or a pre-test hang) rather than a test regression. Re-run the command, ' +
|
|
59
|
+
'raise the retry budget with --harness-retries <n>, or run the file in isolation. ' +
|
|
60
|
+
'If it persists across many runs, inspect the mach virtualenv (mach resyncs psutil on its own; ' +
|
|
61
|
+
'patching it manually does not stick).');
|
|
62
|
+
}
|
|
63
|
+
return (`${commandLabel} could not start a mochitest server on all ${attempts} attempt(s), so no ` +
|
|
64
|
+
'test ran (an infrastructure failure, not a test result).\n\n' +
|
|
65
|
+
`Evidence line: ${signature.line}\n\n` +
|
|
66
|
+
'Another harness (a parallel --jobs instance, a sibling checkout, or a leftover httpd) ' +
|
|
67
|
+
'most likely held the server port when this one started. Re-run the shard, raise the ' +
|
|
68
|
+
'retry budget with --harness-retries <n>, or check "lsof -nP -iTCP:8888".');
|
|
69
|
+
}
|
|
@@ -19,6 +19,12 @@ export interface HarnessCrashSignature {
|
|
|
19
19
|
reason: string;
|
|
20
20
|
/** The concrete output line the recognition was based on. */
|
|
21
21
|
line: string;
|
|
22
|
+
/**
|
|
23
|
+
* The verdict line's `reason=` for an infrastructure shape that is not a
|
|
24
|
+
* crash of the harness: `server-startup` when a mochitest server never
|
|
25
|
+
* came up. Unset: `reason=crash`.
|
|
26
|
+
*/
|
|
27
|
+
verdictReason?: 'server-startup';
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* The verdict line's `started=<n>/<m> first-unstarted=<path>
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
* `FAIL reason=test-failures`, and a short start count is stamped on the
|
|
18
18
|
* verdict line as `started=<n>/<m> first-unstarted=<path>`.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
* requested file. The suite-specific xpcshell
|
|
22
|
-
* with a result-summary block alone and print
|
|
23
|
-
* run that never names its files cannot be
|
|
20
|
+
* From streamed or captured text, the start-count check only runs when at
|
|
21
|
+
* least one start marker names a requested file. The suite-specific xpcshell
|
|
22
|
+
* dispatch can prove execution with a result-summary block alone and print
|
|
23
|
+
* no `TEST_START` line, and a run that never names its files cannot be
|
|
24
|
+
* counted against them. The structured raw log names every test by its
|
|
25
|
+
* manifest id, so a raw log whose starts name none of the requested files
|
|
26
|
+
* ran something else: a `--jobs` instance that read another instance's test
|
|
27
|
+
* list once passed on that instance's file.
|
|
24
28
|
*/
|
|
25
29
|
import { type HarnessRunVerdict } from './test-harness-crash.js';
|
|
26
30
|
import type { IncompleteRunEvidence } from './test-harness-signature.js';
|
|
@@ -62,8 +66,11 @@ export declare function createStartMarkerTracker(): StartMarkerTracker;
|
|
|
62
66
|
* @param evidence - Captured mach stdout and stderr, or the markers
|
|
63
67
|
* collected from the complete stream
|
|
64
68
|
* @param requestedPaths - Dispatched paths, in dispatch order
|
|
69
|
+
* @param authoritative - The markers come from the raw log, so a run whose
|
|
70
|
+
* starts name no requested file is counted (as none started) instead of
|
|
71
|
+
* being left uncounted
|
|
65
72
|
*/
|
|
66
|
-
export declare function countStartedFiles(evidence: string | StartMarkers, requestedPaths: readonly string[]): {
|
|
73
|
+
export declare function countStartedFiles(evidence: string | StartMarkers, requestedPaths: readonly string[], authoritative?: boolean): {
|
|
67
74
|
started: number;
|
|
68
75
|
requested: number;
|
|
69
76
|
firstUnstarted?: string;
|
|
@@ -18,10 +18,14 @@
|
|
|
18
18
|
* `FAIL reason=test-failures`, and a short start count is stamped on the
|
|
19
19
|
* verdict line as `started=<n>/<m> first-unstarted=<path>`.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
* requested file. The suite-specific xpcshell
|
|
23
|
-
* with a result-summary block alone and print
|
|
24
|
-
* run that never names its files cannot be
|
|
21
|
+
* From streamed or captured text, the start-count check only runs when at
|
|
22
|
+
* least one start marker names a requested file. The suite-specific xpcshell
|
|
23
|
+
* dispatch can prove execution with a result-summary block alone and print
|
|
24
|
+
* no `TEST_START` line, and a run that never names its files cannot be
|
|
25
|
+
* counted against them. The structured raw log names every test by its
|
|
26
|
+
* manifest id, so a raw log whose starts name none of the requested files
|
|
27
|
+
* ran something else: a `--jobs` instance that read another instance's test
|
|
28
|
+
* list once passed on that instance's file.
|
|
25
29
|
*/
|
|
26
30
|
import { SHUTDOWN_REENTRY_PATTERN, START_MARKER_PATTERN, TEST_FILE_BASENAME_PATTERN, } from './test-harness-crash.js';
|
|
27
31
|
/**
|
|
@@ -80,8 +84,11 @@ export function createStartMarkerTracker() {
|
|
|
80
84
|
* @param evidence - Captured mach stdout and stderr, or the markers
|
|
81
85
|
* collected from the complete stream
|
|
82
86
|
* @param requestedPaths - Dispatched paths, in dispatch order
|
|
87
|
+
* @param authoritative - The markers come from the raw log, so a run whose
|
|
88
|
+
* starts name no requested file is counted (as none started) instead of
|
|
89
|
+
* being left uncounted
|
|
83
90
|
*/
|
|
84
|
-
export function countStartedFiles(evidence, requestedPaths) {
|
|
91
|
+
export function countStartedFiles(evidence, requestedPaths, authoritative = false) {
|
|
85
92
|
const requested = requestedPaths.filter((path) => TEST_FILE_BASENAME_PATTERN.test(basenameOf(path)));
|
|
86
93
|
if (requested.length === 0)
|
|
87
94
|
return undefined;
|
|
@@ -92,8 +99,8 @@ export function countStartedFiles(evidence, requestedPaths) {
|
|
|
92
99
|
};
|
|
93
100
|
// Counting needs a dialect that names the requested files on its start
|
|
94
101
|
// lines. When no start line names any of them, the output cannot be
|
|
95
|
-
// counted against the request at all.
|
|
96
|
-
if (!requested.some((path) => names(path, started)))
|
|
102
|
+
// counted against the request at all, unless it is the raw log.
|
|
103
|
+
if (!authoritative && !requested.some((path) => names(path, started)))
|
|
97
104
|
return undefined;
|
|
98
105
|
const unstarted = requested.filter((path) => !names(path, started) && !names(path, skipped));
|
|
99
106
|
const firstUnstarted = unstarted[0];
|
|
@@ -125,7 +132,7 @@ export function vetoIncompletePass(verdict, output, requestedPaths, tracked) {
|
|
|
125
132
|
// tracker means nothing was streamed through it, and the capture is then
|
|
126
133
|
// the only evidence there is.
|
|
127
134
|
const useTracked = tracked !== undefined && tracked.markers.started.length > 0;
|
|
128
|
-
const counted = countStartedFiles(useTracked ? tracked.markers : output, requestedPaths);
|
|
135
|
+
const counted = countStartedFiles(useTracked ? tracked.markers : output, requestedPaths, useTracked && tracked.source === 'raw');
|
|
129
136
|
const unexpected = verdict.unexpected ?? 0;
|
|
130
137
|
const truncated = counted?.firstUnstarted !== undefined;
|
|
131
138
|
if (unexpected === 0 && shutdownLine === undefined && !truncated)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BuildBaseline, TestPackagingCoverage } from './build-baseline-types.js';
|
|
1
|
+
import type { BaselineRecorder, BuildBaseline, TestPackagingCoverage } from './build-baseline-types.js';
|
|
2
2
|
import { type StaleExemption } from './test-stale-exempt.js';
|
|
3
3
|
/** Result of the stale-build preflight probe. */
|
|
4
4
|
export interface StaleBuildResult {
|
|
@@ -48,6 +48,44 @@ export declare function checkStaleBuildForTest(projectRoot: string, engineDir: s
|
|
|
48
48
|
* result without matching the rendered copy.
|
|
49
49
|
*/
|
|
50
50
|
export declare function formatStaleBuildWarning(result: StaleBuildResult): string;
|
|
51
|
+
/** Result of {@link findCompiledSourcesNewerThanBinary}. */
|
|
52
|
+
export interface StaleCompiledResult {
|
|
53
|
+
/** Engine-relative compiled sources modified after the binary was linked. Sorted. */
|
|
54
|
+
paths: string[];
|
|
55
|
+
/** Engine-relative file that anchored the comparison (the binary or its libxul). */
|
|
56
|
+
linkedPath: string;
|
|
57
|
+
/** When that file was last written. */
|
|
58
|
+
linkedAt: Date;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Changed compiled sources (C++, Rust, IDL, WebIDL, IPDL, StaticPrefList
|
|
62
|
+
* YAML, …) whose mtime is newer than the launchable build's last link.
|
|
63
|
+
* `mach build faster` compiles none of them, so such a run tests the
|
|
64
|
+
* binary from before the edit, which reads as a failed fix rather than a
|
|
65
|
+
* stale build. Candidates are the engine paths changed since the baseline
|
|
66
|
+
* plus every workdir edit; a fork's persistent patch diff is older than any
|
|
67
|
+
* build that followed it, so only genuine post-build edits remain.
|
|
68
|
+
* Returns undefined when there is nothing to compare against (no
|
|
69
|
+
* launchable binary).
|
|
70
|
+
*
|
|
71
|
+
* @param engineDir - Absolute engine directory
|
|
72
|
+
* @param baseline - Last-build baseline, when one exists
|
|
73
|
+
* @param launchablePath - Engine-relative launchable binary path
|
|
74
|
+
*/
|
|
75
|
+
export declare function findCompiledSourcesNewerThanBinary(engineDir: string, baseline: BuildBaseline | undefined, launchablePath: string | undefined): Promise<StaleCompiledResult | undefined>;
|
|
76
|
+
/**
|
|
77
|
+
* The `stale-compiled` refusal (or, under `--allow-stale-build`, warning).
|
|
78
|
+
*
|
|
79
|
+
* @param result - A non-empty {@link StaleCompiledResult}
|
|
80
|
+
*/
|
|
81
|
+
export declare function formatStaleCompiledRefusal(result: StaleCompiledResult): string;
|
|
82
|
+
/**
|
|
83
|
+
* The notice that explains why a `test --build` pre-test build escalated
|
|
84
|
+
* to a full `mach build` for stale compiled sources.
|
|
85
|
+
*
|
|
86
|
+
* @param result - A non-empty {@link StaleCompiledResult}
|
|
87
|
+
*/
|
|
88
|
+
export declare function formatStaleCompiledBuildNotice(result: StaleCompiledResult): string;
|
|
51
89
|
/**
|
|
52
90
|
* Sentinel returned by {@link findUncoveredRequestPaths} when a full-suite
|
|
53
91
|
* request (no paths) is checked against a scoped coverage record.
|
|
@@ -97,6 +135,15 @@ export interface CoverageRefusalDetail {
|
|
|
97
135
|
changedInputs?: Readonly<Record<string, readonly string[]>>;
|
|
98
136
|
/** The record carries a full build's anchor forward for unchanged paths. */
|
|
99
137
|
carriesFullAnchor?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Who wrote the record and when, so a run whose coverage a peer's scoped
|
|
140
|
+
* build replaced can tell whose build that was.
|
|
141
|
+
*/
|
|
142
|
+
recordedBy?: string | undefined;
|
|
143
|
+
builtAt?: string | undefined;
|
|
144
|
+
recorder?: BaselineRecorder | undefined;
|
|
145
|
+
/** Whether the recorder's process is still running (it may still be using the packaging). */
|
|
146
|
+
recorderAlive?: boolean | undefined;
|
|
100
147
|
}
|
|
101
148
|
/**
|
|
102
149
|
* Formats the refusal shown when a non-`--build` run requests paths the
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* legitimately have a fresh `dist/` with no FireForge-recorded baseline
|
|
23
23
|
* update.
|
|
24
24
|
*/
|
|
25
|
+
import { stat } from 'node:fs/promises';
|
|
26
|
+
import { join, relative } from 'node:path';
|
|
25
27
|
import { toError } from '../utils/errors.js';
|
|
26
28
|
import { verbose, warn } from '../utils/logger.js';
|
|
27
29
|
import { normalizePathSlashes } from '../utils/paths.js';
|
|
@@ -30,6 +32,8 @@ import { readBuildBaseline } from './build-baseline.js';
|
|
|
30
32
|
import { hashEngineFile } from './coverage-extend.js';
|
|
31
33
|
import { isTestManifestPath } from './coverage-records.js';
|
|
32
34
|
import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
|
|
35
|
+
import { readLinkedAt } from './launchable-build.js';
|
|
36
|
+
import { isCompiledSourcePath } from './negative-control-compiled.js';
|
|
33
37
|
import { formatStaleExemptions, partitionStaleCandidates, } from './test-stale-exempt.js';
|
|
34
38
|
/** Cap on the number of changed paths rendered inline. */
|
|
35
39
|
const STALE_PATHS_LIMIT = 10;
|
|
@@ -91,6 +95,83 @@ export function formatStaleBuildWarning(result) {
|
|
|
91
95
|
'"fireforge build --ui") first. Passing --build skips this check.' +
|
|
92
96
|
exemptLine);
|
|
93
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Path segments whose compiled sources never reach the binary under test
|
|
100
|
+
* (gtests, cppunittests, CI config YAML): an edit there must not make the
|
|
101
|
+
* browser read as stale.
|
|
102
|
+
*/
|
|
103
|
+
const NON_RUNTIME_SEGMENTS = new Set(['test', 'tests', 'gtest', 'testing', 'taskcluster']);
|
|
104
|
+
/**
|
|
105
|
+
* Changed compiled sources (C++, Rust, IDL, WebIDL, IPDL, StaticPrefList
|
|
106
|
+
* YAML, …) whose mtime is newer than the launchable build's last link.
|
|
107
|
+
* `mach build faster` compiles none of them, so such a run tests the
|
|
108
|
+
* binary from before the edit, which reads as a failed fix rather than a
|
|
109
|
+
* stale build. Candidates are the engine paths changed since the baseline
|
|
110
|
+
* plus every workdir edit; a fork's persistent patch diff is older than any
|
|
111
|
+
* build that followed it, so only genuine post-build edits remain.
|
|
112
|
+
* Returns undefined when there is nothing to compare against (no
|
|
113
|
+
* launchable binary).
|
|
114
|
+
*
|
|
115
|
+
* @param engineDir - Absolute engine directory
|
|
116
|
+
* @param baseline - Last-build baseline, when one exists
|
|
117
|
+
* @param launchablePath - Engine-relative launchable binary path
|
|
118
|
+
*/
|
|
119
|
+
export async function findCompiledSourcesNewerThanBinary(engineDir, baseline, launchablePath) {
|
|
120
|
+
if (launchablePath === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
const linked = await readLinkedAt(engineDir, launchablePath);
|
|
123
|
+
if (linked === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
const changed = await collectChangedEnginePaths(engineDir, baseline, 'Stale-compiled preflight');
|
|
126
|
+
const candidates = changed.filter((path) => isCompiledSourcePath(path) &&
|
|
127
|
+
!path.split('/').some((segment) => NON_RUNTIME_SEGMENTS.has(segment)));
|
|
128
|
+
const paths = [];
|
|
129
|
+
for (const path of candidates) {
|
|
130
|
+
try {
|
|
131
|
+
const { mtimeMs } = await stat(join(engineDir, path));
|
|
132
|
+
if (mtimeMs > linked.mtimeMs)
|
|
133
|
+
paths.push(path);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// A deleted source cannot be newer than anything; the build that
|
|
137
|
+
// drops it is the stale-build check's business.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
paths: paths.sort(),
|
|
142
|
+
linkedPath: relative(engineDir, linked.path),
|
|
143
|
+
linkedAt: new Date(linked.mtimeMs),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The `stale-compiled` refusal (or, under `--allow-stale-build`, warning).
|
|
148
|
+
*
|
|
149
|
+
* @param result - A non-empty {@link StaleCompiledResult}
|
|
150
|
+
*/
|
|
151
|
+
export function formatStaleCompiledRefusal(result) {
|
|
152
|
+
const head = result.paths.slice(0, STALE_PATHS_LIMIT).map((path) => `engine/${path}`);
|
|
153
|
+
const truncated = result.paths.length - head.length;
|
|
154
|
+
const list = head.join(', ') + (truncated > 0 ? `, … (+${truncated} more)` : '');
|
|
155
|
+
return (`Compiled sources changed after the binary under test was linked ` +
|
|
156
|
+
`(engine/${result.linkedPath}, ${result.linkedAt.toISOString()}): ${list}.\n` +
|
|
157
|
+
'The run would test the binary from before those edits, so a red reads as a failed fix ' +
|
|
158
|
+
'and a green proves nothing about them. Run "fireforge build" first, or rerun with ' +
|
|
159
|
+
'"fireforge test --build", which escalates to a full mach build when compiled sources ' +
|
|
160
|
+
'changed. --allow-stale-build turns this refusal into a warning.');
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The notice that explains why a `test --build` pre-test build escalated
|
|
164
|
+
* to a full `mach build` for stale compiled sources.
|
|
165
|
+
*
|
|
166
|
+
* @param result - A non-empty {@link StaleCompiledResult}
|
|
167
|
+
*/
|
|
168
|
+
export function formatStaleCompiledBuildNotice(result) {
|
|
169
|
+
const head = result.paths.slice(0, STALE_PATHS_LIMIT).map((path) => `engine/${path}`);
|
|
170
|
+
const truncated = result.paths.length - head.length;
|
|
171
|
+
return (`Compiled sources changed after the binary was linked (${head.join(', ')}` +
|
|
172
|
+
`${truncated > 0 ? `, … (+${truncated} more)` : ''}); escalating this pre-test build to a ` +
|
|
173
|
+
'full mach build, since mach build faster compiles nothing.');
|
|
174
|
+
}
|
|
94
175
|
/**
|
|
95
176
|
* Sentinel returned by {@link findUncoveredRequestPaths} when a full-suite
|
|
96
177
|
* request (no paths) is checked against a scoped coverage record.
|
|
@@ -163,6 +244,20 @@ export async function findChangedTestManifestsForPaths(engineDir, baseline, unco
|
|
|
163
244
|
});
|
|
164
245
|
return hits.slice(0, STALE_PATHS_LIMIT);
|
|
165
246
|
}
|
|
247
|
+
/** The refusal's sentence naming who wrote the coverage record. */
|
|
248
|
+
function formatCoverageHolder(detail) {
|
|
249
|
+
if (detail.recorder === undefined && detail.recordedBy === undefined)
|
|
250
|
+
return '';
|
|
251
|
+
const what = detail.recordedBy !== undefined ? `"${detail.recordedBy}"` : 'a build';
|
|
252
|
+
const when = detail.builtAt !== undefined ? ` at ${detail.builtAt}` : '';
|
|
253
|
+
const recorder = detail.recorder;
|
|
254
|
+
const who = recorder === undefined
|
|
255
|
+
? ''
|
|
256
|
+
: ` (pid ${recorder.pid}${detail.recorderAlive === true ? ', still running' : ''}` +
|
|
257
|
+
(recorder.sessionId !== undefined ? `, session ${recorder.sessionId}` : '') +
|
|
258
|
+
`, invocation ${recorder.invocationId})`;
|
|
259
|
+
return ` The current record was written by ${what}${when}${who}.`;
|
|
260
|
+
}
|
|
166
261
|
/**
|
|
167
262
|
* Formats the refusal shown when a non-`--build` run requests paths the
|
|
168
263
|
* packaged runtime's scoped `test --build` coverage does not include. It is
|
|
@@ -186,7 +281,7 @@ export function formatTestCoverageRefusal(uncovered, coverage, changedManifests
|
|
|
186
281
|
};
|
|
187
282
|
const rebuildTargets = uncovered.filter((p) => p !== FULL_SUITE_REQUEST);
|
|
188
283
|
// Per-path retention is the default, so rebuilding only the uncovered
|
|
189
|
-
// paths no
|
|
284
|
+
// paths costs no one else's claim. Saying so is what keeps a
|
|
190
285
|
// peer from rebuilding everything "to be safe".
|
|
191
286
|
const keepHint = rebuildTargets.length > 0
|
|
192
287
|
? ' That scoped build keeps every other recorded path whose own staging inputs (manifest directory and the fixtures its manifest names) and engine/mozconfig are unchanged.'
|
|
@@ -210,7 +305,7 @@ export function formatTestCoverageRefusal(uncovered, coverage, changedManifests
|
|
|
210
305
|
return (`The packaged test runtime was produced by a scoped "fireforge test --build" covering only: ${scope}.\n` +
|
|
211
306
|
`The requested run needs ${cap(uncovered)}, which that packaging does not cover — support ` +
|
|
212
307
|
'fixtures for those manifests may be missing from obj-*/_tests/, and the run can hang ' +
|
|
213
|
-
`rather than fail.${affectedLine}${manifestLine} ${rebuildHint} ` +
|
|
308
|
+
`rather than fail.${formatCoverageHolder(detail)}${affectedLine}${manifestLine} ${rebuildHint} ` +
|
|
214
309
|
'(--allow-stale-build does not bypass this check — it accepts stale content, not missing coverage.)');
|
|
215
310
|
}
|
|
216
311
|
/**
|
|
@@ -37,9 +37,8 @@ const NO_OUTPUT_STALL_CONTROL_STEP = 'First, run a known-good control test. If t
|
|
|
37
37
|
* - (4) is mechanical and was root-caused downstream: the harness serves
|
|
38
38
|
* its manifest from `server.js` on 8888, and a survivor of an
|
|
39
39
|
* interrupted run keeps the port. Its probe is exact, and the preflight
|
|
40
|
-
* in `mochitest-server-port.ts`
|
|
41
|
-
* - (3) is
|
|
42
|
-
* earlier revision recorded. A `chrome://` (or `resource://`) URL that
|
|
40
|
+
* in `mochitest-server-port.ts` refuses it before a run starts.
|
|
41
|
+
* - (3) is mechanical and root-caused. A `chrome://` (or `resource://`) URL that
|
|
43
42
|
* resolves to nothing reaches `CheckForBrokenChromeURL`
|
|
44
43
|
* (netwerk/base/nsNetUtil.cpp), which outside automation only
|
|
45
44
|
* `printf_stderr`s `Missing chrome or resource URL: <uri>`, but under
|
|
@@ -47,9 +46,8 @@ const NO_OUTPUT_STALL_CONTROL_STEP = 'First, run a known-good control test. If t
|
|
|
47
46
|
* `MOZ_CRASH_UNSAFE_PRINTF("Missing chrome or resource URLs: %s")`. A
|
|
48
47
|
* downstream reproduction symbolicated the faulting frame as
|
|
49
48
|
* `CheckForBrokenChromeURL`, which is the established diagnosis the bar
|
|
50
|
-
* in `mach-error-hints.ts` asks for.
|
|
51
|
-
*
|
|
52
|
-
* failed image load in Gecko fires `error` and does not gate the
|
|
49
|
+
* in `mach-error-hints.ts` asks for. It is not a "stalls first paint"
|
|
50
|
+
* problem: a failed image load in Gecko fires `error` and does not gate the
|
|
53
51
|
* document load event.
|
|
54
52
|
*
|
|
55
53
|
* Detection by log line is not specified for (3), because of the mechanism
|
|
@@ -62,7 +60,7 @@ const NO_OUTPUT_STALL_CONTROL_STEP = 'First, run a known-good control test. If t
|
|
|
62
60
|
* the census points at the two artefacts that do carry it: the OS crash
|
|
63
61
|
* report (whose faulting frame names `CheckForBrokenChromeURL`) and the
|
|
64
62
|
* out-of-automation smoke probe (which prints the non-crashing spelling
|
|
65
|
-
* and
|
|
63
|
+
* and counts it as an unallowed error so the exit code
|
|
66
64
|
* agrees with the capture).
|
|
67
65
|
*/
|
|
68
66
|
const NO_OUTPUT_STALL_TRIAGE = [
|
|
@@ -18,8 +18,8 @@ import { escapeRegex } from '../utils/regex.js';
|
|
|
18
18
|
* Body of the first `/* … *\/` comment on `line`, or `undefined` when the
|
|
19
19
|
* line carries no closed block comment.
|
|
20
20
|
*
|
|
21
|
-
* This uses index arithmetic rather than a regex:
|
|
22
|
-
* "banner comment"
|
|
21
|
+
* This uses index arithmetic rather than a regex: the natural regex
|
|
22
|
+
* spellings of "banner comment" are super-linear on a line that
|
|
23
23
|
* repeats the opening shape (CodeQL `js/polynomial-redos`), and `tokens.css`
|
|
24
24
|
* is a file FireForge reads out of a consumer's engine tree, so a
|
|
25
25
|
* pathological line needs no attacker to arrive.
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* tokens each declares, and what a single token resolves to in every theme
|
|
5
5
|
* block that mentions it.
|
|
6
6
|
*
|
|
7
|
-
* `token add` requires `--category
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* `token add` requires `--category`, so it needs a report of the categories
|
|
8
|
+
* a project actually has; without one, the only way to name one correctly
|
|
9
|
+
* is to hand-parse a neighbouring `= Category =` banner out of the file.
|
|
10
10
|
* This module is that report. It reuses the banner and
|
|
11
11
|
* category-region model from `token-category.ts` rather than parsing
|
|
12
12
|
* tokens CSS a second way: two readers that disagree on what a banner is
|
|
@@ -4,7 +4,7 @@ import { type TokenDeclarationLocation } from './token-category.js';
|
|
|
4
4
|
*
|
|
5
5
|
* Derived from {@link TOKEN_MODES} so the runtime allowlist and the type
|
|
6
6
|
* cannot drift: adding a member here without updating the list (or the
|
|
7
|
-
* reverse) is
|
|
7
|
+
* reverse) is impossible, because there is only one list.
|
|
8
8
|
*/
|
|
9
9
|
export declare const TOKEN_MODES: readonly ["auto", "static", "override"];
|
|
10
10
|
/** Dark mode behaviour for a token. */
|
|
@@ -21,7 +21,7 @@ export interface AddTokenOptions {
|
|
|
21
21
|
value: string;
|
|
22
22
|
/**
|
|
23
23
|
* Token category matching section headers in the CSS file. Required for a
|
|
24
|
-
* base declaration. Ignored, and
|
|
24
|
+
* base declaration. Ignored, and not required, under
|
|
25
25
|
* {@link AddTokenOptions.variant}, which routes into a `:root<selector>`
|
|
26
26
|
* block and never touches a category section.
|
|
27
27
|
*/
|