@hominis/fireforge 0.30.1 → 0.31.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 +25 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +5 -15
- package/dist/src/commands/export-flow.d.ts +6 -0
- package/dist/src/commands/export-flow.js +6 -1
- package/dist/src/commands/export-placement-gate.d.ts +38 -0
- package/dist/src/commands/export-placement-gate.js +105 -0
- package/dist/src/commands/export-shared.d.ts +28 -0
- package/dist/src/commands/export-shared.js +36 -0
- package/dist/src/commands/export.js +47 -112
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
- package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
- package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
- package/dist/src/commands/furnace/create.d.ts +1 -2
- package/dist/src/commands/furnace/deploy.js +36 -114
- package/dist/src/commands/furnace/refresh.js +52 -32
- package/dist/src/commands/furnace/sync.js +2 -0
- package/dist/src/commands/import.js +108 -73
- package/dist/src/commands/lint-per-patch.d.ts +1 -1
- package/dist/src/commands/lint-per-patch.js +110 -81
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +96 -84
- package/dist/src/commands/patch/compact.d.ts +5 -2
- package/dist/src/commands/patch/compact.js +85 -25
- package/dist/src/commands/patch/delete.js +17 -17
- package/dist/src/commands/patch/index.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +3 -16
- package/dist/src/commands/patch/move-files.js +2 -0
- package/dist/src/commands/patch/patch-context.d.ts +41 -0
- package/dist/src/commands/patch/patch-context.js +53 -0
- package/dist/src/commands/patch/rename.js +10 -15
- package/dist/src/commands/patch/reorder.d.ts +0 -2
- package/dist/src/commands/patch/reorder.js +18 -19
- package/dist/src/commands/patch/split-plan.d.ts +66 -0
- package/dist/src/commands/patch/split-plan.js +178 -0
- package/dist/src/commands/patch/split.d.ts +30 -0
- package/dist/src/commands/patch/split.js +283 -0
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
- package/dist/src/commands/patch/staged-dependency.js +4 -17
- package/dist/src/commands/patch/tier.js +4 -17
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/rebase/summary.d.ts +1 -5
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/status-output.js +77 -68
- package/dist/src/commands/test-diagnose.d.ts +23 -0
- package/dist/src/commands/test-diagnose.js +210 -0
- package/dist/src/commands/test-run.d.ts +58 -0
- package/dist/src/commands/test-run.js +88 -0
- package/dist/src/commands/test.js +169 -257
- package/dist/src/commands/token.js +15 -1
- package/dist/src/commands/wire.js +109 -78
- package/dist/src/core/build-audit.d.ts +1 -1
- package/dist/src/core/build-audit.js +2 -46
- package/dist/src/core/build-baseline-types.d.ts +38 -0
- package/dist/src/core/build-baseline-types.js +10 -0
- package/dist/src/core/build-baseline.d.ts +1 -31
- package/dist/src/core/build-prepare.d.ts +1 -1
- package/dist/src/core/build-prepare.js +2 -45
- package/dist/src/core/config-paths.d.ts +0 -8
- package/dist/src/core/config-paths.js +4 -4
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -1
- package/dist/src/core/config-validate-patch-policy.js +12 -13
- package/dist/src/core/config-validate.js +48 -28
- package/dist/src/core/engine-changes.d.ts +24 -0
- package/dist/src/core/engine-changes.js +64 -0
- package/dist/src/core/firefox-cache.d.ts +0 -5
- package/dist/src/core/firefox-cache.js +1 -1
- package/dist/src/core/firefox-download.d.ts +0 -6
- package/dist/src/core/firefox-download.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
- package/dist/src/core/furnace-apply-helpers.js +11 -20
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +1 -1
- package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
- package/dist/src/core/furnace-checksum-utils.js +15 -0
- package/dist/src/core/furnace-config-validate.d.ts +31 -0
- package/dist/src/core/furnace-config-validate.js +133 -0
- package/dist/src/core/furnace-config.d.ts +4 -32
- package/dist/src/core/furnace-config.js +15 -111
- package/dist/src/core/furnace-constants.d.ts +0 -10
- package/dist/src/core/furnace-constants.js +2 -2
- package/dist/src/core/furnace-css-fragments.d.ts +79 -0
- package/dist/src/core/furnace-css-fragments.js +243 -0
- package/dist/src/core/furnace-jsconfig.d.ts +63 -0
- package/dist/src/core/furnace-jsconfig.js +171 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
- package/dist/src/core/furnace-validate-helpers.js +40 -1
- package/dist/src/core/furnace-validate-registration.js +16 -1
- package/dist/src/core/furnace-validate.js +54 -2
- package/dist/src/core/git-file-ops.d.ts +0 -12
- package/dist/src/core/git-file-ops.js +2 -2
- package/dist/src/core/lint-cache.d.ts +0 -13
- package/dist/src/core/lint-cache.js +5 -5
- package/dist/src/core/mach.d.ts +5 -1
- package/dist/src/core/mach.js +6 -2
- package/dist/src/core/manifest-register.d.ts +5 -16
- package/dist/src/core/manifest-register.js +3 -1
- package/dist/src/core/patch-lint-checkjs.js +53 -7
- package/dist/src/core/patch-lint-jsdoc.js +63 -4
- package/dist/src/core/patch-lint-observer.d.ts +37 -0
- package/dist/src/core/patch-lint-observer.js +168 -0
- package/dist/src/core/patch-lint.js +132 -125
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +44 -2
- package/dist/src/core/patch-manifest-validate.d.ts +1 -8
- package/dist/src/core/patch-manifest-validate.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-policy.d.ts +0 -4
- package/dist/src/core/patch-policy.js +10 -4
- package/dist/src/core/register-browser-content.d.ts +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-result.d.ts +21 -0
- package/dist/src/core/register-result.js +9 -0
- package/dist/src/core/register-shared-css.d.ts +1 -1
- package/dist/src/core/register-test-manifest.d.ts +1 -1
- package/dist/src/core/test-harness-crash.d.ts +61 -0
- package/dist/src/core/test-harness-crash.js +140 -0
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +2 -46
- package/dist/src/core/test-xpcshell-retry.d.ts +1 -1
- package/dist/src/core/test-xpcshell-retry.js +4 -2
- package/dist/src/core/token-dark-mode.js +14 -26
- package/dist/src/core/token-manager.d.ts +4 -0
- package/dist/src/core/token-manager.js +70 -16
- package/dist/src/core/typecheck-shim.d.ts +0 -21
- package/dist/src/core/typecheck-shim.js +26 -4
- package/dist/src/core/wire-utils.js +37 -44
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +105 -0
- package/dist/src/types/furnace.d.ts +12 -1
- package/dist/src/utils/elapsed.d.ts +0 -2
- package/dist/src/utils/elapsed.js +1 -1
- package/dist/src/utils/fs.d.ts +0 -5
- package/dist/src/utils/fs.js +1 -1
- package/dist/src/utils/regex.d.ts +0 -6
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/validation.d.ts +0 -8
- package/dist/src/utils/validation.js +2 -2
- package/package.json +6 -4
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema validation for patches.json manifest data.
|
|
3
3
|
*/
|
|
4
|
-
import type { PatchCategory, PatchesManifest
|
|
5
|
-
/**
|
|
6
|
-
* Validates a single patch metadata entry from raw data.
|
|
7
|
-
* @param data - Raw data to validate
|
|
8
|
-
* @param index - Array index for error messages
|
|
9
|
-
* @returns Validated PatchMetadata
|
|
10
|
-
*/
|
|
11
|
-
export declare function validatePatchMetadata(data: unknown, index: number): PatchMetadata;
|
|
4
|
+
import type { PatchCategory, PatchesManifest } from '../types/commands/index.js';
|
|
12
5
|
/** Validates raw patches.json data and returns the typed manifest shape. */
|
|
13
6
|
export declare function validatePatchesManifest(data: unknown): PatchesManifest;
|
|
14
7
|
/**
|
|
@@ -39,7 +39,7 @@ function parseStagedDependencies(data, label) {
|
|
|
39
39
|
* @param index - Array index for error messages
|
|
40
40
|
* @returns Validated PatchMetadata
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
function validatePatchMetadata(data, index) {
|
|
43
43
|
const rec = parseObject(data, `patches[${index}]`);
|
|
44
44
|
const filename = rec.string('filename');
|
|
45
45
|
const name = rec.string('name');
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is an implementation detail.
|
|
6
6
|
*/
|
|
7
7
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
8
|
-
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
8
|
+
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, type PatchManifestRowMutation, type PatchManifestRowMutationResult, type PatchRenameEntry, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
9
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
10
10
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
11
11
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* is an implementation detail.
|
|
7
7
|
*/
|
|
8
8
|
export { rebuildPatchesManifest, validatePatchesManifestConsistency, } from './patch-manifest-consistency.js';
|
|
9
|
-
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, savePatchesManifest, } from './patch-manifest-io.js';
|
|
9
|
+
export { addPatchToManifest, loadPatchesManifest, mutatePatchRowsInManifest, PATCHES_MANIFEST, removePatchFileAndManifest, renumberPatchesInManifest, rewriteStagedDependencyOwners, savePatchesManifest, } from './patch-manifest-io.js';
|
|
10
10
|
export { checkVersionCompatibility, findPatchesAffectingFile, getClaimedFiles, stampPatchVersions, validatePatchIntegrity, } from './patch-manifest-query.js';
|
|
11
11
|
export { resolvePatchIdentifier } from './patch-manifest-resolve.js';
|
|
12
12
|
export { validatePatchesManifest } from './patch-manifest-validate.js';
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
10
10
|
import type { FireForgeConfig } from '../types/config.js';
|
|
11
|
-
/** Default patch filename contract used when a policy omits `filenamePattern`. */
|
|
12
|
-
export declare const DEFAULT_PATCH_POLICY_FILENAME_PATTERN = "^(?<order>\\d{3})-(?<category>[a-z][a-z0-9-]*)-(?<slug>[a-z0-9-]+)\\.patch$";
|
|
13
11
|
/** Stable issue codes returned by patch policy evaluation. */
|
|
14
12
|
export type PatchPolicyIssueCode = 'filename-pattern' | 'filename-captures' | 'filename-metadata-mismatch' | 'order-collision' | 'category-range' | 'reserved-range' | 'reserved-documentation' | 'reserved-files' | 'description-required' | 'numeric-gap';
|
|
15
13
|
/** A single patch policy validation finding. */
|
|
@@ -26,8 +24,6 @@ export interface PatchPolicyEnforcementInput {
|
|
|
26
24
|
command: string;
|
|
27
25
|
forceUnsafe?: boolean;
|
|
28
26
|
}
|
|
29
|
-
/** Returns true when the loaded config includes an opt-in patch policy. */
|
|
30
|
-
export declare function hasPatchPolicy(config: FireForgeConfig): boolean;
|
|
31
27
|
/** Returns valid categories for prompts and CLI validation under the config. */
|
|
32
28
|
export declare function getPatchPolicyCategories(config: FireForgeConfig): string[];
|
|
33
29
|
/** Checks whether a category is accepted by legacy defaults or the policy ranges. */
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
11
11
|
import { warn } from '../utils/logger.js';
|
|
12
12
|
import { PATCH_CATEGORIES } from '../utils/validation.js';
|
|
13
|
+
import { rewriteStagedDependencyOwners } from './patch-manifest-io.js';
|
|
13
14
|
/** Default patch filename contract used when a policy omits `filenamePattern`. */
|
|
14
|
-
|
|
15
|
+
const DEFAULT_PATCH_POLICY_FILENAME_PATTERN = '^(?<order>\\d{3})-(?<category>[a-z][a-z0-9-]*)-(?<slug>[a-z0-9-]+)\\.patch$';
|
|
15
16
|
function policy(config) {
|
|
16
17
|
return config.patchPolicy;
|
|
17
18
|
}
|
|
@@ -22,7 +23,7 @@ function issueSeverity(config) {
|
|
|
22
23
|
return mutationMode(config) === 'warn' ? 'warning' : 'error';
|
|
23
24
|
}
|
|
24
25
|
/** Returns true when the loaded config includes an opt-in patch policy. */
|
|
25
|
-
|
|
26
|
+
function hasPatchPolicy(config) {
|
|
26
27
|
return policy(config) !== undefined;
|
|
27
28
|
}
|
|
28
29
|
/** Returns valid categories for prompts and CLI validation under the config. */
|
|
@@ -305,12 +306,17 @@ export function buildProjectedManifest(current, patches) {
|
|
|
305
306
|
}
|
|
306
307
|
/** Applies a filename/order rename projection to a manifest without mutating it. */
|
|
307
308
|
export function applyRenameMapToManifest(manifest, renameMap) {
|
|
309
|
+
const ownerLookup = (oldFilename) => renameMap.get(oldFilename)?.newFilename;
|
|
308
310
|
return buildProjectedManifest(manifest, manifest.patches.map((patch) => {
|
|
311
|
+
// Staged-dependency owners reference other patches' filenames, so the
|
|
312
|
+
// projection rewrites them on every row to mirror what
|
|
313
|
+
// renumberPatchesInManifest persists.
|
|
314
|
+
const withOwners = rewriteStagedDependencyOwners(patch, ownerLookup);
|
|
309
315
|
const rename = renameMap.get(patch.filename);
|
|
310
316
|
if (!rename)
|
|
311
|
-
return
|
|
317
|
+
return withOwners;
|
|
312
318
|
return {
|
|
313
|
-
...
|
|
319
|
+
...withOwners,
|
|
314
320
|
filename: rename.newFilename,
|
|
315
321
|
order: rename.newOrder,
|
|
316
322
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module registration in browser/modules/{binaryName}/moz.build.
|
|
3
3
|
*/
|
|
4
|
-
import type { RegisterResult } from './
|
|
4
|
+
import type { RegisterResult } from './register-result.js';
|
|
5
5
|
/**
|
|
6
6
|
* Registers a module in browser/modules/{binaryName}/moz.build.
|
|
7
7
|
*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared result shape for manifest registration operations, split out of
|
|
3
|
+
* the `manifest-register.ts` barrel so the `register-*` leaf modules can
|
|
4
|
+
* import it without importing the barrel that re-exports them — that
|
|
5
|
+
* type-only back-edge made the registration dependency graph cyclic.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Result of a manifest registration operation.
|
|
9
|
+
*/
|
|
10
|
+
export interface RegisterResult {
|
|
11
|
+
/** The manifest file that was modified */
|
|
12
|
+
manifest: string;
|
|
13
|
+
/** The entry that was inserted */
|
|
14
|
+
entry: string;
|
|
15
|
+
/** The entry after which the new entry was inserted (for user display) */
|
|
16
|
+
previousEntry?: string | undefined;
|
|
17
|
+
/** Whether the entry already existed (skipped) */
|
|
18
|
+
skipped: boolean;
|
|
19
|
+
/** Whether --after target was not found and fell back to alphabetical */
|
|
20
|
+
afterFallback?: boolean | undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Shared result shape for manifest registration operations, split out of
|
|
4
|
+
* the `manifest-register.ts` barrel so the `register-*` leaf modules can
|
|
5
|
+
* import it without importing the barrel that re-exports them — that
|
|
6
|
+
* type-only back-edge made the registration dependency graph cyclic.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=register-result.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CSS registration in browser/themes/shared/jar.inc.mn.
|
|
3
3
|
*/
|
|
4
|
-
import type { RegisterResult } from './
|
|
4
|
+
import type { RegisterResult } from './register-result.js';
|
|
5
5
|
/**
|
|
6
6
|
* Measures the column at which the `(source)` parenthesis opens in
|
|
7
7
|
* adjacent `skin/classic/browser/<x>.css (...)` entries inside an
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test manifest registration in browser/base/moz.build.
|
|
3
3
|
*/
|
|
4
|
-
import type { RegisterResult } from './
|
|
4
|
+
import type { RegisterResult } from './register-result.js';
|
|
5
5
|
/**
|
|
6
6
|
* Registers a test manifest (browser.toml) in browser/base/moz.build.
|
|
7
7
|
*
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness-crash classification for `fireforge test` (field reports C1/C2).
|
|
3
|
+
*
|
|
4
|
+
* The wrapped mach harness exhibits flaky non-test failures that an exit
|
|
5
|
+
* code (or a "did it print a summary" grep) cannot distinguish from real
|
|
6
|
+
* test results:
|
|
7
|
+
*
|
|
8
|
+
* - startup crashes from the mozlog resource monitor on macOS
|
|
9
|
+
* (`AttributeError: 'SystemResourceMonitor' object has no attribute
|
|
10
|
+
* 'poll_interval'`, `host_statistics64(HOST_VM_INFO64) syscall failed`,
|
|
11
|
+
* `(ipc/mig) array not large enough` — a psutil/macOS mismatch) which
|
|
12
|
+
* abort the run before any test executes;
|
|
13
|
+
* - hangs after browser startup that die at the no-output timeout yet
|
|
14
|
+
* still emit a `Passed: 0` summary;
|
|
15
|
+
* - post-green shutdown re-entry, where a fully green run stalls on
|
|
16
|
+
* "must wait for focus" and records "Application shut down (without
|
|
17
|
+
* crashing) in the middle of a test!" as the only unexpected failure.
|
|
18
|
+
*
|
|
19
|
+
* Classification therefore keys on `TEST-START` presence — summary lines
|
|
20
|
+
* never count as proof that tests ran — and recognizes the crash shapes
|
|
21
|
+
* above so the command layer can retry them with a bounded budget instead
|
|
22
|
+
* of reporting phantom test failures (or phantom passes).
|
|
23
|
+
*/
|
|
24
|
+
/** How a completed harness run should be interpreted. */
|
|
25
|
+
export type HarnessRunClassification = 'tests-ran-ok' | 'test-failures' | 'harness-crash' | 'no-tests';
|
|
26
|
+
/** A recognized harness-crash shape with its evidence line. */
|
|
27
|
+
export interface HarnessCrashSignature {
|
|
28
|
+
reason: string;
|
|
29
|
+
line: string;
|
|
30
|
+
}
|
|
31
|
+
/** Result of {@link classifyHarnessRun}. */
|
|
32
|
+
export interface HarnessRunVerdict {
|
|
33
|
+
kind: HarnessRunClassification;
|
|
34
|
+
signature?: HarnessCrashSignature;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Detects the known harness-crash shapes in captured mach output.
|
|
38
|
+
* Returns undefined for anything that looks like a genuine test result.
|
|
39
|
+
*/
|
|
40
|
+
export declare function detectHarnessCrashSignature(output: string): HarnessCrashSignature | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Classifies a completed harness run. The decision tree, in order:
|
|
43
|
+
*
|
|
44
|
+
* 1. A recognized crash signature wins regardless of exit code (the
|
|
45
|
+
* shutdown re-entry shape exits non-zero on an otherwise green run;
|
|
46
|
+
* the hang shape can even exit zero with a `Passed: 0` summary).
|
|
47
|
+
* 2. No `TEST-START` with explicit paths requested means no test ran —
|
|
48
|
+
* `no-tests`, even when the exit code is zero. Summary lines are not
|
|
49
|
+
* trusted as evidence of execution.
|
|
50
|
+
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
51
|
+
* test failure for the regular diagnosis chain.
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifyHarnessRun(exitCode: number, output: string, requestedPaths: readonly string[]): HarnessRunVerdict;
|
|
54
|
+
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
55
|
+
export declare function buildHarnessCrashMessage(signature: HarnessCrashSignature, attempts: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the message for a run that produced no `TEST-START` despite
|
|
58
|
+
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
59
|
+
* summary would otherwise read as a silent false green.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildNoTestsRanMessage(exitCode: number, requestedPaths: readonly string[]): string;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Harness-crash classification for `fireforge test` (field reports C1/C2).
|
|
4
|
+
*
|
|
5
|
+
* The wrapped mach harness exhibits flaky non-test failures that an exit
|
|
6
|
+
* code (or a "did it print a summary" grep) cannot distinguish from real
|
|
7
|
+
* test results:
|
|
8
|
+
*
|
|
9
|
+
* - startup crashes from the mozlog resource monitor on macOS
|
|
10
|
+
* (`AttributeError: 'SystemResourceMonitor' object has no attribute
|
|
11
|
+
* 'poll_interval'`, `host_statistics64(HOST_VM_INFO64) syscall failed`,
|
|
12
|
+
* `(ipc/mig) array not large enough` — a psutil/macOS mismatch) which
|
|
13
|
+
* abort the run before any test executes;
|
|
14
|
+
* - hangs after browser startup that die at the no-output timeout yet
|
|
15
|
+
* still emit a `Passed: 0` summary;
|
|
16
|
+
* - post-green shutdown re-entry, where a fully green run stalls on
|
|
17
|
+
* "must wait for focus" and records "Application shut down (without
|
|
18
|
+
* crashing) in the middle of a test!" as the only unexpected failure.
|
|
19
|
+
*
|
|
20
|
+
* Classification therefore keys on `TEST-START` presence — summary lines
|
|
21
|
+
* never count as proof that tests ran — and recognizes the crash shapes
|
|
22
|
+
* above so the command layer can retry them with a bounded budget instead
|
|
23
|
+
* of reporting phantom test failures (or phantom passes).
|
|
24
|
+
*/
|
|
25
|
+
const TEST_START_PATTERN = /\bTEST-START\b/;
|
|
26
|
+
const UNEXPECTED_LINE_PATTERN = /^.*\bTEST-UNEXPECTED-[A-Z-]+\b.*$/gm;
|
|
27
|
+
const SHUTDOWN_REENTRY_PATTERN = /Application shut down \(without crashing\) in the middle of a test/i;
|
|
28
|
+
const FOCUS_STALL_PATTERN = /must wait for focus/i;
|
|
29
|
+
const TRACEBACK_PATTERN = /Traceback \(most recent call last\)/;
|
|
30
|
+
const NO_OUTPUT_TIMEOUT_PATTERN = /timed out after \d+ seconds with no output/i;
|
|
31
|
+
/**
|
|
32
|
+
* Startup-traceback fingerprints from the mozlog resource monitor / psutil
|
|
33
|
+
* on macOS. Each is matched per-line so the evidence line in the report is
|
|
34
|
+
* the concrete failure, not the whole traceback.
|
|
35
|
+
*/
|
|
36
|
+
const STARTUP_TRACEBACK_SIGNALS = [
|
|
37
|
+
/AttributeError:.*SystemResourceMonitor/,
|
|
38
|
+
/'SystemResourceMonitor' object has no attribute/,
|
|
39
|
+
/poll_interval/,
|
|
40
|
+
/host_statistics64/,
|
|
41
|
+
/HOST_VM_INFO64/,
|
|
42
|
+
/\(ipc\/mig\) array not large enough/,
|
|
43
|
+
/psutil\.[A-Za-z]*Error/,
|
|
44
|
+
];
|
|
45
|
+
function findLine(output, patterns) {
|
|
46
|
+
for (const line of output.split(/\r?\n/)) {
|
|
47
|
+
if (patterns.some((p) => p.test(line)))
|
|
48
|
+
return line.trim();
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/** Unexpected-failure lines that are NOT the shutdown re-entry artifact. */
|
|
53
|
+
function realUnexpectedFailureLines(output) {
|
|
54
|
+
const matches = output.match(UNEXPECTED_LINE_PATTERN) ?? [];
|
|
55
|
+
return matches.filter((line) => !SHUTDOWN_REENTRY_PATTERN.test(line));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Detects the known harness-crash shapes in captured mach output.
|
|
59
|
+
* Returns undefined for anything that looks like a genuine test result.
|
|
60
|
+
*/
|
|
61
|
+
export function detectHarnessCrashSignature(output) {
|
|
62
|
+
const hasTestStart = TEST_START_PATTERN.test(output);
|
|
63
|
+
const realFailures = realUnexpectedFailureLines(output);
|
|
64
|
+
// Startup traceback cluster (resource monitor / psutil). Real test
|
|
65
|
+
// failures take precedence: a traceback printed during teardown of a
|
|
66
|
+
// genuinely failing run must not get the whole run retried.
|
|
67
|
+
if (TRACEBACK_PATTERN.test(output) && realFailures.length === 0) {
|
|
68
|
+
const signalLine = findLine(output, STARTUP_TRACEBACK_SIGNALS);
|
|
69
|
+
if (signalLine) {
|
|
70
|
+
return { reason: 'harness startup traceback (resource monitor/psutil)', line: signalLine };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Post-browser-startup hang: no test ever started, the harness died at
|
|
74
|
+
// the no-output timeout. A trailing "Passed: 0" summary is part of this
|
|
75
|
+
// shape and must not be read as a result.
|
|
76
|
+
if (!hasTestStart) {
|
|
77
|
+
const timeoutLine = findLine(output, [NO_OUTPUT_TIMEOUT_PATTERN]);
|
|
78
|
+
if (timeoutLine) {
|
|
79
|
+
return { reason: 'no-output timeout before any test started', line: timeoutLine };
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
// Post-green shutdown re-entry: every unexpected line is the
|
|
84
|
+
// shutdown-mid-test artifact, the run stalled on focus, and at least one
|
|
85
|
+
// such artifact exists — an otherwise green log.
|
|
86
|
+
const shutdownLine = findLine(output, [SHUTDOWN_REENTRY_PATTERN]);
|
|
87
|
+
if (shutdownLine && realFailures.length === 0 && FOCUS_STALL_PATTERN.test(output)) {
|
|
88
|
+
return { reason: 'post-green shutdown re-entry during harness teardown', line: shutdownLine };
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Classifies a completed harness run. The decision tree, in order:
|
|
94
|
+
*
|
|
95
|
+
* 1. A recognized crash signature wins regardless of exit code (the
|
|
96
|
+
* shutdown re-entry shape exits non-zero on an otherwise green run;
|
|
97
|
+
* the hang shape can even exit zero with a `Passed: 0` summary).
|
|
98
|
+
* 2. No `TEST-START` with explicit paths requested means no test ran —
|
|
99
|
+
* `no-tests`, even when the exit code is zero. Summary lines are not
|
|
100
|
+
* trusted as evidence of execution.
|
|
101
|
+
* 3. Exit code zero with tests started is a pass; anything else is a
|
|
102
|
+
* test failure for the regular diagnosis chain.
|
|
103
|
+
*/
|
|
104
|
+
export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
105
|
+
const signature = detectHarnessCrashSignature(output);
|
|
106
|
+
if (signature) {
|
|
107
|
+
return { kind: 'harness-crash', signature };
|
|
108
|
+
}
|
|
109
|
+
if (!TEST_START_PATTERN.test(output) && requestedPaths.length > 0) {
|
|
110
|
+
return { kind: 'no-tests' };
|
|
111
|
+
}
|
|
112
|
+
return exitCode === 0 ? { kind: 'tests-ran-ok' } : { kind: 'test-failures' };
|
|
113
|
+
}
|
|
114
|
+
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
115
|
+
export function buildHarnessCrashMessage(signature, attempts) {
|
|
116
|
+
return (`mach test crashed in the harness itself (not in your tests) on all ${attempts} attempt(s).\n\n` +
|
|
117
|
+
`Detected shape: ${signature.reason}\n` +
|
|
118
|
+
`Evidence line: ${signature.line}\n\n` +
|
|
119
|
+
'This failure mode is environmental (mozlog resource monitor / psutil on macOS, focus-stall ' +
|
|
120
|
+
'shutdown re-entry, or a pre-test hang) rather than a test regression. Re-run the command, ' +
|
|
121
|
+
'raise the retry budget with --harness-retries <n>, or run the file in isolation. ' +
|
|
122
|
+
'If it persists across many runs, inspect the mach virtualenv (mach resyncs psutil on its own; ' +
|
|
123
|
+
'patching it manually does not stick).');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Builds the message for a run that produced no `TEST-START` despite
|
|
127
|
+
* requesting paths — including exit-code-zero runs whose `Passed: 0`
|
|
128
|
+
* summary would otherwise read as a silent false green.
|
|
129
|
+
*/
|
|
130
|
+
export function buildNoTestsRanMessage(exitCode, requestedPaths) {
|
|
131
|
+
const exitNote = exitCode === 0
|
|
132
|
+
? 'The harness exited 0 and may have printed a summary line, but a summary without a single TEST-START is not a test result.'
|
|
133
|
+
: `The harness exited ${exitCode} before any TEST-START line.`;
|
|
134
|
+
return ('mach test finished without starting any of the requested tests.\n\n' +
|
|
135
|
+
`${exitNote}\n\n` +
|
|
136
|
+
`Requested paths: ${requestedPaths.join(', ')}\n\n` +
|
|
137
|
+
'Check that the paths are registered in their test manifest (browser.toml / xpcshell.toml) ' +
|
|
138
|
+
'and that the manifest is reachable from moz.build, then retry.');
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=test-harness-crash.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BuildBaseline } from './build-baseline.js';
|
|
1
|
+
import type { BuildBaseline } from './build-baseline-types.js';
|
|
2
2
|
/** Result of the stale-build preflight probe. */
|
|
3
3
|
export interface StaleBuildResult {
|
|
4
4
|
/** True when at least one packageable engine file changed since the baseline. */
|
|
@@ -30,53 +30,9 @@ import { toError } from '../utils/errors.js';
|
|
|
30
30
|
import { verbose } from '../utils/logger.js';
|
|
31
31
|
import { isPackageablePath } from './build-audit.js';
|
|
32
32
|
import { readBuildBaseline } from './build-baseline.js';
|
|
33
|
-
import {
|
|
34
|
-
import { git } from './git-base.js';
|
|
35
|
-
import { getUntrackedFiles } from './git-status.js';
|
|
33
|
+
import { collectChangedEnginePaths } from './engine-changes.js';
|
|
36
34
|
/** Cap on the number of changed paths rendered inline. */
|
|
37
35
|
const STALE_PATHS_LIMIT = 10;
|
|
38
|
-
/**
|
|
39
|
-
* Collects engine paths that changed since the baseline SHA plus any
|
|
40
|
-
* workdir modifications. Mirrors the helper inside `build-prepare.ts` but
|
|
41
|
-
* is kept separate so the test-side preflight does not need to pull in
|
|
42
|
-
* the full build-prepare dependency graph (mozconfig generation, furnace
|
|
43
|
-
* apply hooks, …).
|
|
44
|
-
*/
|
|
45
|
-
async function collectChangedEnginePaths(engineDir, baseline) {
|
|
46
|
-
const collected = new Set();
|
|
47
|
-
if (baseline.engineHeadSha) {
|
|
48
|
-
try {
|
|
49
|
-
const diff = await git(['diff', '--name-only', `${baseline.engineHeadSha}..HEAD`], engineDir);
|
|
50
|
-
for (const line of diff.split('\n')) {
|
|
51
|
-
const trimmed = line.trim();
|
|
52
|
-
if (trimmed)
|
|
53
|
-
collected.add(trimmed);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
if (!isMissingHeadError(error)) {
|
|
58
|
-
verbose(`Stale-build preflight: could not diff engine against baseline — ${toError(error).message}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
if (await hasChanges(engineDir)) {
|
|
64
|
-
const worktreeDiff = await git(['diff', '--name-only', 'HEAD'], engineDir);
|
|
65
|
-
for (const line of worktreeDiff.split('\n')) {
|
|
66
|
-
const trimmed = line.trim();
|
|
67
|
-
if (trimmed)
|
|
68
|
-
collected.add(trimmed);
|
|
69
|
-
}
|
|
70
|
-
for (const untracked of await getUntrackedFiles(engineDir)) {
|
|
71
|
-
collected.add(untracked);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
verbose(`Stale-build preflight: could not enumerate workdir changes — ${toError(error).message}`);
|
|
77
|
-
}
|
|
78
|
-
return [...collected];
|
|
79
|
-
}
|
|
80
36
|
/**
|
|
81
37
|
* Probes the engine tree for packageable changes since the last successful
|
|
82
38
|
* `fireforge build`. Returns a summary the `fireforge test` handler renders
|
|
@@ -92,7 +48,7 @@ export async function checkStaleBuildForTest(projectRoot, engineDir) {
|
|
|
92
48
|
if (!baseline) {
|
|
93
49
|
return { stale: false, changedPaths: [], truncated: 0, baseline: undefined };
|
|
94
50
|
}
|
|
95
|
-
const changed = await collectChangedEnginePaths(engineDir, baseline);
|
|
51
|
+
const changed = await collectChangedEnginePaths(engineDir, baseline, 'Stale-build preflight');
|
|
96
52
|
let packageable = changed.filter((path) => isPackageablePath(path)).sort();
|
|
97
53
|
// Content-hash comparison: when the baseline carries a fingerprint set,
|
|
98
54
|
// fold each candidate path through a live re-hash and drop paths whose
|
|
@@ -4,4 +4,4 @@ export interface XpcshellRetryClassification {
|
|
|
4
4
|
nonXpcshell: readonly string[];
|
|
5
5
|
}
|
|
6
6
|
/** Removes a stale xpcshell install symlink and retries the focused mach test once. */
|
|
7
|
-
export declare function retryAfterXpcshellSymlinkRepair(engineDir: string, objDir: string | undefined, result: MachCommandResult, classification: XpcshellRetryClassification, normalizedPaths: string[], extraArgs: string[]): Promise<MachCommandResult>;
|
|
7
|
+
export declare function retryAfterXpcshellSymlinkRepair(engineDir: string, objDir: string | undefined, result: MachCommandResult, classification: XpcshellRetryClassification, normalizedPaths: string[], extraArgs: string[], env?: Record<string, string>): Promise<MachCommandResult>;
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import { testWithOutput } from './mach.js';
|
|
3
3
|
import { tryRepairStaleXpcshellTestSymlink } from './test-stale-symlink.js';
|
|
4
4
|
/** Removes a stale xpcshell install symlink and retries the focused mach test once. */
|
|
5
|
-
export async function retryAfterXpcshellSymlinkRepair(engineDir, objDir, result, classification, normalizedPaths, extraArgs) {
|
|
5
|
+
export async function retryAfterXpcshellSymlinkRepair(engineDir, objDir, result, classification, normalizedPaths, extraArgs, env) {
|
|
6
6
|
if (result.exitCode !== 0 &&
|
|
7
7
|
classification.xpcshell.length > 0 &&
|
|
8
8
|
classification.nonXpcshell.length === 0) {
|
|
9
9
|
const repaired = await tryRepairStaleXpcshellTestSymlink(engineDir, objDir, `${result.stdout}\n${result.stderr}`);
|
|
10
10
|
if (repaired) {
|
|
11
|
-
return
|
|
11
|
+
return env
|
|
12
|
+
? testWithOutput(engineDir, normalizedPaths, extraArgs, env)
|
|
13
|
+
: testWithOutput(engineDir, normalizedPaths, extraArgs);
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
return result;
|
|
@@ -111,13 +111,22 @@ export function findDarkRootInsertionIndex(lines) {
|
|
|
111
111
|
}
|
|
112
112
|
if (rootOpenLine === -1)
|
|
113
113
|
return -1;
|
|
114
|
-
// Depth-count starting from the `:root` opener
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
// Depth-count starting from the `:root` opener; see findBlockCloseIndex.
|
|
115
|
+
return findBlockCloseIndex(stripped, rootOpenLine);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Depth-counts braces from `startLine` (whose lines must already have
|
|
119
|
+
* block comments stripped), returning the index of the line on which the
|
|
120
|
+
* block opened there returns to its entry depth — i.e. the line carrying
|
|
121
|
+
* the block's closing `}` — or -1 when the block never closes. The first
|
|
122
|
+
* `{` encountered sets the entry depth, so the scan may start on the
|
|
123
|
+
* selector/at-rule line itself rather than on the opener.
|
|
124
|
+
*/
|
|
125
|
+
function findBlockCloseIndex(stripped, startLine) {
|
|
117
126
|
let depth = 0;
|
|
118
127
|
let entryDepth = 0;
|
|
119
128
|
let enteredBlock = false;
|
|
120
|
-
for (let i =
|
|
129
|
+
for (let i = startLine; i < stripped.length; i++) {
|
|
121
130
|
const line = stripped[i] ?? '';
|
|
122
131
|
for (const ch of line) {
|
|
123
132
|
if (ch === '{') {
|
|
@@ -156,27 +165,6 @@ export function findDarkMediaCloseIndex(lines) {
|
|
|
156
165
|
}
|
|
157
166
|
if (darkMediaLine === -1)
|
|
158
167
|
return -1;
|
|
159
|
-
|
|
160
|
-
let entryDepth = 0;
|
|
161
|
-
let enteredBlock = false;
|
|
162
|
-
for (let i = darkMediaLine; i < stripped.length; i++) {
|
|
163
|
-
const line = stripped[i] ?? '';
|
|
164
|
-
for (const ch of line) {
|
|
165
|
-
if (ch === '{') {
|
|
166
|
-
depth++;
|
|
167
|
-
if (!enteredBlock) {
|
|
168
|
-
entryDepth = depth - 1;
|
|
169
|
-
enteredBlock = true;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (ch === '}') {
|
|
173
|
-
depth--;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (enteredBlock && depth === entryDepth) {
|
|
177
|
-
return i;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return -1;
|
|
168
|
+
return findBlockCloseIndex(stripped, darkMediaLine);
|
|
181
169
|
}
|
|
182
170
|
//# sourceMappingURL=token-dark-mode.js.map
|
|
@@ -20,6 +20,8 @@ export interface AddTokenOptions {
|
|
|
20
20
|
darkValue?: string | undefined;
|
|
21
21
|
/** Dry run mode */
|
|
22
22
|
dryRun?: boolean | undefined;
|
|
23
|
+
/** Declare the category banner in the tokens CSS when it does not exist yet. */
|
|
24
|
+
createCategory?: boolean | undefined;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Result of adding a token.
|
|
@@ -35,6 +37,8 @@ export interface AddTokenResult {
|
|
|
35
37
|
countUpdated: boolean;
|
|
36
38
|
/** Whether the operation was skipped (already exists) */
|
|
37
39
|
skipped: boolean;
|
|
40
|
+
/** Whether a new category banner was declared by this add. */
|
|
41
|
+
categoryCreated?: boolean;
|
|
38
42
|
}
|
|
39
43
|
/** Returns the token CSS path relative to engine root for a given binary name. */
|
|
40
44
|
export declare function getTokensCssPath(binaryName: string): string;
|