Skip to content

Commit cedda82

Browse files
committed
test(domformat): replace threadpool-racing turn spins with real signal waits
1 parent 6f65a00 commit cedda82

2 files changed

Lines changed: 78 additions & 15 deletions

File tree

‎packages/domformat/test/alternate-viewer.test.js‎

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ async function waitForScheduledWork(fake, predicate, message) {
139139
while (!predicate()) await whenSchedulerRearms(fake, message);
140140
}
141141

142+
// A page load only becomes observable once a shell actually calls its loader, and a shell reaches
143+
// that call behind a libuv-threadpool SHA-256 digest of the page it verified first. Load-count
144+
// assertions therefore wait on the loader itself; a fixed spin of event-loop turns settles in
145+
// microseconds and would race that wall-clock work.
146+
function createLoadSignal() {
147+
let notify = null;
148+
return {
149+
record() {
150+
const resume = notify;
151+
notify = null;
152+
resume?.();
153+
},
154+
async until(predicate, message) {
155+
while (!predicate()) {
156+
await new Promise((resolve, reject) => {
157+
const guard = setTimeout(() => { notify = null; reject(new Error(message)); }, 30_000);
158+
guard.unref?.();
159+
notify = () => { clearTimeout(guard); resolve(); };
160+
});
161+
}
162+
},
163+
};
164+
}
165+
142166
async function mountedPair(input, options = {}) {
143167
const built = buildDom(input);
144168
const externalResources = builtExternalResources(built);
@@ -150,13 +174,16 @@ async function mountedPair(input, options = {}) {
150174
const alternatePhases = [];
151175
const referencePageLoads = [];
152176
const alternatePageLoads = [];
177+
const loads = createLoadSignal();
153178
const eagerResources = new Map(externalResources);
154179
for (const record of built.document.resources.resources) if (record.kind === "state-page") eagerResources.delete(record.id);
155180
const result = await readDomBrowser(built.bytes, {
156181
externalResources: eagerResources,
157182
loadExternalResource(record, signal) {
158183
referencePageLoads.push([record.id, signal, referenceHost.childNodes.length]);
159-
return options.loadStatePage?.("reference", record, signal, externalResources) ?? externalResources.get(record.id);
184+
const loaded = options.loadStatePage?.("reference", record, signal, externalResources) ?? externalResources.get(record.id);
185+
loads.record();
186+
return loaded;
160187
},
161188
});
162189
const referenceRuntime = await mountDom(result, referenceHost, {
@@ -169,7 +196,9 @@ async function mountedPair(input, options = {}) {
169196
mode: options.mode,
170197
loadStatePage(record, signal) {
171198
alternatePageLoads.push([record.id, signal, alternateHost.childNodes.length]);
172-
return options.loadStatePage?.("alternate", record, signal, externalResources) ?? externalResources.get(record.id);
199+
const loaded = options.loadStatePage?.("alternate", record, signal, externalResources) ?? externalResources.get(record.id);
200+
loads.record();
201+
return loaded;
173202
},
174203
onLifecyclePhase: (phase) => alternatePhases.push(phase),
175204
});
@@ -185,6 +214,7 @@ async function mountedPair(input, options = {}) {
185214
alternatePageLoads,
186215
referenceRuntime,
187216
alternateRuntime,
217+
whenLoaded: loads.until,
188218
};
189219
}
190220

@@ -309,7 +339,7 @@ test("alternate and public viewers agree on deferred paged variants, readiness,
309339
assertEquivalent(value, "paged initial readiness");
310340
const initialSourceFrame = value.referenceRuntime.sourceFrame;
311341
assert.equal(value.alternateRuntime.sourceFrame, initialSourceFrame);
312-
await flushAsyncWork();
342+
await value.whenLoaded(() => value.alternatePageLoads.length >= 2, "The alternate viewer never started its lookahead page load.");
313343
assert.deepEqual(value.alternatePageLoads.map(([id]) => id), ["variant-page-1", "variant-page-2"]);
314344

315345
clearWrites(value.reference, value.alternate);
@@ -413,10 +443,11 @@ test("alternate and public interaction schedulers wait at paged animator boundar
413443
assert.doesNotThrow(() => value.alternate.frame(267));
414444
assert.equal(value.referenceRuntime.lifecycle.phase, "publish");
415445
assert.equal(value.alternateRuntime.lifecycle.phase, "publish");
416-
await flushAsyncWork();
446+
const leafIndex = value.result.document.tree.nodes.findIndex((node) => node.id === "synthetic/leaf");
447+
await waitForScheduledWork(value.reference, () => value.reference.namespaced[leafIndex].classes.includes("material-b"), "The reference viewer never resumed past its paged animator boundary.");
448+
await waitForScheduledWork(value.alternate, () => value.alternateRuntime.node("synthetic/leaf").classes.includes("material-b"), "The alternate viewer never resumed past its paged animator boundary.");
417449
assert.equal(value.referenceRuntime.lifecycle.phase, "publish");
418450
assert.equal(value.alternateRuntime.lifecycle.phase, "publish");
419-
const leafIndex = value.result.document.tree.nodes.findIndex((node) => node.id === "synthetic/leaf");
420451
assert.equal(value.reference.namespaced[leafIndex].classes.includes("material-b"), true);
421452
assert.equal(value.alternateRuntime.node("synthetic/leaf").classes.includes("material-b"), true);
422453
assertEquivalent(value, "paged interaction boundary readiness");
@@ -794,10 +825,10 @@ test("alternate and public viewers replace a stale page wait before responsive r
794825
delayPageTwo = true;
795826
assert.equal(value.referenceRuntime.seek(1), 1);
796827
assert.equal(value.alternateRuntime.seek(1), 1);
797-
await flushAsyncWork();
828+
await value.whenLoaded(() => pending.reference.length >= 1 && pending.alternate.length >= 1, "A viewer never started its opportunistic preload after seeking back.");
798829
value.reference.frame(34);
799830
value.alternate.frame(34);
800-
await flushAsyncWork();
831+
await value.whenLoaded(() => pending.reference.length >= 2 && pending.alternate.length >= 2, "A viewer never started both its opportunistic preload and its scheduler wait generation.");
801832
assert.equal(pending.reference.length, 2);
802833
assert.equal(pending.alternate.length, 2);
803834

@@ -807,18 +838,18 @@ test("alternate and public viewers replace a stale page wait before responsive r
807838
}
808839
value.reference.observers[0].callback();
809840
value.alternate.observers[0].callback();
810-
await flushAsyncWork();
811841
for (const path of ["reference", "alternate"]) {
842+
await value.whenLoaded(() => pending[path].length >= 3, `${path} never started its replacement preload`);
812843
assert.equal(pending[path].length, 3, `${path} starts one replacement preload`);
813844
assert.equal(pending[path][0].signal.aborted, true, `${path} cancels the superseded opportunistic preload`);
814845
assert.equal(pending[path][1].signal.aborted, true, `${path} cancels the stale scheduler wait generation`);
815846
assert.equal(pending[path][2].signal.aborted, false, `${path} keeps the post-reschedule preload live`);
816847
pending[path][2].resolve();
817848
}
818-
await flushAsyncWork();
819849
value.reference.frame(68);
820850
value.alternate.frame(68);
821-
await flushAsyncWork();
851+
await waitForScheduledWork(value.reference, () => value.referenceRuntime.sourceFrame === 3, "The reference viewer never published the replacement preload frame.");
852+
await waitForScheduledWork(value.alternate, () => value.alternateRuntime.sourceFrame === 3, "The alternate viewer never published the replacement preload frame.");
822853
assert.equal(value.referenceRuntime.lifecycle.phase, "publish");
823854
assert.equal(value.alternateRuntime.lifecycle.phase, "publish");
824855
assert.equal(value.referenceRuntime.sourceFrame, 3);

‎packages/domformat/test/browser-reader.test.js‎

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ async function waitForScheduledWork(fake, predicate, message) {
6666
while (!predicate()) await whenSchedulerRearms(fake, message);
6767
}
6868

69+
// A page load only becomes observable once the runtime actually calls its loader, and the runtime
70+
// reaches that call behind a libuv-threadpool SHA-256 digest of the page it verified first.
71+
// Load-count assertions therefore wait on the loader itself; a fixed spin of event-loop turns
72+
// settles in microseconds and would race that wall-clock work.
73+
function createLoadSignal() {
74+
let notify = null;
75+
return {
76+
record() {
77+
const resume = notify;
78+
notify = null;
79+
resume?.();
80+
},
81+
async until(predicate, message) {
82+
while (!predicate()) {
83+
await new Promise((resolve, reject) => {
84+
const guard = setTimeout(() => { notify = null; reject(new Error(message)); }, 30_000);
85+
guard.unref?.();
86+
notify = () => { clearTimeout(guard); resolve(); };
87+
});
88+
}
89+
},
90+
};
91+
}
92+
6993
function routeFetch(routes, calls = []) {
7094
return async (input, options) => {
7195
const url = String(input);
@@ -775,10 +799,12 @@ test("paged variants load the initial page before attach and retain the current/
775799
const eager = new Map(all);
776800
for (const record of built.document.resources.resources) if (record.kind === "state-page") eager.delete(record.id);
777801
const calls = [];
802+
const loads = createLoadSignal();
778803
const result = await readDomBrowser(built.bytes, {
779804
externalResources: eager,
780805
async loadExternalResource(record, signal) {
781806
calls.push([record.id, signal]);
807+
loads.record();
782808
return all.get(record.id);
783809
},
784810
});
@@ -792,7 +818,7 @@ test("paged variants load the initial page before attach and retain the current/
792818
const leaf = namespaced[leafIndex];
793819
assert.equal(calls[0][0], "variant-page-1");
794820
assert.equal(leaf.classes.includes("material-a"), true);
795-
await flushAsyncWork();
821+
await loads.until(() => calls.length >= 3, "The mount never started its retained lookahead page loads.");
796822
assert.deepEqual(calls.map(([id]) => id), ["variant-page-1", "variant-page-2", "variant-page-3"]);
797823

798824
assert.equal(await runtime.seekAsync(7), 7);
@@ -1231,18 +1257,20 @@ test("superseding an in-progress page load preserves the currently published fra
12311257
return [node.id, element];
12321258
})) };
12331259
const calls = [];
1260+
const loads = createLoadSignal();
12341261
let releasePage7;
12351262
const page7 = new Promise((resolve) => { releasePage7 = resolve; });
12361263
const pagedState = createPolycssPagedState(built.document, mounted, DEFAULT_LIMITS, async (record) => {
12371264
calls.push(record.id);
1265+
loads.record();
12381266
if (record.id === "playback-page-7") return page7;
12391267
return all.get(record.id);
12401268
});
12411269
await pagedState.prepareInitial();
12421270
await pagedState.ensureFrame(2);
12431271
pagedState.commit(pagedState.stage(2));
12441272
const stale = pagedState.ensureFrame(7);
1245-
await flushAsyncWork(2);
1273+
await loads.until(() => calls.includes("playback-page-7"), "The superseded frame never started its page load.");
12461274
assert.equal(calls.filter((id) => id === "playback-page-7").length, 1);
12471275
await pagedState.ensureFrame(3);
12481276
releasePage7(all.get("playback-page-7"));
@@ -1325,9 +1353,9 @@ test("paged interaction backpressures at a page boundary before mutating or dest
13251353
browser.frame(0);
13261354
assert.doesNotThrow(() => browser.frame(267));
13271355
assert.equal(runtime.lifecycle.phase, "publish");
1328-
await flushAsyncWork();
1329-
assert.equal(runtime.lifecycle.phase, "publish");
13301356
const leafIndex = result.document.tree.nodes.findIndex((node) => node.id === "synthetic/leaf");
1357+
await waitForScheduledWork(browser, () => browser.namespaced[leafIndex].classes.includes("material-b"), "The runtime never resumed past its paged animator boundary.");
1358+
assert.equal(runtime.lifecycle.phase, "publish");
13311359
assert.equal(browser.namespaced[leafIndex].classes.includes("material-b"), true);
13321360
runtime.destroy();
13331361
});
@@ -1341,16 +1369,18 @@ test("paged variants pin the fixed interaction page so a synchronous mode switch
13411369
const eager = new Map(all);
13421370
for (const record of built.document.resources.resources) if (record.kind === "state-page") eager.delete(record.id);
13431371
const calls = [];
1372+
const loads = createLoadSignal();
13441373
const result = await readDomBrowser(built.bytes, {
13451374
externalResources: eager,
13461375
loadExternalResource(record) {
13471376
calls.push(record.id);
1377+
loads.record();
13481378
return all.get(record.id);
13491379
},
13501380
});
13511381
const { document } = fakeBrowserDocument();
13521382
const runtime = await mountDom(result, new FakeElement(document, "main"), { animate: false, mode: "animation" });
1353-
await new Promise((resolve) => setImmediate(resolve));
1383+
await loads.until(() => calls.length >= 3, "The mount never started its pinned and lookahead page loads.");
13541384
assert.deepEqual(calls.slice(0, 3), ["variant-page-1", "variant-page-4", "variant-page-2"]);
13551385
assert.equal(await runtime.seekAsync(3), 3);
13561386
const callsBeforeSwitch = calls.length;
@@ -1376,6 +1406,8 @@ test("paged variant requests cancel stale generations and fail closed on late pa
13761406
});
13771407
const firstBrowser = fakeBrowserDocument();
13781408
const runtime = await mountDom(result, new FakeElement(firstBrowser.document, "main"), { animate: false });
1409+
// Not a load-count gate: variant-page-4 is requested by the stale seek below, not by the mount.
1410+
// Nothing here is asserted against threadpool work — this only lets the mount's own lookahead settle.
13791411
await new Promise((resolve) => setImmediate(resolve));
13801412
const stale = runtime.seekAsync(7);
13811413
const staleRejection = assert.rejects(stale, errorCode("OPERATION_ABORTED"));

0 commit comments

Comments
 (0)