• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// META: global=window,worker
2// META: script=../resources/test-utils.js
3// META: script=../resources/recording-streams.js
4'use strict';
5
6const error1 = new Error('error1!');
7error1.name = 'error1';
8
9promise_test(() => {
10
11  const rs = recordingReadableStream({
12    start(controller) {
13      controller.close();
14    }
15  });
16
17  const ws = recordingWritableStream();
18
19  return rs.pipeTo(ws).then(value => {
20    assert_equals(value, undefined, 'the promise must fulfill with undefined');
21  })
22  .then(() => {
23    assert_array_equals(rs.events, []);
24    assert_array_equals(ws.events, ['close']);
25
26    return Promise.all([
27      rs.getReader().closed,
28      ws.getWriter().closed
29    ]);
30  });
31
32}, 'Closing must be propagated forward: starts closed; preventClose omitted; fulfilled close promise');
33
34promise_test(t => {
35
36  const rs = recordingReadableStream({
37    start(controller) {
38      controller.close();
39    }
40  });
41
42  const ws = recordingWritableStream({
43    close() {
44      throw error1;
45    }
46  });
47
48  return promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
49    assert_array_equals(rs.events, []);
50    assert_array_equals(ws.events, ['close']);
51
52    return Promise.all([
53      rs.getReader().closed,
54      promise_rejects_exactly(t, error1, ws.getWriter().closed)
55    ]);
56  });
57
58}, 'Closing must be propagated forward: starts closed; preventClose omitted; rejected close promise');
59
60for (const falsy of [undefined, null, false, +0, -0, NaN, '']) {
61  const stringVersion = Object.is(falsy, -0) ? '-0' : String(falsy);
62
63  promise_test(() => {
64
65    const rs = recordingReadableStream({
66      start(controller) {
67        controller.close();
68      }
69    });
70
71    const ws = recordingWritableStream();
72
73    return rs.pipeTo(ws, { preventClose: falsy }).then(value => {
74      assert_equals(value, undefined, 'the promise must fulfill with undefined');
75    })
76    .then(() => {
77      assert_array_equals(rs.events, []);
78      assert_array_equals(ws.events, ['close']);
79
80      return Promise.all([
81        rs.getReader().closed,
82        ws.getWriter().closed
83      ]);
84    });
85
86  }, `Closing must be propagated forward: starts closed; preventClose = ${stringVersion} (falsy); fulfilled close ` +
87     `promise`);
88}
89
90for (const truthy of [true, 'a', 1, Symbol(), { }]) {
91  promise_test(() => {
92
93    const rs = recordingReadableStream({
94      start(controller) {
95        controller.close();
96      }
97    });
98
99    const ws = recordingWritableStream();
100
101    return rs.pipeTo(ws, { preventClose: truthy }).then(value => {
102      assert_equals(value, undefined, 'the promise must fulfill with undefined');
103    })
104    .then(() => {
105      assert_array_equals(rs.events, []);
106      assert_array_equals(ws.events, []);
107
108      return rs.getReader().closed;
109    });
110
111  }, `Closing must be propagated forward: starts closed; preventClose = ${String(truthy)} (truthy)`);
112}
113
114promise_test(() => {
115
116  const rs = recordingReadableStream({
117    start(controller) {
118      controller.close();
119    }
120  });
121
122  const ws = recordingWritableStream();
123
124  return rs.pipeTo(ws, { preventClose: true, preventAbort: true }).then(value => {
125    assert_equals(value, undefined, 'the promise must fulfill with undefined');
126  })
127  .then(() => {
128    assert_array_equals(rs.events, []);
129    assert_array_equals(ws.events, []);
130
131    return rs.getReader().closed;
132  });
133
134}, 'Closing must be propagated forward: starts closed; preventClose = true, preventAbort = true');
135
136promise_test(() => {
137
138  const rs = recordingReadableStream({
139    start(controller) {
140      controller.close();
141    }
142  });
143
144  const ws = recordingWritableStream();
145
146  return rs.pipeTo(ws, { preventClose: true, preventAbort: true, preventCancel: true }).then(value => {
147    assert_equals(value, undefined, 'the promise must fulfill with undefined');
148  })
149  .then(() => {
150    assert_array_equals(rs.events, []);
151    assert_array_equals(ws.events, []);
152
153    return rs.getReader().closed;
154  });
155
156}, 'Closing must be propagated forward: starts closed; preventClose = true, preventAbort = true, preventCancel = true');
157
158promise_test(t => {
159
160  const rs = recordingReadableStream();
161
162  const ws = recordingWritableStream();
163
164  const pipePromise = rs.pipeTo(ws);
165
166  t.step_timeout(() => rs.controller.close());
167
168  return pipePromise.then(value => {
169    assert_equals(value, undefined, 'the promise must fulfill with undefined');
170  })
171  .then(() => {
172    assert_array_equals(rs.eventsWithoutPulls, []);
173    assert_array_equals(ws.events, ['close']);
174
175    return Promise.all([
176      rs.getReader().closed,
177      ws.getWriter().closed
178    ]);
179  });
180
181}, 'Closing must be propagated forward: becomes closed asynchronously; preventClose omitted; fulfilled close promise');
182
183promise_test(t => {
184
185  const rs = recordingReadableStream();
186
187  const ws = recordingWritableStream({
188    close() {
189      throw error1;
190    }
191  });
192
193  const pipePromise = promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
194
195  t.step_timeout(() => rs.controller.close());
196
197  return pipePromise.then(() => {
198    assert_array_equals(rs.eventsWithoutPulls, []);
199    assert_array_equals(ws.events, ['close']);
200
201    return Promise.all([
202      rs.getReader().closed,
203      promise_rejects_exactly(t, error1, ws.getWriter().closed)
204    ]);
205  });
206
207}, 'Closing must be propagated forward: becomes closed asynchronously; preventClose omitted; rejected close promise');
208
209promise_test(t => {
210
211  const rs = recordingReadableStream();
212
213  const ws = recordingWritableStream();
214
215  const pipePromise = rs.pipeTo(ws, { preventClose: true });
216
217  t.step_timeout(() => rs.controller.close());
218
219  return pipePromise.then(value => {
220    assert_equals(value, undefined, 'the promise must fulfill with undefined');
221  })
222  .then(() => {
223    assert_array_equals(rs.eventsWithoutPulls, []);
224    assert_array_equals(ws.events, []);
225
226    return rs.getReader().closed;
227  });
228
229}, 'Closing must be propagated forward: becomes closed asynchronously; preventClose = true');
230
231promise_test(t => {
232
233  const rs = recordingReadableStream();
234
235  const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
236
237  const pipePromise = rs.pipeTo(ws);
238
239  t.step_timeout(() => rs.controller.close());
240
241  return pipePromise.then(value => {
242    assert_equals(value, undefined, 'the promise must fulfill with undefined');
243  })
244  .then(() => {
245    assert_array_equals(rs.eventsWithoutPulls, []);
246    assert_array_equals(ws.events, ['close']);
247
248    return Promise.all([
249      rs.getReader().closed,
250      ws.getWriter().closed
251    ]);
252  });
253
254}, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
255   'preventClose omitted; fulfilled close promise');
256
257promise_test(t => {
258
259  const rs = recordingReadableStream();
260
261  const ws = recordingWritableStream({
262    close() {
263      throw error1;
264    }
265  }, new CountQueuingStrategy({ highWaterMark: 0 }));
266
267  const pipePromise = promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
268
269  t.step_timeout(() => rs.controller.close());
270
271  return pipePromise.then(() => {
272    assert_array_equals(rs.eventsWithoutPulls, []);
273    assert_array_equals(ws.events, ['close']);
274
275    return Promise.all([
276      rs.getReader().closed,
277      promise_rejects_exactly(t, error1, ws.getWriter().closed)
278    ]);
279  });
280
281}, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
282   'preventClose omitted; rejected close promise');
283
284promise_test(t => {
285
286  const rs = recordingReadableStream();
287
288  const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
289
290  const pipePromise = rs.pipeTo(ws, { preventClose: true });
291
292  t.step_timeout(() => rs.controller.close());
293
294  return pipePromise.then(value => {
295    assert_equals(value, undefined, 'the promise must fulfill with undefined');
296  })
297  .then(() => {
298    assert_array_equals(rs.eventsWithoutPulls, []);
299    assert_array_equals(ws.events, []);
300
301    return rs.getReader().closed;
302  });
303
304}, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
305   'preventClose = true');
306
307promise_test(t => {
308
309  const rs = recordingReadableStream();
310
311  const ws = recordingWritableStream();
312
313  const pipePromise = rs.pipeTo(ws);
314
315  t.step_timeout(() => {
316    rs.controller.enqueue('Hello');
317    t.step_timeout(() => rs.controller.close());
318  }, 10);
319
320  return pipePromise.then(value => {
321    assert_equals(value, undefined, 'the promise must fulfill with undefined');
322  })
323  .then(() => {
324    assert_array_equals(rs.eventsWithoutPulls, []);
325    assert_array_equals(ws.events, ['write', 'Hello', 'close']);
326
327    return Promise.all([
328      rs.getReader().closed,
329      ws.getWriter().closed
330    ]);
331  });
332
333}, 'Closing must be propagated forward: becomes closed after one chunk; preventClose omitted; fulfilled close promise');
334
335promise_test(t => {
336
337  const rs = recordingReadableStream();
338
339  const ws = recordingWritableStream({
340    close() {
341      throw error1;
342    }
343  });
344
345  const pipePromise = promise_rejects_exactly(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
346
347  t.step_timeout(() => {
348    rs.controller.enqueue('Hello');
349    t.step_timeout(() => rs.controller.close());
350  }, 10);
351
352  return pipePromise.then(() => {
353    assert_array_equals(rs.eventsWithoutPulls, []);
354    assert_array_equals(ws.events, ['write', 'Hello', 'close']);
355
356    return Promise.all([
357      rs.getReader().closed,
358      promise_rejects_exactly(t, error1, ws.getWriter().closed)
359    ]);
360  });
361
362}, 'Closing must be propagated forward: becomes closed after one chunk; preventClose omitted; rejected close promise');
363
364promise_test(t => {
365
366  const rs = recordingReadableStream();
367
368  const ws = recordingWritableStream();
369
370  const pipePromise = rs.pipeTo(ws, { preventClose: true });
371
372  t.step_timeout(() => {
373    rs.controller.enqueue('Hello');
374    t.step_timeout(() => rs.controller.close());
375  }, 10);
376
377  return pipePromise.then(value => {
378    assert_equals(value, undefined, 'the promise must fulfill with undefined');
379  })
380  .then(() => {
381    assert_array_equals(rs.eventsWithoutPulls, []);
382    assert_array_equals(ws.events, ['write', 'Hello']);
383
384    return rs.getReader().closed;
385  });
386
387}, 'Closing must be propagated forward: becomes closed after one chunk; preventClose = true');
388
389promise_test(() => {
390
391  const rs = recordingReadableStream();
392
393  let resolveWritePromise;
394  const ws = recordingWritableStream({
395    write() {
396      return new Promise(resolve => {
397        resolveWritePromise = resolve;
398      });
399    }
400  });
401
402  let pipeComplete = false;
403  const pipePromise = rs.pipeTo(ws).then(() => {
404    pipeComplete = true;
405  });
406
407  rs.controller.enqueue('a');
408  rs.controller.close();
409
410  // Flush async events and verify that no shutdown occurs.
411  return flushAsyncEvents().then(() => {
412    assert_array_equals(ws.events, ['write', 'a']); // no 'close'
413    assert_equals(pipeComplete, false, 'the pipe must not be complete');
414
415    resolveWritePromise();
416
417    return pipePromise.then(() => {
418      assert_array_equals(ws.events, ['write', 'a', 'close']);
419    });
420  });
421
422}, 'Closing must be propagated forward: shutdown must not occur until the final write completes');
423
424promise_test(() => {
425
426  const rs = recordingReadableStream();
427
428  let resolveWritePromise;
429  const ws = recordingWritableStream({
430    write() {
431      return new Promise(resolve => {
432        resolveWritePromise = resolve;
433      });
434    }
435  });
436
437  let pipeComplete = false;
438  const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
439    pipeComplete = true;
440  });
441
442  rs.controller.enqueue('a');
443  rs.controller.close();
444
445  // Flush async events and verify that no shutdown occurs.
446  return flushAsyncEvents().then(() => {
447    assert_array_equals(ws.events, ['write', 'a'],
448      'the chunk must have been written, but close must not have happened');
449    assert_equals(pipeComplete, false, 'the pipe must not be complete');
450
451    resolveWritePromise();
452
453    return pipePromise;
454  }).then(() => flushAsyncEvents()).then(() => {
455    assert_array_equals(ws.events, ['write', 'a'],
456      'the chunk must have been written, but close must not have happened');
457  });
458
459}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; preventClose = true');
460
461promise_test(() => {
462
463  const rs = recordingReadableStream();
464
465  let resolveWriteCalled;
466  const writeCalledPromise = new Promise(resolve => {
467    resolveWriteCalled = resolve;
468  });
469
470  let resolveWritePromise;
471  const ws = recordingWritableStream({
472    write() {
473      resolveWriteCalled();
474
475      return new Promise(resolve => {
476        resolveWritePromise = resolve;
477      });
478    }
479  }, new CountQueuingStrategy({ highWaterMark: 2 }));
480
481  let pipeComplete = false;
482  const pipePromise = rs.pipeTo(ws).then(() => {
483    pipeComplete = true;
484  });
485
486  rs.controller.enqueue('a');
487  rs.controller.enqueue('b');
488
489  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
490    assert_array_equals(ws.events, ['write', 'a'],
491      'the first chunk must have been written, but close must not have happened yet');
492    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
493
494    rs.controller.close();
495    resolveWritePromise();
496  }).then(() => flushAsyncEvents()).then(() => {
497    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
498      'the second chunk must have been written, but close must not have happened yet');
499    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
500
501    resolveWritePromise();
502    return pipePromise;
503  }).then(() => {
504    assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'close'],
505      'all chunks must have been written and close must have happened');
506  });
507
508}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write');
509
510promise_test(() => {
511
512  const rs = recordingReadableStream();
513
514  let resolveWriteCalled;
515  const writeCalledPromise = new Promise(resolve => {
516    resolveWriteCalled = resolve;
517  });
518
519  let resolveWritePromise;
520  const ws = recordingWritableStream({
521    write() {
522      resolveWriteCalled();
523
524      return new Promise(resolve => {
525        resolveWritePromise = resolve;
526      });
527    }
528  }, new CountQueuingStrategy({ highWaterMark: 2 }));
529
530  let pipeComplete = false;
531  const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
532    pipeComplete = true;
533  });
534
535  rs.controller.enqueue('a');
536  rs.controller.enqueue('b');
537
538  return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
539    assert_array_equals(ws.events, ['write', 'a'],
540      'the first chunk must have been written, but close must not have happened');
541    assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
542
543    rs.controller.close();
544    resolveWritePromise();
545  }).then(() => flushAsyncEvents()).then(() => {
546    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
547      'the second chunk must have been written, but close must not have happened');
548    assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
549
550    resolveWritePromise();
551    return pipePromise;
552  }).then(() => flushAsyncEvents()).then(() => {
553    assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
554      'all chunks must have been written, but close must not have happened');
555  });
556
557}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write; preventClose = true');
558
559
560promise_test(t => {
561  const rs = recordingReadableStream({
562    start(c) {
563      c.enqueue('a');
564      c.enqueue('b');
565      c.close();
566    }
567  });
568  let rejectWritePromise;
569  const ws = recordingWritableStream({
570    write() {
571      return new Promise((resolve, reject) => {
572        rejectWritePromise = reject;
573      });
574    }
575  }, { highWaterMark: 3 });
576  const pipeToPromise = rs.pipeTo(ws);
577  return delay(0).then(() => {
578    rejectWritePromise(error1);
579    return promise_rejects_exactly(t, error1, pipeToPromise, 'pipeTo should reject');
580  }).then(() => {
581    assert_array_equals(rs.events, []);
582    assert_array_equals(ws.events, ['write', 'a']);
583
584    return Promise.all([
585      rs.getReader().closed,
586      promise_rejects_exactly(t, error1, ws.getWriter().closed, 'ws should be errored')
587    ]);
588  });
589}, 'Closing must be propagated forward: erroring the writable while flushing pending writes should error pipeTo');
590