7c20d09f30974b0f00fda2d5174a4c068b09fab6
[WebKit-https.git] / LayoutTests / streams / reference-implementation / readable-stream-templated.html
1 <!DOCTYPE html>
2 <script src='../../resources/testharness.js'></script>
3 <script src='../../resources/testharnessreport.js'></script>
4 <script src='resources/streams-utils.js'></script>
5 <script>
6 // This is updated till https://github.com/whatwg/streams/commit/ec5ffa036308d9f6350d2946560d48cdbf090939
7
8 function templatedRSEmpty(label, factory) {
9     test(function() {
10     }, 'Running templatedRSEmpty with ' + label);
11
12     test(function() {
13         var rs = factory();
14
15         assert_equals(typeof rs.locked, 'boolean', 'has a boolean locked getter');
16         assert_equals(typeof rs.cancel, 'function', 'has a cancel method');
17         assert_equals(typeof rs.getReader, 'function', 'has a getReader method');
18         assert_equals(typeof rs.pipeThrough, 'function', 'has a pipeThrough method');
19         assert_equals(typeof rs.pipeTo, 'function', 'has a pipeTo method');
20         assert_equals(typeof rs.tee, 'function', 'has a tee method');
21     }, 'instances have the correct methods and properties');
22 }
23
24 function templatedRSClosed(label, factory) {
25     test(function() {
26     }, 'Running templatedRSClosed with ' + label);
27
28     var test1 = async_test('cancel() should return a distinct fulfilled promise each time');
29     test1.step(function() {
30         var rs = factory();
31         var promisesCount = 0;
32         var allChecked = false;
33
34         var cancelPromise1 = rs.cancel();
35         var cancelPromise2 = rs.cancel();
36
37         cancelPromise1.then(test1.step_func(function(v) {
38             assert_equals(v, undefined, 'first cancel() call should fulfill with undefined');
39             ++promisesCount;
40         }));
41         cancelPromise2.then(test1.step_func(function(v) {
42             assert_equals(v, undefined, 'second cancel() call should fulfill with undefined');
43             assert_equals(++promisesCount, 2);
44             assert_true(allChecked);
45             test1.done();
46         }));
47         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
48         allChecked = true;
49     });
50
51     test(function() {
52         var rs = factory();
53
54         assert_false(rs.locked, 'locked getter should return false');
55     }, 'locked should be false');
56
57     test(function() {
58         var rs = factory();
59
60         rs.getReader(); // getReader() should not throw.
61     }, 'getReader() should be OK');
62
63     var test2 = async_test('piping to a WritableStream in the writable state should close the writable stream');
64     test2.step(function() {
65         var closeCalled = false;
66
67         var rs = factory();
68
69         var startPromise = Promise.resolve();
70         var ws = new WritableStream({
71             start: function() {
72                 return startPromise;
73             },
74             write: function() {
75                 assert_unreached('Unexpected write call');
76             },
77             close: function() {
78                 closeCalled = true;
79             },
80             abort: function() {
81                 assert_unreached('Unexpected abort call');
82             }
83         });
84
85         startPromise.then(test2.step_func(function() {
86             assert_equals(ws.state, 'writable', 'writable stream should start in writable state');
87
88             return rs.pipeTo(ws).then(test2.step_func(function() {
89                 assert_true(closeCalled);
90                 assert_equals(ws.state, 'closed', 'writable stream should become closed');
91                 test2.done('underlying source close should be called');
92             }));
93         })).catch(test2.step_func(function(e) { assert_unreached(e); }));
94     });
95
96     var test3 = async_test('piping to a WritableStream in the writable state with { preventClose: true } should do nothing');
97     test3.step(function() {
98         var rs = factory();
99
100         var startPromise = Promise.resolve();
101         var ws = new WritableStream({
102             start: function() {
103                 return startPromise;
104             },
105             write: function() {
106                 assert_unreached('Unexpected write call');
107             },
108             close: function() {
109                 assert_unreached('Unexpected close call');
110             },
111             abort: function() {
112                 assert_unreached('Unexpected abort call');
113             }
114         });
115
116         startPromise.then(test3.step_func(function() {
117             assert_equals(ws.state, 'writable', 'writable stream should start in writable state');
118
119             return rs.pipeTo(ws, { preventClose: true }).then(test3.step_func(function() {
120                 assert_equals(ws.state, 'writable', 'writable stream should still be writable');
121                 test3.done('pipeTo promise should be fulfilled');
122             }));
123         })).catch(test3.step_func(function(e) { assert_unreached(e); }));
124     });
125
126     test(function() {
127         var rs = factory();
128
129         var reader = rs.getReader();
130         reader.releaseLock();
131
132         reader = rs.getReader(); // Getting a second reader should not throw.
133         reader.releaseLock();
134
135         rs.getReader(); // Getting a third reader should not throw.
136     }, 'should be able to acquire multiple readers if they are released in succession');
137
138     test(function() {
139         var rs = factory();
140
141         rs.getReader();
142
143         assert_throws(new TypeError(), function() { rs.getReader(); }, 'getting a second reader should throw');
144         assert_throws(new TypeError(), function() { rs.getReader(); }, 'getting a third reader should throw');
145     }, 'should not be able to acquire a second reader if we don\'t release the first one');
146 };
147
148 function templatedRSErrored(label, factory, error) {
149     test(function() {
150     }, 'Running templatedRSErrored with ' + label);
151
152     var test1 = async_test('piping to a WritableStream in the writable state should abort the writable stream');
153     test1.step(function() {
154         var rs = factory();
155
156         var startPromise = Promise.resolve();
157         var ws = new WritableStream({
158             start: function() {
159                 return startPromise;
160             },
161             write: function() {
162                 assert_unreached('Unexpected write call');
163             },
164             close: function() {
165                 assert_reached('Unexpected close call');
166             },
167             abort: function(reason) {
168                 assert_equals(reason, error);
169             }
170         });
171
172         startPromise.then(test1.step_func(function() {
173             assert_equals(ws.state, 'writable');
174
175             rs.pipeTo(ws).then(
176                 test1.step_func(function() { assert_unreached('pipeTo promise should not be fulfilled'); }),
177                 test1.step_func(function(e) {
178                     assert_equals(e, error, 'pipeTo promise should be rejected with the passed error');
179                     assert_equals(ws.state, 'errored', 'writable stream should become errored');
180                     test1.done();
181                 })
182             );
183         }));
184     });
185
186     var test2 = async_test('getReader() should return a reader that acts errored');
187     test2.step(function() {
188         var rs = factory();
189         var promisesCount = 0;
190
191         var reader = rs.getReader();
192
193         reader.closed.catch(test2.step_func(function(e) {
194             assert_equals(e, error, 'reader.closed should reject with the error');
195             if (++promisesCount === 2)
196                 test2.done();
197         }));
198         reader.read().catch(test2.step_func(function(e) {
199             assert_equals(e, error, 'reader.read() should reject with the error');
200             if (++promisesCount === 2)
201                 test2.done();
202         }));
203     });
204
205     var test3 = async_test('read() twice should give the error each time');
206     test3.step(function() {
207         var rs = factory();
208         var promiseCount = 0;
209
210         var reader = rs.getReader();
211
212         reader.read().catch(test3.step_func(function(e) {
213             assert_equals(e, error, 'reader.read() should reject with the error');
214             assert_equals(++promiseCount, 1);
215         }));
216         reader.read().catch(test3.step_func(function(e) {
217             assert_equals(e, error, 'reader.read() should reject with the error');
218             assert_equals(++promiseCount, 2);
219         }));
220         reader.closed.catch(test3.step_func(function(e) {
221             assert_equals(e, error, 'reader.closed should reject with the error');
222             assert_equals(++promiseCount, 3);
223             test3.done();
224         }));
225    });
226
227     test(function() {
228         var rs = factory();
229
230         assert_false(rs.locked, 'locked getter should return false');
231     }, 'locked should be false');
232 };
233
234 function templatedRSErroredAsyncOnly(label, factory, error) {
235     test(function() {
236     }, 'Running templatedRSErroredAsyncOnly with ' + label);
237
238     var test1 = async_test('piping with no options');
239     test1.step(function() {
240         var closeCalled = false;
241
242         var rs = factory();
243
244         var ws = new WritableStream({
245             abort: function(r) {
246                 assert_equals(r, error, 'reason passed to abort should equal the source error');
247             }
248         });
249
250         rs.pipeTo(ws).catch(test1.step_func(function(e) {
251             assert_equals(ws.state, 'errored', 'destination should be errored');
252             assert_equals(e, error, 'rejection reason of pipeToPromise should be the source error');
253             assert_true(closeCalled);
254             test1.done();
255         }));
256
257         ws.closed.catch(test1.step_func(function(e) {
258             assert_equals(e, error, 'rejection reason of dest closed should be the source error');
259             closeCalled = true;
260         }))
261     });
262
263     var test2 = async_test('piping with { preventAbort: false }');
264     test2.step(function() {
265         var abortCalled = false;
266         var closeRejected = false;
267
268         var rs = factory();
269
270         var ws = new WritableStream({
271             abort: function(r) {
272                 assert_equals(r, error, 'reason passed to abort should equal the source error');
273                 abortCalled = true;
274             }
275         });
276
277         rs.pipeTo(ws, { preventAbort: false }).catch(test2.step_func(function(e) {
278             assert_equals(ws.state, 'errored', 'destination should be errored');
279             assert_equals(e, error, 'rejection reason of pipeToPromise should be the source error');
280             assert_true(abortCalled);
281             assert_true(closeRejected);
282             test2.done();
283         }));
284
285         ws.closed.catch(test2.step_func(function(e) {
286             assert_equals(e, error, 'rejection reason of dest closed should be the source error');
287             closeRejected = true;
288         }));
289     });
290
291     var test3 = async_test('piping with { preventAbort: true }');
292     test3.step(function() {
293         var rs = factory();
294
295         var ws = new WritableStream({
296             abort: function() {
297                 assert_unreached('underlying sink abort should not be called');
298             }
299         });
300
301         rs.pipeTo(ws, { preventAbort: true }).catch(test3.step_func(function(e) {
302             assert_equals(ws.state, 'writable', 'destination should remain writable');
303             assert_equals(e, error, 'rejection reason of pipeToPromise should be the source error');
304             test3.done();
305         }));
306    });
307 };
308
309 function templatedRSErroredSyncOnly(label, factory, error) {
310     test(function() {
311     }, 'Running templatedRSErroredSyncOnly with ' + label);
312
313     var test1 = async_test('should be able to obtain a second reader, with the correct closed promise');
314     test1.step(function() {
315         var rs = factory();
316
317         rs.getReader().releaseLock();
318
319         var reader = rs.getReader(); // Calling getReader() twice does not throw (the stream is not locked).
320
321         reader.closed.then(
322             test1.step_func(function() { assert_unreached('closed promise should not be fulfilled when stream is errored'); }),
323             test1.step_func(function(err) {
324                 assert_equals(err, error);
325                 test1.done();
326             })
327         );
328     });
329
330     test(function() {
331         var rs = factory();
332
333         rs.getReader();
334
335         assert_throws(new TypeError(), function() { rs.getReader(); }, 'getting a second reader should throw a TypeError');
336         assert_throws(new TypeError(), function() { rs.getReader(); }, 'getting a third reader should throw a TypeError');
337     }, 'should not be able to obtain additional readers if we don\'t release the first lock');
338
339     var test2 = async_test('cancel() should return a distinct rejected promise each time');
340     test2.step(function() {
341         var rs = factory();
342         var promisesCount = 0;
343         var allChecked = false;
344
345         var cancelPromise1 = rs.cancel();
346         var cancelPromise2 = rs.cancel();
347
348         cancelPromise1.catch(test2.step_func(function(e) {
349             assert_equals(e, error, 'first cancel() call should reject with the error');
350             ++promisesCount;
351         }));
352         cancelPromise2.catch(test2.step_func(function(e) {
353             assert_equals(e, error, 'second cancel() call should reject with the error');
354             assert_equals(++promisesCount, 2);
355             assert_true(allChecked);
356             test2.done();
357         }));
358         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
359         allChecked = true;
360     });
361
362     var test3 = async_test('reader cancel() should return a distinct rejected promise each time');
363     test3.step(function() {
364         var rs = factory();
365         var reader = rs.getReader();
366         var promisesCount = 0;
367         var allChecked = false;
368
369         var cancelPromise1 = reader.cancel();
370         var cancelPromise2 = reader.cancel();
371
372         cancelPromise1.catch(test3.step_func(function(e) {
373             assert_equals(e, error, 'first cancel() call should reject with the error');
374             ++promisesCount;
375         }));
376         cancelPromise2.catch(test3.step_func(function(e) {
377             assert_equals(e, error, 'second cancel() call should reject with the error');
378             assert_equals(++promisesCount, 2);
379             assert_true(allChecked);
380             test3.done();
381         }));
382         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
383         allChecked = true;
384     });
385 };
386
387 function templatedRSTwoChunksClosed(label, factory, error) {
388     test(function() {
389     }, 'Running templatedRSTwoChunksClosed with ' + label);
390
391     var test1 = async_test('piping with no options and no destination errors');
392     test1.step(function() {
393         var rs = factory();
394
395         var chunksWritten = [];
396         var ws = new WritableStream({
397             abort: function() {
398                 assert_unreached('unexpected abort call');
399             },
400             write: function(chunk) {
401                 chunksWritten.push(chunk);
402             }
403         });
404
405         rs.pipeTo(ws).then(test1.step_func(function() {
406             assert_equals(ws.state, 'closed', 'destination should be closed');
407             assert_array_equals(chunksWritten, chunks);
408             test1.done();
409         }));
410     });
411
412     var test2 = async_test('piping with { preventClose: false } and no destination errors');
413     test2.step(function() {
414         var rs = factory();
415
416         var chunksWritten = [];
417         var ws = new WritableStream({
418             abort: function() {
419                 assert_unreached('unexpected abort call');
420             },
421             write: function(chunk) {
422                 chunksWritten.push(chunk);
423             }
424         });
425
426         rs.pipeTo(ws).then(test2.step_func(function() {
427             assert_equals(ws.state, 'closed', 'destination should be closed');
428             assert_array_equals(chunksWritten, chunks);
429             test2.done();
430         }));
431     });
432
433     var test3 = async_test('piping with { preventClose: true } and no destination errors');
434     test3.step(function() {
435         var rs = factory();
436
437         var chunksWritten = [];
438         var ws = new WritableStream({
439             close: function() {
440                 assert_unreached('unexpected close call');
441             },
442             abort: function() {
443                 assert_unreached('unexpected abort call');
444             },
445             write: function(chunk) {
446                 chunksWritten.push(chunk);
447             }
448         });
449
450         rs.pipeTo(ws, { preventClose: true }).then(test3.step_func(function() {
451             assert_equals(ws.state, 'writable', 'destination should be writable');
452             assert_array_equals(chunksWritten, chunks);
453             test3.done();
454         }));
455     });
456
457     var test4 = async_test('piping with { preventClose: false } and a destination with that errors synchronously');
458     test4.step(function() {
459         var rs = factory();
460
461         var theError = new Error('!!!');
462         var ws = new WritableStream({
463             close: function() {
464                 assert_unreached('unexpected close call');
465             },
466             abort: function() {
467                 assert_unreached('unexpected abort call');
468             },
469             write: function() {
470                 throw theError;
471             }
472         });
473
474         rs.pipeTo(ws, { preventClose: false }).then(
475             test4.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }),
476             test4.step_func(function(e) {
477                 assert_equals(e, theError, 'pipeTo promise should reject with the write error');
478                 test4.done();
479             })
480         );
481     });
482
483     var test5 = async_test('piping with { preventClose: true } and a destination with that errors synchronously');
484     test5.step(function() {
485         var rs = factory();
486
487         var theError = new Error('!!!');
488         var ws = new WritableStream({
489             close: function() {
490                 assert_unreached('unexpected close call');
491             },
492             abort: function() {
493                 assert_unreached('unexpected abort call');
494             },
495             write: function() {
496                 throw theError;
497             }
498         });
499
500         rs.pipeTo(ws, { preventClose: true }).then(
501             test5.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }),
502             test5.step_func(function(e) {
503                 assert_equals(e, theError, 'pipeTo promise should reject with the write error');
504                 test5.done();
505             })
506         );
507     });
508
509     var test6 = async_test('piping with { preventClose: true } and a destination that errors on the last chunk');
510     test6.step(function() {
511         var rs = factory();
512
513         var theError = new Error('!!!');
514         var chunkCounter = 0;
515         var ws = new WritableStream(
516             {
517                 close: function() {
518                     assert_unreached('unexpected close call');
519                 },
520                 abort: function() {
521                     assert_unreached('unexpected abort call');
522                 },
523                 write: function() {
524                     if (++chunkCounter === 2) {
525                         return new Promise(test6.step_func(function(r, reject) { setTimeout(test6.step_func(function() { reject(theError); }), 200); }));
526                     }
527                 }
528             },
529             {
530                 highWaterMark: Infinity,
531                 size: function() { return 1; }
532             }
533         );
534
535         rs.pipeTo(ws, { preventClose: true }).then(
536             test6.step_func(function() { assert_unreached('pipeTo promise should not fulfill'); }),
537             test6.step_func(function(e) {
538                 assert_equals(e, theError, 'pipeTo promise should reject with the write error');
539                 test6.done();
540             })
541         );
542     });
543 };
544
545 function templatedRSEmptyReader(label, factory) {
546     test(function() {
547     }, 'Running templatedRSEmptyReader with ' + label);
548
549     test(function() {
550         var { reader } = factory();
551
552         assert_true('closed' in reader, 'has a closed property');
553         assert_equals(typeof reader.closed.then, 'function', 'closed property is thenable');
554
555         assert_equals(typeof reader.cancel, 'function', 'has a cancel method');
556         assert_equals(typeof reader.read, 'function', 'has a read method');
557         assert_equals(typeof reader.releaseLock, 'function', 'has a releaseLock method');
558     }, 'instances have the correct methods and properties');
559
560     test(function() {
561         var { stream } = factory();
562
563         assert_true(stream.locked, 'locked getter should return true');
564     }, 'locked should be true');
565
566     var test1 = async_test('read() should never settle');
567     test1.step(function() {
568         var { reader } = factory();
569
570         reader.read().then(
571             test1.step_func(function() { assert_unreached('read() should not fulfill'); }),
572             test1.step_func(function() { assert_unreached('read() should not reject'); })
573         );
574
575         setTimeout(test1.step_func(function() { test1.done(); }), 1000);
576     });
577
578     var test2 = async_test('two read()s should both never settle');
579     test2.step(function() {
580         var { reader } = factory();
581
582         reader.read().then(
583             test2.step_func(function() { assert_unreached('first read() should not fulfill'); }),
584             test2.step_func(function() { assert_unreached('first read() should not reject'); })
585         );
586
587         reader.read().then(
588             test2.step_func(function() { assert_unreached('second read() should not fulfill'); }),
589             test2.step_func(function() { assert_unreached('second read() should not reject'); })
590         );
591
592         setTimeout(test2.step_func(function() { test2.done(); }), 1000);
593     });
594
595     test(function() {
596         var { reader } = factory();
597
598         assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
599     }, 'read() should return distinct promises each time');
600
601     test(function() {
602         var { stream } = factory();
603
604         assert_throws(new TypeError(), function() { stream.getReader(); }, 'stream.getReader() should throw a TypeError');
605     }, 'getReader() again on the stream should fail');
606
607     var test3 = async_test('releasing the lock with pending read requests should throw but the read requests should stay pending');
608     test3.step(function() {
609         var { stream, reader } = factory();
610
611         reader.read().then(
612             test3.step_func(function() { assert_unreached('first read() should not fulfill'); }),
613             test3.step_func(function() { assert_unreached('first read() should not reject'); })
614         );
615
616         reader.read().then(
617             test3.step_func(function() { assert_unreached('second read() should not fulfill'); }),
618             test3.step_func(function() { assert_unreached('second read() should not reject'); })
619         );
620
621         reader.closed.then(
622             test3.step_func(function() { assert_unreached('closed should not fulfill'); }),
623             test3.step_func(function() { assert_unreached('closed should not reject'); })
624         );
625
626         assert_throws(new TypeError(), function() { reader.releaseLock(); }, 'releaseLock should throw a TypeError');
627
628         assert_true(stream.locked, 'the stream should still be locked');
629
630         setTimeout(test3.step_func(function() { test3.done(); }), 1000);
631     });
632
633     var test4 = async_test('releasing the lock should cause further read() calls to reject with a TypeError');
634     test4.step(function() {
635         var promiseCalls = 0;
636         var { reader } = factory();
637
638         reader.releaseLock();
639
640         reader.read().catch(test4.step_func(function(e) {
641             assert_throws(new TypeError(), function() { throw e; }, 'first read() should reject with a TypeError');
642             assert_equals(++promiseCalls, 1);
643         }));
644         reader.read().catch(test4.step_func(function(e) {
645             assert_throws(new TypeError(), function() { throw e; }, 'second read() should reject with a TypeError');
646             assert_equals(++promiseCalls, 2);
647             test4.done();
648         }));
649     });
650
651     var test5 = async_test('releasing the lock should cause closed to reject');
652     test5.step(function() {
653         var { reader } = factory();
654
655         var closedBefore = reader.closed;
656         reader.releaseLock();
657         var closedAfter = reader.closed;
658
659         assert_equals(closedBefore, closedAfter, 'the closed promise should not change identity')
660         closedBefore.catch(test5.step_func(function(e) {
661             assert_throws(new TypeError(), function() { throw e; }, 'reader.closed should reject with a TypeError');
662             test5.done();
663         }));
664     });
665
666     test(function() {
667         var { stream, reader } = factory();
668
669         reader.releaseLock();
670         assert_false(stream.locked, 'locked getter should return false');
671     }, 'releasing the lock should cause locked to become false');
672
673     var test6 = async_test('canceling via the reader should cause the reader to act closed');
674     test6.step(function() {
675         var { reader } = factory();
676
677         reader.cancel();
678         reader.read().then(test6.step_func(function(r) {
679             assert_object_equals(r, { value: undefined, done: true }, 'read()ing from the reader should give a done result');
680             test6.done();
681         }));
682     });
683
684     var test7 = async_test('canceling via the stream should fail');
685     test7.step(function() {
686         var { stream } = factory();
687
688         stream.cancel().catch(test7.step_func(function(e) {
689             assert_throws(new TypeError(), function() { throw e; }, 'cancel() should reject with a TypeError');
690             test7.done();
691         }));
692     });
693 };
694
695 function templatedRSClosedReader(label, factory) {
696     test(function() {
697     }, 'Running templatedRSClosedReader with ' + label);
698
699     var  test1 = async_test('read() should fulfill with { value: undefined, done: true }');
700     test1.step(function() {
701         var { reader } = factory();
702
703         reader.read().then(
704             test1.step_func(function(v) {
705                 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
706                 test1.done();
707             }),
708             test1.step_func(function() { assert_unreached('read() should not return a rejected promise'); })
709         );
710     });
711
712     var test2 = async_test('read() multiple times should fulfill with { value: undefined, done: true }');
713     test2.step(function() {
714         var { reader } = factory();
715         var readCount = 0;
716
717         reader.read().then(
718             test2.step_func(function(v) {
719                 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
720                 ++readCount;
721             }),
722             test2.step_func(function() { assert_unreached('read() should not return a rejected promise'); })
723         );
724         reader.read().then(
725             test2.step_func(function(v) {
726                 assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
727                 assert_equals(++readCount, 2);
728                 test2.done();
729             }),
730             test2.step_func(function() { assert_unreached('read() should not return a rejected promise'); })
731         );
732     });
733
734     var test3 = async_test('read() should work when used within another read() fulfill callback');
735     test3.step(function() {
736         var { reader } = factory();
737
738         reader.read().then(test3.step_func(function() { reader.read().then(test3.step_func(function() { test3.done('read() should fulfill'); })); }));
739     });
740
741     var test4 = async_test('closed should fulfill with undefined');
742     test4.step(function() {
743         var { reader } = factory();
744
745         reader.closed.then(
746             test4.step_func(function(v) {
747                 assert_equals(v, undefined, 'reader closed should fulfill with undefined');
748                 test4.done();
749             }),
750             test4.step_func(function() { assert_unreached('reader closed should not reject'); })
751         );
752     });
753
754     var test5 = async_test('releasing the lock should cause closed to reject and change identity');
755     test5.step(function() {
756         var promiseCalls = 0;
757         var { reader } = factory();
758
759         var closedBefore = reader.closed;
760         reader.releaseLock();
761         var closedAfter = reader.closed;
762
763         assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity')
764         closedBefore.then(test5.step_func(function(v) {
765             assert_equals(v, undefined, 'reader.closed acquired before release should fulfill');
766             assert_equals(++promiseCalls, 1);
767         }));
768         closedAfter.catch(test5.step_func(function(e) {
769             assert_throws(new TypeError(), function() { throw e; }, 'reader.closed acquired after release should reject with a TypeError');
770             assert_equals(++promiseCalls, 2);
771             test5.done();
772         }));
773     });
774
775     var test6 = async_test('cancel() should return a distinct fulfilled promise each time');
776     test6.step(function() {
777         var { reader } = factory();
778         var promiseCount = 0;
779         var allChecked = false;
780
781         var cancelPromise1 = reader.cancel();
782         var cancelPromise2 = reader.cancel();
783         var closedReaderPromise = reader.closed;
784
785         cancelPromise1.then(test6.step_func(function(v) {
786             assert_equals(v, undefined, 'first cancel() call should fulfill with undefined');
787             ++promiseCount;
788         }));
789         cancelPromise2.then(test6.step_func(function(v) {
790             assert_equals(v, undefined, 'second cancel() call should fulfill with undefined');
791             assert_equals(++promiseCount, 2);
792             assert_true(allChecked);
793             test6.done();
794         }));
795         assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
796         assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed');
797         assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed');
798         allChecked = true;
799     });
800 };
801
802 function templatedRSErroredReader(label, factory, error) {
803     test(function() {
804     }, 'Running templatedRSErroredReader with ' + label);
805
806     var test1 = async_test('closed should reject with the error');
807     test1.step(function() {
808         var { reader } = factory();
809
810         reader.closed.then(
811             test1.step_func(function() { assert_unreached('stream closed should not fulfill'); }),
812             test1.step_func(function(r) {
813                 assert_equals(r, error, 'stream closed should reject with the error');
814                 test1.done();
815             })
816         );
817     });
818
819     var test2 = async_test('releasing the lock should cause closed to reject and change identity');
820     test2.step(function() {
821         var { reader } = factory();
822
823         var closedBefore = reader.closed;
824
825         closedBefore.catch(test2.step_func(function(e) {
826             assert_equals(e, error, 'reader.closed acquired before release should reject with the error');
827
828             reader.releaseLock();
829             var closedAfter = reader.closed;
830
831             assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
832
833             return closedAfter.catch(test2.step_func(function(e) {
834                 assert_throws(new TypeError(), function() { throw e; }, 'reader.closed acquired after release should reject with a TypeError');
835                 test2.done();
836             }));
837         })).catch(test2.step_func(function(e) { assert_unreached(e); }));
838     });
839
840     var test3 = async_test('read() should reject with the error');
841     test3.step(function() {
842         var { reader } = factory();
843
844         reader.read().then(
845             test3.step_func(function() {
846                 assert_unreached('read() should not fulfill');
847             }),
848             test3.step_func(function(r) {
849                 assert_equals(r, error, 'read() should reject with the error');
850                 test3.done();
851             })
852         );
853     });
854 };
855
856 function templatedRSTwoChunksOpenReader(label, factory, chunks) {
857     test(function() {
858     }, 'Running templatedRSTwoChunksOpenReader with ' + label);
859
860     var test1 = async_test('calling read() twice without waiting will eventually give both chunks');
861     test1.step(function() {
862         var { reader } = factory();
863         var promiseCount = 0;
864
865         reader.read().then(test1.step_func(function(r) {
866             assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
867             ++promiseCount;
868         }));
869         reader.read().then(test1.step_func(function(r) {
870             assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
871             assert_equals(++promiseCount, 2);
872             test1.done();
873         }));
874     });
875
876     var test2 = async_test('calling read() twice with waiting will eventually give both chunks');
877     test2.step(function() {
878         var { reader } = factory();
879
880         reader.read().then(test2.step_func(function(r) {
881             assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
882
883             return reader.read().then(test2.step_func(function(r) {
884                 assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
885                 test2.done();
886             }));
887         })).catch(test2.step_func(function(e) { assert_unreached(e); }));
888     });
889
890     test(function() {
891         var { reader } = factory();
892
893         assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
894     }, 'read() should return distinct promises each time');
895
896     var test3 = async_test('cancel() after a read() should still give that single read result');
897     test3.step(function() {
898         var { reader } = factory();
899         var promiseCount = 0;
900
901         reader.closed.then(test3.step_func(function(v) {
902             assert_equals(v, undefined, 'reader closed should fulfill with undefined');
903             ++promiseCount;
904         }));
905
906         reader.read().then(test3.step_func(function(r) {
907             assert_object_equals(r, { value: chunks[0], done: false }, 'promise returned before cancellation should fulfill with a chunk');
908             ++promiseCount;
909         }));
910
911         reader.cancel();
912
913         reader.read().then(test3.step_func(function(r) {
914             assert_object_equals(r, { value: undefined, done: true }, 'promise returned after cancellation should fulfill with an end-of-stream signal');
915             assert_equals(++promiseCount, 3);
916             test3.done();
917         }))
918     });
919 };
920
921 function templatedRSTwoChunksClosedReader(label, factory, chunks) {
922     test(function() {
923     }, 'Running templatedRSTwoChunksClosedReader with ' + label);
924
925     var test1 = async_test('third read(), without waiting, should give { value: undefined, done: true }');
926     test1.step(function() {
927         var { reader } = factory();
928         var promiseCount = 0;
929
930         reader.read().then(test1.step_func(function(r) {
931             assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
932             ++promiseCount;
933         }));
934         reader.read().then(test1.step_func(function(r) {
935             assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
936             ++promiseCount;
937         }));
938         reader.read().then(test1.step_func(function(r) {
939             assert_object_equals(r, { value: undefined, done: true }, 'third result should be correct');
940             assert_equals(++promiseCount, 3);
941             test1.done();
942         }))
943     });
944
945     var test2 = async_test('third read, with waiting, should give { value: undefined, done: true }');
946     test2.step(function() {
947         var { reader } = factory();
948
949         reader.read().then(test2.step_func(function(r) {
950             assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
951
952             return reader.read().then(test2.step_func(function(r) {
953                 assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
954
955                 return reader.read().then(test2.step_func(function(r) {
956                     assert_object_equals(r, { value: undefined, done: true }, 'third result should be correct');
957                     test2.done();
958                 }));
959             }));
960         })).catch(test2.step_func(function(e) { assert_unreached(e); }));
961     });
962
963     var test3 = async_test('draining the stream via read() should cause the reader closed promise to fulfill, but locked stays true');
964     test3.step(function() {
965         var { stream, reader } = factory();
966
967         assert_true(stream.locked, 'stream should start locked');
968
969         reader.closed.then(
970             test3.step_func(function(v) {
971                 assert_equals(v, undefined, 'reader closed should fulfill with undefined');
972                 assert_true(stream.locked, 'stream should remain locked');
973                 test3.done();
974             }),
975             test3.step_func(function() { assert_unreached('reader closed should not reject'); })
976         );
977
978         reader.read();
979         reader.read();
980     });
981
982     var test4 = async_test('releasing the lock after the stream is closed should cause locked to become false');
983     test4.step(function() {
984         var { stream, reader } = factory();
985
986         reader.closed.then(test4.step_func(function() {
987             assert_true(stream.locked, 'the stream should start locked');
988             reader.releaseLock(); // Releasing the lock after reader closed should not throw.
989             assert_false(stream.locked, 'the stream should end unlocked');
990             test4.done();
991         }));
992
993         reader.read();
994         reader.read();
995     });
996
997     var test5 = async_test('releasing the lock should cause further read() calls to reject with a TypeError');
998     test5.step(function() {
999         var promiseCalls = 0;
1000         var { reader } = factory();
1001
1002         reader.releaseLock();
1003
1004         reader.read().catch(test5.step_func(function(e) {
1005             assert_throws(new TypeError(), function() { throw e; }, 'first read() should reject with a TypeError');
1006             assert_equals(++promiseCalls, 1);
1007         }));
1008         reader.read().catch(test5.step_func(function(e) {
1009             assert_throws(new TypeError(), function() { throw e; }, 'second read() should reject with a TypeError');
1010             assert_equals(++promiseCalls, 2);
1011         }));
1012         reader.read().catch(test5.step_func(function(e) {
1013             assert_throws(new TypeError(), function() { throw e; }, 'third read() should reject with a TypeError');
1014             assert_equals(++promiseCalls, 3);
1015             test5.done();
1016         }));
1017     });
1018
1019     var test6 = async_test('reader\'s closed property always returns the same promise');
1020     test6.step(function() {
1021         var { reader, stream } = factory();
1022
1023         var readerClosed = reader.closed;
1024
1025         assert_equals(reader.closed, readerClosed, 'accessing reader.closed twice in succession gives the same value');
1026
1027         reader.read().then(test6.step_func(function() {
1028             assert_equals(reader.closed, readerClosed, 'reader.closed is the same after read() fulfills');
1029
1030             reader.releaseLock();
1031
1032             assert_equals(reader.closed, readerClosed, 'reader.closed is the same after releasing the lock');
1033
1034             var newReader = stream.getReader();
1035             newReader.read();
1036
1037             test6.done();
1038         }));
1039
1040         assert_equals(reader.closed, readerClosed, 'reader.closed is the same after calling read()');
1041     });
1042 };
1043
1044 templatedRSEmpty('ReadableStream (empty)', function() {
1045     return new ReadableStream();
1046 });
1047
1048 templatedRSEmptyReader('ReadableStream (empty) reader', function() {
1049     return streamAndDefaultReader(new ReadableStream());
1050 });
1051
1052 templatedRSClosed('ReadableStream (closed via call in start)', function() {
1053     return new ReadableStream({
1054         start: function(c) {
1055             c.close();
1056         }
1057     });
1058 });
1059
1060 templatedRSClosedReader('ReadableStream reader (closed before getting reader)', function() {
1061     var controller;
1062     var stream = new ReadableStream({
1063         start: function(c) {
1064             controller = c;
1065         }
1066     });
1067     controller.close();
1068     var result = streamAndDefaultReader(stream);
1069     return result;
1070 });
1071
1072 templatedRSClosedReader('ReadableStream reader (closed after getting reader)', function() {
1073     var controller;
1074     var stream = new ReadableStream({
1075         start: function(c) {
1076             controller = c;
1077         }
1078     });
1079     var result = streamAndDefaultReader(stream);
1080     controller.close();
1081     return result;
1082 });
1083
1084 templatedRSClosed('ReadableStream (closed via cancel)', function() {
1085     var stream = new ReadableStream();
1086     stream.cancel();
1087     return stream;
1088 });
1089
1090 templatedRSClosedReader('ReadableStream reader (closed via cancel after getting reader)', function() {
1091     var stream = new ReadableStream();
1092     var result = streamAndDefaultReader(stream);
1093     result.reader.cancel();
1094     return result;
1095 });
1096
1097 var theError = new Error('boo!');
1098
1099 templatedRSErrored('ReadableStream (errored via call in start)', function() {
1100     return new ReadableStream({
1101         start: function(c) {
1102             c.error(theError);
1103         }
1104     })},
1105     theError
1106 );
1107
1108 templatedRSErroredSyncOnly('ReadableStream (errored via call in start)', function() {
1109     return new ReadableStream({
1110         start: function(c) {
1111             c.error(theError);
1112         }
1113     })},
1114     theError
1115 );
1116
1117 templatedRSErrored('ReadableStream (errored via returning a rejected promise in start)', function() {
1118     return new ReadableStream({
1119         start: function() {
1120             return Promise.reject(theError);
1121         }
1122     })},
1123     theError
1124 );
1125
1126 templatedRSErroredAsyncOnly('ReadableStream (errored via returning a rejected promise in start) reader', function() {
1127     return new ReadableStream({
1128         start: function() { return Promise.reject(theError); }
1129     })},
1130     theError
1131 );
1132
1133 templatedRSErroredReader('ReadableStream (errored via returning a rejected promise in start) reader', function() {
1134     return streamAndDefaultReader(new ReadableStream({
1135         start: function() {
1136             return Promise.reject(theError);
1137         }
1138     }))},
1139     theError
1140 );
1141
1142 templatedRSErroredReader('ReadableStream reader (errored before getting reader)', function() {
1143     var controller;
1144     var stream = new ReadableStream({
1145         start: function(c) {
1146             controller = c;
1147         }
1148     });
1149     controller.error(theError);
1150     return streamAndDefaultReader(stream);
1151 }, theError);
1152
1153 templatedRSErroredReader('ReadableStream reader (errored after getting reader)', function() {
1154     var controller;
1155     var result = streamAndDefaultReader(new ReadableStream({
1156         start: function(c) {
1157             controller = c;
1158         }
1159     }));
1160     controller.error(theError);
1161     return result;
1162 }, theError);
1163
1164 var chunks = ['a', 'b'];
1165
1166 templatedRSTwoChunksOpenReader('ReadableStream (two chunks enqueued, still open) reader', function() {
1167     return streamAndDefaultReader(new ReadableStream({
1168         start: function(c) {
1169             c.enqueue(chunks[0]);
1170             c.enqueue(chunks[1]);
1171         }
1172     }))},
1173     chunks
1174 );
1175
1176 templatedRSTwoChunksClosed('ReadableStream (two chunks enqueued, then closed)', function() {
1177     return new ReadableStream({
1178         start: function(c) {
1179             c.enqueue(chunks[0]);
1180             c.enqueue(chunks[1]);
1181             c.close();
1182         }
1183     })},
1184     chunks
1185 );
1186
1187 templatedRSTwoChunksClosed('ReadableStream (two chunks enqueued async, then closed)', function() {
1188     return new ReadableStream({
1189         start: function(c) {
1190             setTimeout(function() { c.enqueue(chunks[0]); }, 100);
1191             setTimeout(function() { c.enqueue(chunks[1]); }, 200);
1192             setTimeout(function() { c.close(); }, 300);
1193         }
1194     })},
1195     chunks
1196 );
1197
1198 templatedRSTwoChunksClosed('ReadableStream (two chunks enqueued via pull, then closed)', function() {
1199     var pullCall = 0;
1200
1201     return new ReadableStream({
1202         pull:function(c) {
1203             if (pullCall >= chunks.length) {
1204                 c.close();
1205             } else {
1206                 c.enqueue(chunks[pullCall++]);
1207             }
1208         }
1209     });
1210 },
1211 chunks
1212 );
1213
1214 templatedRSTwoChunksClosedReader('ReadableStream (two chunks enqueued, then closed) reader', function() {
1215     var doClose;
1216     var stream = new ReadableStream({
1217         start: function(c) {
1218             c.enqueue(chunks[0]);
1219             c.enqueue(chunks[1]);
1220             doClose = c.close.bind(c);
1221         }
1222     });
1223     var result = streamAndDefaultReader(stream);
1224     doClose();
1225     return result;
1226 }, chunks);
1227
1228 function streamAndDefaultReader(stream) {
1229   return { stream: stream, reader: stream.getReader() };
1230 }
1231 </script>