Skip to content

Commit ed8fd31

Browse files
MattiasBuelensjgraham
authored andcommitted
Bug 1526596 [wpt PR 15097] - ReadableStream @@asyncIterator, a=testonly
Automatic update from web-platform-tests ReadableStream @@asyncIterator (#15097) Test async iteration of ReadableStream. Standard changes are in whatwg/streams#980. -- wpt-commits: de6f8fcf9b87e80811e9267a886cf891f6f864e0 wpt-pr: 15097
1 parent f6c977d commit ed8fd31

File tree

4 files changed

+432
-2
lines changed

4 files changed

+432
-2
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
// META: global=worker,jsshell
2+
// META: script=../resources/rs-utils.js
3+
// META: script=../resources/test-utils.js
4+
// META: script=../resources/recording-streams.js
5+
'use strict';
6+
7+
test(() => {
8+
assert_equals(ReadableStream.prototype[Symbol.asyncIterator], ReadableStream.prototype.getIterator);
9+
}, '@@asyncIterator() method is === to getIterator() method');
10+
11+
test(() => {
12+
const s = new ReadableStream();
13+
const it = s.getIterator();
14+
const proto = Object.getPrototypeOf(it);
15+
16+
const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype);
17+
assert_equals(Object.getPrototypeOf(proto), AsyncIteratorPrototype, 'prototype should extend AsyncIteratorPrototype');
18+
19+
const methods = ['next', 'return'].sort();
20+
assert_array_equals(Object.getOwnPropertyNames(proto).sort(), methods, 'should have all the correct methods');
21+
22+
for (const m of methods) {
23+
const propDesc = Object.getOwnPropertyDescriptor(proto, m);
24+
assert_false(propDesc.enumerable, 'method should be non-enumerable');
25+
assert_true(propDesc.configurable, 'method should be configurable');
26+
assert_true(propDesc.writable, 'method should be writable');
27+
assert_equals(typeof it[m], 'function', 'method should be a function');
28+
assert_equals(it[m].name, m, 'method should have the correct name');
29+
}
30+
31+
assert_equals(it.next.length, 0, 'next should have no parameters');
32+
assert_equals(it.return.length, 1, 'return should have 1 parameter');
33+
assert_equals(typeof it.throw, 'undefined', 'throw should not exist');
34+
}, 'Async iterator instances should have the correct list of properties');
35+
36+
promise_test(async () => {
37+
const s = new ReadableStream({
38+
start(c) {
39+
c.enqueue(1);
40+
c.enqueue(2);
41+
c.enqueue(3);
42+
c.close();
43+
},
44+
});
45+
46+
const chunks = [];
47+
for await (const chunk of s) {
48+
chunks.push(chunk);
49+
}
50+
assert_array_equals(chunks, [1, 2, 3]);
51+
}, 'Async-iterating a push source');
52+
53+
promise_test(async () => {
54+
let i = 1;
55+
const s = new ReadableStream({
56+
pull(c) {
57+
c.enqueue(i);
58+
if (i >= 3) {
59+
c.close();
60+
}
61+
i += 1;
62+
},
63+
});
64+
65+
const chunks = [];
66+
for await (const chunk of s) {
67+
chunks.push(chunk);
68+
}
69+
assert_array_equals(chunks, [1, 2, 3]);
70+
}, 'Async-iterating a pull source');
71+
72+
promise_test(async () => {
73+
let i = 1;
74+
const s = recordingReadableStream({
75+
pull(c) {
76+
c.enqueue(i);
77+
if (i >= 3) {
78+
c.close();
79+
}
80+
i += 1;
81+
},
82+
}, new CountQueuingStrategy({ highWaterMark: 0 }));
83+
84+
const it = s.getIterator();
85+
assert_array_equals(s.events, []);
86+
87+
const read1 = await it.next();
88+
assert_equals(read1.done, false);
89+
assert_equals(read1.value, 1);
90+
assert_array_equals(s.events, ['pull']);
91+
92+
const read2 = await it.next();
93+
assert_equals(read2.done, false);
94+
assert_equals(read2.value, 2);
95+
assert_array_equals(s.events, ['pull', 'pull']);
96+
97+
const read3 = await it.next();
98+
assert_equals(read3.done, false);
99+
assert_equals(read3.value, 3);
100+
assert_array_equals(s.events, ['pull', 'pull', 'pull']);
101+
102+
const read4 = await it.next();
103+
assert_equals(read4.done, true);
104+
assert_equals(read4.value, undefined);
105+
assert_array_equals(s.events, ['pull', 'pull', 'pull']);
106+
}, 'Async-iterating a pull source manually');
107+
108+
promise_test(async () => {
109+
const s = new ReadableStream({
110+
start(c) {
111+
c.error('e');
112+
},
113+
});
114+
115+
try {
116+
for await (const chunk of s) {}
117+
assert_unreached();
118+
} catch (e) {
119+
assert_equals(e, 'e');
120+
}
121+
}, 'Async-iterating an errored stream throws');
122+
123+
promise_test(async () => {
124+
const s = new ReadableStream({
125+
start(c) {
126+
c.close();
127+
}
128+
});
129+
130+
for await (const chunk of s) {
131+
assert_unreached();
132+
}
133+
}, 'Async-iterating a closed stream never executes the loop body, but works fine');
134+
135+
promise_test(async () => {
136+
const s = new ReadableStream();
137+
138+
const loop = async () => {
139+
for await (const chunk of s) {
140+
assert_unreached();
141+
}
142+
assert_unreached();
143+
};
144+
145+
await Promise.race([
146+
loop(),
147+
flushAsyncEvents()
148+
]);
149+
}, 'Async-iterating an empty but not closed/errored stream never executes the loop body and stalls the async function');
150+
151+
promise_test(async () => {
152+
const s = new ReadableStream({
153+
start(c) {
154+
c.enqueue(1);
155+
c.enqueue(2);
156+
c.enqueue(3);
157+
c.close();
158+
},
159+
});
160+
161+
const reader = s.getReader();
162+
const readResult = await reader.read();
163+
assert_equals(readResult.done, false);
164+
assert_equals(readResult.value, 1);
165+
reader.releaseLock();
166+
167+
const chunks = [];
168+
for await (const chunk of s) {
169+
chunks.push(chunk);
170+
}
171+
assert_array_equals(chunks, [2, 3]);
172+
}, 'Async-iterating a partially consumed stream');
173+
174+
for (const type of ['throw', 'break', 'return']) {
175+
for (const preventCancel of [false, true]) {
176+
promise_test(async () => {
177+
const s = recordingReadableStream({
178+
start(c) {
179+
c.enqueue(0);
180+
}
181+
});
182+
183+
// use a separate function for the loop body so return does not stop the test
184+
const loop = async () => {
185+
for await (const c of s.getIterator({ preventCancel })) {
186+
if (type === 'throw') {
187+
throw new Error();
188+
} else if (type === 'break') {
189+
break;
190+
} else if (type === 'return') {
191+
return;
192+
}
193+
}
194+
};
195+
196+
try {
197+
await loop();
198+
} catch (e) {}
199+
200+
if (preventCancel) {
201+
assert_array_equals(s.events, ['pull'], `cancel() should not be called`);
202+
} else {
203+
assert_array_equals(s.events, ['pull', 'cancel', undefined], `cancel() should be called`);
204+
}
205+
}, `Cancellation behavior when ${type}ing inside loop body; preventCancel = ${preventCancel}`);
206+
}
207+
}
208+
209+
for (const preventCancel of [false, true]) {
210+
promise_test(async () => {
211+
const s = recordingReadableStream({
212+
start(c) {
213+
c.enqueue(0);
214+
}
215+
});
216+
217+
const it = s.getIterator({ preventCancel });
218+
await it.return();
219+
220+
if (preventCancel) {
221+
assert_array_equals(s.events, [], `cancel() should not be called`);
222+
} else {
223+
assert_array_equals(s.events, ['cancel', undefined], `cancel() should be called`);
224+
}
225+
}, `Cancellation behavior when manually calling return(); preventCancel = ${preventCancel}`);
226+
}
227+
228+
promise_test(async () => {
229+
const s = new ReadableStream();
230+
const it = s[Symbol.asyncIterator]();
231+
await it.return();
232+
try {
233+
await it.return();
234+
assert_unreached();
235+
} catch (e) {}
236+
}, 'Calling return() twice rejects');
237+
238+
promise_test(async () => {
239+
const s = new ReadableStream({
240+
start(c) {
241+
c.enqueue(0);
242+
c.close();
243+
},
244+
});
245+
const it = s[Symbol.asyncIterator]();
246+
const next = await it.next();
247+
assert_equals(Object.getPrototypeOf(next), Object.prototype);
248+
assert_array_equals(Object.getOwnPropertyNames(next).sort(), ['done', 'value']);
249+
}, 'next()\'s fulfillment value has the right shape');
250+
251+
promise_test(async t => {
252+
const s = recordingReadableStream();
253+
const it = s[Symbol.asyncIterator]();
254+
it.next();
255+
256+
await promise_rejects(t, new TypeError(), it.return(), 'return() should reject');
257+
assert_array_equals(s.events, ['pull']);
258+
}, 'calling return() while there are pending reads rejects');
259+
260+
test(() => {
261+
const s = new ReadableStream({
262+
start(c) {
263+
c.enqueue(0);
264+
c.close();
265+
},
266+
});
267+
const it = s.getIterator();
268+
assert_throws(new TypeError(), () => s.getIterator(), 'getIterator() should throw');
269+
}, 'getIterator() throws if there\'s already a lock');
270+
271+
promise_test(async () => {
272+
const s = new ReadableStream({
273+
start(c) {
274+
c.enqueue(1);
275+
c.enqueue(2);
276+
c.enqueue(3);
277+
c.close();
278+
},
279+
});
280+
281+
const chunks = [];
282+
for await (const chunk of s) {
283+
chunks.push(chunk);
284+
}
285+
assert_array_equals(chunks, [1, 2, 3]);
286+
287+
const reader = s.getReader();
288+
await reader.closed;
289+
}, 'Acquiring a reader after exhaustively async-iterating a stream');
290+
291+
promise_test(async () => {
292+
const s = new ReadableStream({
293+
start(c) {
294+
c.enqueue(1);
295+
c.enqueue(2);
296+
c.enqueue(3);
297+
c.close();
298+
},
299+
});
300+
301+
// read the first two chunks, then cancel
302+
const chunks = [];
303+
for await (const chunk of s) {
304+
chunks.push(chunk);
305+
if (chunk >= 2) {
306+
break;
307+
}
308+
}
309+
assert_array_equals(chunks, [1, 2]);
310+
311+
const reader = s.getReader();
312+
await reader.closed;
313+
}, 'Acquiring a reader after partially async-iterating a stream');
314+
315+
promise_test(async () => {
316+
const s = new ReadableStream({
317+
start(c) {
318+
c.enqueue(1);
319+
c.enqueue(2);
320+
c.enqueue(3);
321+
c.close();
322+
},
323+
});
324+
325+
// read the first two chunks, then release lock
326+
const chunks = [];
327+
for await (const chunk of s.getIterator({preventCancel: true})) {
328+
chunks.push(chunk);
329+
if (chunk >= 2) {
330+
break;
331+
}
332+
}
333+
assert_array_equals(chunks, [1, 2]);
334+
335+
const reader = s.getReader();
336+
const readResult = await reader.read();
337+
assert_equals(readResult.done, false, 'should not be closed yet');
338+
assert_equals(readResult.value, 3, 'should read remaining chunk');
339+
await reader.closed;
340+
}, 'Acquiring a reader and reading the remaining chunks after partially async-iterating a stream with preventCancel = true');

