• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
2// META: script=../resources/test-utils.js
3// META: script=../resources/rs-utils.js
4'use strict';
5
6const error1 = new Error('error1');
7error1.name = 'error1';
8
9test(() => {
10
11  new ReadableStream(); // ReadableStream constructed with no parameters
12  new ReadableStream({ }); // ReadableStream constructed with an empty object as parameter
13  new ReadableStream({ type: undefined }); // ReadableStream constructed with undefined type
14  new ReadableStream(undefined); // ReadableStream constructed with undefined as parameter
15
16  let x;
17  new ReadableStream(x); // ReadableStream constructed with an undefined variable as parameter
18
19}, 'ReadableStream can be constructed with no errors');
20
21test(() => {
22
23  assert_throws_js(TypeError, () => new ReadableStream(null), 'constructor should throw when the source is null');
24
25}, 'ReadableStream can\'t be constructed with garbage');
26
27test(() => {
28
29  assert_throws_js(TypeError, () => new ReadableStream({ type: null }),
30    'constructor should throw when the type is null');
31  assert_throws_js(TypeError, () => new ReadableStream({ type: '' }),
32    'constructor should throw when the type is empty string');
33  assert_throws_js(TypeError, () => new ReadableStream({ type: 'asdf' }),
34    'constructor should throw when the type is asdf');
35  assert_throws_exactly(
36    error1,
37    () => new ReadableStream({ type: { get toString() { throw error1; } } }),
38    'constructor should throw when ToString() throws'
39  );
40  assert_throws_exactly(
41    error1,
42    () => new ReadableStream({ type: { toString() { throw error1; } } }),
43    'constructor should throw when ToString() throws'
44  );
45
46}, 'ReadableStream can\'t be constructed with an invalid type');
47
48test(() => {
49
50  assert_throws_js(TypeError, () => {
51    new ReadableStream({ start: 'potato' });
52  }, 'constructor should throw when start is not a function');
53
54}, 'ReadableStream constructor should throw for non-function start arguments');
55
56test(() => {
57
58  assert_throws_js(TypeError, () => new ReadableStream({ cancel: '2' }), 'constructor should throw');
59
60}, 'ReadableStream constructor will not tolerate initial garbage as cancel argument');
61
62test(() => {
63
64  assert_throws_js(TypeError, () => new ReadableStream({ pull: { } }), 'constructor should throw');
65
66}, 'ReadableStream constructor will not tolerate initial garbage as pull argument');
67
68test(() => {
69
70  let startCalled = false;
71
72  const source = {
73    start() {
74      assert_equals(this, source, 'source is this during start');
75      startCalled = true;
76    }
77  };
78
79  new ReadableStream(source);
80  assert_true(startCalled);
81
82}, 'ReadableStream start should be called with the proper thisArg');
83
84test(() => {
85
86  let startCalled = false;
87  const source = {
88    start(controller) {
89      const properties = ['close', 'constructor', 'desiredSize', 'enqueue', 'error'];
90      assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
91        'prototype should have the right properties');
92
93      controller.test = '';
94      assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
95        'prototype should still have the right properties');
96      assert_not_equals(Object.getOwnPropertyNames(controller).indexOf('test'), -1,
97        '"test" should be a property of the controller');
98
99      startCalled = true;
100    }
101  };
102
103  new ReadableStream(source);
104  assert_true(startCalled);
105
106}, 'ReadableStream start controller parameter should be extensible');
107
108test(() => {
109  (new ReadableStream()).getReader(undefined);
110  (new ReadableStream()).getReader({});
111  (new ReadableStream()).getReader({ mode: undefined, notmode: 'ignored' });
112  assert_throws_js(TypeError, () => (new ReadableStream()).getReader({ mode: 'potato' }));
113}, 'default ReadableStream getReader() should only accept mode:undefined');
114
115promise_test(() => {
116
117  function SimpleStreamSource() {}
118  let resolve;
119  const promise = new Promise(r => resolve = r);
120  SimpleStreamSource.prototype = {
121    start: resolve
122  };
123
124  new ReadableStream(new SimpleStreamSource());
125  return promise;
126
127}, 'ReadableStream should be able to call start method within prototype chain of its source');
128
129promise_test(() => {
130
131  const rs = new ReadableStream({
132    start(c) {
133      return delay(5).then(() => {
134        c.enqueue('a');
135        c.close();
136      });
137    }
138  });
139
140  const reader = rs.getReader();
141  return reader.read().then(r => {
142    assert_object_equals(r, { value: 'a', done: false }, 'value read should be the one enqueued');
143    return reader.closed;
144  });
145
146}, 'ReadableStream start should be able to return a promise');
147
148promise_test(() => {
149
150  const theError = new Error('rejected!');
151  const rs = new ReadableStream({
152    start() {
153      return delay(1).then(() => {
154        throw theError;
155      });
156    }
157  });
158
159  return rs.getReader().closed.then(() => {
160    assert_unreached('closed promise should be rejected');
161  }, e => {
162    assert_equals(e, theError, 'promise should be rejected with the same error');
163  });
164
165}, 'ReadableStream start should be able to return a promise and reject it');
166
167promise_test(() => {
168
169  const objects = [
170    { potato: 'Give me more!' },
171    'test',
172    1
173  ];
174
175  const rs = new ReadableStream({
176    start(c) {
177      for (const o of objects) {
178        c.enqueue(o);
179      }
180      c.close();
181    }
182  });
183
184  const reader = rs.getReader();
185
186  return Promise.all([reader.read(), reader.read(), reader.read(), reader.closed]).then(r => {
187    assert_object_equals(r[0], { value: objects[0], done: false }, 'value read should be the one enqueued');
188    assert_object_equals(r[1], { value: objects[1], done: false }, 'value read should be the one enqueued');
189    assert_object_equals(r[2], { value: objects[2], done: false }, 'value read should be the one enqueued');
190  });
191
192}, 'ReadableStream should be able to enqueue different objects.');
193
194promise_test(() => {
195
196  const error = new Error('pull failure');
197  const rs = new ReadableStream({
198    pull() {
199      return Promise.reject(error);
200    }
201  });
202
203  const reader = rs.getReader();
204
205  let closed = false;
206  let read = false;
207
208  return Promise.all([
209    reader.closed.then(() => {
210      assert_unreached('closed should be rejected');
211    }, e => {
212      closed = true;
213      assert_false(read);
214      assert_equals(e, error, 'closed should be rejected with the thrown error');
215    }),
216    reader.read().then(() => {
217      assert_unreached('read() should be rejected');
218    }, e => {
219      read = true;
220      assert_true(closed);
221      assert_equals(e, error, 'read() should be rejected with the thrown error');
222    })
223  ]);
224
225}, 'ReadableStream: if pull rejects, it should error the stream');
226
227promise_test(() => {
228
229  let pullCount = 0;
230
231  new ReadableStream({
232    pull() {
233      pullCount++;
234    }
235  });
236
237  return flushAsyncEvents().then(() => {
238    assert_equals(pullCount, 1, 'pull should be called once start finishes');
239    return delay(10);
240  }).then(() => {
241    assert_equals(pullCount, 1, 'pull should be called exactly once');
242  });
243
244}, 'ReadableStream: should only call pull once upon starting the stream');
245
246promise_test(() => {
247
248  let pullCount = 0;
249
250  const rs = new ReadableStream({
251    pull(c) {
252      // Don't enqueue immediately after start. We want the stream to be empty when we call .read() on it.
253      if (pullCount > 0) {
254        c.enqueue(pullCount);
255      }
256      ++pullCount;
257    }
258  });
259
260  return flushAsyncEvents().then(() => {
261    assert_equals(pullCount, 1, 'pull should be called once start finishes');
262  }).then(() => {
263    const reader = rs.getReader();
264    const read = reader.read();
265    assert_equals(pullCount, 2, 'pull should be called when read is called');
266    return read;
267  }).then(result => {
268    assert_equals(pullCount, 3, 'pull should be called again in reaction to calling read');
269    assert_object_equals(result, { value: 1, done: false }, 'the result read should be the one enqueued');
270  });
271
272}, 'ReadableStream: should call pull when trying to read from a started, empty stream');
273
274promise_test(() => {
275
276  let pullCount = 0;
277
278  const rs = new ReadableStream({
279    start(c) {
280      c.enqueue('a');
281    },
282    pull() {
283      pullCount++;
284    }
285  });
286
287  const read = rs.getReader().read();
288  assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
289
290  return flushAsyncEvents().then(() => {
291    assert_equals(pullCount, 1, 'pull should be called once start finishes');
292    return read;
293  }).then(r => {
294    assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
295    assert_equals(pullCount, 1, 'pull should not have been called again');
296    return delay(10);
297  }).then(() => {
298    assert_equals(pullCount, 1, 'pull should be called exactly once');
299  });
300
301}, 'ReadableStream: should only call pull once on a non-empty stream read from before start fulfills');
302
303promise_test(() => {
304
305  let pullCount = 0;
306  const startPromise = Promise.resolve();
307
308  const rs = new ReadableStream({
309    start(c) {
310      c.enqueue('a');
311    },
312    pull() {
313      pullCount++;
314    }
315  });
316
317  return flushAsyncEvents().then(() => {
318    assert_equals(pullCount, 0, 'pull should not be called once start finishes, since the queue is full');
319
320    const read = rs.getReader().read();
321    assert_equals(pullCount, 1, 'calling read() should cause pull to be called immediately');
322    return read;
323  }).then(r => {
324    assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
325    return delay(10);
326  }).then(() => {
327    assert_equals(pullCount, 1, 'pull should be called exactly once');
328  });
329
330}, 'ReadableStream: should only call pull once on a non-empty stream read from after start fulfills');
331
332promise_test(() => {
333
334  let pullCount = 0;
335  let controller;
336
337  const rs = new ReadableStream({
338    start(c) {
339      controller = c;
340    },
341    pull() {
342      ++pullCount;
343    }
344  });
345
346  const reader = rs.getReader();
347  return flushAsyncEvents().then(() => {
348    assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
349
350    controller.enqueue('a');
351    assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
352
353    return reader.read();
354  }).then(() => {
355    assert_equals(pullCount, 2, 'pull should have been called again after read');
356
357    return delay(10);
358  }).then(() => {
359    assert_equals(pullCount, 2, 'pull should be called exactly twice');
360  });
361}, 'ReadableStream: should call pull in reaction to read()ing the last chunk, if not draining');
362
363promise_test(() => {
364
365  let pullCount = 0;
366  let controller;
367
368  const rs = new ReadableStream({
369    start(c) {
370      controller = c;
371    },
372    pull() {
373      ++pullCount;
374    }
375  });
376
377  const reader = rs.getReader();
378
379  return flushAsyncEvents().then(() => {
380    assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
381
382    controller.enqueue('a');
383    assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
384
385    controller.close();
386
387    return reader.read();
388  }).then(() => {
389    assert_equals(pullCount, 1, 'pull should not have been called a second time after read');
390
391    return delay(10);
392  }).then(() => {
393    assert_equals(pullCount, 1, 'pull should be called exactly once');
394  });
395
396}, 'ReadableStream: should not call pull() in reaction to read()ing the last chunk, if draining');
397
398promise_test(() => {
399
400  let resolve;
401  let returnedPromise;
402  let timesCalled = 0;
403
404  const rs = new ReadableStream({
405    pull(c) {
406      c.enqueue(++timesCalled);
407      returnedPromise = new Promise(r => resolve = r);
408      return returnedPromise;
409    }
410  });
411  const reader = rs.getReader();
412
413  return reader.read()
414  .then(result1 => {
415    assert_equals(timesCalled, 1,
416      'pull should have been called once after start, but not yet have been called a second time');
417    assert_object_equals(result1, { value: 1, done: false }, 'read() should fulfill with the enqueued value');
418
419    return delay(10);
420  }).then(() => {
421    assert_equals(timesCalled, 1, 'after 10 ms, pull should still only have been called once');
422
423    resolve();
424    return returnedPromise;
425  }).then(() => {
426    assert_equals(timesCalled, 2,
427      'after the promise returned by pull is fulfilled, pull should be called a second time');
428  });
429
430}, 'ReadableStream: should not call pull until the previous pull call\'s promise fulfills');
431
432promise_test(() => {
433
434  let timesCalled = 0;
435
436  const rs = new ReadableStream(
437    {
438      start(c) {
439        c.enqueue('a');
440        c.enqueue('b');
441        c.enqueue('c');
442      },
443      pull() {
444        ++timesCalled;
445      }
446    },
447    {
448      size() {
449        return 1;
450      },
451      highWaterMark: Infinity
452    }
453  );
454  const reader = rs.getReader();
455
456  return flushAsyncEvents().then(() => {
457    return reader.read();
458  }).then(result1 => {
459    assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected');
460
461    return reader.read();
462  }).then(result2 => {
463    assert_object_equals(result2, { value: 'b', done: false }, 'second chunk should be as expected');
464
465    return reader.read();
466  }).then(result3 => {
467    assert_object_equals(result3, { value: 'c', done: false }, 'third chunk should be as expected');
468
469    return delay(10);
470  }).then(() => {
471    // Once for after start, and once for every read.
472    assert_equals(timesCalled, 4, 'pull() should be called exactly four times');
473  });
474
475}, 'ReadableStream: should pull after start, and after every read');
476
477promise_test(() => {
478
479  let timesCalled = 0;
480  const startPromise = Promise.resolve();
481
482  const rs = new ReadableStream({
483    start(c) {
484      c.enqueue('a');
485      c.close();
486      return startPromise;
487    },
488    pull() {
489      ++timesCalled;
490    }
491  });
492
493  const reader = rs.getReader();
494  return startPromise.then(() => {
495    assert_equals(timesCalled, 0, 'after start finishes, pull should not have been called');
496
497    return reader.read();
498  }).then(() => {
499    assert_equals(timesCalled, 0, 'reading should not have triggered a pull call');
500
501    return reader.closed;
502  }).then(() => {
503    assert_equals(timesCalled, 0, 'stream should have closed with still no calls to pull');
504  });
505
506}, 'ReadableStream: should not call pull after start if the stream is now closed');
507
508promise_test(() => {
509
510  let timesCalled = 0;
511  let resolve;
512  const ready = new Promise(r => resolve = r);
513
514  new ReadableStream(
515    {
516      start() {},
517      pull(c) {
518        c.enqueue(++timesCalled);
519
520        if (timesCalled === 4) {
521          resolve();
522        }
523      }
524    },
525    {
526      size() {
527        return 1;
528      },
529      highWaterMark: 4
530    }
531  );
532
533  return ready.then(() => {
534    // after start: size = 0, pull()
535    // after enqueue(1): size = 1, pull()
536    // after enqueue(2): size = 2, pull()
537    // after enqueue(3): size = 3, pull()
538    // after enqueue(4): size = 4, do not pull
539    assert_equals(timesCalled, 4, 'pull() should have been called four times');
540  });
541
542}, 'ReadableStream: should call pull after enqueueing from inside pull (with no read requests), if strategy allows');
543
544promise_test(() => {
545
546  let pullCalled = false;
547
548  const rs = new ReadableStream({
549    pull(c) {
550      pullCalled = true;
551      c.close();
552    }
553  });
554
555  const reader = rs.getReader();
556  return reader.closed.then(() => {
557    assert_true(pullCalled);
558  });
559
560}, 'ReadableStream pull should be able to close a stream.');
561
562promise_test(t => {
563
564  const controllerError = { name: 'controller error' };
565
566  const rs = new ReadableStream({
567    pull(c) {
568      c.error(controllerError);
569    }
570  });
571
572  return promise_rejects_exactly(t, controllerError, rs.getReader().closed);
573
574}, 'ReadableStream pull should be able to error a stream.');
575
576promise_test(t => {
577
578  const controllerError = { name: 'controller error' };
579  const thrownError = { name: 'thrown error' };
580
581  const rs = new ReadableStream({
582    pull(c) {
583      c.error(controllerError);
584      throw thrownError;
585    }
586  });
587
588  return promise_rejects_exactly(t, controllerError, rs.getReader().closed);
589
590}, 'ReadableStream pull should be able to error a stream and throw.');
591
592test(() => {
593
594  let startCalled = false;
595
596  new ReadableStream({
597    start(c) {
598      assert_equals(c.enqueue('a'), undefined, 'the first enqueue should return undefined');
599      c.close();
600
601      assert_throws_js(TypeError, () => c.enqueue('b'), 'enqueue after close should throw a TypeError');
602      startCalled = true;
603    }
604  });
605
606  assert_true(startCalled);
607
608}, 'ReadableStream: enqueue should throw when the stream is readable but draining');
609
610test(() => {
611
612  let startCalled = false;
613
614  new ReadableStream({
615    start(c) {
616      c.close();
617
618      assert_throws_js(TypeError, () => c.enqueue('a'), 'enqueue after close should throw a TypeError');
619      startCalled = true;
620    }
621  });
622
623  assert_true(startCalled);
624
625}, 'ReadableStream: enqueue should throw when the stream is closed');
626
627promise_test(() => {
628
629  let startCalled = 0;
630  let pullCalled = 0;
631  let cancelCalled = 0;
632
633  /* eslint-disable no-use-before-define */
634  class Source {
635    start(c) {
636      startCalled++;
637      assert_equals(this, theSource, 'start() should be called with the correct this');
638      c.enqueue('a');
639    }
640
641    pull() {
642      pullCalled++;
643      assert_equals(this, theSource, 'pull() should be called with the correct this');
644    }
645
646    cancel() {
647      cancelCalled++;
648      assert_equals(this, theSource, 'cancel() should be called with the correct this');
649    }
650  }
651  /* eslint-enable no-use-before-define */
652
653  const theSource = new Source();
654  theSource.debugName = 'the source object passed to the constructor'; // makes test failures easier to diagnose
655
656  const rs = new ReadableStream(theSource);
657  const reader = rs.getReader();
658
659  return reader.read().then(() => {
660    reader.releaseLock();
661    rs.cancel();
662    assert_equals(startCalled, 1);
663    assert_equals(pullCalled, 1);
664    assert_equals(cancelCalled, 1);
665    return rs.getReader().closed;
666  });
667
668}, 'ReadableStream: should call underlying source methods as methods');
669
670test(() => {
671  new ReadableStream({
672    start(c) {
673      assert_equals(c.desiredSize, 10, 'desiredSize must start at highWaterMark');
674      c.close();
675      assert_equals(c.desiredSize, 0, 'after closing, desiredSize must be 0');
676    }
677  }, {
678    highWaterMark: 10
679  });
680}, 'ReadableStream: desiredSize when closed');
681
682test(() => {
683  new ReadableStream({
684    start(c) {
685      assert_equals(c.desiredSize, 10, 'desiredSize must start at highWaterMark');
686      c.error();
687      assert_equals(c.desiredSize, null, 'after erroring, desiredSize must be null');
688    }
689  }, {
690    highWaterMark: 10
691  });
692}, 'ReadableStream: desiredSize when errored');
693
694test(() => {
695  class Subclass extends ReadableStream {
696    extraFunction() {
697      return true;
698    }
699  }
700  assert_equals(
701      Object.getPrototypeOf(Subclass.prototype), ReadableStream.prototype,
702      'Subclass.prototype\'s prototype should be ReadableStream.prototype');
703  assert_equals(Object.getPrototypeOf(Subclass), ReadableStream,
704                'Subclass\'s prototype should be ReadableStream');
705  const sub = new Subclass();
706  assert_true(sub instanceof ReadableStream,
707              'Subclass object should be an instance of ReadableStream');
708  assert_true(sub instanceof Subclass,
709              'Subclass object should be an instance of Subclass');
710  const lockedGetter = Object.getOwnPropertyDescriptor(
711      ReadableStream.prototype, 'locked').get;
712  assert_equals(lockedGetter.call(sub), sub.locked,
713                'Subclass object should pass brand check');
714  assert_true(sub.extraFunction(),
715              'extraFunction() should be present on Subclass object');
716}, 'Subclassing ReadableStream should work');
717
718test(() => {
719
720  let startCalled = false;
721  new ReadableStream({
722    start(c) {
723      assert_equals(c.desiredSize, 1);
724      c.enqueue('a');
725      assert_equals(c.desiredSize, 0);
726      c.enqueue('b');
727      assert_equals(c.desiredSize, -1);
728      c.enqueue('c');
729      assert_equals(c.desiredSize, -2);
730      c.enqueue('d');
731      assert_equals(c.desiredSize, -3);
732      c.enqueue('e');
733      startCalled = true;
734    }
735  });
736
737  assert_true(startCalled);
738
739}, 'ReadableStream strategies: the default strategy should give desiredSize of 1 to start, decreasing by 1 per enqueue');
740
741promise_test(() => {
742
743  let controller;
744  const rs = new ReadableStream({
745    start(c) {
746      controller = c;
747    }
748  });
749  const reader = rs.getReader();
750
751  assert_equals(controller.desiredSize, 1, 'desiredSize should start at 1');
752  controller.enqueue('a');
753  assert_equals(controller.desiredSize, 0, 'desiredSize should decrease to 0 after first enqueue');
754
755  return reader.read().then(result1 => {
756    assert_object_equals(result1, { value: 'a', done: false }, 'first chunk read should be correct');
757
758    assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the first read');
759    controller.enqueue('b');
760    assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the second enqueue');
761
762    return reader.read();
763  }).then(result2 => {
764    assert_object_equals(result2, { value: 'b', done: false }, 'second chunk read should be correct');
765
766    assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the second read');
767    controller.enqueue('c');
768    assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the third enqueue');
769
770    return reader.read();
771  }).then(result3 => {
772    assert_object_equals(result3, { value: 'c', done: false }, 'third chunk read should be correct');
773
774    assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the third read');
775    controller.enqueue('d');
776    assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the fourth enqueue');
777  });
778
779}, 'ReadableStream strategies: the default strategy should continue giving desiredSize of 1 if the chunks are read immediately');
780
781promise_test(t => {
782
783  const randomSource = new RandomPushSource(8);
784
785  const rs = new ReadableStream({
786    start(c) {
787      assert_equals(typeof c, 'object', 'c should be an object in start');
788      assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function in start');
789      assert_equals(typeof c.close, 'function', 'close should be a function in start');
790      assert_equals(typeof c.error, 'function', 'error should be a function in start');
791
792      randomSource.ondata = t.step_func(chunk => {
793        if (!c.enqueue(chunk) <= 0) {
794          randomSource.readStop();
795        }
796      });
797
798      randomSource.onend = c.close.bind(c);
799      randomSource.onerror = c.error.bind(c);
800    },
801
802    pull(c) {
803      assert_equals(typeof c, 'object', 'c should be an object in pull');
804      assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function in pull');
805      assert_equals(typeof c.close, 'function', 'close should be a function in pull');
806
807      randomSource.readStart();
808    }
809  });
810
811  return readableStreamToArray(rs).then(chunks => {
812    assert_equals(chunks.length, 8, '8 chunks should be read');
813    for (const chunk of chunks) {
814      assert_equals(chunk.length, 128, 'chunk should have 128 bytes');
815    }
816  });
817
818}, 'ReadableStream integration test: adapting a random push source');
819
820promise_test(() => {
821
822  const rs = sequentialReadableStream(10);
823
824  return readableStreamToArray(rs).then(chunks => {
825    assert_true(rs.source.closed, 'source should be closed after all chunks are read');
826    assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
827  });
828
829}, 'ReadableStream integration test: adapting a sync pull source');
830
831promise_test(() => {
832
833  const rs = sequentialReadableStream(10, { async: true });
834
835  return readableStreamToArray(rs).then(chunks => {
836    assert_true(rs.source.closed, 'source should be closed after all chunks are read');
837    assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
838  });
839
840}, 'ReadableStream integration test: adapting an async pull source');
841