• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3// These tests can be run against any readable stream produced by the web platform that meets the given descriptions.
4// For readable stream tests, the factory should return the stream. For reader tests, the factory should return a
5// { stream, reader } object. (You can use this to vary the time at which you acquire a reader.)
6
7self.templatedRSEmpty = (label, factory) => {
8  test(() => {}, 'Running templatedRSEmpty with ' + label);
9
10  test(() => {
11
12    const rs = factory();
13
14    assert_equals(typeof rs.locked, 'boolean', 'has a boolean locked getter');
15    assert_equals(typeof rs.cancel, 'function', 'has a cancel method');
16    assert_equals(typeof rs.getReader, 'function', 'has a getReader method');
17    assert_equals(typeof rs.pipeThrough, 'function', 'has a pipeThrough method');
18    assert_equals(typeof rs.pipeTo, 'function', 'has a pipeTo method');
19    assert_equals(typeof rs.tee, 'function', 'has a tee method');
20
21  }, label + ': instances have the correct methods and properties');
22
23  test(() => {
24    const rs = factory();
25
26    assert_throws_js(TypeError, () => rs.getReader({ mode: '' }), 'empty string mode should throw');
27    assert_throws_js(TypeError, () => rs.getReader({ mode: null }), 'null mode should throw');
28    assert_throws_js(TypeError, () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw');
29    assert_throws_js(TypeError, () => rs.getReader(5), '5 should throw');
30
31    // Should not throw
32    rs.getReader(null);
33
34  }, label + ': calling getReader with invalid arguments should throw appropriate errors');
35};
36
37self.templatedRSClosed = (label, factory) => {
38  test(() => {}, 'Running templatedRSClosed with ' + label);
39
40  promise_test(() => {
41
42    const rs = factory();
43    const cancelPromise1 = rs.cancel();
44    const cancelPromise2 = rs.cancel();
45
46    assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
47
48    return Promise.all([
49      cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() call should fulfill with undefined')),
50      cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() call should fulfill with undefined'))
51    ]);
52
53  }, label + ': cancel() should return a distinct fulfilled promise each time');
54
55  test(() => {
56
57    const rs = factory();
58    assert_false(rs.locked, 'locked getter should return false');
59
60  }, label + ': locked should be false');
61
62  test(() => {
63
64    const rs = factory();
65    rs.getReader(); // getReader() should not throw.
66
67  }, label + ': getReader() should be OK');
68
69  test(() => {
70
71    const rs = factory();
72
73    const reader = rs.getReader();
74    reader.releaseLock();
75
76    const reader2 = rs.getReader(); // Getting a second reader should not throw.
77    reader2.releaseLock();
78
79    rs.getReader(); // Getting a third reader should not throw.
80
81  }, label + ': should be able to acquire multiple readers if they are released in succession');
82
83  test(() => {
84
85    const rs = factory();
86
87    rs.getReader();
88
89    assert_throws_js(TypeError, () => rs.getReader(), 'getting a second reader should throw');
90    assert_throws_js(TypeError, () => rs.getReader(), 'getting a third reader should throw');
91
92  }, label + ': should not be able to acquire a second reader if we don\'t release the first one');
93};
94
95self.templatedRSErrored = (label, factory, error) => {
96  test(() => {}, 'Running templatedRSErrored with ' + label);
97
98  promise_test(t => {
99
100    const rs = factory();
101    const reader = rs.getReader();
102
103    return Promise.all([
104      promise_rejects_exactly(t, error, reader.closed),
105      promise_rejects_exactly(t, error, reader.read())
106    ]);
107
108  }, label + ': getReader() should return a reader that acts errored');
109
110  promise_test(t => {
111
112    const rs = factory();
113    const reader = rs.getReader();
114
115    return Promise.all([
116      promise_rejects_exactly(t, error, reader.read()),
117      promise_rejects_exactly(t, error, reader.read()),
118      promise_rejects_exactly(t, error, reader.closed)
119    ]);
120
121  }, label + ': read() twice should give the error each time');
122
123  test(() => {
124    const rs = factory();
125
126    assert_false(rs.locked, 'locked getter should return false');
127  }, label + ': locked should be false');
128};
129
130self.templatedRSErroredSyncOnly = (label, factory, error) => {
131  test(() => {}, 'Running templatedRSErroredSyncOnly with ' + label);
132
133  promise_test(t => {
134
135    const rs = factory();
136    rs.getReader().releaseLock();
137    const reader = rs.getReader(); // Calling getReader() twice does not throw (the stream is not locked).
138
139    return promise_rejects_exactly(t, error, reader.closed);
140
141  }, label + ': should be able to obtain a second reader, with the correct closed promise');
142
143  test(() => {
144
145    const rs = factory();
146    rs.getReader();
147
148    assert_throws_js(TypeError, () => rs.getReader(), 'getting a second reader should throw a TypeError');
149    assert_throws_js(TypeError, () => rs.getReader(), 'getting a third reader should throw a TypeError');
150
151  }, label + ': should not be able to obtain additional readers if we don\'t release the first lock');
152
153  promise_test(t => {
154
155    const rs = factory();
156    const cancelPromise1 = rs.cancel();
157    const cancelPromise2 = rs.cancel();
158
159    assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
160
161    return Promise.all([
162      promise_rejects_exactly(t, error, cancelPromise1),
163      promise_rejects_exactly(t, error, cancelPromise2)
164    ]);
165
166  }, label + ': cancel() should return a distinct rejected promise each time');
167
168  promise_test(t => {
169
170    const rs = factory();
171    const reader = rs.getReader();
172    const cancelPromise1 = reader.cancel();
173    const cancelPromise2 = reader.cancel();
174
175    assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
176
177    return Promise.all([
178      promise_rejects_exactly(t, error, cancelPromise1),
179      promise_rejects_exactly(t, error, cancelPromise2)
180    ]);
181
182  }, label + ': reader cancel() should return a distinct rejected promise each time');
183};
184
185self.templatedRSEmptyReader = (label, factory) => {
186  test(() => {}, 'Running templatedRSEmptyReader with ' + label);
187
188  test(() => {
189
190    const reader = factory().reader;
191
192    assert_true('closed' in reader, 'has a closed property');
193    assert_equals(typeof reader.closed.then, 'function', 'closed property is thenable');
194
195    assert_equals(typeof reader.cancel, 'function', 'has a cancel method');
196    assert_equals(typeof reader.read, 'function', 'has a read method');
197    assert_equals(typeof reader.releaseLock, 'function', 'has a releaseLock method');
198
199  }, label + ': instances have the correct methods and properties');
200
201  test(() => {
202
203    const stream = factory().stream;
204
205    assert_true(stream.locked, 'locked getter should return true');
206
207  }, label + ': locked should be true');
208
209  promise_test(t => {
210
211    const reader = factory().reader;
212
213    reader.read().then(
214      t.unreached_func('read() should not fulfill'),
215      t.unreached_func('read() should not reject')
216    );
217
218    return delay(500);
219
220  }, label + ': read() should never settle');
221
222  promise_test(t => {
223
224    const reader = factory().reader;
225
226    reader.read().then(
227      t.unreached_func('read() should not fulfill'),
228      t.unreached_func('read() should not reject')
229    );
230
231    reader.read().then(
232      t.unreached_func('read() should not fulfill'),
233      t.unreached_func('read() should not reject')
234    );
235
236    return delay(500);
237
238  }, label + ': two read()s should both never settle');
239
240  test(() => {
241
242    const reader = factory().reader;
243    assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
244
245  }, label + ': read() should return distinct promises each time');
246
247  test(() => {
248
249    const stream = factory().stream;
250    assert_throws_js(TypeError, () => stream.getReader(), 'stream.getReader() should throw a TypeError');
251
252  }, label + ': getReader() again on the stream should fail');
253
254  promise_test(async t => {
255
256    const streamAndReader = factory();
257    const stream = streamAndReader.stream;
258    const reader = streamAndReader.reader;
259
260    const read1 = reader.read();
261    const read2 = reader.read();
262    const closed = reader.closed;
263
264    reader.releaseLock();
265
266    assert_false(stream.locked, 'the stream should be unlocked');
267
268    await Promise.all([
269      promise_rejects_js(t, TypeError, read1, 'first read should reject'),
270      promise_rejects_js(t, TypeError, read2, 'second read should reject'),
271      promise_rejects_js(t, TypeError, closed, 'closed should reject')
272    ]);
273
274  }, label + ': releasing the lock should reject all pending read requests');
275
276  promise_test(t => {
277
278    const reader = factory().reader;
279    reader.releaseLock();
280
281    return Promise.all([
282      promise_rejects_js(t, TypeError, reader.read()),
283      promise_rejects_js(t, TypeError, reader.read())
284    ]);
285
286  }, label + ': releasing the lock should cause further read() calls to reject with a TypeError');
287
288  promise_test(t => {
289
290    const reader = factory().reader;
291
292    const closedBefore = reader.closed;
293    reader.releaseLock();
294    const closedAfter = reader.closed;
295
296    assert_equals(closedBefore, closedAfter, 'the closed promise should not change identity');
297
298    return promise_rejects_js(t, TypeError, closedBefore);
299
300  }, label + ': releasing the lock should cause closed calls to reject with a TypeError');
301
302  test(() => {
303
304    const streamAndReader = factory();
305    const stream = streamAndReader.stream;
306    const reader = streamAndReader.reader;
307
308    reader.releaseLock();
309    assert_false(stream.locked, 'locked getter should return false');
310
311  }, label + ': releasing the lock should cause locked to become false');
312
313  promise_test(() => {
314
315    const reader = factory().reader;
316    reader.cancel();
317
318    return reader.read().then(r => {
319      assert_object_equals(r, { value: undefined, done: true }, 'read()ing from the reader should give a done result');
320    });
321
322  }, label + ': canceling via the reader should cause the reader to act closed');
323
324  promise_test(t => {
325
326    const stream = factory().stream;
327    return promise_rejects_js(t, TypeError, stream.cancel());
328
329  }, label + ': canceling via the stream should fail');
330};
331
332self.templatedRSClosedReader = (label, factory) => {
333  test(() => {}, 'Running templatedRSClosedReader with ' + label);
334
335  promise_test(() => {
336
337    const reader = factory().reader;
338
339    return reader.read().then(v => {
340      assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
341    });
342
343  }, label + ': read() should fulfill with { value: undefined, done: true }');
344
345  promise_test(() => {
346
347    const reader = factory().reader;
348
349    return Promise.all([
350      reader.read().then(v => {
351        assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
352      }),
353      reader.read().then(v => {
354        assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
355      })
356    ]);
357
358  }, label + ': read() multiple times should fulfill with { value: undefined, done: true }');
359
360  promise_test(() => {
361
362    const reader = factory().reader;
363
364    return reader.read().then(() => reader.read()).then(v => {
365      assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
366    });
367
368  }, label + ': read() should work when used within another read() fulfill callback');
369
370  promise_test(() => {
371
372    const reader = factory().reader;
373
374    return reader.closed.then(v => assert_equals(v, undefined, 'reader closed should fulfill with undefined'));
375
376  }, label + ': closed should fulfill with undefined');
377
378  promise_test(t => {
379
380    const reader = factory().reader;
381
382    const closedBefore = reader.closed;
383    reader.releaseLock();
384    const closedAfter = reader.closed;
385
386    assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
387
388    return Promise.all([
389      closedBefore.then(v => assert_equals(v, undefined, 'reader.closed acquired before release should fulfill')),
390      promise_rejects_js(t, TypeError, closedAfter)
391    ]);
392
393  }, label + ': releasing the lock should cause closed to reject and change identity');
394
395  promise_test(() => {
396
397    const reader = factory().reader;
398    const cancelPromise1 = reader.cancel();
399    const cancelPromise2 = reader.cancel();
400    const closedReaderPromise = reader.closed;
401
402    assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
403    assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed');
404    assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed');
405
406    return Promise.all([
407      cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() should fulfill with undefined')),
408      cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() should fulfill with undefined'))
409    ]);
410
411  }, label + ': cancel() should return a distinct fulfilled promise each time');
412};
413
414self.templatedRSErroredReader = (label, factory, error) => {
415  test(() => {}, 'Running templatedRSErroredReader with ' + label);
416
417  promise_test(t => {
418
419    const reader = factory().reader;
420    return promise_rejects_exactly(t, error, reader.closed);
421
422  }, label + ': closed should reject with the error');
423
424  promise_test(t => {
425
426    const reader = factory().reader;
427    const closedBefore = reader.closed;
428
429    return promise_rejects_exactly(t, error, closedBefore).then(() => {
430      reader.releaseLock();
431
432      const closedAfter = reader.closed;
433      assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
434
435      return promise_rejects_js(t, TypeError, closedAfter);
436    });
437
438  }, label + ': releasing the lock should cause closed to reject and change identity');
439
440  promise_test(t => {
441
442    const reader = factory().reader;
443    return promise_rejects_exactly(t, error, reader.read());
444
445  }, label + ': read() should reject with the error');
446};
447
448self.templatedRSTwoChunksOpenReader = (label, factory, chunks) => {
449  test(() => {}, 'Running templatedRSTwoChunksOpenReader with ' + label);
450
451  promise_test(() => {
452
453    const reader = factory().reader;
454
455    return Promise.all([
456      reader.read().then(r => {
457        assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
458      }),
459      reader.read().then(r => {
460        assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
461      })
462    ]);
463
464  }, label + ': calling read() twice without waiting will eventually give both chunks (sequential)');
465
466  promise_test(() => {
467
468    const reader = factory().reader;
469
470    return reader.read().then(r => {
471      assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
472
473      return reader.read().then(r2 => {
474        assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct');
475      });
476    });
477
478  }, label + ': calling read() twice without waiting will eventually give both chunks (nested)');
479
480  test(() => {
481
482    const reader = factory().reader;
483    assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
484
485  }, label + ': read() should return distinct promises each time');
486
487  promise_test(() => {
488
489    const reader = factory().reader;
490
491    const promise1 = reader.closed.then(v => {
492      assert_equals(v, undefined, 'reader closed should fulfill with undefined');
493    });
494
495    const promise2 = reader.read().then(r => {
496      assert_object_equals(r, { value: chunks[0], done: false },
497                           'promise returned before cancellation should fulfill with a chunk');
498    });
499
500    reader.cancel();
501
502    const promise3 = reader.read().then(r => {
503      assert_object_equals(r, { value: undefined, done: true },
504                           'promise returned after cancellation should fulfill with an end-of-stream signal');
505    });
506
507    return Promise.all([promise1, promise2, promise3]);
508
509  }, label + ': cancel() after a read() should still give that single read result');
510};
511
512self.templatedRSTwoChunksClosedReader = function (label, factory, chunks) {
513  test(() => {}, 'Running templatedRSTwoChunksClosedReader with ' + label);
514
515  promise_test(() => {
516
517    const reader = factory().reader;
518
519    return Promise.all([
520      reader.read().then(r => {
521        assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
522      }),
523      reader.read().then(r => {
524        assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
525      }),
526      reader.read().then(r => {
527        assert_object_equals(r, { value: undefined, done: true }, 'third result should be correct');
528      })
529    ]);
530
531  }, label + ': third read(), without waiting, should give { value: undefined, done: true } (sequential)');
532
533  promise_test(() => {
534
535    const reader = factory().reader;
536
537    return reader.read().then(r => {
538      assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
539
540      return reader.read().then(r2 => {
541        assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct');
542
543        return reader.read().then(r3 => {
544          assert_object_equals(r3, { value: undefined, done: true }, 'third result should be correct');
545        });
546      });
547    });
548
549  }, label + ': third read(), without waiting, should give { value: undefined, done: true } (nested)');
550
551  promise_test(() => {
552
553    const streamAndReader = factory();
554    const stream = streamAndReader.stream;
555    const reader = streamAndReader.reader;
556
557    assert_true(stream.locked, 'stream should start locked');
558
559    const promise = reader.closed.then(v => {
560      assert_equals(v, undefined, 'reader closed should fulfill with undefined');
561      assert_true(stream.locked, 'stream should remain locked');
562    });
563
564    reader.read();
565    reader.read();
566
567    return promise;
568
569  }, label +
570     ': draining the stream via read() should cause the reader closed promise to fulfill, but locked stays true');
571
572  promise_test(() => {
573
574    const streamAndReader = factory();
575    const stream = streamAndReader.stream;
576    const reader = streamAndReader.reader;
577
578    const promise = reader.closed.then(() => {
579      assert_true(stream.locked, 'the stream should start locked');
580      reader.releaseLock(); // Releasing the lock after reader closed should not throw.
581      assert_false(stream.locked, 'the stream should end unlocked');
582    });
583
584    reader.read();
585    reader.read();
586
587    return promise;
588
589  }, label + ': releasing the lock after the stream is closed should cause locked to become false');
590
591  promise_test(t => {
592
593    const reader = factory().reader;
594
595    reader.releaseLock();
596
597    return Promise.all([
598      promise_rejects_js(t, TypeError, reader.read()),
599      promise_rejects_js(t, TypeError, reader.read()),
600      promise_rejects_js(t, TypeError, reader.read())
601    ]);
602
603  }, label + ': releasing the lock should cause further read() calls to reject with a TypeError');
604
605  promise_test(() => {
606
607    const streamAndReader = factory();
608    const stream = streamAndReader.stream;
609    const reader = streamAndReader.reader;
610
611    const readerClosed = reader.closed;
612
613    assert_equals(reader.closed, readerClosed, 'accessing reader.closed twice in succession gives the same value');
614
615    const promise = reader.read().then(() => {
616      assert_equals(reader.closed, readerClosed, 'reader.closed is the same after read() fulfills');
617
618      reader.releaseLock();
619
620      assert_equals(reader.closed, readerClosed, 'reader.closed is the same after releasing the lock');
621
622      const newReader = stream.getReader();
623      return newReader.read();
624    });
625
626    assert_equals(reader.closed, readerClosed, 'reader.closed is the same after calling read()');
627
628    return promise;
629
630  }, label + ': reader\'s closed property always returns the same promise');
631};
632
633self.templatedRSTeeCancel = (label, factory) => {
634  test(() => {}, `Running templatedRSTeeCancel with ${label}`);
635
636  promise_test(async () => {
637
638    const reason1 = new Error('We\'re wanted men.');
639    const reason2 = new Error('I have the death sentence on twelve systems.');
640
641    let resolve;
642    const promise = new Promise(r => resolve = r);
643    const rs = factory({
644      cancel(reason) {
645        assert_array_equals(reason, [reason1, reason2],
646          'the cancel reason should be an array containing those from the branches');
647        resolve();
648      }
649    });
650
651    const [branch1, branch2] = rs.tee();
652    await Promise.all([
653      branch1.cancel(reason1),
654      branch2.cancel(reason2),
655      promise
656    ]);
657
658  }, `${label}: canceling both branches should aggregate the cancel reasons into an array`);
659
660  promise_test(async () => {
661
662    const reason1 = new Error('This little one\'s not worth the effort.');
663    const reason2 = new Error('Come, let me get you something.');
664
665    let resolve;
666    const promise = new Promise(r => resolve = r);
667    const rs = factory({
668      cancel(reason) {
669        assert_array_equals(reason, [reason1, reason2],
670          'the cancel reason should be an array containing those from the branches');
671        resolve();
672      }
673    });
674
675    const [branch1, branch2] = rs.tee();
676    await Promise.all([
677      branch2.cancel(reason2),
678      branch1.cancel(reason1),
679      promise
680    ]);
681
682  }, `${label}: canceling both branches in reverse order should aggregate the cancel reasons into an array`);
683
684  promise_test(async t => {
685
686    const theError = { name: 'I\'ll be careful.' };
687    const rs = factory({
688      cancel() {
689        throw theError;
690      }
691    });
692
693    const [branch1, branch2] = rs.tee();
694    await Promise.all([
695      promise_rejects_exactly(t, theError, branch1.cancel()),
696      promise_rejects_exactly(t, theError, branch2.cancel())
697    ]);
698
699  }, `${label}: failing to cancel the original stream should cause cancel() to reject on branches`);
700
701  promise_test(async t => {
702
703    const theError = { name: 'You just watch yourself!' };
704    let controller;
705    const stream = factory({
706      start(c) {
707        controller = c;
708      }
709    });
710
711    const [branch1, branch2] = stream.tee();
712    controller.error(theError);
713
714    await Promise.all([
715      promise_rejects_exactly(t, theError, branch1.cancel()),
716      promise_rejects_exactly(t, theError, branch2.cancel())
717    ]);
718
719  }, `${label}: erroring a teed stream should properly handle canceled branches`);
720
721};
722