@hominis/fireforge 0.52.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 +26 -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.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 +8 -0
- package/dist/src/commands/test-build-phase.js +42 -7
- 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 +68 -7
- package/dist/src/commands/test-multi-verdict.js +1 -0
- package/dist/src/commands/test-orchestrate.d.ts +6 -0
- package/dist/src/commands/test-orchestrate.js +28 -0
- 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 +40 -2
- package/dist/src/commands/test-verdict.d.ts +5 -3
- package/dist/src/commands/test.js +10 -0
- 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 +24 -3
- package/dist/src/core/build-baseline-validate.d.ts +30 -1
- package/dist/src/core/build-baseline-validate.js +127 -35
- package/dist/src/core/build-baseline.d.ts +8 -2
- package/dist/src/core/build-baseline.js +25 -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-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/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 +13 -3
- package/dist/src/core/negative-control-compiled.js +27 -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-stale-check.d.ts +38 -0
- package/dist/src/core/test-stale-check.js +82 -1
- 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.js +1 -1
- 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/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 +21 -23
- package/docs/exit-codes.md +8 -11
- package/docs/furnace.md +3 -3
- package/docs/lifecycle-invariants.md +36 -45
- package/docs/machine-output.md +41 -24
- package/docs/patch-workflow.md +129 -24
- package/docs/run-logs.md +8 -9
- package/docs/testing.md +105 -46
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* judged per file from each dispatch's raw log: `shards=` then counts files
|
|
14
14
|
* and `failed-shards=` names the files that failed.
|
|
15
15
|
*/
|
|
16
|
+
import { formatHarnessServerPorts } from '../core/harness-port-reservation.js';
|
|
16
17
|
import { getActiveRunLogPath, writeToActiveRunLog } from '../core/run-log.js';
|
|
17
18
|
import { buildHarnessCrashMessage, formatFireforgeVerdictLine, headedNoOutputTimeoutHint, } from '../core/test-harness-crash.js';
|
|
18
19
|
import { TestFailureError } from '../errors/build.js';
|
|
@@ -27,19 +28,32 @@ const SHARD_STATUS_LABEL = {
|
|
|
27
28
|
'no-tests': 'NO-TESTS',
|
|
28
29
|
'harness-arguments': 'ARGUMENTS',
|
|
29
30
|
};
|
|
31
|
+
/** The summary box's status word; a server that never started is not a crash. */
|
|
32
|
+
function shardStatusLabel(verdict) {
|
|
33
|
+
return verdict.signature?.verdictReason === 'server-startup'
|
|
34
|
+
? 'SERVER'
|
|
35
|
+
: SHARD_STATUS_LABEL[verdict.kind];
|
|
36
|
+
}
|
|
30
37
|
/**
|
|
31
38
|
* Derives the aggregate classification for a sharded run: all-green is
|
|
32
39
|
* `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
|
|
33
40
|
* shard makes the whole run `harness-crash`, then `no-tests`, then plain
|
|
34
41
|
* `test-failures`, so the aggregate `reason=` keeps the classifier-driven
|
|
35
|
-
* contract instead of collapsing every failure to `test-failures`.
|
|
42
|
+
* contract instead of collapsing every failure to `test-failures`. When
|
|
43
|
+
* every crashed shard is a server that never started, the aggregate keeps
|
|
44
|
+
* that signature, so its line says `reason=server-startup` too.
|
|
36
45
|
*/
|
|
37
46
|
export function deriveAggregateShardVerdict(shards) {
|
|
38
47
|
const kinds = new Set(shards.map(({ outcome }) => outcome.verdict.kind));
|
|
39
48
|
if (kinds.has('harness-arguments'))
|
|
40
49
|
return { kind: 'harness-arguments' };
|
|
41
|
-
if (kinds.has('harness-crash'))
|
|
42
|
-
|
|
50
|
+
if (kinds.has('harness-crash')) {
|
|
51
|
+
const crashes = shards.filter(({ outcome }) => outcome.verdict.kind === 'harness-crash');
|
|
52
|
+
const signature = crashes[0]?.outcome.verdict.signature;
|
|
53
|
+
return crashes.every(({ outcome }) => outcome.verdict.signature?.verdictReason === 'server-startup') && signature !== undefined
|
|
54
|
+
? { kind: 'harness-crash', signature }
|
|
55
|
+
: { kind: 'harness-crash' };
|
|
56
|
+
}
|
|
43
57
|
if (kinds.has('no-tests'))
|
|
44
58
|
return { kind: 'no-tests' };
|
|
45
59
|
if (kinds.has('test-failures'))
|
|
@@ -48,15 +62,18 @@ export function deriveAggregateShardVerdict(shards) {
|
|
|
48
62
|
}
|
|
49
63
|
/**
|
|
50
64
|
* The `FIREFORGE-SHARD:` line for one finished shard: the verdict it would
|
|
51
|
-
* have printed alone, plus `shard=<i>/<n>` and `path=<label
|
|
52
|
-
* `
|
|
65
|
+
* have printed alone, plus `shard=<i>/<n>` and `path=<label>`, and
|
|
66
|
+
* `ports=http:N,ssl:N,ws:N` when the harness reported the mochitest server
|
|
67
|
+
* ports it took. Never a `FIREFORGE-VERDICT:` line, so the run keeps
|
|
68
|
+
* exactly one.
|
|
53
69
|
*/
|
|
54
70
|
export function formatShardLine(shard, index, total, failedFiles) {
|
|
55
71
|
const body = formatFireforgeVerdictLine(shard.outcome.verdict).slice('FIREFORGE-VERDICT: '.length);
|
|
56
72
|
const files = failedFiles !== undefined && failedFiles.length > 0
|
|
57
73
|
? ` failed-files=${failedFiles.join(',')}`
|
|
58
74
|
: '';
|
|
59
|
-
|
|
75
|
+
const ports = formatHarnessServerPorts(shard.outcome.serverPorts);
|
|
76
|
+
return `FIREFORGE-SHARD: ${body} shard=${index + 1}/${total} path=${shard.label.replace(/\s+/g, '_')}${files}${ports === undefined ? '' : ` ports=${ports}`}`;
|
|
60
77
|
}
|
|
61
78
|
function writeShardLine(line) {
|
|
62
79
|
process.stdout.write(`${line}\n`);
|
|
@@ -163,7 +180,7 @@ function reportProgress(finished, total, jobs) {
|
|
|
163
180
|
* counts as a whole.
|
|
164
181
|
*/
|
|
165
182
|
function summarizeShards(shards, perFile) {
|
|
166
|
-
const lines = shards.map(({ label, outcome }) => `${
|
|
183
|
+
const lines = shards.map(({ label, outcome }) => `${shardStatusLabel(outcome.verdict).padEnd(8)} ${label}` +
|
|
167
184
|
(outcome.attempts > 1 ? ` (${outcome.attempts} attempts)` : ''));
|
|
168
185
|
const failingShards = shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
|
|
169
186
|
let passed = shards.length - failingShards.length;
|
|
@@ -29,9 +29,14 @@ import type { TestOptions } from '../types/commands/index.js';
|
|
|
29
29
|
* stale-content refusal still applies to it unchanged.
|
|
30
30
|
*/
|
|
31
31
|
export declare function enforceStaleBuildGate(projectRoot: string, engineDir: string, options: TestOptions, normalizedPaths: readonly string[],
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* `quiet`: refuse only, without the exemption note (the pre-lock pass).
|
|
34
|
+
* `launchablePath`: the engine-relative binary the run launches, which
|
|
35
|
+
* anchors the stale-compiled check (skipped without one).
|
|
36
|
+
*/
|
|
33
37
|
gate?: {
|
|
34
38
|
quiet?: boolean;
|
|
39
|
+
launchablePath?: string | undefined;
|
|
35
40
|
}): Promise<void>;
|
|
36
41
|
/**
|
|
37
42
|
* Compiled-StaticComponents gate: refuses runs whose child process would
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `src/core/test-stale-check.ts`.
|
|
9
9
|
*/
|
|
10
10
|
import { filterByCarriedFullCoverage } from '../core/coverage-dispatch.js';
|
|
11
|
-
import { checkStaleBuildForTest, checkStaticComponentsStale, findChangedTestManifestsForPaths, findUncoveredRequestPaths, formatStaleBuildWarning, formatStaticComponentsRefusal, formatTestCoverageRefusal, FULL_SUITE_REQUEST, } from '../core/test-stale-check.js';
|
|
11
|
+
import { checkStaleBuildForTest, checkStaticComponentsStale, findChangedTestManifestsForPaths, findCompiledSourcesNewerThanBinary, findUncoveredRequestPaths, formatStaleBuildWarning, formatStaleCompiledRefusal, formatStaticComponentsRefusal, formatTestCoverageRefusal, FULL_SUITE_REQUEST, } from '../core/test-stale-check.js';
|
|
12
12
|
import { formatStaleExemptions } from '../core/test-stale-exempt.js';
|
|
13
13
|
import { PreflightRefusalError } from '../errors/base.js';
|
|
14
14
|
import { isProcessAlive } from '../utils/errors.js';
|
|
@@ -35,14 +35,20 @@ import { describePerfStaleImpact } from './test-perf-scope.js';
|
|
|
35
35
|
* stale-content refusal still applies to it unchanged.
|
|
36
36
|
*/
|
|
37
37
|
export async function enforceStaleBuildGate(projectRoot, engineDir, options, normalizedPaths,
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* `quiet`: refuse only, without the exemption note (the pre-lock pass).
|
|
40
|
+
* `launchablePath`: the engine-relative binary the run launches, which
|
|
41
|
+
* anchors the stale-compiled check (skipped without one).
|
|
42
|
+
*/
|
|
39
43
|
gate = {}) {
|
|
40
44
|
const stale = await checkStaleBuildForTest(projectRoot, engineDir);
|
|
45
|
+
enforceControlBuiltGate(stale.baseline);
|
|
41
46
|
const dispatchesNoTests = options.doctor === true && normalizedPaths.length === 0;
|
|
42
47
|
if (!dispatchesNoTests) {
|
|
43
48
|
await enforceCoverageGate(engineDir, stale.baseline, normalizedPaths);
|
|
44
49
|
}
|
|
45
50
|
await enforceStaticComponentsGate(engineDir, stale.baseline, options);
|
|
51
|
+
await enforceStaleCompiledGate(engineDir, stale.baseline, options, gate.launchablePath);
|
|
46
52
|
const exempt = stale.exempt ?? [];
|
|
47
53
|
if (!stale.stale && exempt.length > 0 && gate.quiet !== true) {
|
|
48
54
|
info(`Stale-build preflight: ${exempt.length} edit(s) since the last build need no rebuild: ${formatStaleExemptions(exempt)}.`);
|
|
@@ -73,6 +79,38 @@ gate = {}) {
|
|
|
73
79
|
throw new PreflightRefusalError(staleMessage, 'stale-build');
|
|
74
80
|
}
|
|
75
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Control-built refusal: the recorded build compiled a negative control
|
|
84
|
+
* into the binary, and no build since has recompiled the restored source
|
|
85
|
+
* (the run was killed, or its restore build failed). Every test against
|
|
86
|
+
* that binary tests the mutation. Not bypassable: `--allow-stale-build`
|
|
87
|
+
* accepts old content, not a deliberately broken binary.
|
|
88
|
+
*/
|
|
89
|
+
function enforceControlBuiltGate(baseline) {
|
|
90
|
+
const stamp = baseline?.controlBuild;
|
|
91
|
+
if (stamp === undefined)
|
|
92
|
+
return;
|
|
93
|
+
throw new PreflightRefusalError(`The binary under test was built with the negative control ${stamp.diffFile} applied ` +
|
|
94
|
+
`(${stamp.targets.map((target) => `engine/${target}`).join(', ')}, applied ${stamp.appliedAt}), ` +
|
|
95
|
+
'and no build has recompiled the restored source since. Run "fireforge build" to ' +
|
|
96
|
+
'rebuild the binary without the control, then rerun.', 'control-built');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Stale-compiled refusal: compiled sources modified after the binary under
|
|
100
|
+
* test was linked. `--allow-stale-build` downgrades it to a warning, as it
|
|
101
|
+
* does the stale-build refusal.
|
|
102
|
+
*/
|
|
103
|
+
async function enforceStaleCompiledGate(engineDir, baseline, options, launchablePath) {
|
|
104
|
+
const result = await findCompiledSourcesNewerThanBinary(engineDir, baseline, launchablePath);
|
|
105
|
+
if (result === undefined || result.paths.length === 0)
|
|
106
|
+
return;
|
|
107
|
+
const message = formatStaleCompiledRefusal(result);
|
|
108
|
+
if (options.allowStaleBuild === true) {
|
|
109
|
+
warn(message);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
throw new PreflightRefusalError(message, 'stale-compiled');
|
|
113
|
+
}
|
|
76
114
|
/**
|
|
77
115
|
* Packaging-coverage refusal. A path outside the recorded scoped list is
|
|
78
116
|
* still covered while its own staging inputs are unchanged against a
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { HarnessRunVerdict } from '../core/test-harness-crash.js';
|
|
2
2
|
/**
|
|
3
3
|
* Reason codes the emission layer can put on a `FIREFORGE-VERDICT: FAIL`
|
|
4
|
-
* line. The first three mirror the harness classifier
|
|
4
|
+
* line. The first three mirror the harness classifier, as does
|
|
5
|
+
* `server-startup`: a harness-crash-class infrastructure failure where a
|
|
6
|
+
* mochitest server never came up, so no test ran. `preflight` covers
|
|
5
7
|
* every failure before harness classification (missing/stale builds,
|
|
6
8
|
* invalid paths, port conflicts, config errors, spawn failures),
|
|
7
9
|
* `inconclusive` marks a run whose harness result exists but cannot be
|
|
@@ -11,9 +13,9 @@ import type { HarnessRunVerdict } from '../core/test-harness-crash.js';
|
|
|
11
13
|
* and `killed` marks a run terminated by a signal before it could reach any
|
|
12
14
|
* of the others.
|
|
13
15
|
*/
|
|
14
|
-
export type FireforgeVerdictReason = 'crash' | 'harness-arguments' | 'no-tests' | 'test-failures' | 'preflight' | 'inconclusive' | 'lock-timeout' | 'killed';
|
|
16
|
+
export type FireforgeVerdictReason = 'crash' | 'harness-arguments' | 'no-tests' | 'test-failures' | 'server-startup' | 'preflight' | 'inconclusive' | 'lock-timeout' | 'killed';
|
|
15
17
|
/** Additive keys a run can stamp on its verdict line. */
|
|
16
|
-
export type VerdictRunAttributeKey = 'shuffle' | 'orphans-reaped' | 'host-load' | 'host-cpu-warning' | 'power-source' | 'power-changed' | 'furnace-applied' | 'furnace-apply' | 'furnace-skipped' | 'control-build' | 'drift-tolerated' | 'lock' | 'jobs' | 'results';
|
|
18
|
+
export type VerdictRunAttributeKey = 'shuffle' | 'orphans-reaped' | 'host-load' | 'host-cpu-warning' | 'power-source' | 'power-changed' | 'furnace-applied' | 'furnace-apply' | 'furnace-skipped' | 'control-build' | 'control-restore-build' | 'build-id' | 'drift-tolerated' | 'lock' | 'jobs' | 'upload-dirs' | 'results';
|
|
17
19
|
/** Records an additive attribute for this run's verdict line. */
|
|
18
20
|
export declare function setVerdictRunAttribute(key: VerdictRunAttributeKey, value: string): void;
|
|
19
21
|
/**
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
4
4
|
import { assertEngineExists } from '../core/engine-precondition.js';
|
|
5
|
+
import { readBuildId } from '../core/launchable-build.js';
|
|
5
6
|
import { hasBuildArtifacts } from '../core/mach.js';
|
|
6
7
|
import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
|
|
7
8
|
import { ensureLaunchableBrowserNotRunning } from '../core/marionette-port.js';
|
|
@@ -349,9 +350,18 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
349
350
|
classification,
|
|
350
351
|
normalizedPaths: hooks.coveragePaths ?? normalizedPaths,
|
|
351
352
|
...(hooks.fullBuildReason === undefined ? {} : { fullBuildReason: hooks.fullBuildReason }),
|
|
353
|
+
...(hooks.controlBuild === undefined ? {} : { controlBuild: hooks.controlBuild }),
|
|
354
|
+
launchablePath,
|
|
352
355
|
})) {
|
|
353
356
|
return;
|
|
354
357
|
}
|
|
358
|
+
// Which binary this run exercises, read after the pre-test build so it
|
|
359
|
+
// names the build the tests actually ran against (`moz:buildID`).
|
|
360
|
+
if (launchablePath !== undefined) {
|
|
361
|
+
const buildId = await readBuildId(paths.engine, launchablePath);
|
|
362
|
+
if (buildId !== undefined)
|
|
363
|
+
setVerdictRunAttribute('build-id', buildId);
|
|
364
|
+
}
|
|
355
365
|
const { forwardedPort, effectivePort } = await ensureTestBrowserEnvironment(paths.engine, launchablePath, xpcshellOnly, projectConfig, options, buildCheck.objDir);
|
|
356
366
|
if (options.doctor) {
|
|
357
367
|
const doctorOutcome = await runDoctorPreflight({
|
|
@@ -42,8 +42,8 @@ async function normalizeTokenNameForProject(projectRoot, rawTokenName) {
|
|
|
42
42
|
*
|
|
43
43
|
* A variant skip has no category to name, because the declaration lives in
|
|
44
44
|
* a `:root<selector>` block, so the location reads as that block. The
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* location matters: without it a re-run meant to change a value exits 0
|
|
46
|
+
* having silently changed nothing.
|
|
47
47
|
*
|
|
48
48
|
* @param tokenName - Full token name including prefix
|
|
49
49
|
* @param result - Result carrying the skip location, when known
|
|
@@ -328,7 +328,7 @@ export function registerVerify(program, { getProjectRoot, withErrorHandling }) {
|
|
|
328
328
|
program
|
|
329
329
|
.command('verify')
|
|
330
330
|
.description('Read-only fsck for the patch queue (manifest + cross-patch lint)')
|
|
331
|
-
.option('--patches <names...>', 'Judge only findings that name these patches (filename, stem, order number, manifest name, or slug; repeat or
|
|
331
|
+
.option('--patches <names...>', 'Judge only findings that name these patches (filename, stem, order number, manifest name, or slug; repeat, or separate with commas or whitespace). Queue-wide findings stay; unowned worktree changes drop out')
|
|
332
332
|
.action(withErrorHandling(async (options) => {
|
|
333
333
|
await verifyCommand(getProjectRoot(), options);
|
|
334
334
|
}));
|
|
@@ -195,7 +195,7 @@ export async function watchCommand(projectRoot) {
|
|
|
195
195
|
catch (error) {
|
|
196
196
|
throw new BuildError('Watch process failed to start', 'mach watch', error instanceof Error ? error : undefined);
|
|
197
197
|
}
|
|
198
|
-
// On Ctrl+C the bin signal handler
|
|
198
|
+
// On Ctrl+C the bin signal handler waits for the mach child to shut
|
|
199
199
|
// down (waitForActiveChildShutdown) before exiting, so this continuation
|
|
200
200
|
// can run and whitelist the SIGINT exit code, though the bin may still
|
|
201
201
|
// win the race and terminate first. Both outcomes are acceptable.
|
|
@@ -208,7 +208,7 @@ function buildWireSubscriptOptions(options, domFilePath, domTargetPath, subscrip
|
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
/**
|
|
211
|
-
* Surfaces any
|
|
211
|
+
* Surfaces any regex parser fallbacks the wiring run consumed, so the
|
|
212
212
|
* operator knows which files were mutated by the regex path rather than
|
|
213
213
|
* the AST path.
|
|
214
214
|
*/
|
|
@@ -29,11 +29,10 @@ export declare function countTrailingSegmentMatches(a: string, b: string): numbe
|
|
|
29
29
|
*
|
|
30
30
|
* Shared on purpose: this set governs two halves of one decision,
|
|
31
31
|
* `scoreCandidate` (selection) and `isConfidentMatch` (confirmation, in
|
|
32
|
-
* `build-audit.ts`).
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* automatic.
|
|
32
|
+
* `build-audit.ts`). Separate copies would disagree, so a source path
|
|
33
|
+
* could earn a selection bonus for a segment that confirmation then
|
|
34
|
+
* rejected as meaningless, leaving the chosen artifact unconfirmable. One
|
|
35
|
+
* set makes every addition apply to both halves.
|
|
37
36
|
*/
|
|
38
37
|
export declare const GENERIC_PATH_SEGMENTS: ReadonlySet<string>;
|
|
39
38
|
/**
|
|
@@ -96,11 +96,10 @@ export function countTrailingSegmentMatches(a, b) {
|
|
|
96
96
|
*
|
|
97
97
|
* Shared on purpose: this set governs two halves of one decision,
|
|
98
98
|
* `scoreCandidate` (selection) and `isConfidentMatch` (confirmation, in
|
|
99
|
-
* `build-audit.ts`).
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* automatic.
|
|
99
|
+
* `build-audit.ts`). Separate copies would disagree, so a source path
|
|
100
|
+
* could earn a selection bonus for a segment that confirmation then
|
|
101
|
+
* rejected as meaningless, leaving the chosen artifact unconfirmable. One
|
|
102
|
+
* set makes every addition apply to both halves.
|
|
104
103
|
*/
|
|
105
104
|
export const GENERIC_PATH_SEGMENTS = new Set([
|
|
106
105
|
'content',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Why a changed engine path was not audited.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Each reason is structured so summaries and machine output can count
|
|
5
|
+
* skips by cause instead of reading verbose log strings.
|
|
6
6
|
*/
|
|
7
7
|
export type AuditSkipReason = 'not-packageable' | 'platform-gated' | 'platform-gated-ancestor' | 'tests-not-packaged' | 'source-unreadable' | 'branding-not-selected' | 'storybook-story' | 'declared-unpackaged';
|
|
8
8
|
/** Render order for the `Packaged:` summary's per-class breakdown. */
|
|
@@ -40,10 +40,9 @@ export interface AuditEntry {
|
|
|
40
40
|
status: 'updated' | 'stale' | 'missing' | 'skipped';
|
|
41
41
|
/**
|
|
42
42
|
* Why a `skipped` entry was skipped. Present only for `skipped`. The
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* file an operator must act on.
|
|
43
|
+
* reason lets the counts distinguish a structural non-input from an
|
|
44
|
+
* unregistered file an operator must act on, so the summary line never
|
|
45
|
+
* reports skips as misses.
|
|
47
46
|
*/
|
|
48
47
|
skipReason?: AuditSkipReason;
|
|
49
48
|
}
|
|
@@ -93,10 +93,11 @@ export interface BuildBaseline {
|
|
|
93
93
|
* mozconfig and complete testInputFingerprints outside the rebuilt scope
|
|
94
94
|
* remain anchored. Explicitly rebuilt paths may change. A failed anchor
|
|
95
95
|
* replaces coverage with a dropped-path notice, or refuses when the caller
|
|
96
|
-
* required --extend-coverage.
|
|
97
|
-
* coverage because they cannot prove test scripts and
|
|
96
|
+
* required --extend-coverage. Anchors that fingerprint only packageable
|
|
97
|
+
* inputs cannot retain coverage because they cannot prove test scripts and
|
|
98
|
+
* fixtures unchanged.
|
|
98
99
|
*
|
|
99
|
-
*
|
|
100
|
+
* Retention is decided per staged path: see
|
|
100
101
|
* {@link BuildBaseline.testCoverageRecords} and
|
|
101
102
|
* {@link BuildBaseline.fullCoverageAnchor}.
|
|
102
103
|
*/
|
|
@@ -151,6 +152,26 @@ export interface BuildBaseline {
|
|
|
151
152
|
* session's scoped build replaced can tell whose build that was.
|
|
152
153
|
*/
|
|
153
154
|
recorder?: BaselineRecorder;
|
|
155
|
+
/**
|
|
156
|
+
* Set when this build compiled a negative control into the binary
|
|
157
|
+
* (`test --negative-control --build` on compiled source). The source is
|
|
158
|
+
* restored byte-exact after the run, but the objdir binary keeps the
|
|
159
|
+
* mutation until a build recompiles it. FireForge runs that restore build
|
|
160
|
+
* itself, inside the same lock hold, and its baseline drops the stamp.
|
|
161
|
+
* A stamp still present (the run was killed, or the restore build
|
|
162
|
+
* failed) makes the next `fireforge test` refuse with `control-built`
|
|
163
|
+
* until `fireforge build` replaces the baseline.
|
|
164
|
+
*/
|
|
165
|
+
controlBuild?: ControlBuildStamp;
|
|
166
|
+
}
|
|
167
|
+
/** See {@link BuildBaseline.controlBuild}. */
|
|
168
|
+
export interface ControlBuildStamp {
|
|
169
|
+
/** The `--negative-control` diff file, as given. */
|
|
170
|
+
diffFile: string;
|
|
171
|
+
/** Engine-relative compiled targets the control edited. */
|
|
172
|
+
targets: string[];
|
|
173
|
+
/** ISO-8601 time the control was applied. */
|
|
174
|
+
appliedAt: string;
|
|
154
175
|
}
|
|
155
176
|
/** See {@link BuildBaseline.recorder}. */
|
|
156
177
|
export interface BaselineRecorder {
|
|
@@ -1,3 +1,32 @@
|
|
|
1
1
|
import { type BuildBaseline } from './build-baseline-types.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The first field of a persisted baseline that fails validation, and the
|
|
4
|
+
* offending value: what a refusal names so the operator can tell a scoped
|
|
5
|
+
* coverage claim from a corrupt marker.
|
|
6
|
+
*/
|
|
7
|
+
export interface BuildBaselineProblem {
|
|
8
|
+
/** Field path, e.g. `testPackagingCoverage[2]` or `testCoverageRecords["a/"]`. */
|
|
9
|
+
field: string;
|
|
10
|
+
/** The offending value, as found (may be any JSON value). */
|
|
11
|
+
value: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** True when the problem sits in a packaging-coverage field. */
|
|
14
|
+
export declare function isCoverageProblem(problem: BuildBaselineProblem): boolean;
|
|
15
|
+
/** Renders a problem for a refusal: the field and a bounded JSON rendering of its value. */
|
|
16
|
+
export declare function describeBuildBaselineProblem(problem: BuildBaselineProblem): string;
|
|
17
|
+
/**
|
|
18
|
+
* Validates persisted fields without converting malformed coverage into a
|
|
19
|
+
* full claim, and returns the first failing field with its value, or
|
|
20
|
+
* undefined when the baseline is valid.
|
|
21
|
+
*
|
|
22
|
+
* @param value - The parsed `last-build.json`
|
|
23
|
+
*/
|
|
24
|
+
export declare function findBuildBaselineProblem(value: unknown): BuildBaselineProblem | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Validates persisted fields without converting malformed coverage into a
|
|
27
|
+
* full claim. The boolean form of {@link findBuildBaselineProblem}.
|
|
28
|
+
*
|
|
29
|
+
* @internal Exported only so tests can reach it. Production reads go
|
|
30
|
+
* through `findBuildBaselineProblem`, which names the failing field.
|
|
31
|
+
*/
|
|
3
32
|
export declare function isBuildBaseline(value: unknown): value is BuildBaseline;
|
|
@@ -24,13 +24,14 @@ function isCoverageAnchor(value) {
|
|
|
24
24
|
isFingerprint(value['mozconfigHash']) &&
|
|
25
25
|
isFingerprintMap(value['fingerprints']));
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
/** A `controlBuild` stamp: the control's diff file, its targets and when it went in. */
|
|
28
|
+
function isControlBuildStamp(value) {
|
|
28
29
|
return (isObject(value) &&
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
typeof value['diffFile'] === 'string' &&
|
|
31
|
+
Array.isArray(value['targets']) &&
|
|
32
|
+
value['targets'].every(isBaselinePath) &&
|
|
33
|
+
typeof value['appliedAt'] === 'string' &&
|
|
34
|
+
Number.isFinite(Date.parse(value['appliedAt'])));
|
|
34
35
|
}
|
|
35
36
|
/** A `recorder` stamp: an integer pid, an invocation id and an optional session id. */
|
|
36
37
|
function isBaselineRecorder(value) {
|
|
@@ -39,43 +40,134 @@ function isBaselineRecorder(value) {
|
|
|
39
40
|
typeof value['invocationId'] === 'string' &&
|
|
40
41
|
(value['sessionId'] === undefined || typeof value['sessionId'] === 'string'));
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
43
|
+
/** Fields whose problem a scoped `test --build` can leave and a fresh one replaces. */
|
|
44
|
+
const COVERAGE_FIELDS = ['testPackagingCoverage', 'testCoverageRecords', 'fullCoverageAnchor'];
|
|
45
|
+
/** True when the problem sits in a packaging-coverage field. */
|
|
46
|
+
export function isCoverageProblem(problem) {
|
|
47
|
+
return COVERAGE_FIELDS.some((field) => problem.field === field ||
|
|
48
|
+
problem.field.startsWith(`${field}[`) ||
|
|
49
|
+
problem.field.startsWith(`${field}.`));
|
|
50
|
+
}
|
|
51
|
+
/** Renders a problem for a refusal: the field and a bounded JSON rendering of its value. */
|
|
52
|
+
export function describeBuildBaselineProblem(problem) {
|
|
53
|
+
let rendered;
|
|
54
|
+
try {
|
|
55
|
+
// JSON.stringify(undefined) returns undefined at runtime despite its type.
|
|
56
|
+
const json = JSON.stringify(problem.value);
|
|
57
|
+
rendered = json ?? 'undefined';
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
rendered = String(problem.value);
|
|
61
|
+
}
|
|
62
|
+
if (rendered.length > 120)
|
|
63
|
+
rendered = `${rendered.slice(0, 117)}...`;
|
|
64
|
+
return `${problem.field} = ${rendered}`;
|
|
65
|
+
}
|
|
66
|
+
function findFingerprintMapProblem(field, value) {
|
|
67
|
+
if (!isObject(value))
|
|
68
|
+
return { field, value };
|
|
69
|
+
for (const [path, fingerprint] of Object.entries(value)) {
|
|
70
|
+
if (!isBaselinePath(path))
|
|
71
|
+
return { field: `${field}[${JSON.stringify(path)}]`, value: path };
|
|
72
|
+
if (!isFingerprint(fingerprint)) {
|
|
73
|
+
return { field: `${field}[${JSON.stringify(path)}]`, value: fingerprint };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function findCoverageRecordProblem(value) {
|
|
79
|
+
const field = 'testCoverageRecords';
|
|
80
|
+
if (!isObject(value))
|
|
81
|
+
return { field, value };
|
|
82
|
+
for (const [path, record] of Object.entries(value)) {
|
|
83
|
+
const entry = `${field}[${JSON.stringify(path)}]`;
|
|
84
|
+
if (!isBaselinePath(path))
|
|
85
|
+
return { field: entry, value: path };
|
|
86
|
+
if (!isCoverageAnchor(record) || !isObject(record) || !Array.isArray(record['inputs'])) {
|
|
87
|
+
return { field: entry, value: record };
|
|
88
|
+
}
|
|
89
|
+
const inputs = record['inputs'];
|
|
90
|
+
const input = inputs.find((item) => !isBaselinePath(item));
|
|
91
|
+
if (input !== undefined)
|
|
92
|
+
return { field: `${entry}.inputs`, value: input };
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Validates persisted fields without converting malformed coverage into a
|
|
98
|
+
* full claim, and returns the first failing field with its value, or
|
|
99
|
+
* undefined when the baseline is valid.
|
|
100
|
+
*
|
|
101
|
+
* @param value - The parsed `last-build.json`
|
|
102
|
+
*/
|
|
103
|
+
export function findBuildBaselineProblem(value) {
|
|
44
104
|
if (!isObject(value))
|
|
45
|
-
return
|
|
46
|
-
if (typeof value['engineHeadSha'] !== 'string'
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
105
|
+
return { field: '(root)', value };
|
|
106
|
+
if (typeof value['engineHeadSha'] !== 'string') {
|
|
107
|
+
return { field: 'engineHeadSha', value: value['engineHeadSha'] };
|
|
108
|
+
}
|
|
109
|
+
if (typeof value['builtAt'] !== 'string' || !Number.isFinite(Date.parse(value['builtAt']))) {
|
|
110
|
+
return { field: 'builtAt', value: value['builtAt'] };
|
|
111
|
+
}
|
|
112
|
+
if (typeof value['binaryName'] !== 'string' || !/^[a-z][a-z0-9-]*$/.test(value['binaryName'])) {
|
|
113
|
+
return { field: 'binaryName', value: value['binaryName'] };
|
|
114
|
+
}
|
|
52
115
|
for (const field of [
|
|
53
116
|
'packageableFingerprints',
|
|
54
117
|
'testInputFingerprints',
|
|
55
118
|
'buildInputFingerprints',
|
|
56
119
|
]) {
|
|
57
|
-
if (value[field]
|
|
58
|
-
|
|
120
|
+
if (value[field] === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
const problem = findFingerprintMapProblem(field, value[field]);
|
|
123
|
+
if (problem !== undefined)
|
|
124
|
+
return problem;
|
|
59
125
|
}
|
|
60
126
|
const coverage = value['testPackagingCoverage'];
|
|
61
|
-
if (coverage !== undefined &&
|
|
62
|
-
coverage
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (value['
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
127
|
+
if (coverage !== undefined && coverage !== 'full') {
|
|
128
|
+
if (!Array.isArray(coverage))
|
|
129
|
+
return { field: 'testPackagingCoverage', value: coverage };
|
|
130
|
+
const index = coverage.findIndex((path) => !isBaselinePath(path));
|
|
131
|
+
if (index >= 0) {
|
|
132
|
+
return { field: `testPackagingCoverage[${index}]`, value: coverage[index] };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (value['mozconfigHash'] !== undefined && !isFingerprint(value['mozconfigHash'])) {
|
|
136
|
+
return { field: 'mozconfigHash', value: value['mozconfigHash'] };
|
|
137
|
+
}
|
|
138
|
+
if (value['recordedBy'] !== undefined && typeof value['recordedBy'] !== 'string') {
|
|
139
|
+
return { field: 'recordedBy', value: value['recordedBy'] };
|
|
140
|
+
}
|
|
141
|
+
if (value['recorder'] !== undefined && !isBaselineRecorder(value['recorder'])) {
|
|
142
|
+
return { field: 'recorder', value: value['recorder'] };
|
|
143
|
+
}
|
|
144
|
+
if (value['testCoverageRecords'] !== undefined) {
|
|
145
|
+
const problem = findCoverageRecordProblem(value['testCoverageRecords']);
|
|
146
|
+
if (problem !== undefined)
|
|
147
|
+
return problem;
|
|
148
|
+
}
|
|
149
|
+
if (value['fullCoverageAnchor'] !== undefined && !isCoverageAnchor(value['fullCoverageAnchor'])) {
|
|
150
|
+
return { field: 'fullCoverageAnchor', value: value['fullCoverageAnchor'] };
|
|
151
|
+
}
|
|
76
152
|
const components = value['staticComponentsBaseline'];
|
|
77
|
-
|
|
78
|
-
(isObject(components) &&
|
|
153
|
+
if (components !== undefined &&
|
|
154
|
+
!(isObject(components) &&
|
|
79
155
|
typeof components['engineHeadSha'] === 'string' &&
|
|
80
|
-
isFingerprintMap(components['fingerprints'])))
|
|
156
|
+
isFingerprintMap(components['fingerprints']))) {
|
|
157
|
+
return { field: 'staticComponentsBaseline', value: components };
|
|
158
|
+
}
|
|
159
|
+
if (value['controlBuild'] !== undefined && !isControlBuildStamp(value['controlBuild'])) {
|
|
160
|
+
return { field: 'controlBuild', value: value['controlBuild'] };
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Validates persisted fields without converting malformed coverage into a
|
|
166
|
+
* full claim. The boolean form of {@link findBuildBaselineProblem}.
|
|
167
|
+
*
|
|
168
|
+
* @internal Exported only so tests can reach it. Production reads go
|
|
169
|
+
* through `findBuildBaselineProblem`, which names the failing field.
|
|
170
|
+
*/
|
|
171
|
+
export function isBuildBaseline(value) {
|
|
172
|
+
return findBuildBaselineProblem(value) === undefined;
|
|
81
173
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* on successful build completion. A failed build does not update it, so a
|
|
16
16
|
* subsequent run still audits against the last known-good tree.
|
|
17
17
|
*/
|
|
18
|
-
import { type BuildBaseline, type CoverageAnchor, type CoverageRecord, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
18
|
+
import { type BuildBaseline, type ControlBuildStamp, type CoverageAnchor, type CoverageRecord, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
19
19
|
/**
|
|
20
20
|
* Which mach build produced the baseline being written. Decides whether
|
|
21
21
|
* the `jar.mn` half of {@link BuildBaseline.buildInputFingerprints} is
|
|
@@ -35,7 +35,7 @@ export declare function getBuildBaselinePath(projectRoot: string): string;
|
|
|
35
35
|
* Reads the last-build baseline if present. Returns undefined when no
|
|
36
36
|
* previous successful build has been recorded. Builds can replace invalid
|
|
37
37
|
* markers; dispatch preflights pass `refuse` so invalid coverage never
|
|
38
|
-
* becomes the
|
|
38
|
+
* becomes the implicit full claim of a baseline without coverage.
|
|
39
39
|
* @param projectRoot - Root directory of the project
|
|
40
40
|
*/
|
|
41
41
|
export declare function readBuildBaseline(projectRoot: string, invalid?: 'ignore' | 'refuse'): Promise<BuildBaseline | undefined>;
|
|
@@ -85,6 +85,12 @@ export interface WriteBuildBaselineOptions {
|
|
|
85
85
|
* escalation no full build has honoured.
|
|
86
86
|
*/
|
|
87
87
|
buildKind?: BaselineBuildKind | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Stamp for a build that compiled a negative control into the binary.
|
|
90
|
+
* Omitted → field left off, which is how every other build clears it.
|
|
91
|
+
* See {@link BuildBaseline.controlBuild}.
|
|
92
|
+
*/
|
|
93
|
+
controlBuild?: ControlBuildStamp | undefined;
|
|
88
94
|
}
|
|
89
95
|
/**
|
|
90
96
|
* Records a successful build by writing a fresh baseline marker. Captures
|