@hominis/fireforge 0.47.0 → 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 +11 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- 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 +2 -1
- 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 +19 -4
- package/dist/src/commands/test-verdict.js +22 -4
- package/dist/src/commands/test.js +18 -10
- 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/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.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/parent-exit-watchdog.d.ts +62 -0
- package/dist/src/core/parent-exit-watchdog.js +69 -0
- package/dist/src/types/commands/options.d.ts +11 -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 +7 -1
- package/docs/lifecycle-invariants.md +17 -1
- package/docs/machine-output.md +22 -9
- package/docs/testing.md +68 -4
- package/package.json +1 -1
|
@@ -284,7 +284,10 @@ Wait ergonomics are opt-in: `pollMaxMs` (the poll interval doubles from
|
|
|
284
284
|
`pollMs` up to the cap) and `waitProgressMs` with `onWaitProgress` (a periodic
|
|
285
285
|
progress callback carrying the holder's PID, liveness, and owner-metadata
|
|
286
286
|
lines from the pid file). All three are inert when unset, so existing callers
|
|
287
|
-
keep byte-identical behavior.
|
|
287
|
+
keep byte-identical behavior. Within one poll iteration a due progress probe
|
|
288
|
+
runs before the deadline check, so a wait that times out has always reported
|
|
289
|
+
the holder at least once when its budget exceeds `waitProgressMs`, however
|
|
290
|
+
slow the host (`file-lock.test.ts`, "budget shorter than one iteration").
|
|
288
291
|
|
|
289
292
|
### `src/core/file-lock-owner.ts`
|
|
290
293
|
|
|
@@ -430,10 +433,23 @@ On SIGINT or SIGTERM, `bin/fireforge.ts`:
|
|
|
430
433
|
- `waitForActiveCriticalSections(5 s)`, which holds the exit for in-flight
|
|
431
434
|
"apply plus persist" pairs (invariant 4). It is bounded so that a stuck
|
|
432
435
|
write cannot postpone the exit the user asked for.
|
|
436
|
+
- `waitForActiveChildShutdown(12 s)`, which holds the exit until every
|
|
437
|
+
tracked child has closed. For a process-group dispatch (`mach` under
|
|
438
|
+
`test`, `build`, `run`) "closed" includes the exec layer's post-close
|
|
439
|
+
work: the group sweep and the caller's `postCloseSweep` (the harness
|
|
440
|
+
helper reap in `fireforge test`). Both run before the child's closure
|
|
441
|
+
settles, so a forwarded SIGTERM cannot race `process.exit` past them.
|
|
433
442
|
3. Force-releases the furnace lock directories of active operations, since
|
|
434
443
|
`withFileLock`'s `finally` will never run past `process.exit`.
|
|
435
444
|
4. Calls `process.exit(130 | 143)`.
|
|
436
445
|
|
|
446
|
+
A parent that dies without signalling FireForge (SIGKILL on the `npm` above
|
|
447
|
+
it) enters no handler here. `fireforge test` covers that case itself with
|
|
448
|
+
the parent-exit watchdog (`src/core/parent-exit-watchdog.ts`): it writes the
|
|
449
|
+
killed verdict, reaps the mach group and the tracked helpers, and lets the
|
|
450
|
+
dispatch loop end the run; the process then exits through the ordinary
|
|
451
|
+
error path, not through this pipeline.
|
|
452
|
+
|
|
437
453
|
## Choosing a primitive for a new mutation
|
|
438
454
|
|
|
439
455
|
| Your operation | Use |
|
package/docs/machine-output.md
CHANGED
|
@@ -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
|
|
|
@@ -124,6 +124,19 @@ tasks _inside_ a file is harness code that reads the exported seed; FireForge
|
|
|
124
124
|
owns the seed, the plumbing and the record. Absent when the run did not
|
|
125
125
|
shuffle.
|
|
126
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
|
+
|
|
127
140
|
A preflight refusal's own text is written to stdout before the verdict line,
|
|
128
141
|
and into the run log, so the verdict stays the last stdout write while the
|
|
129
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>] [shuffle=<seed>] [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,
|