From fd8c36d72e503ec1043113aa87e96a03c6c6e0c6 Mon Sep 17 00:00:00 2001 From: robot Date: Mon, 24 Aug 2026 05:10:56 +0000 Subject: [PATCH] fix(tests): stop hls_qa deep-probe suite hanging under jsdom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- __tests__/hls_qa.test.mjs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/__tests__/hls_qa.test.mjs b/__tests__/hls_qa.test.mjs index a5114c7..ff98290 100644 --- a/__tests__/hls_qa.test.mjs +++ b/__tests__/hls_qa.test.mjs @@ -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})(); -- 2.54.0