@hominis/fireforge 0.46.2 → 0.47.1
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 +39 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/config.js +1 -2
- package/dist/src/commands/furnace/deploy-fragment-includers.d.ts +59 -0
- package/dist/src/commands/furnace/deploy-fragment-includers.js +120 -0
- package/dist/src/commands/furnace/deploy.js +36 -3
- package/dist/src/commands/furnace/validation-output.d.ts +5 -0
- package/dist/src/commands/furnace/validation-output.js +35 -9
- package/dist/src/commands/patch/delete-gap-notice.d.ts +24 -0
- package/dist/src/commands/patch/delete-gap-notice.js +46 -0
- package/dist/src/commands/patch/delete.js +11 -0
- package/dist/src/commands/re-export-lint.d.ts +26 -1
- package/dist/src/commands/re-export-lint.js +60 -20
- package/dist/src/commands/re-export-scan.d.ts +8 -0
- package/dist/src/commands/re-export-scan.js +12 -2
- package/dist/src/commands/re-export-single.js +24 -5
- package/dist/src/commands/run.js +42 -17
- package/dist/src/commands/test-harness-env.d.ts +24 -0
- package/dist/src/commands/test-harness-env.js +61 -0
- package/dist/src/commands/test-harness-teardown.d.ts +62 -0
- package/dist/src/commands/test-harness-teardown.js +128 -0
- package/dist/src/commands/test-register.js +8 -3
- package/dist/src/commands/test-run.d.ts +2 -0
- package/dist/src/commands/test-run.js +36 -7
- package/dist/src/commands/test-verdict.d.ts +21 -4
- package/dist/src/commands/test-verdict.js +37 -4
- package/dist/src/commands/test.js +27 -26
- package/dist/src/core/build-jar-escalation.d.ts +1 -4
- package/dist/src/core/build-jar-escalation.js +1 -4
- package/dist/src/core/build-prepare.js +72 -55
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +1 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/file-lock.js +16 -9
- package/dist/src/core/firefox-cache.js +1 -2
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +19 -4
- package/dist/src/core/furnace-css-fragments.d.ts +7 -0
- package/dist/src/core/furnace-css-fragments.js +12 -3
- package/dist/src/core/furnace-fragment-includers.d.ts +56 -0
- package/dist/src/core/furnace-fragment-includers.js +122 -0
- package/dist/src/core/git-diff-new-file.js +1 -2
- package/dist/src/core/git-file-ops.js +1 -2
- package/dist/src/core/git.js +1 -2
- package/dist/src/core/harness-helper-pids.d.ts +98 -0
- package/dist/src/core/harness-helper-pids.js +240 -0
- package/dist/src/core/harness-orphans.d.ts +53 -8
- package/dist/src/core/harness-orphans.js +125 -44
- package/dist/src/core/mach-build-artifacts.js +1 -2
- package/dist/src/core/mach.d.ts +16 -0
- package/dist/src/core/mach.js +6 -1
- package/dist/src/core/marionette-preflight.js +9 -3
- package/dist/src/core/mochitest-server-port.d.ts +43 -4
- package/dist/src/core/mochitest-server-port.js +64 -11
- package/dist/src/core/parent-exit-watchdog.d.ts +62 -0
- package/dist/src/core/parent-exit-watchdog.js +69 -0
- package/dist/src/core/patch-lint-imports.js +4 -4
- package/dist/src/core/smoke-patterns.d.ts +24 -0
- package/dist/src/core/smoke-patterns.js +50 -5
- package/dist/src/types/commands/options.d.ts +17 -1
- package/dist/src/types/config.d.ts +7 -0
- package/dist/src/utils/process-group.d.ts +5 -1
- package/dist/src/utils/process-group.js +7 -3
- package/dist/src/utils/process.d.ts +18 -0
- package/dist/src/utils/process.js +14 -2
- package/docs/configuration.md +15 -1
- package/docs/exit-codes.md +19 -19
- package/docs/lifecycle-invariants.md +17 -1
- package/docs/machine-output.md +44 -19
- package/docs/testing.md +80 -4
- package/package.json +5 -5
package/docs/machine-output.md
CHANGED
|
@@ -53,16 +53,16 @@ if (options.json === true) {
|
|
|
53
53
|
|
|
54
54
|
## Current surfaces
|
|
55
55
|
|
|
56
|
-
| Surface | Shape
|
|
57
|
-
| ---------------------------------- |
|
|
58
|
-
| `status --json` | `{ schemaVersion: 1, … }`
|
|
59
|
-
| `tree list --json` | `{ schemaVersion: 1, trees }`
|
|
60
|
-
| `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }`
|
|
61
|
-
| `token show --json` | `{ schemaVersion: 1, name, category, declarations }`
|
|
62
|
-
| `test` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL reason=… [note=<class>] [log=<path>]` | Not JSON. A single final stdout line, sealed by `setStdoutSealed` so nothing can displace it. `log=` names the run's own complete output under `.fireforge/logs/`, so a piped or truncated run still leaves a re-readable artifact |
|
|
63
|
-
| `lint --per-patch --report <path>` | `{ schemaVersion: 1, … }`
|
|
64
|
-
| `status --raw` | `git status --porcelain` lines
|
|
65
|
-
| `build` log announcement | `Full build output: <path>`
|
|
56
|
+
| Surface | Shape | Notes |
|
|
57
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `status --json` | `{ schemaVersion: 1, … }` | Error envelope on three engine preconditions |
|
|
59
|
+
| `tree list --json` | `{ schemaVersion: 1, trees }` | Error envelope added in 0.44.0 |
|
|
60
|
+
| `token list --json` | `{ schemaVersion: 1, tokensCssPath, categories }` | Categories in file order, each with its tokens (`name`, `line`, `value`) |
|
|
61
|
+
| `token show --json` | `{ schemaVersion: 1, name, category, declarations }` | One entry per declaring block, with its selector trail and line |
|
|
62
|
+
| `test` verdict line | `FIREFORGE-VERDICT: PASS\|FAIL reason=… [note=<class>] [shuffle=<seed>] [log=<path>]` | Not JSON. A single final stdout line, sealed by `setStdoutSealed` so nothing can displace it. `log=` names the run's own complete output under `.fireforge/logs/`, so a piped or truncated run still leaves a re-readable artifact |
|
|
63
|
+
| `lint --per-patch --report <path>` | `{ schemaVersion: 1, … }` | Written to a file, never read back by FireForge |
|
|
64
|
+
| `status --raw` | `git status --porcelain` lines | Not JSON, but still a machine surface: it engages the same stdout discipline, so rules 3 and 4 apply and rules 1 and 2 do not. A clean tree prints nothing |
|
|
65
|
+
| `build` log announcement | `Full build output: <path>` | Not JSON. `build` prints no verdict line, so it names its run log directly on the way out |
|
|
66
66
|
|
|
67
67
|
`--raw` is why `cli.ts` reads `process.argv` for `--json` or `--raw` before
|
|
68
68
|
dispatching. Rule 3 is about who owns stdout, and that question is settled
|
|
@@ -86,15 +86,15 @@ moved.
|
|
|
86
86
|
`FIREFORGE-VERDICT: FAIL reason=<reason>` uses a closed set
|
|
87
87
|
(`FireforgeVerdictReason`, `src/commands/test-verdict.ts`):
|
|
88
88
|
|
|
89
|
-
| Reason | Meaning
|
|
90
|
-
| --------------- |
|
|
91
|
-
| `crash` | The harness or the browser died, so there is no trustworthy suite result.
|
|
92
|
-
| `no-tests` | The run dispatched but nothing ran.
|
|
93
|
-
| `test-failures` | The suite ran and reported unexpected results.
|
|
94
|
-
| `preflight` | The run was refused before the harness was reached.
|
|
95
|
-
| `inconclusive` | A result exists, but `engine/` moved under it, so the result was discarded.
|
|
96
|
-
| `lock-timeout` | The run never started: the engine session lock stayed contended.
|
|
97
|
-
| `killed` | A signal terminated the run. Written from the signal handler so that a log tail always describes itself. A killed run must never be silent, or "killed", "still running" and "never started" all look alike. |
|
|
89
|
+
| Reason | Meaning |
|
|
90
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `crash` | The harness or the browser died, so there is no trustworthy suite result. |
|
|
92
|
+
| `no-tests` | The run dispatched but nothing ran. |
|
|
93
|
+
| `test-failures` | The suite ran and reported unexpected results. |
|
|
94
|
+
| `preflight` | The run was refused before the harness was reached. |
|
|
95
|
+
| `inconclusive` | A result exists, but `engine/` moved under it, so the result was discarded. |
|
|
96
|
+
| `lock-timeout` | The run never started: the engine session lock stayed contended. |
|
|
97
|
+
| `killed` | A signal terminated the run, or FireForge ended it because its parent process vanished. Written from the signal handler (or the parent-exit watchdog) so that a log tail always describes itself. A killed run must never be silent, or "killed", "still running" and "never started" all look alike. `signal=` names the cause: a signal name, or `parent-exit`. |
|
|
98
98
|
|
|
99
99
|
### The additive `note=` key
|
|
100
100
|
|
|
@@ -112,6 +112,31 @@ consumers should treat an unrecognised one as opaque. Current values:
|
|
|
112
112
|
`stale-browser`, `stale-browser-kill-failed`, `marionette-port-busy`,
|
|
113
113
|
`coverage-replaced`, `stale-build`, `stale-components`.
|
|
114
114
|
|
|
115
|
+
### The additive `shuffle=` key
|
|
116
|
+
|
|
117
|
+
A `fireforge test --shuffle [seed]` run forwards mach's `--shuffle` (an
|
|
118
|
+
unseeded shuffle of file order in the harness) and exports the seed FireForge
|
|
119
|
+
drew or was given as `FIREFORGE_SHUFFLE_SEED` in the harness environment. The
|
|
120
|
+
seed is appended to the verdict line as `shuffle=<seed>` on every emission
|
|
121
|
+
form, so a red found by shuffling carries its own reproduction
|
|
122
|
+
(`fireforge test --shuffle=<seed> …`) on the one line a pipe keeps. Reordering
|
|
123
|
+
tasks _inside_ a file is harness code that reads the exported seed; FireForge
|
|
124
|
+
owns the seed, the plumbing and the record. Absent when the run did not
|
|
125
|
+
shuffle.
|
|
126
|
+
|
|
127
|
+
### The additive `orphans-reaped=` key
|
|
128
|
+
|
|
129
|
+
How many harness processes FireForge terminated itself during the run: at
|
|
130
|
+
preflight, survivors of an earlier run in this objdir under `--reap-orphans`
|
|
131
|
+
or `test.reapOrphans: "reap"`; at teardown, helpers this run's harness
|
|
132
|
+
launched (httpd, websocket server, ssltunnel, process bridge, the browser,
|
|
133
|
+
`moz-http2`) that were still alive after mach exited. Summed across the census, every
|
|
134
|
+
retry attempt and every shard. Absent when nothing was reaped, so a green on
|
|
135
|
+
a quiet machine prints the line it always did, and a green after a reap is
|
|
136
|
+
distinguishable from it. A run ended by SIGTERM or by the parent-exit
|
|
137
|
+
watchdog writes its verdict before the teardown reap runs, so that line never
|
|
138
|
+
carries the key; the reap is reported on stderr instead.
|
|
139
|
+
|
|
115
140
|
A preflight refusal's own text is written to stdout before the verdict line,
|
|
116
141
|
and into the run log, so the verdict stays the last stdout write while the
|
|
117
142
|
reason survives both a redirect and a `tail`. See
|
package/docs/testing.md
CHANGED
|
@@ -87,21 +87,85 @@ a slow build for a silently wrong test.
|
|
|
87
87
|
earlier run in this project's objdir. It runs at preflight, before this run
|
|
88
88
|
spawns anything, so every hit is necessarily a survivor. A match has to be
|
|
89
89
|
objdir-anchored, since `xpcshell` and `server.js` are far too generic to
|
|
90
|
-
report on their own
|
|
90
|
+
report on their own, and structural: the process's executable is a helper
|
|
91
|
+
binary, or an interpreter whose script argument is a helper script. A
|
|
92
|
+
process that merely mentions such a path (an editor, a `grep`, the shell
|
|
93
|
+
FireForge runs under) is not a helper, and FireForge's own ancestors are
|
|
94
|
+
never candidates. Survivors slow every later run without appearing in
|
|
91
95
|
its output. The usual symptom is a three-second suite taking minutes of
|
|
92
|
-
wall clock. The census is report-only by default
|
|
93
|
-
|
|
96
|
+
wall clock. The census is report-only by default; `--reap-orphans`, or
|
|
97
|
+
`test.reapOrphans: "reap"` in `fireforge.json`, terminates what it finds
|
|
98
|
+
(SIGTERM, a 500 ms grace, SIGKILL). It never refuses a run. Every reap is
|
|
99
|
+
stamped on the verdict line as `orphans-reaped=<n>`. A different shape,
|
|
94
100
|
reparented Python `multiprocessing` workers, is covered by the
|
|
95
101
|
`Orphaned harness workers` doctor check.
|
|
102
|
+
- Every dispatch owns its own kill path. The harness announces what it
|
|
103
|
+
launches (`runtests.py | Server pid: <n>`, the websocket server, the SSL
|
|
104
|
+
tunnel, the websocket/process bridge, and the browser as `Application
|
|
105
|
+
pid`); FireForge tracks those pids and, after mach has exited for any
|
|
106
|
+
reason (a clean finish, a harness crash before a retry, a no-output
|
|
107
|
+
timeout, a forwarded SIGTERM, the parent-exit watchdog below), terminates
|
|
108
|
+
any of them still alive. A tracked pid is re-read from `ps` first and must
|
|
109
|
+
still be anchored to this objdir and still look like the thing that was
|
|
110
|
+
launched, so a recycled pid is never signalled. One helper is never
|
|
111
|
+
announced: mozserve starts `moz-http2` in a session of its own and logs no
|
|
112
|
+
pid, so it is in neither the group nor the announcements. For it, FireForge
|
|
113
|
+
snapshots the same-objdir helpers before the dispatch spawns and, after
|
|
114
|
+
mach exits, reaps any helper-shaped process under this checkout's absolute
|
|
115
|
+
objdir that is new since that snapshot and reparented to launchd. An
|
|
116
|
+
earlier run's survivor is in the snapshot and stays the census's call. This
|
|
117
|
+
reap runs inside the exec layer's close path, so the signal handler's
|
|
118
|
+
bounded child-shutdown wait covers it. The count joins `orphans-reaped=`.
|
|
119
|
+
- FireForge watches its own parent while a dispatch runs. A supervisor that
|
|
120
|
+
SIGKILLs the process above `fireforge test` (an `npm run` step killed at
|
|
121
|
+
its bound) signals nothing below it; FireForge notices the reparenting
|
|
122
|
+
within about two seconds, writes `FIREFORGE-VERDICT: FAIL reason=killed
|
|
123
|
+
signal=parent-exit`, reaps the harness process group and the tracked
|
|
124
|
+
helpers, and ends the run without classifying or retrying.
|
|
96
125
|
- `--perf-samples <path>` publishes a perf-sample artifact path to the
|
|
97
126
|
harness (exported as `<BINARYNAME>_PERF_SAMPLE_JSON`).
|
|
98
127
|
|
|
128
|
+
## Process groups and supervisors
|
|
129
|
+
|
|
130
|
+
`fireforge test` spawns mach as the leader of its **own** process group
|
|
131
|
+
(`detached: true`), so that FireForge can signal the whole harness tree with
|
|
132
|
+
one negative-pid kill and sweep the group after mach exits. Two consequences
|
|
133
|
+
for anything that supervises `fireforge test`:
|
|
134
|
+
|
|
135
|
+
- A supervisor that kills _its own_ process group (`kill -- -<npm pid>`) does
|
|
136
|
+
not reach mach: mach is not in that group. Killing the `npm`/`node` chain
|
|
137
|
+
above FireForge leaves the harness tree running.
|
|
138
|
+
- SIGTERM to FireForge is the right signal. FireForge forwards it to the
|
|
139
|
+
mach group, escalates to SIGKILL after a grace period, sweeps the group,
|
|
140
|
+
reaps the tracked helpers, and writes `FAIL reason=killed signal=SIGTERM`.
|
|
141
|
+
(That line, like the watchdog's `signal=parent-exit` line, is written
|
|
142
|
+
before the teardown reap runs, so it cannot carry `orphans-reaped=`; the
|
|
143
|
+
reap reports to stderr.)
|
|
144
|
+
- A supervisor that has to SIGKILL FireForge should pass
|
|
145
|
+
`--pgid-file <path>`. FireForge rewrites the file with the harness group id
|
|
146
|
+
on every mach spawn (each retry attempt and shard) and removes it when the
|
|
147
|
+
run ends under its own control. After a SIGKILL the file is still there:
|
|
148
|
+
`kill -TERM -- -$(cat <path>)`, then `-KILL`, takes mach and every helper
|
|
149
|
+
that shares its group. Two processes are outside it: the browser
|
|
150
|
+
(mozprocess puts it in a group of its own) and `moz-http2` (mozserve
|
|
151
|
+
starts it in its own session). A SIGTERM to the group gives `runtests.py`
|
|
152
|
+
the chance to run its own cleanup, which normally takes the browser with
|
|
153
|
+
it; nothing takes `moz-http2`, so after a SIGKILL of FireForge it stays
|
|
154
|
+
until the next run's census, which is why that run should carry the reap
|
|
155
|
+
posture. A browser that does survive is the Marionette preflight's and
|
|
156
|
+
`--kill-stale-marionette`'s to remove.
|
|
157
|
+
|
|
158
|
+
The mochitest helpers (httpd, websocket server, ssltunnel, process bridge)
|
|
159
|
+
are plain children of `runtests.py` and share mach's group. The browser and
|
|
160
|
+
`moz-http2` do not, which is why the teardown reap above exists alongside the
|
|
161
|
+
group sweep.
|
|
162
|
+
|
|
99
163
|
## The verdict line
|
|
100
164
|
|
|
101
165
|
Every test run ends with one machine-readable line:
|
|
102
166
|
|
|
103
167
|
```
|
|
104
|
-
FIREFORGE-VERDICT: PASS|FAIL reason=… [note=<class>] [log=<path>]
|
|
168
|
+
FIREFORGE-VERDICT: PASS|FAIL reason=… [note=<class>] [shuffle=<seed>] [orphans-reaped=<n>] [log=<path>]
|
|
105
169
|
```
|
|
106
170
|
|
|
107
171
|
Automation should branch on this line rather than on the raw process code,
|
|
@@ -117,6 +181,18 @@ text is written to stdout before the verdict line and into the run log, so a
|
|
|
117
181
|
redirected run keeps the reason as well as the verdict. See
|
|
118
182
|
[`run-logs.md`](run-logs.md).
|
|
119
183
|
|
|
184
|
+
## Seeded shuffle
|
|
185
|
+
|
|
186
|
+
`fireforge test --shuffle` runs a mochitest selection in a seeded random file
|
|
187
|
+
order and prints the seed; `--shuffle=<seed>` replays it. FireForge forwards
|
|
188
|
+
mach's own `--shuffle`, exports `FIREFORGE_SHUFFLE_SEED=<seed>` to the harness
|
|
189
|
+
so in-file task reordering (harness `head.js` code, not FireForge) can key off
|
|
190
|
+
the same number, and stamps `shuffle=<seed>` on the verdict line. It is
|
|
191
|
+
mochitest-only: the xpcshell harness has no shuffle, and generic `mach test`
|
|
192
|
+
dispatch would hand the flag to every suite. Write the flag as
|
|
193
|
+
`--shuffle=<seed>` or after the paths, because a bare `--shuffle <path>` reads
|
|
194
|
+
the path as the seed.
|
|
195
|
+
|
|
120
196
|
Exit code 14 (`INCONCLUSIVE`) is not red: it means `engine/` moved while the
|
|
121
197
|
harness ran and the result was thrown away. Exit 15 (`LOCK_TIMEOUT`) means
|
|
122
198
|
the run never started. See [`exit-codes.md`](exit-codes.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hominis/fireforge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.1",
|
|
4
4
|
"description": "FireForge — a build tool for customizing Firefox",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@eslint/js": "^10.0.0",
|
|
68
68
|
"@types/estree": "^1.0.8",
|
|
69
|
-
"@types/node": "^
|
|
70
|
-
"@vitest/coverage-v8": "^
|
|
69
|
+
"@types/node": "^26.4.1",
|
|
70
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
71
71
|
"dpdm": "4.3.0",
|
|
72
72
|
"eslint": "^10.0.0",
|
|
73
73
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
76
76
|
"fast-check": "^4.6.0",
|
|
77
77
|
"husky": "^9.1.7",
|
|
78
|
-
"knip": "6.
|
|
78
|
+
"knip": "6.35.1",
|
|
79
79
|
"lint-staged": "^17.0.4",
|
|
80
80
|
"prettier": "3.9.6",
|
|
81
81
|
"tsx": "^4.7.0",
|
|
82
82
|
"typescript": "~6.0.0",
|
|
83
83
|
"typescript-eslint": "^8.0.0",
|
|
84
84
|
"vite": "^8.0.7",
|
|
85
|
-
"vitest": "^
|
|
85
|
+
"vitest": "^5.0.0"
|
|
86
86
|
},
|
|
87
87
|
"engines": {
|
|
88
88
|
"node": ">=22.22.2"
|