Doc 20 D-1, second slice: the injector now injects asyncify-scheduler.js unconditionally — the WX_SCHEDULER=0 legacy opt-out, the SHIM_DISABLE_HANDLESLEEP ablation skip, and the SHIM_DISABLE_TRAMPOLINE_HEAL ablation skip are gone, and scripts/common/shims/handlesleep.js is deleted (the scheduler subsumed its capture/restore, fiber guard, and trampoline-heal duties in S2). Comment-only scrubs point the remaining references at the scheduler shim. .ci-cache-epoch bumped (shim/injector behavior changed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TEHGiiXMShNXbBr7gSJ7iz
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import { test, expect, tryLoadApp } from '../e2e/utils/fixtures';
|
|
|
|
/**
|
|
* S2 scheduler-core gates (docs/features/async/17 §3d N1/N4, §4 S2).
|
|
* Runs against the races harness; the scheduler shim is the only runtime
|
|
* (the legacy handlesleep opt-out was deleted at doc 20 D-1). The self-skip
|
|
* on shim-less glue is kept as a stale-build guard.
|
|
*
|
|
* N1 — single-writer tripwire: Asyncify.currData is an accessor; a pure-JS
|
|
* write without scheduler authorization beacons (and throws in strict mode).
|
|
* The meta-test INTRODUCES a stray writer and expects the tripwire to fire —
|
|
* proving the alarm works, not merely that nobody tripped it.
|
|
*
|
|
* N4 — wake-never-rewinds-mid-transition: across the races battery (which
|
|
* stages overlapping parks, nested modals, out-of-order wakes) the
|
|
* scheduler's books must be coherent at settle: no queued wake left, every
|
|
* deferral drained, zero unplanned strays, battery green.
|
|
*/
|
|
|
|
type SchedulerState = {
|
|
state(): string;
|
|
strayWrites: number;
|
|
strictStrays: boolean;
|
|
readyWakes: unknown[];
|
|
deferredWakes: number;
|
|
drainedWakes: number;
|
|
};
|
|
|
|
function findSummary(logs: string[]) {
|
|
return logs.find((log) => log.includes('[ASYNCIFY_RACES] SUMMARY'));
|
|
}
|
|
|
|
async function bootAndSettle(
|
|
page: import('@playwright/test').Page,
|
|
testLogger: { consoleLogs: string[] },
|
|
): Promise<boolean> {
|
|
await page.goto('/standalone/asyncify-races/races_test.html');
|
|
const loaded = await tryLoadApp(page, 30000);
|
|
expect(loaded, 'races harness should load').toBe(true);
|
|
await expect
|
|
.poll(() => findSummary(testLogger.consoleLogs) ?? null, {
|
|
timeout: 60000,
|
|
message: 'battery should emit its SUMMARY line',
|
|
})
|
|
.not.toBeNull();
|
|
return page.evaluate(
|
|
() => !!(globalThis as unknown as { __wxScheduler?: unknown }).__wxScheduler,
|
|
);
|
|
}
|
|
|
|
test.describe('S2 scheduler core (scheduler glue)', () => {
|
|
test('N4: battery leaves coherent books — wakes drained, no strays, battery green', async ({
|
|
page,
|
|
testLogger,
|
|
}) => {
|
|
test.setTimeout(180000);
|
|
const scheduler = await bootAndSettle(page, testLogger);
|
|
test.skip(!scheduler, 'legacy glue — scheduler core absent');
|
|
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
page.evaluate(
|
|
() =>
|
|
(globalThis as unknown as { __wxScheduler: SchedulerState }).__wxScheduler
|
|
.readyWakes.length,
|
|
),
|
|
{ timeout: 30000, intervals: [250] },
|
|
)
|
|
.toBe(0);
|
|
|
|
const books = await page.evaluate(() => {
|
|
const S = (globalThis as unknown as { __wxScheduler: SchedulerState }).__wxScheduler;
|
|
return {
|
|
ready: S.readyWakes.length,
|
|
deferred: S.deferredWakes,
|
|
drained: S.drainedWakes,
|
|
strays: S.strayWrites,
|
|
state: S.state(),
|
|
};
|
|
});
|
|
console.log(`[TEST] scheduler books: ${books.state}`);
|
|
|
|
expect(books.ready, 'no wake left queued after settle').toBe(0);
|
|
expect(books.drained, 'every deferred wake was drained').toBe(books.deferred);
|
|
expect(books.strays, 'no stray currData writes during the battery').toBe(0);
|
|
const fails = testLogger.consoleLogs.filter((l) => l.includes('[ASYNCIFY_RACES] FAIL '));
|
|
expect(fails, 'battery green under the scheduler core').toEqual([]);
|
|
});
|
|
|
|
test('N1 meta: an introduced stray currData write trips the alarm', async ({
|
|
page,
|
|
testLogger,
|
|
}) => {
|
|
test.setTimeout(180000);
|
|
const scheduler = await bootAndSettle(page, testLogger);
|
|
test.skip(!scheduler, 'legacy glue — scheduler core absent');
|
|
|
|
const result = await page.evaluate(() => {
|
|
const S = (globalThis as unknown as { __wxScheduler: SchedulerState }).__wxScheduler;
|
|
const A = (globalThis as unknown as { Asyncify: { currData: number | null } }).Asyncify;
|
|
const before = S.strayWrites;
|
|
const saved = A.currData;
|
|
A.currData = saved; // value-preserving, still a stray WRITE
|
|
const counted = S.strayWrites === before + 1;
|
|
S.strictStrays = true;
|
|
let threw = false;
|
|
try {
|
|
A.currData = saved;
|
|
} catch {
|
|
threw = true;
|
|
}
|
|
S.strictStrays = false;
|
|
return { counted, threw };
|
|
});
|
|
|
|
expect(result.counted, 'stray write was counted').toBe(true);
|
|
expect(result.threw, 'strict mode throws on stray write').toBe(true);
|
|
const beacons = testLogger.consoleLogs.filter((l) => l.includes('stray-currdata-write'));
|
|
expect(beacons.length, 'stray write beaconed to the console').toBeGreaterThan(0);
|
|
});
|
|
});
|