@hominis/fireforge 0.44.3 → 0.45.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/build.js +9 -4
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.js +2 -0
- package/dist/src/commands/export-flow.js +3 -18
- package/dist/src/commands/furnace/create-browser-test.d.ts +9 -0
- package/dist/src/commands/furnace/create-browser-test.js +6 -6
- package/dist/src/commands/furnace/create-dry-run.d.ts +10 -0
- package/dist/src/commands/furnace/create-dry-run.js +16 -11
- package/dist/src/commands/furnace/create-xpcshell.js +2 -2
- package/dist/src/commands/furnace/create.js +5 -0
- package/dist/src/commands/patch/move-files-into.js +3 -6
- package/dist/src/commands/patch/split-plan.js +3 -6
- package/dist/src/commands/re-export-adjacent.d.ts +8 -0
- package/dist/src/commands/re-export-adjacent.js +26 -1
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +61 -0
- package/dist/src/commands/re-export-files.js +3 -6
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +3 -6
- package/dist/src/commands/re-export.js +16 -16
- package/dist/src/commands/test-build-phase.js +3 -2
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-register.js +1 -0
- package/dist/src/commands/test-run.js +4 -3
- package/dist/src/commands/test-stale-gate.js +5 -2
- package/dist/src/commands/test.js +9 -2
- package/dist/src/core/build-audit.d.ts +20 -0
- package/dist/src/core/build-audit.js +69 -3
- package/dist/src/core/build-baseline-types.d.ts +28 -0
- package/dist/src/core/build-baseline.d.ts +16 -1
- package/dist/src/core/build-baseline.js +38 -2
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +127 -0
- package/dist/src/core/build-prepare.d.ts +12 -1
- package/dist/src/core/build-prepare.js +68 -6
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate.js +6 -0
- package/dist/src/core/engine-changes.d.ts +18 -0
- package/dist/src/core/engine-changes.js +34 -0
- package/dist/src/core/furnace-constants.d.ts +21 -0
- package/dist/src/core/furnace-constants.js +26 -1
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +186 -0
- package/dist/src/core/license-headers.d.ts +9 -5
- package/dist/src/core/license-headers.js +33 -7
- package/dist/src/core/mach-known-noise-filter.d.ts +21 -0
- package/dist/src/core/mach-known-noise-filter.js +24 -1
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +18 -2
- package/dist/src/core/patch-apply.js +49 -20
- package/dist/src/core/patch-lint-checkjs.js +6 -1
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.js +23 -24
- package/dist/src/core/patch-transform.d.ts +21 -0
- package/dist/src/core/patch-transform.js +38 -0
- package/dist/src/core/smoke-patterns.js +12 -0
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -6
- package/dist/src/core/test-stale-check.js +77 -15
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +49 -0
- package/dist/src/types/commands/options.d.ts +19 -0
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +41 -0
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +3 -1
- package/docs/configuration.md +47 -0
- package/docs/exit-codes.md +4 -2
- package/docs/testing.md +83 -0
- package/package.json +1 -1
|
@@ -12,12 +12,33 @@ export declare const JAR_MN = "toolkit/content/jar.mn";
|
|
|
12
12
|
export declare const WIDGETS_DIR = "toolkit/content/widgets";
|
|
13
13
|
/** Default Fluent localization directory for toolkit global components, relative to engine root */
|
|
14
14
|
export declare const FTL_DIR = "toolkit/locales/en-US/toolkit/global";
|
|
15
|
+
/**
|
|
16
|
+
* Engine-relative root every browser-chrome / xpcshell test scaffold lives
|
|
17
|
+
* under, trailing slash included. A `--test-dir` override must stay below
|
|
18
|
+
* it so the `browser/base/moz.build` manifest registration keeps working.
|
|
19
|
+
*/
|
|
20
|
+
export declare const BROWSER_TEST_SCAFFOLD_ROOT = "browser/base/content/test/";
|
|
15
21
|
/**
|
|
16
22
|
* Returns the engine-relative directory that holds xpcshell scaffolds for
|
|
17
23
|
* a given binary. Matches the form `create-xpcshell.ts` writes and the
|
|
18
24
|
* path `remove.ts` / `rename.ts` / `validate.ts` must clean up.
|
|
19
25
|
*/
|
|
20
26
|
export declare function xpcshellTestParentDir(binaryName: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the engine-relative directory a browser-chrome scaffold is
|
|
29
|
+
* written to: the `--test-dir` override when given, else
|
|
30
|
+
* `browser/base/content/test/<binaryName>`. The scaffolder AND the
|
|
31
|
+
* dry-run / success formatters resolve through this one function, so the
|
|
32
|
+
* printed plan cannot disagree with the files that land on disk.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveBrowserChromeTestDir(binaryName: string, override?: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the engine-relative directory an xpcshell scaffold is written
|
|
37
|
+
* to. A `--test-dir` override names the FINAL directory (no per-component
|
|
38
|
+
* segment is appended); the default is
|
|
39
|
+
* `browser/base/content/test/<binaryName>-xpcshell/<componentName>`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveXpcshellTestDir(binaryName: string, componentName: string, override?: string): string;
|
|
21
42
|
/** Returns true when `fileName` has one of the standard component file extensions. */
|
|
22
43
|
export declare function isComponentSourceFile(fileName: string): boolean;
|
|
23
44
|
/**
|
|
@@ -13,6 +13,12 @@ export const JAR_MN = 'toolkit/content/jar.mn';
|
|
|
13
13
|
export const WIDGETS_DIR = 'toolkit/content/widgets';
|
|
14
14
|
/** Default Fluent localization directory for toolkit global components, relative to engine root */
|
|
15
15
|
export const FTL_DIR = 'toolkit/locales/en-US/toolkit/global';
|
|
16
|
+
/**
|
|
17
|
+
* Engine-relative root every browser-chrome / xpcshell test scaffold lives
|
|
18
|
+
* under, trailing slash included. A `--test-dir` override must stay below
|
|
19
|
+
* it so the `browser/base/moz.build` manifest registration keeps working.
|
|
20
|
+
*/
|
|
21
|
+
export const BROWSER_TEST_SCAFFOLD_ROOT = 'browser/base/content/test/';
|
|
16
22
|
/**
|
|
17
23
|
* Suffix for the per-binary xpcshell scaffold parent directory. Components
|
|
18
24
|
* created with `furnace create --with-tests --xpcshell` land at
|
|
@@ -27,7 +33,26 @@ const XPCSHELL_TEST_DIR_SUFFIX = '-xpcshell';
|
|
|
27
33
|
* path `remove.ts` / `rename.ts` / `validate.ts` must clean up.
|
|
28
34
|
*/
|
|
29
35
|
export function xpcshellTestParentDir(binaryName) {
|
|
30
|
-
return
|
|
36
|
+
return `${BROWSER_TEST_SCAFFOLD_ROOT}${binaryName}${XPCSHELL_TEST_DIR_SUFFIX}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolves the engine-relative directory a browser-chrome scaffold is
|
|
40
|
+
* written to: the `--test-dir` override when given, else
|
|
41
|
+
* `browser/base/content/test/<binaryName>`. The scaffolder AND the
|
|
42
|
+
* dry-run / success formatters resolve through this one function, so the
|
|
43
|
+
* printed plan cannot disagree with the files that land on disk.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveBrowserChromeTestDir(binaryName, override) {
|
|
46
|
+
return override ?? `${BROWSER_TEST_SCAFFOLD_ROOT}${binaryName}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the engine-relative directory an xpcshell scaffold is written
|
|
50
|
+
* to. A `--test-dir` override names the FINAL directory (no per-component
|
|
51
|
+
* segment is appended); the default is
|
|
52
|
+
* `browser/base/content/test/<binaryName>-xpcshell/<componentName>`.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveXpcshellTestDir(binaryName, componentName, override) {
|
|
55
|
+
return override ?? `${xpcshellTestParentDir(binaryName)}/${componentName}`;
|
|
31
56
|
}
|
|
32
57
|
/** File extensions that constitute a Furnace component's source files. */
|
|
33
58
|
const COMPONENT_FILE_EXTENSIONS = ['.mjs', '.css', '.ftl'];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight census of ORPHANED test-harness helper processes.
|
|
3
|
+
*
|
|
4
|
+
* Stopping a long mochitest run mid-flight does not necessarily take its
|
|
5
|
+
* helpers with it. A downstream incident left `xpcshell` (the harness
|
|
6
|
+
* httpd, pegged at 100% CPU), `pywebsocket`, `ssltunnel` and `moz-http2`
|
|
7
|
+
* alive for an hour. Every subsequent run crawled — a three-second suite
|
|
8
|
+
* took six minutes of wall clock and audio-start waits timed out — and
|
|
9
|
+
* nothing in FireForge's output connected the slowness to the survivors.
|
|
10
|
+
* The hour was spent mis-attributing it to the change under test.
|
|
11
|
+
*
|
|
12
|
+
* Visibility alone would have collapsed that hour, which is why the census
|
|
13
|
+
* runs by default and the kill does not. Two rules keep it honest:
|
|
14
|
+
*
|
|
15
|
+
* - The census runs at PREFLIGHT, before this run spawns anything, so
|
|
16
|
+
* every matching process is by construction a survivor of an earlier
|
|
17
|
+
* run. No age heuristic is needed, and none is used.
|
|
18
|
+
* - A match must be anchored to a Firefox OBJDIR. `xpcshell` and
|
|
19
|
+
* `server.js` are far too generic on their own; the same rule
|
|
20
|
+
* `mochitest-server-port.ts` applies to the httpd holder applies here,
|
|
21
|
+
* for the same reason — FireForge has no business reporting, let alone
|
|
22
|
+
* terminating, a process it cannot attribute to the harness.
|
|
23
|
+
*
|
|
24
|
+
* `--reap-orphans` opts into termination. Without it the census is
|
|
25
|
+
* report-only, exactly like the `Orphaned harness workers` doctor check
|
|
26
|
+
* (which covers a DIFFERENT shape: reparented Python multiprocessing
|
|
27
|
+
* workers, matched on PPID 1 and accumulated CPU time).
|
|
28
|
+
*/
|
|
29
|
+
/** One surviving harness helper process. */
|
|
30
|
+
export interface OrphanedHarnessProcess {
|
|
31
|
+
pid: number;
|
|
32
|
+
ppid: number;
|
|
33
|
+
/** Raw `ps` ELAPSED column. */
|
|
34
|
+
elapsed: string;
|
|
35
|
+
/** Parsed elapsed seconds since the process started. */
|
|
36
|
+
elapsedSeconds: number;
|
|
37
|
+
command: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Scans `ps -axo pid=,ppid=,etime=,command=` output for surviving harness
|
|
41
|
+
* helpers. Pure — fixture-testable without spawning anything.
|
|
42
|
+
*
|
|
43
|
+
* @param psOutput - Raw `ps` output
|
|
44
|
+
* @param objDir - Absolute objdir of this project, when known; widens the
|
|
45
|
+
* provenance test beyond the generic `obj-…` path segment
|
|
46
|
+
* @param selfPid - This process's pid, excluded so FireForge cannot report
|
|
47
|
+
* itself
|
|
48
|
+
*/
|
|
49
|
+
export declare function findOrphanedHarnessProcesses(psOutput: string, objDir?: string, selfPid?: number): OrphanedHarnessProcess[];
|
|
50
|
+
/**
|
|
51
|
+
* Renders the census. Names every process with its elapsed time, because
|
|
52
|
+
* "a leftover harness process exists" and "a leftover harness process has
|
|
53
|
+
* been burning a core for 58 minutes" are different findings.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatOrphanReport(orphans: readonly OrphanedHarnessProcess[]): string;
|
|
56
|
+
/**
|
|
57
|
+
* Preflight census of surviving harness helpers, run before a test
|
|
58
|
+
* dispatch.
|
|
59
|
+
*
|
|
60
|
+
* Best-effort by construction: a host without a usable `ps` (Windows, a
|
|
61
|
+
* locked-down container) logs at verbose and runs exactly as before. It
|
|
62
|
+
* never refuses a run — unlike the server-port preflight, a survivor here
|
|
63
|
+
* degrades performance rather than making the run dispatch against the
|
|
64
|
+
* wrong server, so the correct response is to say so loudly, not to stop.
|
|
65
|
+
*
|
|
66
|
+
* @param objDir - Absolute objdir of this project, when known
|
|
67
|
+
* @param options - `reap` terminates each recognized survivor (SIGTERM,
|
|
68
|
+
* then SIGKILL for anything that stays)
|
|
69
|
+
* @returns The census, empty when nothing was found or nothing could be probed
|
|
70
|
+
*/
|
|
71
|
+
export declare function reportOrphanedHarnessProcesses(objDir: string | undefined, options?: {
|
|
72
|
+
reap?: boolean;
|
|
73
|
+
}): Promise<OrphanedHarnessProcess[]>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Preflight census of ORPHANED test-harness helper processes.
|
|
4
|
+
*
|
|
5
|
+
* Stopping a long mochitest run mid-flight does not necessarily take its
|
|
6
|
+
* helpers with it. A downstream incident left `xpcshell` (the harness
|
|
7
|
+
* httpd, pegged at 100% CPU), `pywebsocket`, `ssltunnel` and `moz-http2`
|
|
8
|
+
* alive for an hour. Every subsequent run crawled — a three-second suite
|
|
9
|
+
* took six minutes of wall clock and audio-start waits timed out — and
|
|
10
|
+
* nothing in FireForge's output connected the slowness to the survivors.
|
|
11
|
+
* The hour was spent mis-attributing it to the change under test.
|
|
12
|
+
*
|
|
13
|
+
* Visibility alone would have collapsed that hour, which is why the census
|
|
14
|
+
* runs by default and the kill does not. Two rules keep it honest:
|
|
15
|
+
*
|
|
16
|
+
* - The census runs at PREFLIGHT, before this run spawns anything, so
|
|
17
|
+
* every matching process is by construction a survivor of an earlier
|
|
18
|
+
* run. No age heuristic is needed, and none is used.
|
|
19
|
+
* - A match must be anchored to a Firefox OBJDIR. `xpcshell` and
|
|
20
|
+
* `server.js` are far too generic on their own; the same rule
|
|
21
|
+
* `mochitest-server-port.ts` applies to the httpd holder applies here,
|
|
22
|
+
* for the same reason — FireForge has no business reporting, let alone
|
|
23
|
+
* terminating, a process it cannot attribute to the harness.
|
|
24
|
+
*
|
|
25
|
+
* `--reap-orphans` opts into termination. Without it the census is
|
|
26
|
+
* report-only, exactly like the `Orphaned harness workers` doctor check
|
|
27
|
+
* (which covers a DIFFERENT shape: reparented Python multiprocessing
|
|
28
|
+
* workers, matched on PPID 1 and accumulated CPU time).
|
|
29
|
+
*/
|
|
30
|
+
import { toError } from '../utils/errors.js';
|
|
31
|
+
import { info, verbose, warn } from '../utils/logger.js';
|
|
32
|
+
import { exec } from '../utils/process.js';
|
|
33
|
+
import { parsePsDuration } from '../utils/ps-duration.js';
|
|
34
|
+
/**
|
|
35
|
+
* Harness helper executables/scripts. Every one of these is started by the
|
|
36
|
+
* mochitest/xpcshell harness and is expected to die with it; none of them
|
|
37
|
+
* is a thing a developer runs by hand.
|
|
38
|
+
*/
|
|
39
|
+
const HARNESS_HELPER_PATTERN = /\b(?:xpcshell|pywebsocket\w*|websocket_server\.py|ssltunnel|moz-http2|http2_server|httpd\.js|server\.js|runtests\.py|runxpcshelltests\.py)\b/;
|
|
40
|
+
/** Objdir provenance: an explicit objdir path, a `obj-…` path segment, or `_tests/`. */
|
|
41
|
+
function isObjdirAnchored(command, objDir) {
|
|
42
|
+
if (objDir !== undefined && objDir.length > 0 && command.includes(objDir))
|
|
43
|
+
return true;
|
|
44
|
+
if (/[/\\]obj-[^/\\\s]*[/\\]/.test(command))
|
|
45
|
+
return true;
|
|
46
|
+
return /[/\\]_tests[/\\]/.test(command);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Scans `ps -axo pid=,ppid=,etime=,command=` output for surviving harness
|
|
50
|
+
* helpers. Pure — fixture-testable without spawning anything.
|
|
51
|
+
*
|
|
52
|
+
* @param psOutput - Raw `ps` output
|
|
53
|
+
* @param objDir - Absolute objdir of this project, when known; widens the
|
|
54
|
+
* provenance test beyond the generic `obj-…` path segment
|
|
55
|
+
* @param selfPid - This process's pid, excluded so FireForge cannot report
|
|
56
|
+
* itself
|
|
57
|
+
*/
|
|
58
|
+
export function findOrphanedHarnessProcesses(psOutput, objDir, selfPid = process.pid) {
|
|
59
|
+
const found = [];
|
|
60
|
+
for (const line of psOutput.split('\n')) {
|
|
61
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/.exec(line);
|
|
62
|
+
if (!match)
|
|
63
|
+
continue;
|
|
64
|
+
const pid = Number(match[1]);
|
|
65
|
+
const ppid = Number(match[2]);
|
|
66
|
+
const elapsed = match[3] ?? '';
|
|
67
|
+
const command = (match[4] ?? '').trim();
|
|
68
|
+
if (pid === selfPid || ppid === selfPid)
|
|
69
|
+
continue;
|
|
70
|
+
if (!HARNESS_HELPER_PATTERN.test(command))
|
|
71
|
+
continue;
|
|
72
|
+
if (!isObjdirAnchored(command, objDir))
|
|
73
|
+
continue;
|
|
74
|
+
const elapsedSeconds = parsePsDuration(elapsed);
|
|
75
|
+
found.push({
|
|
76
|
+
pid,
|
|
77
|
+
ppid,
|
|
78
|
+
elapsed,
|
|
79
|
+
elapsedSeconds: Number.isNaN(elapsedSeconds) ? 0 : elapsedSeconds,
|
|
80
|
+
command,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return found;
|
|
84
|
+
}
|
|
85
|
+
/** Grace period between SIGTERM and SIGKILL when reaping. */
|
|
86
|
+
const REAP_GRACE_MS = 500;
|
|
87
|
+
/** Longest command excerpt carried into the report line. */
|
|
88
|
+
const COMMAND_EXCERPT_LIMIT = 160;
|
|
89
|
+
/**
|
|
90
|
+
* Renders the census. Names every process with its elapsed time, because
|
|
91
|
+
* "a leftover harness process exists" and "a leftover harness process has
|
|
92
|
+
* been burning a core for 58 minutes" are different findings.
|
|
93
|
+
*/
|
|
94
|
+
export function formatOrphanReport(orphans) {
|
|
95
|
+
const rows = orphans
|
|
96
|
+
.map((p) => ` PID ${String(p.pid)} (up ${p.elapsed}): ${p.command.slice(0, COMMAND_EXCERPT_LIMIT)}`)
|
|
97
|
+
.join('\n');
|
|
98
|
+
const pids = orphans.map((p) => String(p.pid)).join(' ');
|
|
99
|
+
return (`${String(orphans.length)} harness helper process(es) from an EARLIER run are still alive ` +
|
|
100
|
+
`(this preflight runs before the current run spawns anything, so none of these belong to ` +
|
|
101
|
+
`it):\n${rows}\n` +
|
|
102
|
+
`Survivors like these slow every later run without appearing anywhere in its output — a ` +
|
|
103
|
+
`three-second suite taking minutes of wall clock is the usual symptom. Terminate them with ` +
|
|
104
|
+
`"kill ${pids}", or re-run with --reap-orphans to have FireForge do it.`);
|
|
105
|
+
}
|
|
106
|
+
/** Thin exec wrapper for the process listing (tests mock `exec` instead). */
|
|
107
|
+
async function listSystemProcesses() {
|
|
108
|
+
const result = await exec('ps', ['-axo', 'pid=,ppid=,etime=,command='], { timeout: 10000 });
|
|
109
|
+
if (result.exitCode !== 0) {
|
|
110
|
+
throw new Error(`ps exited ${String(result.exitCode)}`);
|
|
111
|
+
}
|
|
112
|
+
return result.stdout;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Preflight census of surviving harness helpers, run before a test
|
|
116
|
+
* dispatch.
|
|
117
|
+
*
|
|
118
|
+
* Best-effort by construction: a host without a usable `ps` (Windows, a
|
|
119
|
+
* locked-down container) logs at verbose and runs exactly as before. It
|
|
120
|
+
* never refuses a run — unlike the server-port preflight, a survivor here
|
|
121
|
+
* degrades performance rather than making the run dispatch against the
|
|
122
|
+
* wrong server, so the correct response is to say so loudly, not to stop.
|
|
123
|
+
*
|
|
124
|
+
* @param objDir - Absolute objdir of this project, when known
|
|
125
|
+
* @param options - `reap` terminates each recognized survivor (SIGTERM,
|
|
126
|
+
* then SIGKILL for anything that stays)
|
|
127
|
+
* @returns The census, empty when nothing was found or nothing could be probed
|
|
128
|
+
*/
|
|
129
|
+
export async function reportOrphanedHarnessProcesses(objDir, options = {}) {
|
|
130
|
+
if (process.platform === 'win32')
|
|
131
|
+
return [];
|
|
132
|
+
let psOutput;
|
|
133
|
+
try {
|
|
134
|
+
psOutput = await listSystemProcesses();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
verbose(`Orphan preflight: could not scan processes (${toError(error).message}); skipping.`);
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const orphans = findOrphanedHarnessProcesses(psOutput, objDir);
|
|
141
|
+
if (orphans.length === 0) {
|
|
142
|
+
verbose('Orphan preflight: no surviving harness helper processes.');
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
warn(formatOrphanReport(orphans));
|
|
146
|
+
if (options.reap === true) {
|
|
147
|
+
await reapOrphanedHarnessProcesses(orphans);
|
|
148
|
+
}
|
|
149
|
+
return orphans;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Terminates each census entry: SIGTERM first, then SIGKILL for anything
|
|
153
|
+
* still alive. Failures are reported, never thrown — a process that exited
|
|
154
|
+
* between the census and the signal is the common case, not an error.
|
|
155
|
+
*/
|
|
156
|
+
async function reapOrphanedHarnessProcesses(orphans) {
|
|
157
|
+
for (const orphan of orphans) {
|
|
158
|
+
try {
|
|
159
|
+
process.kill(orphan.pid, 'SIGTERM');
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
verbose(`--reap-orphans: SIGTERM to ${String(orphan.pid)} failed (${toError(error).message}).`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Grace period before escalating: the httpd shape in the field
|
|
166
|
+
// incident ignored SIGTERM while spinning, but an ordinary helper exits
|
|
167
|
+
// promptly and must not be SIGKILLed for being slow by a millisecond.
|
|
168
|
+
await new Promise((resolve) => setTimeout(resolve, REAP_GRACE_MS));
|
|
169
|
+
let alive = true;
|
|
170
|
+
try {
|
|
171
|
+
process.kill(orphan.pid, 0);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
alive = false;
|
|
175
|
+
}
|
|
176
|
+
if (alive) {
|
|
177
|
+
try {
|
|
178
|
+
process.kill(orphan.pid, 'SIGKILL');
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
verbose(`--reap-orphans: SIGKILL to ${String(orphan.pid)} failed (${toError(error).message}).`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
info(`--reap-orphans: terminated PID ${String(orphan.pid)}.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -69,7 +69,9 @@ export declare function hasUpstreamMplBlockHeader(content: string): boolean;
|
|
|
69
69
|
export declare function hasAnyLicenseHeaderAnyStyle(content: string): boolean;
|
|
70
70
|
/**
|
|
71
71
|
* Returns true if the first few lines of `content` contain a recognized
|
|
72
|
-
* upstream license identifier string
|
|
72
|
+
* upstream license identifier string — Mozilla's MPL boilerplate, an SPDX
|
|
73
|
+
* tag, the common permissive/copyleft license names, or the CC0
|
|
74
|
+
* public-domain dedication upstream test files carry.
|
|
73
75
|
*
|
|
74
76
|
* @param content - File content to check
|
|
75
77
|
* @param maxLines - Number of leading lines to inspect (default 10)
|
|
@@ -77,10 +79,12 @@ export declare function hasAnyLicenseHeaderAnyStyle(content: string): boolean;
|
|
|
77
79
|
export declare function containsUpstreamLicenseText(content: string, maxLines?: number): boolean;
|
|
78
80
|
/**
|
|
79
81
|
* Returns true when the head of `content` carries a recognized THIRD-PARTY
|
|
80
|
-
* permissive license banner (MIT / ISC / BSD-2 / BSD-3 / Apache-2.0)
|
|
81
|
-
* comment style. Used by `export` to
|
|
82
|
-
* to prepend the project's own
|
|
83
|
-
* upstream bundle
|
|
82
|
+
* permissive license banner (MIT / ISC / BSD-2 / BSD-3 / Apache-2.0) or the
|
|
83
|
+
* CC0 public-domain dedication, in any comment style. Used by `export` to
|
|
84
|
+
* treat such files as vendored: offering to prepend the project's own
|
|
85
|
+
* license header onto a byte-identity-pinned upstream bundle — or onto a
|
|
86
|
+
* test file Mozilla dedicated to the public domain — would mislicense code
|
|
87
|
+
* the project does not own.
|
|
84
88
|
*
|
|
85
89
|
* @param content - File content to check
|
|
86
90
|
* @param maxLines - Number of leading lines to inspect (default 30 — full
|
|
@@ -227,9 +227,31 @@ function normalizeLicenseHeadForScan(head) {
|
|
|
227
227
|
s = s.replace(/\n[ \t]*\*[ \t]*/g, ' ');
|
|
228
228
|
return s.replace(/\s+/g, ' ').trim();
|
|
229
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Recognizes the CC0 public-domain dedication Mozilla puts on most of its
|
|
232
|
+
* test files, in every spelling the tree carries:
|
|
233
|
+
*
|
|
234
|
+
* - the prose (`Any copyright is dedicated to the Public Domain.`), which
|
|
235
|
+
* the block, `//` and `#` comment forms all reduce to after
|
|
236
|
+
* {@link normalizeLicenseHeadForScan};
|
|
237
|
+
* - the dedication URL (`creativecommons.org/publicdomain/zero/1.0/`, with
|
|
238
|
+
* either scheme);
|
|
239
|
+
* - the SPDX identifier `CC0-1.0`.
|
|
240
|
+
*
|
|
241
|
+
* Deliberately narrow: a bare "public domain" mention, a different Creative
|
|
242
|
+
* Commons license (`licenses/by/4.0/`, `CC-BY-4.0`) or a made-up
|
|
243
|
+
* `publicdomain/zero/2.0/` is NOT a CC0 dedication and must not be
|
|
244
|
+
* accepted as one. `scanText` must already be normalized (and may be
|
|
245
|
+
* lowercased — the match is case-insensitive).
|
|
246
|
+
*/
|
|
247
|
+
function hasCc0Dedication(scanText) {
|
|
248
|
+
return /dedicated to the public domain|creativecommons\.org\/publicdomain\/zero\/1\.0|spdx-license-identifier:\s*cc0-1\.0\b/i.test(scanText);
|
|
249
|
+
}
|
|
230
250
|
/**
|
|
231
251
|
* Returns true if the first few lines of `content` contain a recognized
|
|
232
|
-
* upstream license identifier string
|
|
252
|
+
* upstream license identifier string — Mozilla's MPL boilerplate, an SPDX
|
|
253
|
+
* tag, the common permissive/copyleft license names, or the CC0
|
|
254
|
+
* public-domain dedication upstream test files carry.
|
|
233
255
|
*
|
|
234
256
|
* @param content - File content to check
|
|
235
257
|
* @param maxLines - Number of leading lines to inspect (default 10)
|
|
@@ -244,14 +266,16 @@ export function containsUpstreamLicenseText(content, maxLines = 10) {
|
|
|
244
266
|
'MIT License',
|
|
245
267
|
'GNU General Public License',
|
|
246
268
|
];
|
|
247
|
-
return markers.some((marker) => scanText.includes(marker));
|
|
269
|
+
return markers.some((marker) => scanText.includes(marker)) || hasCc0Dedication(scanText);
|
|
248
270
|
}
|
|
249
271
|
/**
|
|
250
272
|
* Returns true when the head of `content` carries a recognized THIRD-PARTY
|
|
251
|
-
* permissive license banner (MIT / ISC / BSD-2 / BSD-3 / Apache-2.0)
|
|
252
|
-
* comment style. Used by `export` to
|
|
253
|
-
* to prepend the project's own
|
|
254
|
-
* upstream bundle
|
|
273
|
+
* permissive license banner (MIT / ISC / BSD-2 / BSD-3 / Apache-2.0) or the
|
|
274
|
+
* CC0 public-domain dedication, in any comment style. Used by `export` to
|
|
275
|
+
* treat such files as vendored: offering to prepend the project's own
|
|
276
|
+
* license header onto a byte-identity-pinned upstream bundle — or onto a
|
|
277
|
+
* test file Mozilla dedicated to the public domain — would mislicense code
|
|
278
|
+
* the project does not own.
|
|
255
279
|
*
|
|
256
280
|
* @param content - File content to check
|
|
257
281
|
* @param maxLines - Number of leading lines to inspect (default 30 — full
|
|
@@ -271,7 +295,9 @@ export function hasThirdPartyPermissiveBanner(content, maxLines = 30) {
|
|
|
271
295
|
];
|
|
272
296
|
if (markers.some((marker) => scanText.includes(marker)))
|
|
273
297
|
return true;
|
|
274
|
-
|
|
298
|
+
if (hasCc0Dedication(scanText))
|
|
299
|
+
return true;
|
|
300
|
+
return /spdx-license-identifier:\s*(mit|isc|bsd-2-clause|bsd-3-clause|apache-2\.0|cc0-1\.0)\b/.test(scanText);
|
|
275
301
|
}
|
|
276
302
|
/**
|
|
277
303
|
* Prepends the license header to a file on disk if it is not already present.
|
|
@@ -25,6 +25,20 @@
|
|
|
25
25
|
* echoed verbatim, always.
|
|
26
26
|
* - The hold buffer is bounded; on overflow the block is flushed verbatim
|
|
27
27
|
* and the filter returns to pass-through, so output is never lost.
|
|
28
|
+
*
|
|
29
|
+
* The COLLAPSE is test-only, deliberately, and not an oversight — the two
|
|
30
|
+
* reasons are worth stating because from outside it reads like one:
|
|
31
|
+
* 1. Recognition requires a previously-seen SUITE_END shutdown marker, and
|
|
32
|
+
* `mach build` never prints one. Wiring the option into the build
|
|
33
|
+
* dispatch would therefore be a silent no-op.
|
|
34
|
+
* 2. Relaxing that gate for builds would be wrong on its own terms. The
|
|
35
|
+
* marker is what separates teardown noise from a traceback raised while
|
|
36
|
+
* work was still happening; a build has no equivalent boundary, so
|
|
37
|
+
* FireForge cannot tell the known-cosmetic case from a real build-time
|
|
38
|
+
* failure and must not withhold the block.
|
|
39
|
+
* The build path instead RECOGNIZES the signature and says so beside the
|
|
40
|
+
* verbatim traceback — see {@link KNOWN_TEARDOWN_NOISE_BUILD_NOTE} and
|
|
41
|
+
* `runProtectedMachBuild`.
|
|
28
42
|
*/
|
|
29
43
|
/**
|
|
30
44
|
* True when CAPTURED output carries the documented mozsystemmonitor
|
|
@@ -76,6 +90,13 @@ export interface TeardownNoiseContext {
|
|
|
76
90
|
}
|
|
77
91
|
/** Fresh per-run context — hand the same instance to both stream filters. */
|
|
78
92
|
export declare function createTeardownNoiseContext(): TeardownNoiseContext;
|
|
93
|
+
/**
|
|
94
|
+
* Build-phase note for the SAME recognized signature. The build path does
|
|
95
|
+
* not COLLAPSE the traceback (see the module doc's "test-only" note) — it
|
|
96
|
+
* prints verbatim and this line is added beside it, so one signature reads
|
|
97
|
+
* the same in both phases without a build losing output it may need.
|
|
98
|
+
*/
|
|
99
|
+
export declare const KNOWN_TEARDOWN_NOISE_BUILD_NOTE: string;
|
|
79
100
|
/** One line replaces the whole recognized traceback block in the echo. */
|
|
80
101
|
export declare const KNOWN_TEARDOWN_NOISE_ANNOTATION: string;
|
|
81
102
|
/** Chunk-safe, line-buffered echo filter. See module doc. */
|
|
@@ -26,6 +26,20 @@
|
|
|
26
26
|
* echoed verbatim, always.
|
|
27
27
|
* - The hold buffer is bounded; on overflow the block is flushed verbatim
|
|
28
28
|
* and the filter returns to pass-through, so output is never lost.
|
|
29
|
+
*
|
|
30
|
+
* The COLLAPSE is test-only, deliberately, and not an oversight — the two
|
|
31
|
+
* reasons are worth stating because from outside it reads like one:
|
|
32
|
+
* 1. Recognition requires a previously-seen SUITE_END shutdown marker, and
|
|
33
|
+
* `mach build` never prints one. Wiring the option into the build
|
|
34
|
+
* dispatch would therefore be a silent no-op.
|
|
35
|
+
* 2. Relaxing that gate for builds would be wrong on its own terms. The
|
|
36
|
+
* marker is what separates teardown noise from a traceback raised while
|
|
37
|
+
* work was still happening; a build has no equivalent boundary, so
|
|
38
|
+
* FireForge cannot tell the known-cosmetic case from a real build-time
|
|
39
|
+
* failure and must not withhold the block.
|
|
40
|
+
* The build path instead RECOGNIZES the signature and says so beside the
|
|
41
|
+
* verbatim traceback — see {@link KNOWN_TEARDOWN_NOISE_BUILD_NOTE} and
|
|
42
|
+
* `runProtectedMachBuild`.
|
|
29
43
|
*/
|
|
30
44
|
const TRACEBACK_HEADER_PATTERN = /^Traceback \(most recent call last\)/;
|
|
31
45
|
const CHAINED_EXCEPTION_CONNECTOR_PATTERN = /^(?:During handling of the above exception, another exception occurred:|The above exception was the direct cause of the following exception:)\s*$/;
|
|
@@ -93,9 +107,18 @@ export function isKnownTeardownNoiseLine(line) {
|
|
|
93
107
|
export function createTeardownNoiseContext() {
|
|
94
108
|
return { shutdownSeen: false };
|
|
95
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Build-phase note for the SAME recognized signature. The build path does
|
|
112
|
+
* not COLLAPSE the traceback (see the module doc's "test-only" note) — it
|
|
113
|
+
* prints verbatim and this line is added beside it, so one signature reads
|
|
114
|
+
* the same in both phases without a build losing output it may need.
|
|
115
|
+
*/
|
|
116
|
+
export const KNOWN_TEARDOWN_NOISE_BUILD_NOTE = '[FireForge] The mozsystemmonitor traceback above is the known upstream ' +
|
|
117
|
+
'SystemResourceMonitor AttributeError — cosmetic, and not a build failure. It is left ' +
|
|
118
|
+
'verbatim here (unlike a test run, which collapses it); see docs/testing.md.';
|
|
96
119
|
/** One line replaces the whole recognized traceback block in the echo. */
|
|
97
120
|
export const KNOWN_TEARDOWN_NOISE_ANNOTATION = '[FireForge] Known upstream mozsystemmonitor teardown noise (SystemResourceMonitor ' +
|
|
98
|
-
'AttributeError at harness shutdown) — not a test failure. See
|
|
121
|
+
'AttributeError at harness shutdown) — not a test failure. See docs/testing.md.\n';
|
|
99
122
|
/** Bounds on the held traceback block before flushing verbatim. */
|
|
100
123
|
const HOLD_LINE_LIMIT = 100;
|
|
101
124
|
const HOLD_BYTE_LIMIT = 16 * 1024;
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -23,6 +23,13 @@ export interface MachOptions {
|
|
|
23
23
|
* stdout/stderr stay raw — the harness classifier depends on the raw
|
|
24
24
|
* traceback. Unrecognized tracebacks always echo verbatim. Opted into by
|
|
25
25
|
* the test dispatchers only.
|
|
26
|
+
*
|
|
27
|
+
* Consumed by {@link runMachCapture} ALONE. `runMachInheritCapture` accepts
|
|
28
|
+
* it in the type and ignores it — the inherit path has no per-chunk hook,
|
|
29
|
+
* only a mirror stream that feeds the terminal and the run log the same
|
|
30
|
+
* string, so filtering there would strip the traceback from the raw log
|
|
31
|
+
* too. The build path uses the recognition NOTE instead; see
|
|
32
|
+
* `mach-known-noise-filter.ts`.
|
|
26
33
|
*/
|
|
27
34
|
annotateKnownTeardownNoise?: boolean;
|
|
28
35
|
/**
|
package/dist/src/core/mach.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
3
|
import { MachNotFoundError } from '../errors/build.js';
|
|
4
4
|
import { pathExists } from '../utils/fs.js';
|
|
5
|
-
import { warn } from '../utils/logger.js';
|
|
5
|
+
import { info, warn } from '../utils/logger.js';
|
|
6
6
|
import { exec, execInherit, execInheritCapture, execSmokeRun, execStream, } from '../utils/process.js';
|
|
7
7
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
8
8
|
import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
|
|
9
9
|
import { explainMachError } from './mach-error-hints.js';
|
|
10
|
-
import { createKnownTeardownNoiseFilter, createTeardownNoiseContext, } from './mach-known-noise-filter.js';
|
|
10
|
+
import { createKnownTeardownNoiseFilter, createTeardownNoiseContext, hasKnownTeardownNoise, KNOWN_TEARDOWN_NOISE_BUILD_NOTE, } from './mach-known-noise-filter.js';
|
|
11
11
|
import { getPython } from './mach-python.js';
|
|
12
12
|
import { installMachResourceGuard } from './mach-resource-shim.js';
|
|
13
13
|
import { teeToRunLog, writeToActiveRunLog } from './run-log.js';
|
|
@@ -193,6 +193,20 @@ function surfaceMachErrorHints(result) {
|
|
|
193
193
|
* the same crash family the same number of times.
|
|
194
194
|
*/
|
|
195
195
|
const DEFAULT_BUILD_CRASH_RETRIES = 2;
|
|
196
|
+
/**
|
|
197
|
+
* Names the known mozsystemmonitor teardown traceback when a BUILD carried
|
|
198
|
+
* it. The test path collapses this signature in the echo; a build cannot
|
|
199
|
+
* (see `mach-known-noise-filter.ts`), so it reached operators as an
|
|
200
|
+
* unexplained traceback in a build log — a recognized, documented upstream
|
|
201
|
+
* defect wearing the appearance of a new one. The output is left verbatim
|
|
202
|
+
* and this one line is added beside it, so the same signature reads the same
|
|
203
|
+
* in both phases.
|
|
204
|
+
*/
|
|
205
|
+
function noteKnownTeardownNoiseInBuild(result) {
|
|
206
|
+
if (!hasKnownTeardownNoise(`${result.stdout}\n${result.stderr}`))
|
|
207
|
+
return;
|
|
208
|
+
info(KNOWN_TEARDOWN_NOISE_BUILD_NOTE);
|
|
209
|
+
}
|
|
196
210
|
/**
|
|
197
211
|
* The single protected path every FireForge mach build dispatch routes
|
|
198
212
|
* through — `build`, `build --ui`, and the pre-test `--build` step all use
|
|
@@ -222,6 +236,7 @@ export async function runProtectedMachBuild(kind, engineDir, options = {}) {
|
|
|
222
236
|
const { env } = await installMachResourceGuard(engineDir);
|
|
223
237
|
const result = await runMachInheritCapture(args, engineDir, { env });
|
|
224
238
|
if (result.exitCode === 0) {
|
|
239
|
+
noteKnownTeardownNoiseInBuild(result);
|
|
225
240
|
return { ...result, attempts: attempt };
|
|
226
241
|
}
|
|
227
242
|
const signature = detectHarnessCrashSignature(`${result.stdout}\n${result.stderr}`);
|
|
@@ -233,6 +248,7 @@ export async function runProtectedMachBuild(kind, engineDir, options = {}) {
|
|
|
233
248
|
continue;
|
|
234
249
|
}
|
|
235
250
|
surfaceMachErrorHints(result);
|
|
251
|
+
noteKnownTeardownNoiseInBuild(result);
|
|
236
252
|
return { ...result, attempts: attempt, ...(signature ? { crashSignature: signature } : {}) };
|
|
237
253
|
}
|
|
238
254
|
}
|