@hominis/fireforge 0.34.0 → 0.34.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
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
- Reworked the macOS resource-monitor (psutil) crash mitigation to an **in-process guard FireForge owns on its mach dispatches**: a `fireforge_mach_guard.pth` + module pair is installed directly into every discovered mach virtualenv site-packages (objdir `_virtualenvs` and the `~/.mozbuild` state dir) before each dispatch, because mach's venv re-exec drops `PYTHONPATH` and the 0.33.0 `sitecustomize.py` route never loaded (the env-var shim is retained only for the pre-venv bootstrap phase). The guard now covers the whole crash family: `psutil.virtual_memory`/`swap_memory`/`cpu_percent`/`cpu_times`/`disk_io_counters` are wrapped module-wide (which also covers the direct `psutil.virtual_memory()` call in `mozbuild.base._run_make`), and `SystemResourceMonitor` **construction** is guarded via an import hook that pre-populates `poll_interval` and degrades a partially-constructed monitor to a no-op — so the `AttributeError: ... poll_interval` variant can no longer escape a polling-only patch.
|
|
6
6
|
- Routed every mach build dispatch — plain `fireforge build`, `build --ui` (`mach build faster`), and the pre-test build step of `fireforge test --build` — through one protected path (`runProtectedMachBuild`) with a **uniform recognized-crash retry budget** (default 2 retries, matching `--harness-retries`; the pre-test step forwards the operator's `--harness-retries` value). Each retry spawns a fresh mach process AND re-installs the guard (re-discovering venvs), so a venv materialized by a crashed first attempt is guarded on the next one instead of every retry dying on the same wedged state. Non-crash build failures are never retried; exhausted budgets surface the crash shape and evidence line above the regular diagnostics. The suite-specific test dispatches (`mach test` / `xpcshell-test` / `mochitest`) install the same guard.
|
|
7
7
|
- Investigated the field incident where a failed pre-test build was followed by a ~64-minute full rebuild: FireForge's protected path performs **no configure/clobber beyond what `mach build faster` itself does** (retries never re-run `prepareBuildEnvironment`/`mach configure`; there is no fallback-to-full-build in FireForge). The pre-test build now also passes the previous build baseline into `prepareBuildEnvironment` exactly like `fireforge build` does, so auto-configure runs under identical conditions on both paths instead of diverging.
|
|
8
|
+
- Fixed the degraded-psutil fallback crashing mozsystemmonitor on the fallback itself (downstream report: `TypeError: '_DegradedReading' object is not subscriptable` from `_build_meta`'s `psutil.virtual_memory()[0]`, aborting `mach mochitest` at startup with zero `TEST-START` lines, and `_collect failed: '_DegradedReading' object is not iterable`). The guard's degraded reading is now a **real duck type of psutil's namedtuple results**: each wrapped function degrades to a zeroed instance of psutil's own result namedtuple (`svmem`/`sswap`/`scputimes`/`sdiskio`, resolved via `getattr` without touching the failing syscall), falling back to an extended `_DegradedReading` that supports subscripting, iteration, `len()`, `_fields` (svmem field order), and `_asdict()`. `cpu_percent` degrades to a plain `0.0` float (callers do arithmetic on it), and `disk_io_counters` deliberately degrades to a zeroed struct rather than `None`. Runtime-verified by new python3-executed regression tests covering a mozsystemmonitor-style `_build_meta`/`_collect` sequence against a degraded host.
|
|
9
|
+
- Taught the harness-crash classifier the two `_DegradedReading` crash signatures (`object is not subscriptable` / `object is not iterable`), so a startup abort with zero `TEST-START` lines from this family classifies as crash-not-failure and retries; the `_collect failed: '_DegradedReading' ...` evidence line is exempted from the noise-stripping that otherwise drops `_collect failed` degradation chatter, while a completed green summary still vetoes the classification.
|
|
8
10
|
- Fixed the sharded/retry crash classifier marking fully green runs as `CRASH (N attempts)`: a completed green embedded summary (a `TEST_START`/`TEST-START` execution signal, `Unexpected results: 0`, and `SUITE_END`) now **vetoes** signature-based crash classification for that attempt, and resource-monitor degradation lines (`UserWarning: psutil failed to run: ...`, `_collect failed: ...`, FireForge's own degradation notice) plus mach's caught telemetry tracebacks are stripped from crash evidence entirely. Exit codes follow the corrected verdict: a run whose every shard is green exits 0, and a non-sharded (`--no-shard`) run whose embedded summary completed green exits 0 even when mach's own exit code went non-zero on harness noise (a note explains the override) — parsing embedded summaries by hand is no longer necessary. Runs with a non-zero unexpected count or real `TEST-UNEXPECTED-*` lines still fail, and the post-green shutdown re-entry shape still classifies as a crash.
|
|
9
11
|
- Fixed `fireforge test <directory>` dispatching xpcshell-only directories to the mochitest runner ("could not find any mochitests under the following test path(s)"): the manifest walk now starts at the directory itself, so a directory whose own manifest is an `xpcshell.toml` dispatches to `mach xpcshell-test` just like an explicit `test_*.js` path.
|
|
10
12
|
- Added `fireforge register --create-manifest`: registering a module under a directory with no `moz.build` now scaffolds the directory manifest (MPL-header + `EXTRA_JS_MODULES.<namespace>` list) and wires the parent `DIRS` chain up to the nearest existing moz.build (creating intermediates); without the flag the "Manifest not found" error names it. Also added an **xpcshell test-file pattern** (`**/test_*.js` outside `browser/base/content/test/`) that inserts the `["test_*.js"]` section into the directory's `xpcshell.toml` in mozbuild sort order — or creates the manifest and wires `XPCSHELL_TESTS_MANIFESTS` with `--create-manifest` — and extended the browser-chrome manifest rule to fork-owned `browser.toml` manifests at **arbitrary depth** under `browser/base/content/test/` (previously one level only).
|
|
@@ -27,7 +27,14 @@
|
|
|
27
27
|
* `cpu_times` / `disk_io_counters` module-wide, so every call site —
|
|
28
28
|
* including the direct `psutil.virtual_memory()` in
|
|
29
29
|
* `mozbuild.base._run_make` — degrades to a `UserWarning` and a zeroed
|
|
30
|
-
* reading instead of aborting
|
|
30
|
+
* reading instead of aborting. Downstream report (0.34.0 cycle):
|
|
31
|
+
* mozsystemmonitor subscripts (`_build_meta` does
|
|
32
|
+
* `psutil.virtual_memory()[0]`) and iterates/unpacks the reading, so the
|
|
33
|
+
* zeroed fallback is a full namedtuple duck type — a zeroed instance of
|
|
34
|
+
* psutil's own result namedtuple where resolvable without touching the
|
|
35
|
+
* failing syscall, else `_DegradedReading` (subscriptable, iterable, with
|
|
36
|
+
* `len()`, `_fields`, `_asdict()`); `cpu_percent` degrades to `0.0`
|
|
37
|
+
* because callers do arithmetic on it;
|
|
31
38
|
* - guards `SystemResourceMonitor` CONSTRUCTION via an import hook:
|
|
32
39
|
* `poll_interval` is pre-populated before the real `__init__` runs, a
|
|
33
40
|
* failing `__init__` marks the instance degraded instead of raising,
|
|
@@ -35,6 +42,16 @@
|
|
|
35
42
|
* partially-constructed monitor can never surface the
|
|
36
43
|
* `AttributeError: poll_interval` variant.
|
|
37
44
|
*/
|
|
45
|
+
/**
|
|
46
|
+
* Python guard body, importable both as the in-venv `fireforge_mach_guard`
|
|
47
|
+
* module (loaded by the sibling `.pth` at interpreter startup) and as the
|
|
48
|
+
* PYTHONPATH `sitecustomize` fallback. Defensive throughout: a missing or
|
|
49
|
+
* broken psutil/mozsystemmonitor leaves mach untouched, and every wrapper
|
|
50
|
+
* only intercepts the failure path. Exported for the Python-executing
|
|
51
|
+
* regression test (mach-resource-shim.python.test.ts), which asserts the
|
|
52
|
+
* degraded readings' runtime behavior rather than string-matching the source.
|
|
53
|
+
*/
|
|
54
|
+
export declare const GUARD_PYTHON_SOURCE = "# Generated by FireForge - do not edit.\n# Degrades the broken host resource monitor family (psutil vs Darwin) from\n# fatal startup errors into non-fatal warnings, so mach builds and tests\n# proceed. Installed into the mach virtualenv site-packages via a .pth file\n# (survives mach's venv re-exec, which drops PYTHONPATH).\nimport builtins\nimport sys\nimport warnings\n\n\ndef _fireforge_degraded_notice(exc):\n warnings.warn(\n \"FireForge: host resource monitor degraded (%s); continuing without resource monitoring.\" % exc\n )\n\n\nclass _DegradedReading(object):\n # Duck type of psutil's namedtuple results (svmem field order):\n # mozsystemmonitor subscripts, iterates, and unpacks readings, so the\n # degraded fallback must survive r[0], list(r), len(r), r._fields and\n # r._asdict(), not just attribute access.\n _fields = (\"total\", \"available\", \"percent\", \"used\", \"free\", \"active\", \"inactive\", \"wired\")\n total = available = used = free = active = inactive = wired = 0\n percent = 0.0\n\n def __getattr__(self, _name):\n return 0\n\n def _values(self):\n return tuple(getattr(self, _field) for _field in self._fields)\n\n def __getitem__(self, index):\n return self._values()[index]\n\n def __iter__(self):\n return iter(self._values())\n\n def __len__(self):\n return len(self._fields)\n\n def _asdict(self):\n return dict(zip(self._fields, self._values()))\n\n\n# psutil result namedtuple per wrapped function, resolvable via getattr from\n# the platform module / _common without invoking the failing syscall.\n_PSUTIL_RESULT_CLASSES = {\n \"virtual_memory\": \"svmem\",\n \"swap_memory\": \"sswap\",\n \"cpu_times\": \"scputimes\",\n \"disk_io_counters\": \"sdiskio\",\n}\n\n\ndef _degraded_result_factory(psutil, name):\n # cpu_percent returns a plain float callers do arithmetic on.\n if name == \"cpu_percent\":\n return lambda: 0.0\n _cls_name = _PSUTIL_RESULT_CLASSES.get(name)\n if _cls_name is not None:\n for _mod in (getattr(psutil, \"_psplatform\", None), getattr(psutil, \"_common\", None)):\n _cls = getattr(_mod, _cls_name, None) if _mod is not None else None\n _fields = getattr(_cls, \"_fields\", None)\n if _cls is not None and _fields:\n return lambda _cls=_cls, _n=len(_fields): _cls(*([0] * _n))\n return _DegradedReading\n\n\ndef _guard_psutil():\n try:\n import psutil\n except Exception:\n return\n\n def _wrap(orig, fallback):\n def wrapper(*args, **kwargs):\n try:\n return orig(*args, **kwargs)\n except Exception as exc: # noqa: BLE001\n _fireforge_degraded_notice(exc)\n try:\n return fallback()\n except Exception: # noqa: BLE001\n return _DegradedReading()\n\n return wrapper\n\n for _name in (\"virtual_memory\", \"swap_memory\", \"cpu_percent\", \"cpu_times\", \"disk_io_counters\"):\n _orig = getattr(psutil, _name, None)\n if _orig is not None and not getattr(_orig, \"_fireforge_guarded\", False):\n try:\n _fallback = _degraded_result_factory(psutil, _name)\n except Exception: # noqa: BLE001\n _fallback = _DegradedReading\n _wrapped = _wrap(_orig, _fallback)\n _wrapped._fireforge_guarded = True\n setattr(psutil, _name, _wrapped)\n\n\n_MONITOR_METHOD_NAMES = (\n \"start\",\n \"stop\",\n \"record_event\",\n \"record_marker\",\n \"begin_phase\",\n \"finish_phase\",\n \"aggregate_cpu_percent\",\n \"aggregate_cpu_times\",\n \"aggregate_io\",\n \"min_memory_available\",\n \"as_dict\",\n)\n\n\ndef _patch_monitor_class(cls):\n if cls is None or getattr(cls, \"_fireforge_guarded\", False):\n return\n orig_init = cls.__init__\n\n def guarded_init(self, *args, **kwargs):\n # Pre-populate the attributes a partially-constructed instance is\n # later polled for, so a failing __init__ can never surface\n # AttributeError: poll_interval.\n self.poll_interval = kwargs.get(\"poll_interval\") or 1.0\n self._fireforge_degraded = False\n try:\n orig_init(self, *args, **kwargs)\n except Exception as exc: # noqa: BLE001\n self._fireforge_degraded = True\n _fireforge_degraded_notice(exc)\n\n cls.__init__ = guarded_init\n\n def _wrap_method(orig):\n def wrapper(self, *args, **kwargs):\n if getattr(self, \"_fireforge_degraded\", False):\n return None\n try:\n return orig(self, *args, **kwargs)\n except Exception as exc: # noqa: BLE001\n self._fireforge_degraded = True\n _fireforge_degraded_notice(exc)\n return None\n\n return wrapper\n\n for _name in _MONITOR_METHOD_NAMES:\n _orig = getattr(cls, _name, None)\n if _orig is not None:\n setattr(cls, _name, _wrap_method(_orig))\n cls._fireforge_guarded = True\n\n\n_MONITOR_MODULES = (\"mozsystemmonitor.resourcemonitor\", \"mozbuild.resources\")\n\n\ndef _patch_loaded_monitor_modules():\n for _mod_name in _MONITOR_MODULES:\n _mod = sys.modules.get(_mod_name)\n if _mod is not None:\n try:\n _patch_monitor_class(getattr(_mod, \"SystemResourceMonitor\", None))\n except Exception: # noqa: BLE001\n pass\n\n\ndef _install_import_hook():\n _orig_import = builtins.__import__\n if getattr(_orig_import, \"_fireforge_guarded\", False):\n return\n\n def _guarding_import(name, *args, **kwargs):\n _module = _orig_import(name, *args, **kwargs)\n try:\n _patch_loaded_monitor_modules()\n except Exception: # noqa: BLE001\n pass\n return _module\n\n _guarding_import._fireforge_guarded = True\n builtins.__import__ = _guarding_import\n\n\ntry:\n _guard_psutil()\n _patch_loaded_monitor_modules()\n _install_import_hook()\nexcept Exception: # noqa: BLE001\n pass\n";
|
|
38
55
|
/** Result of installing the mach resource guard before a dispatch. */
|
|
39
56
|
export interface MachResourceGuardInstallation {
|
|
40
57
|
/**
|
|
@@ -28,7 +28,14 @@
|
|
|
28
28
|
* `cpu_times` / `disk_io_counters` module-wide, so every call site —
|
|
29
29
|
* including the direct `psutil.virtual_memory()` in
|
|
30
30
|
* `mozbuild.base._run_make` — degrades to a `UserWarning` and a zeroed
|
|
31
|
-
* reading instead of aborting
|
|
31
|
+
* reading instead of aborting. Downstream report (0.34.0 cycle):
|
|
32
|
+
* mozsystemmonitor subscripts (`_build_meta` does
|
|
33
|
+
* `psutil.virtual_memory()[0]`) and iterates/unpacks the reading, so the
|
|
34
|
+
* zeroed fallback is a full namedtuple duck type — a zeroed instance of
|
|
35
|
+
* psutil's own result namedtuple where resolvable without touching the
|
|
36
|
+
* failing syscall, else `_DegradedReading` (subscriptable, iterable, with
|
|
37
|
+
* `len()`, `_fields`, `_asdict()`); `cpu_percent` degrades to `0.0`
|
|
38
|
+
* because callers do arithmetic on it;
|
|
32
39
|
* - guards `SystemResourceMonitor` CONSTRUCTION via an import hook:
|
|
33
40
|
* `poll_interval` is pre-populated before the real `__init__` runs, a
|
|
34
41
|
* failing `__init__` marks the instance degraded instead of raising,
|
|
@@ -51,9 +58,11 @@ const GUARD_MODULE_NAME = 'fireforge_mach_guard';
|
|
|
51
58
|
* module (loaded by the sibling `.pth` at interpreter startup) and as the
|
|
52
59
|
* PYTHONPATH `sitecustomize` fallback. Defensive throughout: a missing or
|
|
53
60
|
* broken psutil/mozsystemmonitor leaves mach untouched, and every wrapper
|
|
54
|
-
* only intercepts the failure path.
|
|
61
|
+
* only intercepts the failure path. Exported for the Python-executing
|
|
62
|
+
* regression test (mach-resource-shim.python.test.ts), which asserts the
|
|
63
|
+
* degraded readings' runtime behavior rather than string-matching the source.
|
|
55
64
|
*/
|
|
56
|
-
const GUARD_PYTHON_SOURCE = `# Generated by FireForge - do not edit.
|
|
65
|
+
export const GUARD_PYTHON_SOURCE = `# Generated by FireForge - do not edit.
|
|
57
66
|
# Degrades the broken host resource monitor family (psutil vs Darwin) from
|
|
58
67
|
# fatal startup errors into non-fatal warnings, so mach builds and tests
|
|
59
68
|
# proceed. Installed into the mach virtualenv site-packages via a .pth file
|
|
@@ -70,12 +79,56 @@ def _fireforge_degraded_notice(exc):
|
|
|
70
79
|
|
|
71
80
|
|
|
72
81
|
class _DegradedReading(object):
|
|
82
|
+
# Duck type of psutil's namedtuple results (svmem field order):
|
|
83
|
+
# mozsystemmonitor subscripts, iterates, and unpacks readings, so the
|
|
84
|
+
# degraded fallback must survive r[0], list(r), len(r), r._fields and
|
|
85
|
+
# r._asdict(), not just attribute access.
|
|
86
|
+
_fields = ("total", "available", "percent", "used", "free", "active", "inactive", "wired")
|
|
73
87
|
total = available = used = free = active = inactive = wired = 0
|
|
74
88
|
percent = 0.0
|
|
75
89
|
|
|
76
90
|
def __getattr__(self, _name):
|
|
77
91
|
return 0
|
|
78
92
|
|
|
93
|
+
def _values(self):
|
|
94
|
+
return tuple(getattr(self, _field) for _field in self._fields)
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, index):
|
|
97
|
+
return self._values()[index]
|
|
98
|
+
|
|
99
|
+
def __iter__(self):
|
|
100
|
+
return iter(self._values())
|
|
101
|
+
|
|
102
|
+
def __len__(self):
|
|
103
|
+
return len(self._fields)
|
|
104
|
+
|
|
105
|
+
def _asdict(self):
|
|
106
|
+
return dict(zip(self._fields, self._values()))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# psutil result namedtuple per wrapped function, resolvable via getattr from
|
|
110
|
+
# the platform module / _common without invoking the failing syscall.
|
|
111
|
+
_PSUTIL_RESULT_CLASSES = {
|
|
112
|
+
"virtual_memory": "svmem",
|
|
113
|
+
"swap_memory": "sswap",
|
|
114
|
+
"cpu_times": "scputimes",
|
|
115
|
+
"disk_io_counters": "sdiskio",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _degraded_result_factory(psutil, name):
|
|
120
|
+
# cpu_percent returns a plain float callers do arithmetic on.
|
|
121
|
+
if name == "cpu_percent":
|
|
122
|
+
return lambda: 0.0
|
|
123
|
+
_cls_name = _PSUTIL_RESULT_CLASSES.get(name)
|
|
124
|
+
if _cls_name is not None:
|
|
125
|
+
for _mod in (getattr(psutil, "_psplatform", None), getattr(psutil, "_common", None)):
|
|
126
|
+
_cls = getattr(_mod, _cls_name, None) if _mod is not None else None
|
|
127
|
+
_fields = getattr(_cls, "_fields", None)
|
|
128
|
+
if _cls is not None and _fields:
|
|
129
|
+
return lambda _cls=_cls, _n=len(_fields): _cls(*([0] * _n))
|
|
130
|
+
return _DegradedReading
|
|
131
|
+
|
|
79
132
|
|
|
80
133
|
def _guard_psutil():
|
|
81
134
|
try:
|
|
@@ -83,20 +136,27 @@ def _guard_psutil():
|
|
|
83
136
|
except Exception:
|
|
84
137
|
return
|
|
85
138
|
|
|
86
|
-
def _wrap(orig):
|
|
139
|
+
def _wrap(orig, fallback):
|
|
87
140
|
def wrapper(*args, **kwargs):
|
|
88
141
|
try:
|
|
89
142
|
return orig(*args, **kwargs)
|
|
90
143
|
except Exception as exc: # noqa: BLE001
|
|
91
144
|
_fireforge_degraded_notice(exc)
|
|
92
|
-
|
|
145
|
+
try:
|
|
146
|
+
return fallback()
|
|
147
|
+
except Exception: # noqa: BLE001
|
|
148
|
+
return _DegradedReading()
|
|
93
149
|
|
|
94
150
|
return wrapper
|
|
95
151
|
|
|
96
152
|
for _name in ("virtual_memory", "swap_memory", "cpu_percent", "cpu_times", "disk_io_counters"):
|
|
97
153
|
_orig = getattr(psutil, _name, None)
|
|
98
154
|
if _orig is not None and not getattr(_orig, "_fireforge_guarded", False):
|
|
99
|
-
|
|
155
|
+
try:
|
|
156
|
+
_fallback = _degraded_result_factory(psutil, _name)
|
|
157
|
+
except Exception: # noqa: BLE001
|
|
158
|
+
_fallback = _DegradedReading
|
|
159
|
+
_wrapped = _wrap(_orig, _fallback)
|
|
100
160
|
_wrapped._fireforge_guarded = True
|
|
101
161
|
setattr(psutil, _name, _wrapped)
|
|
102
162
|
|
|
@@ -56,6 +56,17 @@ const NO_OUTPUT_TIMEOUT_PATTERN = /timed out after \d+ seconds with no output/i;
|
|
|
56
56
|
* on macOS. Each is matched per-line so the evidence line in the report is
|
|
57
57
|
* the concrete failure, not the whole traceback.
|
|
58
58
|
*/
|
|
59
|
+
// Downstream report (0.34.0 cycle): a pre-fix _DegradedReading fallback that
|
|
60
|
+
// wasn't a real namedtuple duck type crashed mozsystemmonitor on the fallback
|
|
61
|
+
// itself (`_build_meta` subscripts the reading; `_collect` unpacks it); a
|
|
62
|
+
// startup abort with zero TEST-START lines from this family is a crash, not
|
|
63
|
+
// a test failure. The `_collect failed` variant is caught-and-logged, so it
|
|
64
|
+
// carries no Traceback header — it gets its own zero-TEST-START check in
|
|
65
|
+
// `detectHarnessCrashSignature` besides joining the traceback cluster.
|
|
66
|
+
const DEGRADED_READING_CRASH_SIGNALS = [
|
|
67
|
+
/'_DegradedReading' object is not subscriptable/,
|
|
68
|
+
/'_DegradedReading' object is not iterable/,
|
|
69
|
+
];
|
|
59
70
|
const STARTUP_TRACEBACK_SIGNALS = [
|
|
60
71
|
/AttributeError:.*SystemResourceMonitor/,
|
|
61
72
|
/'SystemResourceMonitor' object has no attribute/,
|
|
@@ -64,6 +75,7 @@ const STARTUP_TRACEBACK_SIGNALS = [
|
|
|
64
75
|
/HOST_VM_INFO64/,
|
|
65
76
|
/\(ipc\/mig\) array not large enough/,
|
|
66
77
|
/psutil\.[A-Za-z]*Error/,
|
|
78
|
+
...DEGRADED_READING_CRASH_SIGNALS,
|
|
67
79
|
];
|
|
68
80
|
function findLine(output, patterns) {
|
|
69
81
|
for (const line of output.split(/\r?\n/)) {
|
|
@@ -83,7 +95,11 @@ function findLine(output, patterns) {
|
|
|
83
95
|
const NOISE_LINE_PATTERNS = [
|
|
84
96
|
/\bUserWarning\b/,
|
|
85
97
|
/psutil failed to run/i,
|
|
86
|
-
|
|
98
|
+
// `_collect failed` chatter is noise on green runs, EXCEPT the
|
|
99
|
+
// `_DegradedReading` variant: that is the crash evidence for the
|
|
100
|
+
// "object is not iterable" startup-abort signature above and must
|
|
101
|
+
// survive stripNonSignalNoise.
|
|
102
|
+
/_collect failed(?!.*_DegradedReading)/i,
|
|
87
103
|
/FireForge: host resource monitor degraded/i,
|
|
88
104
|
/warnings\.warn\(/,
|
|
89
105
|
];
|
|
@@ -197,6 +213,18 @@ export function detectHarnessCrashSignature(output) {
|
|
|
197
213
|
// the no-output timeout. A trailing "Passed: 0" summary is part of this
|
|
198
214
|
// shape and must not be read as a result.
|
|
199
215
|
if (!hasTestStart) {
|
|
216
|
+
// Startup abort on the degraded-reading fallback family: the `_collect
|
|
217
|
+
// failed: '_DegradedReading' ...` variant is caught-and-logged (no
|
|
218
|
+
// Traceback header), so the traceback cluster above never sees it.
|
|
219
|
+
if (realFailures.length === 0) {
|
|
220
|
+
const degradedLine = findLine(evidence, DEGRADED_READING_CRASH_SIGNALS);
|
|
221
|
+
if (degradedLine) {
|
|
222
|
+
return {
|
|
223
|
+
reason: 'degraded resource reading crashed the harness fallback (_DegradedReading)',
|
|
224
|
+
line: degradedLine,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
200
228
|
const timeoutLine = findLine(evidence, [NO_OUTPUT_TIMEOUT_PATTERN]);
|
|
201
229
|
if (timeoutLine) {
|
|
202
230
|
return { reason: 'no-output timeout before any test started', line: timeoutLine };
|