Compare commits

..
2 Commits
Author SHA1 Message Date
kfj001 9420bce9c1 Merge pull request 'fix(tests): stop hls_qa deep-probe suite hanging under jsdom (test-only)' (#3) from fix/jsdom-cycle-hang-hls-qa into master
CI / test (push) Successful in 36s
Reviewed-on: http://192.168.1.74:30008/kfj001/goon_plugin/pulls/3
Reviewed-by: kfj001 <kfj001@gmail.com>
2026-08-24 10:28:08 -07:00
robot fd8c36d72e fix(tests): stop hls_qa deep-probe suite hanging under jsdom
CI / test (pull_request) Successful in 1m56s
CI times out on the 7 "HLS QA: Deep probe strategies" tests. Root cause
is a jsdom-only artifact, not a production bug: jsdom exposes the
self-referential cyclic globals (_globalObject/_globalProxy/_top/_parent/
window/self/top/parent/frames) as ENUMERABLE own-props on the window.
Strategy H in m3u8DeepProbeFn() runs Object.keys(window) and a no-
visited-set walk() over each, which explodes to O(204^5) ≈ 3.5e11 ops and
never terminates within the depth-5 cap, so the whole suite hangs forever.

A real Chrome MAIN-world window does NOT expose those keys as enumerable
own-props, so this is purely a test-environment mismatch. The fix is
test-only: sanitizeJsdomWinForProbe() marks the jsdom cyclic globals
non-enumerable in runProbeInWin() before eval, mirroring a real page.
No production/underlying source (src/viewer/index.js) is modified.

Local (Node 22.22.3 / Linux == CI ubuntu-latest + Node 22):
before: npm test hangs indefinitely (no Results line; CI job killed)
after:  npm run build && npm test -> 83/83 passed in ~12.5s
2026-08-24 05:10:56 +00:00
+35
View File
@@ -679,8 +679,43 @@ if (!probeMatch) {
}
const probeFnSource = probeMatch[0];
// jsdom exposes a set of self-referential cyclic globals as *enumerable own-*
// properties on the window object. A real Chrome MAIN-world window does not:
// `window`, `self`, `top`, `parent`, `frames` are non-enumerable, and the
// jsdom-internal `_globalObject`/`_globalProxy`/`_top`/`_parent` keys don't
// exist at all. The deep-probe's Strategy H runs `Object.keys(window)` and a
// no-visited-set `walk()` over each player-like value, so these cyclic
// jsdom-only keys make the walk explode to O(keys^depth) (~3.5e11 ops) and the
// suite never terminates. We can't fix the probe (production src/ is off-limits
// for this test-only PR), so instead we make this jsdom window mirror a real
// page by marking the cyclic globals non-enumerable before the probe runs.
const JSDOM_CYCLIC_GLOBALS = [
'_globalObject', '_globalProxy', '_top', '_parent',
'window', 'self', 'top', 'parent', 'frames',
];
function sanitizeJsdomWinForProbe(win) {
for (const key of JSDOM_CYCLIC_GLOBALS) {
if (!Object.prototype.hasOwnProperty.call(win, key)) continue;
let desc;
try { desc = Object.getOwnPropertyDescriptor(win, key); } catch { continue; }
if (!desc) continue;
try {
// Re-assert the same descriptor but with enumerable:false so the probe's
// `Object.keys(window)` no longer sees jsdom's cyclic self-references.
Object.defineProperty(win, key, { ...desc, enumerable: false });
} catch {
// Non-configurable descriptors (e.g. the `window`/`top` getters) can't be
// redefined; skip them. The remaining cyclic keys are already suppressed,
// leaving only a shallow 2-branch recursion that terminates in ms.
}
}
}
// Helper: eval the probe function inside a JSDOM window, then return results
function runProbeInWin(win) {
// Mirror a real page environment (see JSDOM_CYCLIC_GLOBALS note above):
// non-enumerable cyclic globals prevent Strategy H's window scan from looping.
sanitizeJsdomWinForProbe(win);
// Define the function in the JSDOM context and call it
const result = win.eval(`
(${probeFnSource})();