blob: 5709acc507ed3cefb07b7f939fc161098aa97e6d [file] [edit]
// Verifies the for-await fast consumer consumes the EXACT number of microtask turns per step that the
// spec (and V8/Node) require -- no more, no fewer. Each scenario launches a fixed 8-turn background
// microtask "ruler" before the consumer; the interleaving of ruler ticks (Rn) with consumer/producer
// events makes the per-step tick cadence observable. The expected sequences below were captured from
// V8 (Node) and are the spec-correct ground truth; the fast consumer must reproduce them byte-for-byte.
// A drifted tick count (e.g. an extra/missing await turn) reorders these and fails.
function assert(cond, message) {
if (!cond)
throw new Error("Assertion failed: " + message);
}
async function scenario(body) {
const log = [];
const E = s => log.push(s);
let p = Promise.resolve();
for (let i = 0; i < 8; i++) {
const j = i;
p = p.then(() => E("R" + j));
}
await body(E);
return log.join(",");
}
const scenarios = {
async gen3(E) {
async function* g() { yield 1; yield 2; yield 3; }
for await (const x of g()) E("v" + x);
E("done");
},
async break(E) {
async function* g() { try { yield 1; yield 2; yield 3; } finally { E("fin"); } }
for await (const x of g()) { E("v" + x); if (x === 2) break; }
E("after");
},
async throw(E) {
async function* g() { try { yield 1; yield 2; } finally { E("fin"); } }
try { for await (const x of g()) { E("v" + x); if (x === 1) throw new Error("b"); } } catch (e) { E("caught"); }
},
async delegate(E) {
async function* inner() { yield "a"; yield "b"; }
async function* g() { yield 0; yield* inner(); yield 9; }
for await (const x of g()) E("v" + x);
},
async internalAwait(E) {
async function* g() { await 0; yield 1; await 0; yield 2; }
for await (const x of g()) E("v" + x);
},
async yieldPromise(E) {
async function* g() { yield Promise.resolve("p"); yield "q"; }
for await (const x of g()) E("v" + x);
},
async nested(E) {
async function* inner(k) { yield k + "a"; yield k + "b"; }
async function* outer() { yield 1; yield 2; }
for await (const o of outer()) for await (const i of inner(o)) E("v" + i);
},
async empty(E) {
async function* g() { }
for await (const x of g()) E("v" + x);
E("done");
},
async completedReconsume(E) {
async function* g() { yield 1; }
const it = g();
for await (const x of it) E("a" + x);
for await (const x of it) E("b" + x); // completed
E("done");
},
};
// Spec-correct interleavings (captured from V8 / Node).
const expected = {
gen3: "R0,R1,v1,R2,R3,v2,R4,R5,v3,R6,done,R7",
break: "R0,R1,v1,R2,R3,v2,R4,fin,R5,after,R6",
throw: "R0,R1,v1,R2,fin,R3,caught,R4",
delegate: "R0,R1,v0,R2,R3,R4,va,R5,R6,R7,vb,v9",
internalAwait: "R0,R1,R2,v1,R3,R4,R5,v2,R6,R7",
yieldPromise: "R0,R1,vp,R2,R3,vq,R4,R5",
nested: "R0,R1,R2,R3,v1a,R4,R5,v1b,R6,R7,v2a,v2b",
empty: "R0,done,R1",
completedReconsume: "R0,R1,a1,R2,R3,done,R4",
};
let done = false;
let error = null;
async function main() {
for (const name of Object.keys(expected)) {
const got = await scenario(scenarios[name]);
assert(got === expected[name], name + "\n expected: " + expected[name] + "\n got: " + got);
}
}
main().then(() => { done = true; }, (e) => { error = e; });
drainMicrotasks();
if (error)
throw error;
assert(done, "async main() did not complete");