• 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
9const error2 = new Error('error2');
10error2.name = 'error2';
11
12promise_test(() => {
13  const ws = new WritableStream({
14    close() {
15      return 'Hello';
16    }
17  });
18
19  const writer = ws.getWriter();
20
21  const closePromise = writer.close();
22  return closePromise.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined'));
23}, 'fulfillment value of writer.close() call must be undefined even if the underlying sink returns a non-undefined ' +
24    'value');
25
26promise_test(() => {
27  let controller;
28  let resolveClose;
29  const ws = new WritableStream({
30    start(c) {
31      controller = c;
32    },
33    close() {
34      return new Promise(resolve => {
35        resolveClose = resolve;
36      });
37    }
38  });
39
40  const writer = ws.getWriter();
41
42  const closePromise = writer.close();
43  return flushAsyncEvents().then(() => {
44    controller.error(error1);
45    return flushAsyncEvents();
46  }).then(() => {
47    resolveClose();
48    return Promise.all([
49      closePromise,
50      writer.closed,
51      flushAsyncEvents().then(() => writer.closed)]);
52  });
53}, 'when sink calls error asynchronously while sink close is in-flight, the stream should not become errored');
54
55promise_test(() => {
56  let controller;
57  const passedError = new Error('error me');
58  const ws = new WritableStream({
59    start(c) {
60      controller = c;
61    },
62    close() {
63      controller.error(passedError);
64    }
65  });
66
67  const writer = ws.getWriter();
68
69  return writer.close().then(() => writer.closed);
70}, 'when sink calls error synchronously while closing, the stream should not become errored');
71
72promise_test(t => {
73  const ws = new WritableStream({
74    close() {
75      throw error1;
76    }
77  });
78
79  const writer = ws.getWriter();
80
81  return Promise.all([
82    writer.write('y'),
83    promise_rejects_exactly(t, error1, writer.close(), 'close() must reject with the error'),
84    promise_rejects_exactly(t, error1, writer.closed, 'closed must reject with the error')
85  ]);
86}, 'when the sink throws during close, and the close is requested while a write is still in-flight, the stream should ' +
87   'become errored during the close');
88
89promise_test(() => {
90  const ws = new WritableStream({
91    write(chunk, controller) {
92      controller.error(error1);
93      return new Promise(() => {});
94    }
95  });
96
97  const writer = ws.getWriter();
98  writer.write('a');
99
100  return delay(0).then(() => {
101    writer.releaseLock();
102  });
103}, 'releaseLock on a stream with a pending write in which the stream has been errored');
104
105promise_test(() => {
106  let controller;
107  const ws = new WritableStream({
108    start(c) {
109      controller = c;
110    },
111    close() {
112      controller.error(error1);
113      return new Promise(() => {});
114    }
115  });
116
117  const writer = ws.getWriter();
118  writer.close();
119
120  return delay(0).then(() => {
121    writer.releaseLock();
122  });
123}, 'releaseLock on a stream with a pending close in which controller.error() was called');
124
125promise_test(() => {
126  const ws = recordingWritableStream();
127
128  const writer = ws.getWriter();
129
130  return writer.ready.then(() => {
131    assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
132
133    writer.close();
134    assert_equals(writer.desiredSize, 1, 'desiredSize should be still 1');
135
136    return writer.ready.then(v => {
137      assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
138      assert_array_equals(ws.events, ['close'], 'write and abort should not be called');
139    });
140  });
141}, 'when close is called on a WritableStream in writable state, ready should return a fulfilled promise');
142
143promise_test(() => {
144  const ws = recordingWritableStream({
145    write() {
146      return new Promise(() => {});
147    }
148  });
149
150  const writer = ws.getWriter();
151
152  return writer.ready.then(() => {
153    writer.write('a');
154
155    assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
156
157    let calledClose = false;
158    return Promise.all([
159      writer.ready.then(v => {
160        assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
161        assert_true(calledClose, 'ready should not be fulfilled before writer.close() is called');
162        assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called');
163      }),
164      flushAsyncEvents().then(() => {
165        writer.close();
166        calledClose = true;
167      })
168    ]);
169  });
170}, 'when close is called on a WritableStream in waiting state, ready promise should be fulfilled');
171
172promise_test(() => {
173  let asyncCloseFinished = false;
174  const ws = recordingWritableStream({
175    close() {
176      return flushAsyncEvents().then(() => {
177        asyncCloseFinished = true;
178      });
179    }
180  });
181
182  const writer = ws.getWriter();
183  return writer.ready.then(() => {
184    writer.write('a');
185
186    writer.close();
187
188    return writer.ready.then(v => {
189      assert_false(asyncCloseFinished, 'ready promise should be fulfilled before async close completes');
190      assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
191      assert_array_equals(ws.events, ['write', 'a', 'close'], 'sink abort() should not be called');
192    });
193  });
194}, 'when close is called on a WritableStream in waiting state, ready should be fulfilled immediately even if close ' +
195    'takes a long time');
196
197promise_test(t => {
198  const rejection = { name: 'letter' };
199  const ws = new WritableStream({
200    close() {
201      return {
202        then(onFulfilled, onRejected) { onRejected(rejection); }
203      };
204    }
205  });
206  return promise_rejects_exactly(t, rejection, ws.getWriter().close(), 'close() should return a rejection');
207}, 'returning a thenable from close() should work');
208
209promise_test(t => {
210  const ws = new WritableStream();
211  const writer = ws.getWriter();
212  return writer.ready.then(() => {
213    const closePromise = writer.close();
214    const closedPromise = writer.closed;
215    writer.releaseLock();
216    return Promise.all([
217      closePromise,
218      promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected')
219    ]);
220  });
221}, 'releaseLock() should not change the result of sync close()');
222
223promise_test(t => {
224  const ws = new WritableStream({
225    close() {
226      return flushAsyncEvents();
227    }
228  });
229  const writer = ws.getWriter();
230  return writer.ready.then(() => {
231    const closePromise = writer.close();
232    const closedPromise = writer.closed;
233    writer.releaseLock();
234    return Promise.all([
235      closePromise,
236      promise_rejects_js(t, TypeError, closedPromise, '.closed promise should be rejected')
237    ]);
238  });
239}, 'releaseLock() should not change the result of async close()');
240
241promise_test(() => {
242  let resolveClose;
243  const ws = new WritableStream({
244    close() {
245      const promise = new Promise(resolve => {
246        resolveClose = resolve;
247      });
248      return promise;
249    }
250  });
251  const writer = ws.getWriter();
252  const closePromise = writer.close();
253  writer.releaseLock();
254  return delay(0).then(() => {
255    resolveClose();
256    return closePromise.then(() => {
257      assert_equals(ws.getWriter().desiredSize, 0, 'desiredSize should be 0');
258    });
259  });
260}, 'close() should set state to CLOSED even if writer has detached');
261
262promise_test(() => {
263  let resolveClose;
264  const ws = new WritableStream({
265    close() {
266      const promise = new Promise(resolve => {
267        resolveClose = resolve;
268      });
269      return promise;
270    }
271  });
272  const writer = ws.getWriter();
273  writer.close();
274  writer.releaseLock();
275  return delay(0).then(() => {
276    const abortingWriter = ws.getWriter();
277    const abortPromise = abortingWriter.abort();
278    abortingWriter.releaseLock();
279    resolveClose();
280    return abortPromise;
281  });
282}, 'the promise returned by async abort during close should resolve');
283
284// Though the order in which the promises are fulfilled or rejected is arbitrary, we're checking it for
285// interoperability. We can change the order as long as we file bugs on all implementers to update to the latest tests
286// to keep them interoperable.
287
288promise_test(() => {
289  const ws = new WritableStream({});
290
291  const writer = ws.getWriter();
292
293  const closePromise = writer.close();
294
295  const events = [];
296  return Promise.all([
297    closePromise.then(() => {
298      events.push('closePromise');
299    }),
300    writer.closed.then(() => {
301      events.push('closed');
302    })
303  ]).then(() => {
304    assert_array_equals(events, ['closePromise', 'closed'],
305                        'promises must fulfill/reject in the expected order');
306  });
307}, 'promises must fulfill/reject in the expected order on closure');
308
309promise_test(() => {
310  const ws = new WritableStream({});
311
312  // Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be
313  // processed without waiting for completion of the close().
314  return delay(0).then(() => {
315    const writer = ws.getWriter();
316
317    const closePromise = writer.close();
318    const abortPromise = writer.abort(error1);
319
320    const events = [];
321    return Promise.all([
322      closePromise.then(() => {
323        events.push('closePromise');
324      }),
325      abortPromise.then(() => {
326        events.push('abortPromise');
327      }),
328      writer.closed.then(() => {
329        events.push('closed');
330      })
331    ]).then(() => {
332      assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
333                          'promises must fulfill/reject in the expected order');
334    });
335  });
336}, 'promises must fulfill/reject in the expected order on aborted closure');
337
338promise_test(t => {
339  const ws = new WritableStream({
340    close() {
341      return Promise.reject(error1);
342    }
343  });
344
345  // Wait until the WritableStream starts so that the close() call gets processed.
346  return delay(0).then(() => {
347    const writer = ws.getWriter();
348
349    const closePromise = writer.close();
350    const abortPromise = writer.abort(error2);
351
352    const events = [];
353    closePromise.catch(() => events.push('closePromise'));
354    abortPromise.catch(() => events.push('abortPromise'));
355    writer.closed.catch(() => events.push('closed'));
356    return Promise.all([
357      promise_rejects_exactly(t, error1, closePromise,
358                             'closePromise must reject with the error returned from the sink\'s close method'),
359      promise_rejects_exactly(t, error1, abortPromise,
360                             'abortPromise must reject with the error returned from the sink\'s close method'),
361      promise_rejects_exactly(t, error2, writer.closed,
362                              'writer.closed must reject with error2')
363    ]).then(() => {
364      assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
365                          'promises must fulfill/reject in the expected order');
366    });
367  });
368}, 'promises must fulfill/reject in the expected order on aborted and errored closure');
369
370promise_test(t => {
371  let resolveWrite;
372  let controller;
373  const ws = new WritableStream({
374    write(chunk, c) {
375      controller = c;
376      return new Promise(resolve => {
377        resolveWrite = resolve;
378      });
379    }
380  });
381  const writer = ws.getWriter();
382  return writer.ready.then(() => {
383    const writePromise = writer.write('c');
384    controller.error(error1);
385    const closePromise = writer.close();
386    let closeRejected = false;
387    closePromise.catch(() => {
388      closeRejected = true;
389    });
390    return flushAsyncEvents().then(() => {
391      assert_false(closeRejected);
392      resolveWrite();
393      return Promise.all([
394        writePromise,
395        promise_rejects_exactly(t, error1, closePromise, 'close() should reject')
396      ]).then(() => {
397        assert_true(closeRejected);
398      });
399    });
400  });
401}, 'close() should not reject until no sink methods are in flight');
402
403promise_test(() => {
404  const ws = new WritableStream();
405  const writer1 = ws.getWriter();
406  return writer1.close().then(() => {
407    writer1.releaseLock();
408    const writer2 = ws.getWriter();
409    const ready = writer2.ready;
410    assert_equals(ready.constructor, Promise);
411    return ready;
412  });
413}, 'ready promise should be initialised as fulfilled for a writer on a closed stream');
414
415promise_test(() => {
416  const ws = new WritableStream();
417  ws.close();
418  const writer = ws.getWriter();
419  return writer.closed;
420}, 'close() on a writable stream should work');
421
422promise_test(t => {
423  const ws = new WritableStream();
424  ws.getWriter();
425  return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
426}, 'close() on a locked stream should reject');
427
428promise_test(t => {
429  const ws = new WritableStream({
430    start(controller) {
431      controller.error(error1);
432    }
433  });
434  return promise_rejects_exactly(t, error1, ws.close(), 'close should reject with error1');
435}, 'close() on an erroring stream should reject');
436
437promise_test(t => {
438  const ws = new WritableStream({
439    start(controller) {
440      controller.error(error1);
441    }
442  });
443  const writer = ws.getWriter();
444  return promise_rejects_exactly(t, error1, writer.closed, 'closed should reject with the error').then(() => {
445    writer.releaseLock();
446    return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
447  });
448}, 'close() on an errored stream should reject');
449
450promise_test(t => {
451  const ws = new WritableStream();
452  const writer = ws.getWriter();
453  return writer.close().then(() => {
454    return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
455  });
456}, 'close() on an closed stream should reject');
457
458promise_test(t => {
459  const ws = new WritableStream({
460    close() {
461      return new Promise(() => {});
462    }
463  });
464
465  const writer = ws.getWriter();
466  writer.close();
467  writer.releaseLock();
468
469  return promise_rejects_js(t, TypeError, ws.close(), 'close should reject');
470}, 'close() on a stream with a pending close should reject');
471