@hominis/fireforge 0.50.0 → 0.52.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 +67 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/check-report.d.ts +17 -4
- package/dist/src/commands/check-report.js +19 -6
- package/dist/src/commands/check-run.d.ts +5 -0
- package/dist/src/commands/check-run.js +9 -1
- package/dist/src/commands/check-steps.d.ts +8 -0
- package/dist/src/commands/check-steps.js +31 -6
- package/dist/src/commands/check.js +1 -1
- package/dist/src/commands/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- package/dist/src/commands/export-placement-gate.js +8 -0
- package/dist/src/commands/furnace/rename-registrations.js +7 -2
- package/dist/src/commands/furnace/validate.js +26 -1
- package/dist/src/commands/lint-per-patch.js +6 -2
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.js +7 -2
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -0
- package/dist/src/commands/patch/delete.js +11 -6
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +8 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.js +39 -29
- package/dist/src/commands/patch/move-files-create.js +11 -4
- package/dist/src/commands/patch/move-files-into.js +10 -4
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- package/dist/src/commands/patch/rename.js +15 -3
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.js +11 -6
- package/dist/src/commands/patch/split-plan.d.ts +10 -1
- package/dist/src/commands/patch/split-plan.js +16 -1
- package/dist/src/commands/patch/split.js +14 -5
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +40 -98
- package/dist/src/commands/re-export-adjacent.d.ts +5 -0
- package/dist/src/commands/re-export-adjacent.js +4 -3
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- package/dist/src/commands/re-export-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- package/dist/src/commands/re-export-options.js +29 -2
- package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +15 -1
- package/dist/src/commands/re-export-removed.d.ts +1 -1
- package/dist/src/commands/re-export-removed.js +17 -2
- package/dist/src/commands/re-export-scan.d.ts +2 -0
- package/dist/src/commands/re-export-scan.js +4 -1
- package/dist/src/commands/re-export-single.js +102 -42
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- package/dist/src/commands/re-export.js +35 -34
- package/dist/src/commands/status-output.js +5 -1
- package/dist/src/commands/test-build-furnace.d.ts +3 -2
- package/dist/src/commands/test-build-furnace.js +8 -2
- package/dist/src/commands/test-build-phase.d.ts +2 -0
- package/dist/src/commands/test-build-phase.js +34 -17
- package/dist/src/commands/test-checkjs.js +3 -2
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +220 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -0
- package/dist/src/commands/test-orchestrate.d.ts +14 -0
- package/dist/src/commands/test-orchestrate.js +25 -4
- package/dist/src/commands/test-perf-scope.d.ts +21 -0
- package/dist/src/commands/test-perf-scope.js +55 -0
- package/dist/src/commands/test-prelock.js +100 -4
- package/dist/src/commands/test-register.js +18 -3
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +73 -101
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +11 -2
- package/dist/src/commands/test-verdict.d.ts +2 -1
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +68 -75
- package/dist/src/commands/token-lint.js +48 -8
- package/dist/src/commands/verify.js +9 -2
- package/dist/src/core/build-baseline-types.d.ts +13 -0
- package/dist/src/core/build-baseline-validate.js +9 -0
- package/dist/src/core/build-baseline.js +7 -0
- package/dist/src/core/build-prepare.d.ts +6 -0
- package/dist/src/core/build-prepare.js +29 -3
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-check.js +83 -7
- package/dist/src/core/config-validate-test-toolchains.js +14 -0
- package/dist/src/core/config-validate-token-scales.d.ts +3 -0
- package/dist/src/core/config-validate-token-scales.js +47 -5
- package/dist/src/core/config-validate.js +6 -0
- package/dist/src/core/coverage-extend.js +2 -2
- package/dist/src/core/coverage-records.d.ts +11 -0
- package/dist/src/core/coverage-records.js +34 -0
- package/dist/src/core/engine-session-lock.d.ts +6 -0
- package/dist/src/core/engine-session-lock.js +10 -2
- package/dist/src/core/file-lock-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +9 -0
- package/dist/src/core/file-lock.js +58 -2
- package/dist/src/core/furnace-apply.js +10 -10
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +11 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +59 -8
- package/dist/src/core/furnace-config-validate.js +7 -1
- package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +5 -3
- package/dist/src/core/furnace-validate-accessibility.js +75 -30
- package/dist/src/core/furnace-validate-exceptions.d.ts +13 -4
- package/dist/src/core/furnace-validate-exceptions.js +59 -4
- package/dist/src/core/furnace-validate-icon-button.js +30 -1
- package/dist/src/core/history-log-file.js +8 -1
- package/dist/src/core/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +24 -9
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +36 -0
- package/dist/src/core/negative-control-compiled.js +87 -0
- package/dist/src/core/negative-control-diff.d.ts +14 -2
- package/dist/src/core/negative-control-diff.js +49 -6
- package/dist/src/core/negative-control.d.ts +12 -0
- package/dist/src/core/negative-control.js +48 -5
- package/dist/src/core/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +4 -0
- package/dist/src/core/patch-export-update.js +12 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +21 -14
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +6 -4
- package/dist/src/core/patch-lint-checkjs-any-import.js +9 -7
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +13 -3
- package/dist/src/core/patch-lint-cross.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +5 -0
- package/dist/src/core/patch-lint-css.js +14 -10
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +5 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +1 -1
- package/dist/src/core/patch-lint-ignore.d.ts +56 -12
- package/dist/src/core/patch-lint-ignore.js +173 -43
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -0
- package/dist/src/core/patch-lint-size.js +9 -1
- package/dist/src/core/patch-lint-token-scale.d.ts +26 -8
- package/dist/src/core/patch-lint-token-scale.js +206 -29
- package/dist/src/core/patch-lint.js +2 -2
- package/dist/src/core/patch-manifest-io.js +8 -1
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +27 -1
- package/dist/src/core/patch-order-legal.d.ts +2 -1
- package/dist/src/core/patch-order-legal.js +7 -2
- package/dist/src/core/patch-policy.d.ts +6 -2
- package/dist/src/core/patch-policy.js +14 -7
- package/dist/src/core/patch-retired.d.ts +26 -4
- package/dist/src/core/patch-retired.js +40 -2
- package/dist/src/core/run-log.d.ts +6 -0
- package/dist/src/core/run-log.js +17 -1
- package/dist/src/core/test-harness-crash.d.ts +2 -1
- package/dist/src/core/test-harness-crash.js +5 -8
- package/dist/src/core/test-harness-signature.d.ts +16 -0
- package/dist/src/core/test-harness-signature.js +19 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +47 -7
- package/dist/src/core/test-run-completeness.js +70 -23
- package/dist/src/core/test-stale-check.d.ts +10 -1
- package/dist/src/core/test-stale-check.js +15 -1
- package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +5 -2
- package/dist/src/core/token-lint.js +8 -3
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +2 -0
- package/dist/src/core/typecheck-shim.d.ts +6 -1
- package/dist/src/core/typecheck-shim.js +139 -2
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +9 -3
- package/dist/src/index.js +4 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +68 -7
- package/dist/src/types/commands/patches.d.ts +43 -0
- package/dist/src/types/config.d.ts +74 -2
- package/dist/src/types/furnace.d.ts +29 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +3 -2
- package/dist/src/utils/options.js +9 -4
- package/docs/configuration.md +110 -9
- package/docs/furnace.md +61 -1
- package/docs/lifecycle-invariants.md +13 -11
- package/docs/machine-output.md +68 -18
- package/docs/patch-workflow.md +174 -13
- package/docs/testing.md +89 -5
- package/package.json +3 -3
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { type DisplaySleepState } from '../core/display-state.js';
|
|
13
13
|
import { type MachCommandResult } from '../core/mach.js';
|
|
14
14
|
import { type HarnessRunVerdict } from '../core/test-harness-crash.js';
|
|
15
|
+
import { type RawTestResult } from '../core/test-raw-log.js';
|
|
15
16
|
/** Default bounded retry budget for recognized harness crashes. */
|
|
16
17
|
export declare const DEFAULT_HARNESS_RETRIES = 2;
|
|
17
18
|
/**
|
|
@@ -49,6 +50,15 @@ export interface TestRunContext {
|
|
|
49
50
|
fullOutput?: boolean;
|
|
50
51
|
/** `--pgid-file`: where each dispatch publishes its harness process group id. */
|
|
51
52
|
pgidFile?: string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Where each dispatch writes its mozlog raw log (`--results`,
|
|
55
|
+
* `--shard-size`). Unset: no `--log-raw` is added and nothing is parsed.
|
|
56
|
+
*/
|
|
57
|
+
rawLogDir?: string | undefined;
|
|
58
|
+
/** `--repeat` run this dispatch belongs to (1-based), for `--results`. */
|
|
59
|
+
repetition?: number | undefined;
|
|
60
|
+
/** Prefix for every echoed and logged output line (`--jobs` instances). */
|
|
61
|
+
outputPrefix?: string | undefined;
|
|
52
62
|
}
|
|
53
63
|
/** Outcome of one (possibly retried) harness invocation. */
|
|
54
64
|
export interface TestRunOutcome {
|
|
@@ -62,64 +72,12 @@ export interface TestRunOutcome {
|
|
|
62
72
|
* when the shape did not apply and no probe ran.
|
|
63
73
|
*/
|
|
64
74
|
displayState?: DisplaySleepState | undefined;
|
|
75
|
+
/** Per-file results from the raw log, when the run wrote one. */
|
|
76
|
+
fileResults?: RawTestResult[] | undefined;
|
|
65
77
|
}
|
|
66
78
|
/**
|
|
67
79
|
* Runs one mach test invocation for `paths`, retrying recognized harness
|
|
68
80
|
* crashes up to the configured budget. Every attempt goes through the
|
|
69
81
|
* stale-xpcshell-symlink repair path the single-run flow already used.
|
|
70
82
|
*/
|
|
71
|
-
export declare function runTestsWithRetries(ctx: TestRunContext, paths: string[]): Promise<TestRunOutcome>;
|
|
72
|
-
/**
|
|
73
|
-
* One sequential harness invocation of a sharded run: a requested path
|
|
74
|
-
* argument and the mach paths dispatched for it. A file argument is a group
|
|
75
|
-
* of one. A directory argument groups its enumerated test files so the whole
|
|
76
|
-
* directory still runs in ONE browser instance, keeping cross-file state
|
|
77
|
-
* behaviour within a directory run.
|
|
78
|
-
*/
|
|
79
|
-
export interface ShardGroup {
|
|
80
|
-
/** The path argument as the operator passed it (display label). */
|
|
81
|
-
label: string;
|
|
82
|
-
/** The mach dispatch paths for this argument. */
|
|
83
|
-
paths: string[];
|
|
84
|
-
}
|
|
85
|
-
/** Per-shard summary entry. */
|
|
86
|
-
export interface ShardOutcome {
|
|
87
|
-
label: string;
|
|
88
|
-
outcome: TestRunOutcome;
|
|
89
|
-
}
|
|
90
|
-
/** Aggregate result of a sharded run, verdict emission deferred to the caller. */
|
|
91
|
-
export interface ShardedRunSummary {
|
|
92
|
-
shards: ShardOutcome[];
|
|
93
|
-
passed: number;
|
|
94
|
-
total: number;
|
|
95
|
-
/** Aggregate classifier verdict. See {@link deriveAggregateShardVerdict}. */
|
|
96
|
-
aggregate: HarnessRunVerdict;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Derives the aggregate classification for a sharded run: all-green is
|
|
100
|
-
* `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
|
|
101
|
-
* shard makes the whole run `harness-crash`, then `no-tests`, then plain
|
|
102
|
-
* `test-failures`, so the aggregate `reason=` keeps the classifier-driven
|
|
103
|
-
* contract instead of collapsing every failure to `test-failures`.
|
|
104
|
-
*/
|
|
105
|
-
export declare function deriveAggregateShardVerdict(shards: ShardOutcome[]): HarnessRunVerdict;
|
|
106
|
-
/**
|
|
107
|
-
* Runs each requested path argument as its own sequential harness
|
|
108
|
-
* invocation (a directory argument's enumerated files stay together in
|
|
109
|
-
* one invocation, see {@link ShardGroup}) and prints an aggregate
|
|
110
|
-
* report. Per-shard failures are diagnosed via `diagnoseShardFailure`
|
|
111
|
-
* (which receives the throwing diagnosis chain from the command layer)
|
|
112
|
-
* but downgraded to warnings so every shard runs. The aggregate verdict
|
|
113
|
-
* line and the aggregate error are not produced here: the caller runs
|
|
114
|
-
* the engine-generation integrity check first and then calls
|
|
115
|
-
* {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
|
|
116
|
-
* `engine/` mutation can never print `PASS` before the check fails.
|
|
117
|
-
*/
|
|
118
|
-
export declare function runShardedTests(ctx: TestRunContext, groups: ShardGroup[], diagnoseShardFailure: (outcome: TestRunOutcome, label: string) => string | undefined): Promise<ShardedRunSummary>;
|
|
119
|
-
/**
|
|
120
|
-
* Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
|
|
121
|
-
* throws the aggregate error when any shard did not pass, the same
|
|
122
|
-
* emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
|
|
123
|
-
* the engine-generation check succeeded.
|
|
124
|
-
*/
|
|
125
|
-
export declare function finalizeShardedOutcome(summary: ShardedRunSummary): void;
|
|
83
|
+
export declare function runTestsWithRetries(ctx: TestRunContext, paths: string[], label?: string): Promise<TestRunOutcome>;
|
|
@@ -14,19 +14,21 @@ import { join } from 'node:path';
|
|
|
14
14
|
import { probeDisplaySleepState } from '../core/display-state.js';
|
|
15
15
|
import { runMachTestSuite } from '../core/mach.js';
|
|
16
16
|
import { parentExited } from '../core/parent-exit-watchdog.js';
|
|
17
|
-
import { getActiveRunLogPath } from '../core/run-log.js';
|
|
17
|
+
import { activeRunLogLine, getActiveRunLogPath } from '../core/run-log.js';
|
|
18
18
|
import { changedPrefNoiseVerdictNote } from '../core/test-changed-prefs.js';
|
|
19
|
-
import {
|
|
19
|
+
import { classifyHarnessRun, headedDisplayAsleepVerdictNote, } from '../core/test-harness-crash.js';
|
|
20
20
|
import { reportTestHost, sampleTestHost, stampPerfHost } from '../core/test-host-state.js';
|
|
21
|
-
import {
|
|
21
|
+
import { forwardedRawLogPath, hasForwardedRawLog, readRawTestLog, } from '../core/test-raw-log.js';
|
|
22
|
+
import { createStartMarkerTracker, vetoIncompletePass, } from '../core/test-run-completeness.js';
|
|
22
23
|
import { retryAfterXpcshellSymlinkRepair } from '../core/test-xpcshell-retry.js';
|
|
23
24
|
import { withXpcshellProfileDir } from '../core/xpcshell-profile-dir.js';
|
|
24
25
|
import { TestFailureError } from '../errors/build.js';
|
|
25
|
-
import {
|
|
26
|
+
import { warn } from '../utils/logger.js';
|
|
26
27
|
import { getPlatform } from '../utils/platform.js';
|
|
27
28
|
import { maybeInjectAppdirArg } from './test-appdir.js';
|
|
28
29
|
import { createHarnessTeardown } from './test-harness-teardown.js';
|
|
29
|
-
import {
|
|
30
|
+
import { recordTestResults } from './test-results.js';
|
|
31
|
+
import { setVerdictRunAttribute } from './test-verdict.js';
|
|
30
32
|
/** Default bounded retry budget for recognized harness crashes. */
|
|
31
33
|
export const DEFAULT_HARNESS_RETRIES = 2;
|
|
32
34
|
/** Resolves the mach test command a suite dispatches to. */
|
|
@@ -42,21 +44,21 @@ function machKindForSuite(suite) {
|
|
|
42
44
|
* crashes up to the configured budget. Every attempt goes through the
|
|
43
45
|
* stale-xpcshell-symlink repair path the single-run flow already used.
|
|
44
46
|
*/
|
|
45
|
-
export async function runTestsWithRetries(ctx, paths) {
|
|
47
|
+
export async function runTestsWithRetries(ctx, paths, label = paths.join(' ')) {
|
|
46
48
|
// Pure mochitest dispatches keep their env byte-identical, since the profile
|
|
47
49
|
// variable is xpcshell-harness-specific. Generic `mach
|
|
48
50
|
// test` runs may include xpcshell paths, so they get the isolation too.
|
|
49
51
|
if (ctx.suite === 'mochitest') {
|
|
50
|
-
return runTestsWithRetriesInner(ctx, paths, ctx.env);
|
|
52
|
+
return runTestsWithRetriesInner(ctx, paths, ctx.env, label);
|
|
51
53
|
}
|
|
52
54
|
// Fresh profile dir per harness invocation: shards isolate from each
|
|
53
55
|
// other and from concurrent `fireforge test` processes. The crash /
|
|
54
56
|
// symlink-repair retries inside one invocation reuse the same directory
|
|
55
57
|
// on purpose (they are the same logical run and the harness
|
|
56
58
|
// re-initialises the profile contents itself).
|
|
57
|
-
return withXpcshellProfileDir(ctx.env, (env) => runTestsWithRetriesInner(ctx, paths, env));
|
|
59
|
+
return withXpcshellProfileDir(ctx.env, (env) => runTestsWithRetriesInner(ctx, paths, env, label));
|
|
58
60
|
}
|
|
59
|
-
async function runTestsWithRetriesInner(ctx, paths, env) {
|
|
61
|
+
async function runTestsWithRetriesInner(ctx, paths, env, label) {
|
|
60
62
|
const extraArgs = [...ctx.baseExtraArgs];
|
|
61
63
|
const appdirInjectionAttempted = await maybeInjectAppdirArg(ctx.engineDir, paths, ctx.objDir, extraArgs);
|
|
62
64
|
const kind = machKindForSuite(ctx.suite);
|
|
@@ -64,9 +66,15 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
|
|
|
64
66
|
let attempts = 0;
|
|
65
67
|
let result;
|
|
66
68
|
let verdict;
|
|
69
|
+
let rawLog;
|
|
70
|
+
let logLine;
|
|
67
71
|
for (;;) {
|
|
68
72
|
attempts += 1;
|
|
69
|
-
|
|
73
|
+
let tracker = createStartMarkerTracker();
|
|
74
|
+
const raw = rawLogDispatchArgs(ctx, extraArgs);
|
|
75
|
+
logLine = activeRunLogLine();
|
|
76
|
+
const dispatched = await runTrackedMachTestSuite(ctx, kind, paths, raw.args, env, tracker);
|
|
77
|
+
result = dispatched;
|
|
70
78
|
// The parent that was reading this run is gone and the watchdog has
|
|
71
79
|
// already killed the harness. Classifying the corpse would call it a
|
|
72
80
|
// harness crash and retry it under nobody's supervision.
|
|
@@ -74,21 +82,32 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
|
|
|
74
82
|
throw new TestFailureError('The parent process exited while the harness was running; the harness tree was ' +
|
|
75
83
|
'terminated and the run ended without a result.', `mach ${kind}`, undefined, getActiveRunLogPath());
|
|
76
84
|
}
|
|
85
|
+
const retryTracker = createStartMarkerTracker();
|
|
77
86
|
result = await retryAfterXpcshellSymlinkRepair({
|
|
78
87
|
engineDir: ctx.engineDir,
|
|
79
88
|
objDir: ctx.objDir,
|
|
80
89
|
result,
|
|
81
90
|
classification: ctx.classification,
|
|
82
91
|
normalizedPaths: paths,
|
|
83
|
-
extraArgs,
|
|
92
|
+
extraArgs: raw.args,
|
|
84
93
|
env,
|
|
85
94
|
kind,
|
|
86
95
|
fullOutput: ctx.fullOutput,
|
|
96
|
+
onOutputChunk: (stream, chunk) => {
|
|
97
|
+
retryTracker.onChunk(stream, chunk);
|
|
98
|
+
},
|
|
87
99
|
});
|
|
100
|
+
if (result !== dispatched)
|
|
101
|
+
tracker = retryTracker;
|
|
102
|
+
rawLog = raw.path === undefined ? undefined : await readRawTestLog(raw.path);
|
|
88
103
|
const combined = `${result.stdout}\n${result.stderr}`;
|
|
89
104
|
// An exit-0 run is still not a pass when it stopped partway or
|
|
90
|
-
// reported unexpected results (see test-run-completeness.ts).
|
|
91
|
-
|
|
105
|
+
// reported unexpected results (see test-run-completeness.ts). The
|
|
106
|
+
// start count reads the structured raw log when the run wrote one, and
|
|
107
|
+
// the streamed markers otherwise, never the 2 MB capture tail.
|
|
108
|
+
verdict = vetoIncompletePass(classifyHarnessRun(result.exitCode, combined, paths), combined, paths, rawLog !== undefined && rawLog.markers.started.length > 0
|
|
109
|
+
? { markers: rawLog.markers, source: 'raw' }
|
|
110
|
+
: { markers: tracker.markers(), source: 'stream' });
|
|
92
111
|
if (verdict.kind !== 'harness-crash' || attempts >= maxAttempts)
|
|
93
112
|
break;
|
|
94
113
|
warn(`Harness crash detected (${verdict.signature?.reason ?? 'unknown shape'}): ` +
|
|
@@ -113,14 +132,45 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
|
|
|
113
132
|
if (note !== undefined)
|
|
114
133
|
verdict = { ...verdict, note };
|
|
115
134
|
}
|
|
116
|
-
|
|
135
|
+
if (rawLog !== undefined) {
|
|
136
|
+
recordTestResults(rawLog.results, rawLog.unfinished, {
|
|
137
|
+
run: ctx.repetition ?? 1,
|
|
138
|
+
shard: label,
|
|
139
|
+
logLine,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
result,
|
|
144
|
+
verdict,
|
|
145
|
+
attempts,
|
|
146
|
+
appdirInjectionAttempted,
|
|
147
|
+
displayState,
|
|
148
|
+
...(rawLog === undefined ? {} : { fileResults: rawLog.results }),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Sequence for raw-log file names, unique within the process. */
|
|
152
|
+
let rawLogSequence = 0;
|
|
153
|
+
/**
|
|
154
|
+
* The dispatch arguments with a `--log-raw` destination added, when this run
|
|
155
|
+
* collects per-file results. An operator's own forwarded `--log-raw <file>`
|
|
156
|
+
* is read instead of adding a second one.
|
|
157
|
+
*/
|
|
158
|
+
function rawLogDispatchArgs(ctx, extraArgs) {
|
|
159
|
+
if (ctx.rawLogDir === undefined)
|
|
160
|
+
return { args: [...extraArgs], path: undefined };
|
|
161
|
+
if (hasForwardedRawLog(extraArgs)) {
|
|
162
|
+
return { args: [...extraArgs], path: forwardedRawLogPath(extraArgs) };
|
|
163
|
+
}
|
|
164
|
+
rawLogSequence += 1;
|
|
165
|
+
const path = join(ctx.rawLogDir, `dispatch-${rawLogSequence}.raw.jsonl`);
|
|
166
|
+
return { args: [...extraArgs, `--log-raw=${path}`], path };
|
|
117
167
|
}
|
|
118
168
|
/**
|
|
119
169
|
* One mach dispatch with its kill path attached: helper-pid tracking and
|
|
120
170
|
* post-close reaping, the process-group announcement, and the parent-exit
|
|
121
171
|
* watchdog for exactly as long as mach runs.
|
|
122
172
|
*/
|
|
123
|
-
async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env) {
|
|
173
|
+
async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env, tracker) {
|
|
124
174
|
const teardown = createHarnessTeardown({
|
|
125
175
|
objDir: ctx.objDir === undefined ? undefined : join(ctx.engineDir, ctx.objDir),
|
|
126
176
|
pgidFile: ctx.pgidFile,
|
|
@@ -142,7 +192,14 @@ async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env) {
|
|
|
142
192
|
args: extraArgs,
|
|
143
193
|
env,
|
|
144
194
|
fullOutput: ctx.fullOutput,
|
|
145
|
-
|
|
195
|
+
...(ctx.outputPrefix === undefined ? {} : { outputPrefix: ctx.outputPrefix }),
|
|
196
|
+
teardown: {
|
|
197
|
+
...teardown.hooks,
|
|
198
|
+
onOutputChunk: (stream, chunk) => {
|
|
199
|
+
tracker.onChunk(stream, chunk);
|
|
200
|
+
teardown.hooks.onOutputChunk?.(stream, chunk);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
146
203
|
});
|
|
147
204
|
}
|
|
148
205
|
finally {
|
|
@@ -173,88 +230,3 @@ async function probeDisplayStateForStall(verdict, headless) {
|
|
|
173
230
|
return undefined;
|
|
174
231
|
return probeDisplaySleepState(getPlatform());
|
|
175
232
|
}
|
|
176
|
-
const SHARD_STATUS_LABEL = {
|
|
177
|
-
'tests-ran-ok': 'PASS',
|
|
178
|
-
'test-failures': 'FAIL',
|
|
179
|
-
'harness-crash': 'CRASH',
|
|
180
|
-
'no-tests': 'NO-TESTS',
|
|
181
|
-
'harness-arguments': 'ARGUMENTS',
|
|
182
|
-
};
|
|
183
|
-
/**
|
|
184
|
-
* Derives the aggregate classification for a sharded run: all-green is
|
|
185
|
-
* `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
|
|
186
|
-
* shard makes the whole run `harness-crash`, then `no-tests`, then plain
|
|
187
|
-
* `test-failures`, so the aggregate `reason=` keeps the classifier-driven
|
|
188
|
-
* contract instead of collapsing every failure to `test-failures`.
|
|
189
|
-
*/
|
|
190
|
-
export function deriveAggregateShardVerdict(shards) {
|
|
191
|
-
const kinds = new Set(shards.map(({ outcome }) => outcome.verdict.kind));
|
|
192
|
-
if (kinds.has('harness-arguments'))
|
|
193
|
-
return { kind: 'harness-arguments' };
|
|
194
|
-
if (kinds.has('harness-crash'))
|
|
195
|
-
return { kind: 'harness-crash' };
|
|
196
|
-
if (kinds.has('no-tests'))
|
|
197
|
-
return { kind: 'no-tests' };
|
|
198
|
-
if (kinds.has('test-failures'))
|
|
199
|
-
return { kind: 'test-failures' };
|
|
200
|
-
return { kind: 'tests-ran-ok' };
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Runs each requested path argument as its own sequential harness
|
|
204
|
-
* invocation (a directory argument's enumerated files stay together in
|
|
205
|
-
* one invocation, see {@link ShardGroup}) and prints an aggregate
|
|
206
|
-
* report. Per-shard failures are diagnosed via `diagnoseShardFailure`
|
|
207
|
-
* (which receives the throwing diagnosis chain from the command layer)
|
|
208
|
-
* but downgraded to warnings so every shard runs. The aggregate verdict
|
|
209
|
-
* line and the aggregate error are not produced here: the caller runs
|
|
210
|
-
* the engine-generation integrity check first and then calls
|
|
211
|
-
* {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
|
|
212
|
-
* `engine/` mutation can never print `PASS` before the check fails.
|
|
213
|
-
*/
|
|
214
|
-
export async function runShardedTests(ctx, groups, diagnoseShardFailure) {
|
|
215
|
-
const shards = [];
|
|
216
|
-
for (const [index, group] of groups.entries()) {
|
|
217
|
-
info(`— Shard ${index + 1}/${groups.length}: ${group.label}`);
|
|
218
|
-
const outcome = await runTestsWithRetries(ctx, group.paths);
|
|
219
|
-
shards.push({ label: group.label, outcome });
|
|
220
|
-
if (outcome.verdict.kind === 'harness-crash' && outcome.verdict.signature) {
|
|
221
|
-
const base = buildHarnessCrashMessage(outcome.verdict.signature, outcome.attempts);
|
|
222
|
-
const hint = headedNoOutputTimeoutHint(outcome.verdict.signature, {
|
|
223
|
-
headless: ctx.headless,
|
|
224
|
-
platform: getPlatform(),
|
|
225
|
-
...(outcome.displayState !== undefined ? { displayState: outcome.displayState } : {}),
|
|
226
|
-
});
|
|
227
|
-
warn(hint ? `${base}\n\n${hint}` : base);
|
|
228
|
-
}
|
|
229
|
-
else if (outcome.verdict.kind !== 'tests-ran-ok') {
|
|
230
|
-
const diagnosis = diagnoseShardFailure(outcome, group.label);
|
|
231
|
-
if (diagnosis)
|
|
232
|
-
warn(diagnosis);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const lines = shards.map(({ label, outcome }) => `${SHARD_STATUS_LABEL[outcome.verdict.kind].padEnd(8)} ${label}` +
|
|
236
|
-
(outcome.attempts > 1 ? ` (${outcome.attempts} attempts)` : ''));
|
|
237
|
-
const failing = shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
|
|
238
|
-
note(`${lines.join('\n')}\n\n${shards.length - failing.length}/${shards.length} shard(s) passed`, 'Sharded Test Summary');
|
|
239
|
-
return {
|
|
240
|
-
shards,
|
|
241
|
-
passed: shards.length - failing.length,
|
|
242
|
-
total: shards.length,
|
|
243
|
-
aggregate: deriveAggregateShardVerdict(shards),
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
|
|
248
|
-
* throws the aggregate error when any shard did not pass, the same
|
|
249
|
-
* emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
|
|
250
|
-
* the engine-generation check succeeded.
|
|
251
|
-
*/
|
|
252
|
-
export function finalizeShardedOutcome(summary) {
|
|
253
|
-
emitHarnessVerdict(summary.aggregate, { passed: summary.passed, total: summary.total });
|
|
254
|
-
if (summary.passed < summary.total) {
|
|
255
|
-
const failing = summary.shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
|
|
256
|
-
throw new TestFailureError(`${failing.length} of ${summary.total} sharded test run(s) did not pass: ` +
|
|
257
|
-
`${failing.map(({ label }) => label).join(', ')}. ` +
|
|
258
|
-
'See the per-shard diagnosis above. Use --no-shard to reproduce the combined single-invocation behaviour.', 'mach test', undefined, getActiveRunLogPath());
|
|
259
|
-
}
|
|
260
|
-
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sharded `fireforge test` runs: one harness invocation per shard, run in
|
|
3
|
+
* order on one harness instance, or on a pool of `--jobs <n>` instances.
|
|
4
|
+
*
|
|
5
|
+
* Each finished shard prints a `FIREFORGE-SHARD:` line, and the aggregate
|
|
6
|
+
* verdict names the shards that did not pass as `failed-shards=`. Before,
|
|
7
|
+
* the failing shards were named only in the prose of the aggregate error
|
|
8
|
+
* and the human summary box, so a wrapper that reruns failed shards had to
|
|
9
|
+
* parse a sentence that a reworded message would silently break.
|
|
10
|
+
*
|
|
11
|
+
* Under `--shard-size <n>` a shard is a chunk of n files, and the run is
|
|
12
|
+
* judged per file from each dispatch's raw log: `shards=` then counts files
|
|
13
|
+
* and `failed-shards=` names the files that failed.
|
|
14
|
+
*/
|
|
15
|
+
import { type HarnessRunVerdict } from '../core/test-harness-crash.js';
|
|
16
|
+
import { type TestRunContext, type TestRunOutcome } from './test-run.js';
|
|
17
|
+
/**
|
|
18
|
+
* One harness invocation of a sharded run: a requested path argument and
|
|
19
|
+
* the mach paths dispatched for it. A file argument is a group of one. A
|
|
20
|
+
* directory argument groups its enumerated test files so the whole
|
|
21
|
+
* directory still runs in ONE browser instance, keeping cross-file state
|
|
22
|
+
* behaviour within a directory run. A `--shard-size` chunk is a group of
|
|
23
|
+
* up to n files.
|
|
24
|
+
*/
|
|
25
|
+
export interface ShardGroup {
|
|
26
|
+
/** The path argument as the operator passed it (display label). */
|
|
27
|
+
label: string;
|
|
28
|
+
/** The mach dispatch paths for this argument. */
|
|
29
|
+
paths: string[];
|
|
30
|
+
}
|
|
31
|
+
/** Per-shard summary entry. */
|
|
32
|
+
export interface ShardOutcome {
|
|
33
|
+
label: string;
|
|
34
|
+
outcome: TestRunOutcome;
|
|
35
|
+
/** The shard's dispatch paths (per-file judging). */
|
|
36
|
+
paths?: string[];
|
|
37
|
+
}
|
|
38
|
+
/** Aggregate result of a sharded run, verdict emission deferred to the caller. */
|
|
39
|
+
export interface ShardedRunSummary {
|
|
40
|
+
shards: ShardOutcome[];
|
|
41
|
+
passed: number;
|
|
42
|
+
total: number;
|
|
43
|
+
/** What did not pass: shard labels, or file paths when judged per file. */
|
|
44
|
+
failed: string[];
|
|
45
|
+
/** Aggregate classifier verdict. See {@link deriveAggregateShardVerdict}. */
|
|
46
|
+
aggregate: HarnessRunVerdict;
|
|
47
|
+
}
|
|
48
|
+
/** How {@link runShardedTests} schedules and judges its shards. */
|
|
49
|
+
export interface ShardRunOptions {
|
|
50
|
+
/** Harness instances to run shards on at once (`--jobs`). Default 1. */
|
|
51
|
+
jobs?: number;
|
|
52
|
+
/**
|
|
53
|
+
* The context for instance `index` (0-based). Instance 0 must be the
|
|
54
|
+
* base context; others carry their own ports and output prefix.
|
|
55
|
+
*/
|
|
56
|
+
contextFor?: (index: number) => Promise<TestRunContext> | TestRunContext;
|
|
57
|
+
/** Judge per file from the raw log (`--shard-size`). */
|
|
58
|
+
perFile?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Derives the aggregate classification for a sharded run: all-green is
|
|
62
|
+
* `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
|
|
63
|
+
* shard makes the whole run `harness-crash`, then `no-tests`, then plain
|
|
64
|
+
* `test-failures`, so the aggregate `reason=` keeps the classifier-driven
|
|
65
|
+
* contract instead of collapsing every failure to `test-failures`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function deriveAggregateShardVerdict(shards: ShardOutcome[]): HarnessRunVerdict;
|
|
68
|
+
/**
|
|
69
|
+
* The `FIREFORGE-SHARD:` line for one finished shard: the verdict it would
|
|
70
|
+
* have printed alone, plus `shard=<i>/<n>` and `path=<label>`. Never a
|
|
71
|
+
* `FIREFORGE-VERDICT:` line, so the run keeps exactly one.
|
|
72
|
+
*/
|
|
73
|
+
export declare function formatShardLine(shard: ShardOutcome, index: number, total: number, failedFiles?: readonly string[]): string;
|
|
74
|
+
/**
|
|
75
|
+
* The files of one shard that did not pass, judged from its raw log. A file
|
|
76
|
+
* with no `test_end` failed (it never started, or never finished). Returns
|
|
77
|
+
* undefined when the shard wrote no raw log, so the caller judges the
|
|
78
|
+
* shard as a whole.
|
|
79
|
+
*/
|
|
80
|
+
export declare function failedFilesOf(shard: ShardOutcome): string[] | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Runs each shard as its own harness invocation and prints an aggregate
|
|
83
|
+
* report. Per-shard failures are diagnosed via `diagnoseShardFailure`
|
|
84
|
+
* (which receives the throwing diagnosis chain from the command layer)
|
|
85
|
+
* but downgraded to warnings so every shard runs. The aggregate verdict
|
|
86
|
+
* line and the aggregate error are not produced here: the caller runs
|
|
87
|
+
* the engine-generation integrity check first and then calls
|
|
88
|
+
* {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
|
|
89
|
+
* `engine/` mutation can never print `PASS` before the check fails.
|
|
90
|
+
*/
|
|
91
|
+
export declare function runShardedTests(ctx: TestRunContext, groups: ShardGroup[], diagnoseShardFailure: (outcome: TestRunOutcome, label: string) => string | undefined, options?: ShardRunOptions): Promise<ShardedRunSummary>;
|
|
92
|
+
/**
|
|
93
|
+
* Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
|
|
94
|
+
* throws the aggregate error when any shard did not pass, the same
|
|
95
|
+
* emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
|
|
96
|
+
* the engine-generation check succeeded.
|
|
97
|
+
*/
|
|
98
|
+
export declare function finalizeShardedOutcome(summary: ShardedRunSummary): void;
|
|
99
|
+
/**
|
|
100
|
+
* Splits the dispatch paths of every group into consecutive chunks of at
|
|
101
|
+
* most `size` files (`--shard-size`). Order is kept, and a chunk never
|
|
102
|
+
* mixes groups' order: it is the flattened request, cut every `size`
|
|
103
|
+
* files.
|
|
104
|
+
*/
|
|
105
|
+
export declare function chunkShardGroups(groups: readonly ShardGroup[], size: number): ShardGroup[];
|
|
106
|
+
/**
|
|
107
|
+
* Splits one combined request into `parts` contiguous chunks of near-equal
|
|
108
|
+
* size, in order (`--jobs` with `--no-shard`).
|
|
109
|
+
*/
|
|
110
|
+
export declare function splitContiguous(paths: readonly string[], parts: number): ShardGroup[];
|