testing/web-platform/tests/streams/readable-streams/brand-checks.any.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
let ReadableStreamDefaultReader;
66
let ReadableStreamDefaultController;
7+
let ReadableStreamAsyncIteratorPrototype;
78

89
test(() => {
910

@@ -23,6 +24,13 @@ test(() => {
2324

2425
}, 'Can get the ReadableStreamDefaultController constructor indirectly');
2526

27+
test(() => {
28+
29+
const rs = new ReadableStream();
30+
ReadableStreamAsyncIteratorPrototype = Object.getPrototypeOf(rs.getIterator());
31+
32+
}, 'Can get ReadableStreamAsyncIteratorPrototype object indirectly');
33+
2634
function fakeRS() {
2735
return Object.setPrototypeOf({
2836
cancel() { return Promise.resolve(); },
@@ -68,6 +76,13 @@ function realRSDefaultController() {
6876
return controller;
6977
}
7078

79+
function fakeRSAsyncIterator() {
80+
return Object.setPrototypeOf({
81+
next() { },
82+
return(value = undefined) { }
83+
}, ReadableStreamAsyncIteratorPrototype);
84+
}
85+
7186
promise_test(t => {
7287

7388
return methodRejectsForAll(t, ReadableStream.prototype, 'cancel',
@@ -157,3 +172,17 @@ test(() => {
157172
[fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
158173

159174
}, 'ReadableStreamDefaultController.prototype.error enforces a brand check');
175+
176+
promise_test(t => {
177+
178+
return methodRejectsForAll(t, ReadableStreamAsyncIteratorPrototype, 'next',
179+
[fakeRSAsyncIterator(), realRS(), realRSDefaultReader(), undefined, null]);
180+
181+
}, 'ReadableStreamAsyncIteratorPrototype.next enforces a brand check');
182+
183+
promise_test(t => {
184+
185+
return methodRejectsForAll(t, ReadableStreamAsyncIteratorPrototype, 'return',
186+
[fakeRSAsyncIterator(), realRS(), realRSDefaultReader(), undefined, null]);
187+
188+
}, 'ReadableStreamAsyncIteratorPrototype.return enforces a brand check');

0 commit comments

Comments
 (0)