@hominis/fireforge 0.50.0 → 0.51.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 +47 -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/test-build-furnace.d.ts +3 -2
- package/dist/src/commands/test-build-furnace.js +8 -2
- package/dist/src/commands/test-build-phase.js +24 -7
- 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 +211 -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 +9 -0
- package/dist/src/commands/test-orchestrate.js +22 -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 +4 -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 +65 -75
- package/dist/src/commands/token-lint.js +48 -8
- package/dist/src/commands/verify.js +9 -2
- 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 +10 -3
- package/dist/src/core/furnace-config-validate-exceptions.js +39 -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 +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +61 -27
- package/dist/src/core/furnace-validate-exceptions.d.ts +12 -4
- package/dist/src/core/furnace-validate-exceptions.js +41 -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 +27 -0
- package/dist/src/core/negative-control-compiled.js +73 -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 +36 -3
- package/dist/src/core/test-run-completeness.js +57 -17
- 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 +5 -1
- package/dist/src/core/typecheck-shim.js +115 -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 +20 -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 +94 -9
- package/docs/furnace.md +43 -1
- package/docs/lifecycle-invariants.md +13 -11
- package/docs/machine-output.md +64 -18
- package/docs/patch-workflow.md +174 -13
- package/docs/testing.md +73 -4
- package/package.json +3 -3
|
@@ -25,6 +25,15 @@ export interface TestRunHooks {
|
|
|
25
25
|
coveragePaths?: string[];
|
|
26
26
|
/** Runs after every preflight gate and the pre-test build, right before dispatch. */
|
|
27
27
|
beforeDispatch?: () => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Runs right before the pre-test build: a negative control on compiled
|
|
30
|
+
* source, which only reaches the binary when the build compiles it.
|
|
31
|
+
*/
|
|
32
|
+
beforeBuild?: () => Promise<void>;
|
|
33
|
+
/** `--repeat` run this sub-run is (1-based), for `--results`. */
|
|
34
|
+
repetition?: number;
|
|
35
|
+
/** Where dispatches write their mozlog raw logs (`--results`, `--shard-size`). */
|
|
36
|
+
rawLogDir?: string;
|
|
28
37
|
}
|
|
29
38
|
/** The single-run body (`runTestCommandBody`), injected to avoid an import cycle. */
|
|
30
39
|
export type TestRunBody = (paths: string[], options: TestOptions, hooks: TestRunHooks) => Promise<void>;
|
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { getProjectPaths } from '../core/config.js';
|
|
19
19
|
import { applyNegativeControl, planNegativeControl, recoverPendingNegativeControl, restoreNegativeControl, } from '../core/negative-control.js';
|
|
20
|
+
import { formatCompiledControlRefusal } from '../core/negative-control-compiled.js';
|
|
20
21
|
import { getActiveRunLogPath, writeToActiveRunLog } from '../core/run-log.js';
|
|
21
|
-
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
22
|
+
import { GeneralError, InvalidArgumentError, PreflightRefusalError } from '../errors/base.js';
|
|
22
23
|
import { TestFailureError } from '../errors/build.js';
|
|
23
24
|
import { toError } from '../utils/errors.js';
|
|
24
25
|
import { info, notice, success } from '../utils/logger.js';
|
|
25
26
|
import { stripEnginePrefix } from '../utils/paths.js';
|
|
26
27
|
import { classifyTestHarnesses } from './test-modes.js';
|
|
27
28
|
import { deriveControlOutcome, describeControlOutcome, formatAggregateBody, formatRunLine, parseCapturedVerdict, worstRun, } from './test-multi-verdict.js';
|
|
28
|
-
import { emitAggregateVerdict, emitFailVerdict, setVerdictCapture, verdictEmitted, } from './test-verdict.js';
|
|
29
|
+
import { emitAggregateVerdict, emitFailVerdict, setVerdictCapture, setVerdictRunAttribute, verdictEmitted, } from './test-verdict.js';
|
|
29
30
|
function assertMultiRunUsage(testPaths, options) {
|
|
30
31
|
const flag = options.negativeControl !== undefined ? '--negative-control' : '--repeat';
|
|
31
32
|
if (testPaths.length === 0) {
|
|
@@ -84,7 +85,10 @@ async function planRuns(engineDir, testPaths, options) {
|
|
|
84
85
|
...(group.harness === undefined ? {} : { harness: group.harness }),
|
|
85
86
|
paths: group.paths,
|
|
86
87
|
options: first ? options : laterRunOptions(options),
|
|
87
|
-
hooks:
|
|
88
|
+
hooks: {
|
|
89
|
+
repetition,
|
|
90
|
+
...(first && union !== undefined ? { coveragePaths: union } : {}),
|
|
91
|
+
},
|
|
88
92
|
});
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -158,6 +162,16 @@ export async function orchestrateTestRuns(projectRoot, testPaths, options, body,
|
|
|
158
162
|
: await planNegativeControl(projectRoot, projectPaths.engine, options.negativeControl, {
|
|
159
163
|
deferApplyCheck: options.build === true,
|
|
160
164
|
});
|
|
165
|
+
// A compiled target only reaches the binary through a build, so its
|
|
166
|
+
// control goes in before the first run's pre-test build.
|
|
167
|
+
const compiled = plan?.compiled;
|
|
168
|
+
const compiledControl = compiled !== undefined;
|
|
169
|
+
if (compiledControl) {
|
|
170
|
+
if (options.build !== true) {
|
|
171
|
+
throw new PreflightRefusalError(formatCompiledControlRefusal(compiled), 'negative-control');
|
|
172
|
+
}
|
|
173
|
+
setVerdictRunAttribute('control-build', 'compiled');
|
|
174
|
+
}
|
|
161
175
|
const results = [];
|
|
162
176
|
let restoreError;
|
|
163
177
|
for (const [index, run] of runs.entries()) {
|
|
@@ -170,7 +184,11 @@ export async function orchestrateTestRuns(projectRoot, testPaths, options, body,
|
|
|
170
184
|
try {
|
|
171
185
|
await body(run.paths, run.options, {
|
|
172
186
|
...run.hooks,
|
|
173
|
-
...(plan === undefined
|
|
187
|
+
...(plan === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: compiledControl && index === 0
|
|
190
|
+
? { beforeBuild: controlHook(controlPaths, plan, slot) }
|
|
191
|
+
: { beforeDispatch: controlHook(controlPaths, plan, slot) }),
|
|
174
192
|
});
|
|
175
193
|
}
|
|
176
194
|
catch (caught) {
|
|
@@ -27,3 +27,24 @@ export declare function describePerfStaleImpact(engineDir: string, stalePaths: r
|
|
|
27
27
|
* @param engineDir - Absolute engine directory
|
|
28
28
|
*/
|
|
29
29
|
export declare function perfGenerationTolerance(engineDir: string): (before: string, after: string) => Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Tolerance for `assertEngineGenerationUnchanged` on an ordinary run: true
|
|
32
|
+
* (with a warning, and `drift-tolerated=<n>` on the verdict) when HEAD did
|
|
33
|
+
* not move and every changed path is one the running browser cannot see.
|
|
34
|
+
*
|
|
35
|
+
* A peer deleting an unrelated chrome script used to make a whole run
|
|
36
|
+
* inconclusive. What a running browser can see mid-run is narrower than
|
|
37
|
+
* "anything under engine/": a file the objdir links to its source (`live`),
|
|
38
|
+
* one the harness stages at dispatch (`at-dispatch`), and the run's own
|
|
39
|
+
* test files and the staging inputs their manifests name. A compiled
|
|
40
|
+
* source, or a file the build copied into the package, is already fixed in
|
|
41
|
+
* the running binary or package; the next run's stale-build gate judges it.
|
|
42
|
+
*
|
|
43
|
+
* Perf runs keep {@link perfGenerationTolerance}: a sample is also void
|
|
44
|
+
* when a packaged path changes.
|
|
45
|
+
*
|
|
46
|
+
* @param engineDir - Absolute engine directory
|
|
47
|
+
* @param requested - Normalized request paths of the run
|
|
48
|
+
* @param onTolerated - Receives the tolerated path count
|
|
49
|
+
*/
|
|
50
|
+
export declare function runGenerationTolerance(engineDir: string, requested: readonly string[], onTolerated: (count: number) => void): (before: string, after: string) => Promise<boolean>;
|
|
@@ -81,3 +81,58 @@ export function perfGenerationTolerance(engineDir) {
|
|
|
81
81
|
return true;
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Tolerance for `assertEngineGenerationUnchanged` on an ordinary run: true
|
|
86
|
+
* (with a warning, and `drift-tolerated=<n>` on the verdict) when HEAD did
|
|
87
|
+
* not move and every changed path is one the running browser cannot see.
|
|
88
|
+
*
|
|
89
|
+
* A peer deleting an unrelated chrome script used to make a whole run
|
|
90
|
+
* inconclusive. What a running browser can see mid-run is narrower than
|
|
91
|
+
* "anything under engine/": a file the objdir links to its source (`live`),
|
|
92
|
+
* one the harness stages at dispatch (`at-dispatch`), and the run's own
|
|
93
|
+
* test files and the staging inputs their manifests name. A compiled
|
|
94
|
+
* source, or a file the build copied into the package, is already fixed in
|
|
95
|
+
* the running binary or package; the next run's stale-build gate judges it.
|
|
96
|
+
*
|
|
97
|
+
* Perf runs keep {@link perfGenerationTolerance}: a sample is also void
|
|
98
|
+
* when a packaged path changes.
|
|
99
|
+
*
|
|
100
|
+
* @param engineDir - Absolute engine directory
|
|
101
|
+
* @param requested - Normalized request paths of the run
|
|
102
|
+
* @param onTolerated - Receives the tolerated path count
|
|
103
|
+
*/
|
|
104
|
+
export function runGenerationTolerance(engineDir, requested, onTolerated) {
|
|
105
|
+
return async (before, after) => {
|
|
106
|
+
const delta = generationDeltaPaths(before, after);
|
|
107
|
+
if (delta.headMoved || delta.paths.length === 0)
|
|
108
|
+
return false;
|
|
109
|
+
const index = await loadEngineInstallIndex(engineDir);
|
|
110
|
+
if (index === undefined)
|
|
111
|
+
return false;
|
|
112
|
+
const inputs = [...requested];
|
|
113
|
+
for (const path of requested)
|
|
114
|
+
inputs.push(...(await collectStagingInputs(engineDir, path)));
|
|
115
|
+
for (const path of delta.paths) {
|
|
116
|
+
const bare = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
117
|
+
if (inputs.some((input) => isUnderPath(bare, input) || isUnderPath(input, bare))) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// A path-less run requests every test, so any test file is its own.
|
|
121
|
+
if (requested.length === 0 && isTestPath(path))
|
|
122
|
+
return false;
|
|
123
|
+
if (path.endsWith('/')) {
|
|
124
|
+
if (installsFromDirectory(index, path))
|
|
125
|
+
return false;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const delivery = await classifyRuntimeDelivery(index, engineDir, path);
|
|
129
|
+
if (delivery === 'live' || delivery === 'at-dispatch')
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
warn(`engine/ changed while this run was in flight, but only in files the running browser ` +
|
|
133
|
+
`cannot see (${capList(delta.paths)}); the verdict stands. The next run's stale-build ` +
|
|
134
|
+
'gate judges whether they need a rebuild.');
|
|
135
|
+
onTolerated(delta.paths.length);
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -22,10 +22,13 @@ import { join } from 'node:path';
|
|
|
22
22
|
import { describeBuildRunState, readBuildRunState } from '../core/build-run-record.js';
|
|
23
23
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
24
24
|
import { assertEngineExists } from '../core/engine-precondition.js';
|
|
25
|
+
import { autoPortsRequested } from '../core/harness-port-fallback.js';
|
|
25
26
|
import { hasBuildArtifacts, hasRunnableBundle } from '../core/mach.js';
|
|
26
27
|
import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
|
|
27
28
|
import { assertMarionettePortAvailable, ensureLaunchableBrowserNotRunning, extractForwardedMarionettePort, } from '../core/marionette-port.js';
|
|
28
29
|
import { ensureMochitestServerPortAvailable } from '../core/mochitest-server-port.js';
|
|
30
|
+
import { planNegativeControl } from '../core/negative-control.js';
|
|
31
|
+
import { formatCompiledControlRefusal } from '../core/negative-control-compiled.js';
|
|
29
32
|
import { waitBeforeLock } from '../core/preflight-wait.js';
|
|
30
33
|
import { PreflightRefusalError } from '../errors/base.js';
|
|
31
34
|
import { info } from '../utils/logger.js';
|
|
@@ -33,6 +36,7 @@ import { resolveWaitLockSeconds } from '../utils/options.js';
|
|
|
33
36
|
import { stripEnginePrefix } from '../utils/paths.js';
|
|
34
37
|
import { checkRequestedTestTypes } from './test-checkjs.js';
|
|
35
38
|
import { assertPathlessTestMode, classifyBeforeDispatch, resolveCanaryPath } from './test-modes.js';
|
|
39
|
+
import { enforceStaleBuildGate } from './test-stale-gate.js';
|
|
36
40
|
/**
|
|
37
41
|
* Resolves the launchable binary a test run needs, or refuses naming why
|
|
38
42
|
* it is missing. The cause comes from the build run record, which any
|
|
@@ -95,10 +99,11 @@ async function loadPreLockContext(projectRoot, testPaths, options) {
|
|
|
95
99
|
* @param options - Parsed test options
|
|
96
100
|
*/
|
|
97
101
|
export async function runTestPreLockPreflight(projectRoot, testPaths, options) {
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
return options;
|
|
101
|
-
const { paths, config, objDir } =
|
|
102
|
+
const loaded = await loadPreLockContext(projectRoot, testPaths, options);
|
|
103
|
+
if (loaded === undefined)
|
|
104
|
+
return withLockMode(options, 'exclusive');
|
|
105
|
+
const { paths, config, objDir } = loaded;
|
|
106
|
+
options = withLockMode(options, resolveTestLockMode(options, config));
|
|
102
107
|
let launchablePath;
|
|
103
108
|
try {
|
|
104
109
|
launchablePath = await resolveLaunchablePathForTests(projectRoot, paths.engine, config.binaryName, objDir);
|
|
@@ -115,11 +120,102 @@ export async function runTestPreLockPreflight(projectRoot, testPaths, options) {
|
|
|
115
120
|
if (options.build === true || options.buildOnly === true) {
|
|
116
121
|
await checkRequestedTestTypes(paths, config, normalizedPaths);
|
|
117
122
|
}
|
|
123
|
+
else {
|
|
124
|
+
await refuseCompiledControlBeforeLock(projectRoot, paths.engine, options);
|
|
125
|
+
await refuseStalePackagingBeforeLock(projectRoot, paths.engine, options, normalizedPaths);
|
|
126
|
+
}
|
|
118
127
|
return waitForResourcesBeforeLock(paths.engine, config, options, {
|
|
119
128
|
launchablePath,
|
|
120
129
|
normalizedPaths,
|
|
121
130
|
});
|
|
122
131
|
}
|
|
132
|
+
/** `options` with `sharedLock` set to the resolved decision. */
|
|
133
|
+
function withLockMode(options, mode) {
|
|
134
|
+
if (mode === 'exclusive' && options.sharedLock === undefined)
|
|
135
|
+
return options;
|
|
136
|
+
const rest = { ...options };
|
|
137
|
+
delete rest.sharedLock;
|
|
138
|
+
return mode === 'shared' ? { ...rest, sharedLock: true } : rest;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* How this run holds the engine session lock. Shared only on request
|
|
142
|
+
* (`--shared-lock` or `test.lockMode: "shared"`), and only for a run that
|
|
143
|
+
* writes nothing into engine/ or the objdir and can move its ports: no
|
|
144
|
+
* build, no negative control, auto ports. Every other run stays exclusive,
|
|
145
|
+
* so a shared holder can never overlap a build or a re-export.
|
|
146
|
+
*/
|
|
147
|
+
function resolveTestLockMode(options, config) {
|
|
148
|
+
const requested = options.sharedLock === true || config.test?.lockMode === 'shared';
|
|
149
|
+
if (!requested)
|
|
150
|
+
return 'exclusive';
|
|
151
|
+
const writes = options.build === true ||
|
|
152
|
+
options.buildOnly === true ||
|
|
153
|
+
options.negativeControl !== undefined ||
|
|
154
|
+
options.canary !== undefined ||
|
|
155
|
+
options.doctor === true;
|
|
156
|
+
if (writes || !autoPortsRequested(options, config)) {
|
|
157
|
+
info('Shared test lock requested, but this run ' +
|
|
158
|
+
(writes
|
|
159
|
+
? 'builds, applies a negative control, or runs a canary/doctor preflight'
|
|
160
|
+
: 'does not use auto ports (--auto-ports or test.mochitestPorts: "auto")') +
|
|
161
|
+
', so it holds the engine lock exclusively.');
|
|
162
|
+
return 'exclusive';
|
|
163
|
+
}
|
|
164
|
+
return 'shared';
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Refuses a build-less `--negative-control` on compiled source before the
|
|
168
|
+
* lock: applied after the build it is never compiled, so the run could only
|
|
169
|
+
* report `control=missed` against the unmutated binary.
|
|
170
|
+
*/
|
|
171
|
+
async function refuseCompiledControlBeforeLock(projectRoot, engineDir, options) {
|
|
172
|
+
if (options.negativeControl === undefined)
|
|
173
|
+
return;
|
|
174
|
+
let plan;
|
|
175
|
+
try {
|
|
176
|
+
plan = await planNegativeControl(projectRoot, engineDir, options.negativeControl);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Any other refusal (including a control a killed run left applied,
|
|
180
|
+
// which only the in-lock recovery may undo) is the in-lock pass's.
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (plan.compiled === undefined)
|
|
184
|
+
return;
|
|
185
|
+
throw new PreflightRefusalError(formatCompiledControlRefusal(plan.compiled), 'negative-control');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Runs the build-less run's packaging gates (coverage, stale content,
|
|
189
|
+
* StaticComponents) before the lock, so a refusal surfaces before the run
|
|
190
|
+
* queues instead of after it reached the head of the queue. The same gate
|
|
191
|
+
* runs again under the lock, where a peer's build may since have changed
|
|
192
|
+
* the answer; this pass only refuses early what would refuse anyway.
|
|
193
|
+
*
|
|
194
|
+
* Skipped while a build runs (it will rewrite the record this reads) and
|
|
195
|
+
* under `--allow-stale-build`/`--allow-stale-components`, whose warnings
|
|
196
|
+
* belong to the in-lock pass alone. A `--negative-control` refusal says to
|
|
197
|
+
* add `--build`: without it the control has nothing current to run on, and
|
|
198
|
+
* the invocation used to end `control=inconclusive` after a full queue.
|
|
199
|
+
*/
|
|
200
|
+
async function refuseStalePackagingBeforeLock(projectRoot, engineDir, options, normalizedPaths) {
|
|
201
|
+
if (options.allowStaleBuild === true || options.allowStaleComponents === true)
|
|
202
|
+
return;
|
|
203
|
+
if ((await readBuildRunState(projectRoot)).kind === 'running')
|
|
204
|
+
return;
|
|
205
|
+
try {
|
|
206
|
+
await enforceStaleBuildGate(projectRoot, engineDir, options, normalizedPaths, {
|
|
207
|
+
quiet: true,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (options.negativeControl === undefined || !(error instanceof PreflightRefusalError)) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
throw new PreflightRefusalError(`${error.message}\n\nThis --negative-control run has no --build, so its control would ` +
|
|
215
|
+
'run on this packaging and end inconclusive. Add --build: the control is then planned ' +
|
|
216
|
+
'against a fresh build of the requested paths.', error.note);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
123
219
|
/** `options` without one wait budget, which this pass already spent. */
|
|
124
220
|
function withoutOption(options, key) {
|
|
125
221
|
const rest = { ...options };
|
|
@@ -106,7 +106,21 @@ function registerTestSharingOptions(test) {
|
|
|
106
106
|
function registerTestBuildOptions(test) {
|
|
107
107
|
return test
|
|
108
108
|
.option('--extend-coverage', 'With --build/--build-only and explicit paths, refuse instead of dropping any earlier packaging claim. Scoped builds keep each earlier path by default while its own staging inputs (manifest directory and the fixtures its manifest names) and engine/mozconfig are unchanged; this flag turns a drop into a refusal before the build.')
|
|
109
|
-
.option('--
|
|
109
|
+
.option('--furnace-apply <mode>', 'With --build/--build-only: all (default; deploy every component with undeployed edits), reached (only components a requested test names; the rest are reported as furnace-skipped=<names>), or none. Same as test.furnaceApply.', commanderArgParser((raw) => {
|
|
110
|
+
if (raw !== 'all' && raw !== 'reached' && raw !== 'none') {
|
|
111
|
+
throw new GeneralError(`--furnace-apply must be all, reached or none (got "${raw}")`);
|
|
112
|
+
}
|
|
113
|
+
return raw;
|
|
114
|
+
}))
|
|
115
|
+
.option('--no-furnace-apply', 'With --build/--build-only, skip the Furnace apply so the build packages engine/ as it stands; names the components whose undeployed components/ edits it leaves out (same as --furnace-apply none)');
|
|
116
|
+
}
|
|
117
|
+
/** Options that run a request on several harness instances, or judge it per file. */
|
|
118
|
+
function registerTestParallelOptions(test) {
|
|
119
|
+
return test
|
|
120
|
+
.option('--jobs <n>', 'Run shards on n harness instances at once inside one lock hold, each with its own profile, Marionette port and (via auto ports) server ports. With --no-shard, or one directory argument, the files are split in order into n contiguous chunks. Prints a FIREFORGE-SHARD line per shard and one aggregate verdict. Requires --auto-ports or test.mochitestPorts: "auto".', commanderArgParser((raw) => parsePositiveIntegerFlag('--jobs', raw)))
|
|
121
|
+
.option('--shard-size <n>', 'Dispatch n files per mach invocation (mochitest restarts the browser between files) and judge each file from the structured log: shards= then counts files and failed-shards= names failed files. Combines with --jobs.', commanderArgParser((raw) => parsePositiveIntegerFlag('--shard-size', raw)))
|
|
122
|
+
.option('--results <path>', 'Write a JSON record per test file and --repeat run (status, expected, duration, run-log line) from the mozlog structured log, and name it on the verdict as results=<path>')
|
|
123
|
+
.option('--shared-lock', 'Hold the engine session lock shared when this run is build-less, applies no negative control and uses auto ports, so peers\' build-less runs on this objdir overlap; builds and re-exports still wait. Same as test.lockMode: "shared".');
|
|
110
124
|
}
|
|
111
125
|
/** Registers the test command on the CLI program. */
|
|
112
126
|
export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
@@ -146,13 +160,14 @@ export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
|
146
160
|
return n;
|
|
147
161
|
}))
|
|
148
162
|
.addHelpText('after', TEST_HELP_TEXT);
|
|
149
|
-
addWaitLockOption(registerTestBuildOptions(registerTestMultiRunOptions(registerTestSharingOptions(test)))).action(withErrorHandling(async (paths, options) => {
|
|
163
|
+
addWaitLockOption(registerTestParallelOptions(registerTestBuildOptions(registerTestMultiRunOptions(registerTestSharingOptions(test))))).action(withErrorHandling(async (paths, options) => {
|
|
150
164
|
const projectRoot = getProjectRoot();
|
|
151
165
|
// Fallback for a verdict testCommand did not emit (it takes the lock).
|
|
152
166
|
resetVerdictEmission();
|
|
153
167
|
try {
|
|
154
|
-
await testCommand(projectRoot, paths, pickDefined(options), (body) => withEngineSessionLock(projectRoot, 'test', body, {
|
|
168
|
+
await testCommand(projectRoot, paths, pickDefined(options), (body, mode) => withEngineSessionLock(projectRoot, 'test', body, {
|
|
155
169
|
waitLockSeconds: resolveWaitLockSeconds(options.waitLock),
|
|
170
|
+
...(mode === 'shared' ? { mode } : {}),
|
|
156
171
|
}));
|
|
157
172
|
}
|
|
158
173
|
catch (error) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireforge test --results <path>`: one record per test file and per
|
|
3
|
+
* `--repeat` run, from the mozlog raw log each dispatch writes.
|
|
4
|
+
*
|
|
5
|
+
* Gates and retry wrappers downstream named a run's failing files by
|
|
6
|
+
* reading `TEST_START`/`TEST_END` lines and the prose of the "sharded test
|
|
7
|
+
* run(s) did not pass" error out of the human log, which nothing promised
|
|
8
|
+
* to keep stable. The results file is the stable form: JSON with a
|
|
9
|
+
* `schemaVersion`, written once the invocation has finished, and named on
|
|
10
|
+
* the verdict line as `results=<path>`.
|
|
11
|
+
*/
|
|
12
|
+
import type { RawTestResult } from '../core/test-raw-log.js';
|
|
13
|
+
/** One file's result in one dispatch. */
|
|
14
|
+
export interface TestFileResult extends RawTestResult {
|
|
15
|
+
/** `--repeat` run (1-based); 1 for a single run. */
|
|
16
|
+
run: number;
|
|
17
|
+
/** The shard (path argument or `--shard-size` chunk) the file ran in. */
|
|
18
|
+
shard: string;
|
|
19
|
+
/** Line of the run log where this file's dispatch began (1-based). */
|
|
20
|
+
logLine: number;
|
|
21
|
+
}
|
|
22
|
+
/** The document `--results` writes. */
|
|
23
|
+
export interface TestResultsDocument {
|
|
24
|
+
schemaVersion: 1;
|
|
25
|
+
/** Files in dispatch-completion order. */
|
|
26
|
+
files: TestFileResult[];
|
|
27
|
+
/** Started files that never ended, per dispatch. */
|
|
28
|
+
unfinished: Array<{
|
|
29
|
+
test: string;
|
|
30
|
+
run: number;
|
|
31
|
+
shard: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
/** Clears the collector at the start of an invocation. */
|
|
35
|
+
export declare function resetTestResults(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Adds one dispatch's parsed raw log to the invocation's results.
|
|
38
|
+
*
|
|
39
|
+
* @param results - The dispatch's finished files
|
|
40
|
+
* @param unfinished - Its started-but-unfinished files
|
|
41
|
+
* @param context - Run, shard and run-log line of the dispatch
|
|
42
|
+
*/
|
|
43
|
+
export declare function recordTestResults(results: readonly RawTestResult[], unfinished: readonly string[], context: {
|
|
44
|
+
run: number;
|
|
45
|
+
shard: string;
|
|
46
|
+
logLine: number;
|
|
47
|
+
}): void;
|
|
48
|
+
/** The results collected so far (for tests and the shard summary). */
|
|
49
|
+
export declare function collectedTestResults(): TestResultsDocument;
|
|
50
|
+
/**
|
|
51
|
+
* Writes the collected results.
|
|
52
|
+
*
|
|
53
|
+
* @param path - Destination of `--results`
|
|
54
|
+
*/
|
|
55
|
+
export declare function writeTestResults(path: string): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Creates the scratch directory the dispatches' raw logs go to, runs
|
|
58
|
+
* `body`, and removes it afterwards.
|
|
59
|
+
*
|
|
60
|
+
* @param body - Receives the directory
|
|
61
|
+
*/
|
|
62
|
+
export declare function withRawLogDir<T>(body: (dir: string) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge test --results <path>`: one record per test file and per
|
|
4
|
+
* `--repeat` run, from the mozlog raw log each dispatch writes.
|
|
5
|
+
*
|
|
6
|
+
* Gates and retry wrappers downstream named a run's failing files by
|
|
7
|
+
* reading `TEST_START`/`TEST_END` lines and the prose of the "sharded test
|
|
8
|
+
* run(s) did not pass" error out of the human log, which nothing promised
|
|
9
|
+
* to keep stable. The results file is the stable form: JSON with a
|
|
10
|
+
* `schemaVersion`, written once the invocation has finished, and named on
|
|
11
|
+
* the verdict line as `results=<path>`.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { writeJson } from '../utils/fs.js';
|
|
17
|
+
let collected = { schemaVersion: 1, files: [], unfinished: [] };
|
|
18
|
+
/** Clears the collector at the start of an invocation. */
|
|
19
|
+
export function resetTestResults() {
|
|
20
|
+
collected = { schemaVersion: 1, files: [], unfinished: [] };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Adds one dispatch's parsed raw log to the invocation's results.
|
|
24
|
+
*
|
|
25
|
+
* @param results - The dispatch's finished files
|
|
26
|
+
* @param unfinished - Its started-but-unfinished files
|
|
27
|
+
* @param context - Run, shard and run-log line of the dispatch
|
|
28
|
+
*/
|
|
29
|
+
export function recordTestResults(results, unfinished, context) {
|
|
30
|
+
for (const result of results)
|
|
31
|
+
collected.files.push({ ...result, ...context });
|
|
32
|
+
for (const test of unfinished) {
|
|
33
|
+
collected.unfinished.push({ test, run: context.run, shard: context.shard });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The results collected so far (for tests and the shard summary). */
|
|
37
|
+
export function collectedTestResults() {
|
|
38
|
+
return collected;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Writes the collected results.
|
|
42
|
+
*
|
|
43
|
+
* @param path - Destination of `--results`
|
|
44
|
+
*/
|
|
45
|
+
export async function writeTestResults(path) {
|
|
46
|
+
await writeJson(path, collectedTestResults());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates the scratch directory the dispatches' raw logs go to, runs
|
|
50
|
+
* `body`, and removes it afterwards.
|
|
51
|
+
*
|
|
52
|
+
* @param body - Receives the directory
|
|
53
|
+
*/
|
|
54
|
+
export async function withRawLogDir(body) {
|
|
55
|
+
const dir = await mkdtemp(join(tmpdir(), 'fireforge-raw-log-'));
|
|
56
|
+
try {
|
|
57
|
+
return await body(dir);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await rm(dir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -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>;
|