@hominis/fireforge 0.33.0 → 0.34.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 +14 -0
- package/dist/src/commands/build.js +9 -1
- package/dist/src/commands/doctor-furnace-jar.d.ts +16 -0
- package/dist/src/commands/doctor-furnace-jar.js +47 -0
- package/dist/src/commands/doctor-furnace.js +2 -0
- package/dist/src/commands/export-flow.js +4 -1
- package/dist/src/commands/export-placement-gate.js +4 -1
- package/dist/src/commands/export.js +61 -4
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +20 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +52 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +10 -0
- package/dist/src/commands/furnace/chrome-doc.js +31 -4
- package/dist/src/commands/furnace/create-browser-test.d.ts +30 -0
- package/dist/src/commands/furnace/create-browser-test.js +180 -0
- package/dist/src/commands/furnace/create-mochikit.js +11 -4
- package/dist/src/commands/furnace/create-xpcshell.d.ts +1 -1
- package/dist/src/commands/furnace/create-xpcshell.js +38 -12
- package/dist/src/commands/furnace/create.js +7 -114
- package/dist/src/commands/furnace/index.js +6 -1
- package/dist/src/commands/furnace/scan.d.ts +1 -0
- package/dist/src/commands/furnace/scan.js +57 -27
- package/dist/src/commands/furnace/validate.js +17 -1
- package/dist/src/commands/re-export-options.d.ts +26 -0
- package/dist/src/commands/re-export-options.js +48 -1
- package/dist/src/commands/re-export.js +4 -1
- package/dist/src/commands/register.js +10 -1
- package/dist/src/commands/test-diagnose.js +12 -0
- package/dist/src/commands/test.js +29 -22
- package/dist/src/core/furnace-registration.d.ts +24 -0
- package/dist/src/core/furnace-registration.js +56 -3
- package/dist/src/core/furnace-validate-registration.js +18 -0
- package/dist/src/core/mach-resource-shim.d.ts +53 -16
- package/dist/src/core/mach-resource-shim.js +249 -51
- package/dist/src/core/mach.d.ts +51 -10
- package/dist/src/core/mach.js +76 -32
- package/dist/src/core/manifest-helpers.d.ts +13 -0
- package/dist/src/core/manifest-helpers.js +1 -1
- package/dist/src/core/manifest-rules.d.ts +13 -3
- package/dist/src/core/manifest-rules.js +44 -17
- package/dist/src/core/patch-export.d.ts +10 -0
- package/dist/src/core/patch-export.js +23 -2
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-module.js +15 -2
- package/dist/src/core/register-result.d.ts +9 -0
- package/dist/src/core/register-scaffold.d.ts +55 -0
- package/dist/src/core/register-scaffold.js +146 -0
- package/dist/src/core/register-test-manifest.js +3 -1
- package/dist/src/core/register-xpcshell-test.d.ts +30 -0
- package/dist/src/core/register-xpcshell-test.js +96 -0
- package/dist/src/core/test-harness-crash.d.ts +26 -1
- package/dist/src/core/test-harness-crash.js +121 -11
- package/dist/src/core/xpcshell-appdir.d.ts +7 -0
- package/dist/src/core/xpcshell-appdir.js +12 -3
- package/dist/src/types/commands/options.d.ts +12 -0
- package/package.json +3 -3
|
@@ -23,6 +23,15 @@
|
|
|
23
23
|
* of reporting phantom test failures (or phantom passes).
|
|
24
24
|
*/
|
|
25
25
|
const TEST_START_PATTERN = /\bTEST-START\b/;
|
|
26
|
+
/**
|
|
27
|
+
* Structured-log execution marker (`mach xpcshell-test` mozlog output uses
|
|
28
|
+
* `TEST_START`/`SUITE_END` with underscores, not the hyphenated
|
|
29
|
+
* `TEST-START` the browser-chrome dispatch prints).
|
|
30
|
+
*/
|
|
31
|
+
const STRUCTURED_TEST_START_PATTERN = /\bTEST_START\b/;
|
|
32
|
+
const SUITE_END_PATTERN = /\bSUITE_END\b/;
|
|
33
|
+
const GREEN_UNEXPECTED_SUMMARY_PATTERN = /\bUnexpected results:\s*0\b/;
|
|
34
|
+
const NONZERO_UNEXPECTED_SUMMARY_PATTERN = /\bUnexpected results:\s*[1-9]/;
|
|
26
35
|
/**
|
|
27
36
|
* Execution signals emitted by the suite-specific xpcshell dispatch
|
|
28
37
|
* (`mach xpcshell-test`), which does NOT print `TEST-START` lines the way
|
|
@@ -63,6 +72,87 @@ function findLine(output, patterns) {
|
|
|
63
72
|
}
|
|
64
73
|
return undefined;
|
|
65
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Non-signal noise lines: the resource-monitor degrade path (FireForge's
|
|
77
|
+
* own guard plus mozlog's `_collect failed`) prints warnings on runs that
|
|
78
|
+
* then complete green. Field report (0.34.0 cycle): every multi-file suite
|
|
79
|
+
* was reported CRASH because these lines matched the startup-traceback
|
|
80
|
+
* signals even though the embedded summary was fully green. They are
|
|
81
|
+
* excluded from crash evidence entirely.
|
|
82
|
+
*/
|
|
83
|
+
const NOISE_LINE_PATTERNS = [
|
|
84
|
+
/\bUserWarning\b/,
|
|
85
|
+
/psutil failed to run/i,
|
|
86
|
+
/_collect failed/i,
|
|
87
|
+
/FireForge: host resource monitor degraded/i,
|
|
88
|
+
/warnings\.warn\(/,
|
|
89
|
+
];
|
|
90
|
+
/** Matches the caught/telemetry context that marks a traceback as benign. */
|
|
91
|
+
const BENIGN_TRACEBACK_CONTEXT = /telemetry|glean/i;
|
|
92
|
+
/**
|
|
93
|
+
* Strips non-signal noise from captured output before crash-signature
|
|
94
|
+
* matching: resource-monitor degradation warnings, and traceback blocks
|
|
95
|
+
* that mach caught itself (telemetry submission tracebacks are printed but
|
|
96
|
+
* never abort the run). The stripped text is used ONLY as crash evidence —
|
|
97
|
+
* classification of test results still reads the full output.
|
|
98
|
+
*/
|
|
99
|
+
export function stripNonSignalNoise(output) {
|
|
100
|
+
const lines = output.split(/\r?\n/);
|
|
101
|
+
const kept = [];
|
|
102
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
103
|
+
const line = lines[i] ?? '';
|
|
104
|
+
if (NOISE_LINE_PATTERNS.some((p) => p.test(line)))
|
|
105
|
+
continue;
|
|
106
|
+
if (TRACEBACK_PATTERN.test(line)) {
|
|
107
|
+
// Collect the whole traceback block: the header, indented frame/code
|
|
108
|
+
// lines, and the trailing unindented exception line.
|
|
109
|
+
const block = [line];
|
|
110
|
+
let j = i + 1;
|
|
111
|
+
for (; j < lines.length; j += 1) {
|
|
112
|
+
const blockLine = lines[j] ?? '';
|
|
113
|
+
block.push(blockLine);
|
|
114
|
+
const isIndented = /^\s/.test(blockLine) || blockLine.trim().length === 0;
|
|
115
|
+
if (!isIndented)
|
|
116
|
+
break; // unindented exception line terminates the block
|
|
117
|
+
}
|
|
118
|
+
if (BENIGN_TRACEBACK_CONTEXT.test(block.join('\n'))) {
|
|
119
|
+
i = j; // drop the whole caught-telemetry traceback block
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// A real traceback stays in evidence line-by-line (minus noise lines
|
|
123
|
+
// already filtered above).
|
|
124
|
+
}
|
|
125
|
+
kept.push(line);
|
|
126
|
+
}
|
|
127
|
+
return kept.join('\n');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* True when the output carries any execution signal: the browser-chrome
|
|
131
|
+
* `TEST-START` marker, the structured-log `TEST_START` marker, or the
|
|
132
|
+
* suite-specific xpcshell result-summary block.
|
|
133
|
+
*/
|
|
134
|
+
function hasExecutionSignal(output) {
|
|
135
|
+
return (TEST_START_PATTERN.test(output) ||
|
|
136
|
+
STRUCTURED_TEST_START_PATTERN.test(output) ||
|
|
137
|
+
hasXpcshellResultSummary(output));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* True when the output embeds a COMPLETED, GREEN suite summary: an
|
|
141
|
+
* execution signal, `Unexpected results: 0` (and no non-zero unexpected
|
|
142
|
+
* count), a `SUITE_END` marker, and no real `TEST-UNEXPECTED-*` lines.
|
|
143
|
+
* Such a run finished its suite; any startup-traceback-shaped noise in the
|
|
144
|
+
* same output is by definition non-fatal, so this vetoes signature-based
|
|
145
|
+
* crash classification (field report: fully green sharded runs reported
|
|
146
|
+
* `CRASH (N attempts)` because degradation warnings matched the psutil
|
|
147
|
+
* signals). Exported for direct unit testing.
|
|
148
|
+
*/
|
|
149
|
+
export function hasCompletedGreenSummary(output) {
|
|
150
|
+
return (hasExecutionSignal(output) &&
|
|
151
|
+
SUITE_END_PATTERN.test(output) &&
|
|
152
|
+
GREEN_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
|
|
153
|
+
!NONZERO_UNEXPECTED_SUMMARY_PATTERN.test(output) &&
|
|
154
|
+
realUnexpectedFailureLines(output).length === 0);
|
|
155
|
+
}
|
|
66
156
|
/**
|
|
67
157
|
* True when the captured output carries the suite-specific xpcshell
|
|
68
158
|
* result-summary block, which proves tests executed even though the
|
|
@@ -83,13 +173,22 @@ function realUnexpectedFailureLines(output) {
|
|
|
83
173
|
* Returns undefined for anything that looks like a genuine test result.
|
|
84
174
|
*/
|
|
85
175
|
export function detectHarnessCrashSignature(output) {
|
|
86
|
-
const hasTestStart =
|
|
176
|
+
const hasTestStart = hasExecutionSignal(output);
|
|
87
177
|
const realFailures = realUnexpectedFailureLines(output);
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
178
|
+
// A completed green embedded summary vetoes signature-based crash
|
|
179
|
+
// classification outright: the suite finished, so any startup-shaped
|
|
180
|
+
// noise in the same output was non-fatal. (The post-green shutdown
|
|
181
|
+
// re-entry shape below is exempt — it is deliberately a crash verdict on
|
|
182
|
+
// an otherwise-green log, keyed on its own explicit markers.)
|
|
183
|
+
const greenSummaryVeto = hasCompletedGreenSummary(output);
|
|
184
|
+
// Startup traceback cluster (resource monitor / psutil), scanned over
|
|
185
|
+
// noise-stripped evidence so degradation warnings and caught telemetry
|
|
186
|
+
// tracebacks never count. Real test failures take precedence: a
|
|
187
|
+
// traceback printed during teardown of a genuinely failing run must not
|
|
188
|
+
// get the whole run retried.
|
|
189
|
+
const evidence = stripNonSignalNoise(output);
|
|
190
|
+
if (!greenSummaryVeto && TRACEBACK_PATTERN.test(evidence) && realFailures.length === 0) {
|
|
191
|
+
const signalLine = findLine(evidence, STARTUP_TRACEBACK_SIGNALS);
|
|
93
192
|
if (signalLine) {
|
|
94
193
|
return { reason: 'harness startup traceback (resource monitor/psutil)', line: signalLine };
|
|
95
194
|
}
|
|
@@ -98,7 +197,7 @@ export function detectHarnessCrashSignature(output) {
|
|
|
98
197
|
// the no-output timeout. A trailing "Passed: 0" summary is part of this
|
|
99
198
|
// shape and must not be read as a result.
|
|
100
199
|
if (!hasTestStart) {
|
|
101
|
-
const timeoutLine = findLine(
|
|
200
|
+
const timeoutLine = findLine(evidence, [NO_OUTPUT_TIMEOUT_PATTERN]);
|
|
102
201
|
if (timeoutLine) {
|
|
103
202
|
return { reason: 'no-output timeout before any test started', line: timeoutLine };
|
|
104
203
|
}
|
|
@@ -133,15 +232,26 @@ export function classifyHarnessRun(exitCode, output, requestedPaths) {
|
|
|
133
232
|
if (signature) {
|
|
134
233
|
return { kind: 'harness-crash', signature };
|
|
135
234
|
}
|
|
136
|
-
const ranTests =
|
|
235
|
+
const ranTests = hasExecutionSignal(output);
|
|
137
236
|
if (!ranTests && requestedPaths.length > 0) {
|
|
138
237
|
return { kind: 'no-tests' };
|
|
139
238
|
}
|
|
140
|
-
|
|
239
|
+
if (exitCode === 0) {
|
|
240
|
+
return { kind: 'tests-ran-ok' };
|
|
241
|
+
}
|
|
242
|
+
// Exit codes follow the corrected verdict: a run whose embedded summary
|
|
243
|
+
// completed green is a pass even when the wrapper exit code went
|
|
244
|
+
// non-zero on harness noise (field report: a fully green --no-shard run
|
|
245
|
+
// exited 1). Real failures always carry a non-zero unexpected count or
|
|
246
|
+
// TEST-UNEXPECTED lines, both of which fail the green-summary check.
|
|
247
|
+
if (hasCompletedGreenSummary(output)) {
|
|
248
|
+
return { kind: 'tests-ran-ok', greenSummaryOverride: true };
|
|
249
|
+
}
|
|
250
|
+
return { kind: 'test-failures' };
|
|
141
251
|
}
|
|
142
252
|
/** Builds the operator-facing failure message after retries are exhausted. */
|
|
143
|
-
export function buildHarnessCrashMessage(signature, attempts) {
|
|
144
|
-
return (
|
|
253
|
+
export function buildHarnessCrashMessage(signature, attempts, commandLabel = 'mach test') {
|
|
254
|
+
return (`${commandLabel} crashed in the harness itself (not in your tests) on all ${attempts} attempt(s).\n\n` +
|
|
145
255
|
`Detected shape: ${signature.reason}\n` +
|
|
146
256
|
`Evidence line: ${signature.line}\n\n` +
|
|
147
257
|
'This failure mode is environmental (mozlog resource monitor / psutil on macOS, focus-stall ' +
|
|
@@ -77,6 +77,13 @@ export declare function parseAppdirFromToml(tomlText: string, key: string): {
|
|
|
77
77
|
*
|
|
78
78
|
* Special-cases `startPath` itself when it already ends with
|
|
79
79
|
* `xpcshell.toml` — operators sometimes pass a manifest path directly.
|
|
80
|
+
*
|
|
81
|
+
* When `startPath` is a DIRECTORY, the walk starts at the directory itself
|
|
82
|
+
* (checking `<dir>/xpcshell.toml` first) rather than at its parent. Field
|
|
83
|
+
* report (0.34.0 cycle): a directory whose own manifest is an
|
|
84
|
+
* `xpcshell.toml` was classified non-xpcshell — the walk began at the
|
|
85
|
+
* parent, missed the manifest, and `fireforge test <dir>` dispatched to
|
|
86
|
+
* the mochitest runner, which found no mochitests.
|
|
80
87
|
*/
|
|
81
88
|
export declare function findNearestXpcshellManifest(engineDir: string, startPath: string): Promise<string | null>;
|
|
82
89
|
/**
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* test.ts; we skip injection when `--app-path=` already appears in the
|
|
39
39
|
* forwarded args).
|
|
40
40
|
*/
|
|
41
|
-
import { readdir } from 'node:fs/promises';
|
|
41
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
42
42
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
43
43
|
import { pathExists, readJson, readText } from '../utils/fs.js';
|
|
44
44
|
import { isObject, isString } from '../utils/validation.js';
|
|
@@ -110,19 +110,28 @@ function stripQuotes(raw) {
|
|
|
110
110
|
*
|
|
111
111
|
* Special-cases `startPath` itself when it already ends with
|
|
112
112
|
* `xpcshell.toml` — operators sometimes pass a manifest path directly.
|
|
113
|
+
*
|
|
114
|
+
* When `startPath` is a DIRECTORY, the walk starts at the directory itself
|
|
115
|
+
* (checking `<dir>/xpcshell.toml` first) rather than at its parent. Field
|
|
116
|
+
* report (0.34.0 cycle): a directory whose own manifest is an
|
|
117
|
+
* `xpcshell.toml` was classified non-xpcshell — the walk began at the
|
|
118
|
+
* parent, missed the manifest, and `fireforge test <dir>` dispatched to
|
|
119
|
+
* the mochitest runner, which found no mochitests.
|
|
113
120
|
*/
|
|
114
121
|
export async function findNearestXpcshellManifest(engineDir, startPath) {
|
|
115
122
|
const absStart = resolve(engineDir, startPath);
|
|
116
123
|
if (absStart.toLowerCase().endsWith(`${sep}xpcshell.toml`)) {
|
|
117
124
|
return (await pathExists(absStart)) ? absStart : null;
|
|
118
125
|
}
|
|
126
|
+
const startIsDirectory = await stat(absStart).then((stats) => stats.isDirectory(), () => false);
|
|
119
127
|
const engineAbs = resolve(engineDir);
|
|
120
128
|
let current = absStart;
|
|
121
|
-
// First iteration
|
|
129
|
+
// First iteration resolves the starting directory (the path itself for a
|
|
130
|
+
// directory argument, its parent for a file); subsequent ones walk up.
|
|
122
131
|
// Cap iterations defensively — a pathological symlink loop would
|
|
123
132
|
// otherwise spin until the call stack overflows.
|
|
124
133
|
for (let i = 0; i < 64; i += 1) {
|
|
125
|
-
const dir = i === 0 ? dirname(absStart) : dirname(current);
|
|
134
|
+
const dir = i === 0 ? (startIsDirectory ? absStart : dirname(absStart)) : dirname(current);
|
|
126
135
|
const candidate = join(dir, 'xpcshell.toml');
|
|
127
136
|
if (await pathExists(candidate))
|
|
128
137
|
return candidate;
|
|
@@ -530,6 +530,16 @@ export interface FurnaceRemoveOptions {
|
|
|
530
530
|
export interface FurnaceCreateOptions {
|
|
531
531
|
/** Component description */
|
|
532
532
|
description?: string;
|
|
533
|
+
/**
|
|
534
|
+
* Engine-relative directory the test scaffold writes into, instead of
|
|
535
|
+
* the default `browser/base/content/test/<binaryName>/` (browser-chrome)
|
|
536
|
+
* or `browser/base/content/test/<binaryName>-xpcshell/<component>/`
|
|
537
|
+
* (xpcshell). Must stay under `browser/base/content/test/` so the
|
|
538
|
+
* manifest registration keeps working. Not supported for
|
|
539
|
+
* `--test-style=mochikit` (that harness lives in the upstream
|
|
540
|
+
* toolkit/content/tests/widgets tree).
|
|
541
|
+
*/
|
|
542
|
+
testDir?: string;
|
|
533
543
|
/** Include Fluent l10n support */
|
|
534
544
|
localized?: boolean;
|
|
535
545
|
/** Register in customElements.js (default: true) */
|
|
@@ -614,6 +624,8 @@ export interface WireOptions {
|
|
|
614
624
|
export interface RegisterOptions {
|
|
615
625
|
dryRun?: boolean;
|
|
616
626
|
after?: string;
|
|
627
|
+
/** Scaffold a missing manifest (moz.build / xpcshell.toml) and wire the parent chain. */
|
|
628
|
+
createManifest?: boolean;
|
|
617
629
|
}
|
|
618
630
|
/**
|
|
619
631
|
* Options for the patch delete command.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hominis/fireforge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "FireForge — a build tool for customizing Firefox",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"eslint-plugin-simple-import-sort": "^13.0.0",
|
|
73
73
|
"fast-check": "^4.6.0",
|
|
74
74
|
"husky": "^9.1.7",
|
|
75
|
-
"knip": "6.
|
|
75
|
+
"knip": "6.23.0",
|
|
76
76
|
"lint-staged": "^17.0.4",
|
|
77
|
-
"prettier": "
|
|
77
|
+
"prettier": "3.9.4",
|
|
78
78
|
"tsx": "^4.7.0",
|
|
79
79
|
"typescript": "~6.0.0",
|
|
80
80
|
"typescript-eslint": "^8.0.0",
|