| // META: global=worker,jsshell |
| // META: script=../resources/test-utils.js |
| // META: script=../resources/recording-streams.js |
| 'use strict'; |
| |
| const error1 = new Error('error1'); |
| error1.name = 'error1'; |
| |
| const error2 = new Error('error2'); |
| error2.name = 'error2'; |
| |
| promise_test(() => { |
| const ws = new WritableStream({ |
| close() { |
| return 'Hello'; |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| const closePromise = writer.close(); |
| return closePromise.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined')); |
| }, 'fulfillment value of ws.close() call must be undefined even if the underlying sink returns a non-undefined ' + |
| 'value'); |
| |
| promise_test(() => { |
| let controller; |
| let resolveClose; |
| const ws = new WritableStream({ |
| start(c) { |
| controller = c; |
| }, |
| close() { |
| return new Promise(resolve => { |
| resolveClose = resolve; |
| }); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| const closePromise = writer.close(); |
| return flushAsyncEvents().then(() => { |
| controller.error(error1); |
| return flushAsyncEvents(); |
| }).then(() => { |
| resolveClose(); |
| return Promise.all([ |
| closePromise, |
| writer.closed, |
| flushAsyncEvents().then(() => writer.closed)]); |
| }); |
| }, 'when sink calls error asynchronously while sink close is in-flight, the stream should not become errored'); |
| |
| promise_test(() => { |
| let controller; |
| const passedError = new Error('error me'); |
| const ws = new WritableStream({ |
| start(c) { |
| controller = c; |
| }, |
| close() { |
| controller.error(passedError); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| return writer.close().then(() => writer.closed); |
| }, 'when sink calls error synchronously while closing, the stream should not become errored'); |
| |
| promise_test(t => { |
| const ws = new WritableStream({ |
| close() { |
| throw error1; |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| return Promise.all([ |
| writer.write('y'), |
| promise_rejects(t, error1, writer.close(), 'close() must reject with the error'), |
| promise_rejects(t, error1, writer.closed, 'closed must reject with the error') |
| ]); |
| }, 'when the sink throws during close, and the close is requested while a write is still in-flight, the stream should ' + |
| 'become errored during the close'); |
| |
| promise_test(() => { |
| const ws = new WritableStream({ |
| write(chunk, controller) { |
| controller.error(error1); |
| return new Promise(() => {}); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| writer.write('a'); |
| |
| return delay(0).then(() => { |
| writer.releaseLock(); |
| }); |
| }, 'releaseLock on a stream with a pending write in which the stream has been errored'); |
| |
| promise_test(() => { |
| let controller; |
| const ws = new WritableStream({ |
| start(c) { |
| controller = c; |
| }, |
| close() { |
| controller.error(error1); |
| return new Promise(() => {}); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| writer.close(); |
| |
| return delay(0).then(() => { |
| writer.releaseLock(); |
| }); |
| }, 'releaseLock on a stream with a pending close in which controller.error() was called'); |
| |
| promise_test(() => { |
| const ws = recordingWritableStream(); |
| |
| const writer = ws.getWriter(); |
| |
| return writer.ready.then(() => { |
| assert_equals(writer.desiredSize, 1, 'desiredSize should be 1'); |
| |
| writer.close(); |
| assert_equals(writer.desiredSize, 1, 'desiredSize should be still 1'); |
| |
| return writer.ready.then(v => { |
| assert_equals(v, undefined, 'ready promise should be fulfilled with undefined'); |
| assert_array_equals(ws.events, ['close'], 'write and abort should not be called'); |
| }); |
| }); |
| }, 'when close is called on a WritableStream in writable state, ready should return a fulfilled promise'); |
| |
| promise_test(() => { |
| const ws = recordingWritableStream({ |
| write() { |
| return new Promise(() => {}); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| |
| return writer.ready.then(() => { |
| writer.write('a'); |
| |
| assert_equals(writer.desiredSize, 0, 'desiredSize should be 0'); |
| |
| let calledClose = false; |
| return Promise.all([ |
| writer.ready.then(v => { |
| assert_equals(v, undefined, 'ready promise should be fulfilled with undefined'); |
| assert_true(calledClose, 'ready should not be fulfilled before writer.close() is called'); |
| assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called'); |
| }), |
| flushAsyncEvents().then(() => { |
| writer.close(); |
| calledClose = true; |
| }) |
| ]); |
| }); |
| }, 'when close is called on a WritableStream in waiting state, ready promise should be fulfilled'); |
| |
| promise_test(() => { |
| let asyncCloseFinished = false; |
| const ws = recordingWritableStream({ |
| close() { |
| return flushAsyncEvents().then(() => { |
| asyncCloseFinished = true; |
| }); |
| } |
| }); |
| |
| const writer = ws.getWriter(); |
| return writer.ready.then(() => { |
| writer.write('a'); |
| |
| writer.close(); |
| |
| return writer.ready.then(v => { |
| assert_false(asyncCloseFinished, 'ready promise should be fulfilled before async close completes'); |
| assert_equals(v, undefined, 'ready promise should be fulfilled with undefined'); |
| assert_array_equals(ws.events, ['write', 'a', 'close'], 'sink abort() should not be called'); |
| }); |
| }); |
| }, 'when close is called on a WritableStream in waiting state, ready should be fulfilled immediately even if close ' + |
| 'takes a long time'); |
| |
| promise_test(t => { |
| const rejection = { name: 'letter' }; |
| const ws = new WritableStream({ |
| close() { |
| return { |
| then(onFulfilled, onRejected) { onRejected(rejection); } |
| }; |
| } |
| }); |
| return promise_rejects(t, rejection, ws.getWriter().close(), 'close() should return a rejection'); |
| }, 'returning a thenable from close() should work'); |
| |
| promise_test(t => { |
| const ws = new WritableStream(); |
| const writer = ws.getWriter(); |
| return writer.ready.then(() => { |
| const closePromise = writer.close(); |
| const closedPromise = writer.closed; |
| writer.releaseLock(); |
| return Promise.all([ |
| closePromise, |
| promise_rejects(t, new TypeError(), closedPromise, '.closed promise should be rejected') |
| ]); |
| }); |
| }, 'releaseLock() should not change the result of sync close()'); |
| |
| promise_test(t => { |
| const ws = new WritableStream({ |
| close() { |
| return flushAsyncEvents(); |
| } |
| }); |
| const writer = ws.getWriter(); |
| return writer.ready.then(() => { |
| const closePromise = writer.close(); |
| const closedPromise = writer.closed; |
| writer.releaseLock(); |
| return Promise.all([ |
| closePromise, |
| promise_rejects(t, new TypeError(), closedPromise, '.closed promise should be rejected') |
| ]); |
| }); |
| }, 'releaseLock() should not change the result of async close()'); |
| |
| promise_test(() => { |
| let resolveClose; |
| const ws = new WritableStream({ |
| close() { |
| const promise = new Promise(resolve => { |
| resolveClose = resolve; |
| }); |
| return promise; |
| } |
| }); |
| const writer = ws.getWriter(); |
| const closePromise = writer.close(); |
| writer.releaseLock(); |
| return delay(0).then(() => { |
| resolveClose(); |
| return closePromise.then(() => { |
| assert_equals(ws.getWriter().desiredSize, 0, 'desiredSize should be 0'); |
| }); |
| }); |
| }, 'close() should set state to CLOSED even if writer has detached'); |
| |
| promise_test(() => { |
| let resolveClose; |
| const ws = new WritableStream({ |
| close() { |
| const promise = new Promise(resolve => { |
| resolveClose = resolve; |
| }); |
| return promise; |
| } |
| }); |
| const writer = ws.getWriter(); |
| writer.close(); |
| writer.releaseLock(); |
| return delay(0).then(() => { |
| const abortingWriter = ws.getWriter(); |
| const abortPromise = abortingWriter.abort(); |
| abortingWriter.releaseLock(); |
| resolveClose(); |
| return abortPromise; |
| }); |
| }, 'the promise returned by async abort during close should resolve'); |
| |
| // Though the order in which the promises are fulfilled or rejected is arbitrary, we're checking it for |
| // interoperability. We can change the order as long as we file bugs on all implementers to update to the latest tests |
| // to keep them interoperable. |
| |
| promise_test(() => { |
| const ws = new WritableStream({}); |
| |
| const writer = ws.getWriter(); |
| |
| const closePromise = writer.close(); |
| |
| const events = []; |
| return Promise.all([ |
| closePromise.then(() => { |
| events.push('closePromise'); |
| }), |
| writer.closed.then(() => { |
| events.push('closed'); |
| }) |
| ]).then(() => { |
| assert_array_equals(events, ['closePromise', 'closed'], |
| 'promises must fulfill/reject in the expected order'); |
| }); |
| }, 'promises must fulfill/reject in the expected order on closure'); |
| |
| promise_test(() => { |
| const ws = new WritableStream({}); |
| |
| // Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be |
| // processed without waiting for completion of the close(). |
| return delay(0).then(() => { |
| const writer = ws.getWriter(); |
| |
| const closePromise = writer.close(); |
| const abortPromise = writer.abort(error1); |
| |
| const events = []; |
| return Promise.all([ |
| closePromise.then(() => { |
| events.push('closePromise'); |
| }), |
| abortPromise.then(() => { |
| events.push('abortPromise'); |
| }), |
| writer.closed.then(() => { |
| events.push('closed'); |
| }) |
| ]).then(() => { |
| assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'], |
| 'promises must fulfill/reject in the expected order'); |
| }); |
| }); |
| }, 'promises must fulfill/reject in the expected order on aborted closure'); |
| |
| promise_test(t => { |
| const ws = new WritableStream({ |
| close() { |
| return Promise.reject(error1); |
| } |
| }); |
| |
| // Wait until the WritableStream starts so that the close() call gets processed. |
| return delay(0).then(() => { |
| const writer = ws.getWriter(); |
| |
| const closePromise = writer.close(); |
| const abortPromise = writer.abort(error2); |
| |
| const events = []; |
| closePromise.catch(() => events.push('closePromise')); |
| abortPromise.catch(() => events.push('abortPromise')); |
| writer.closed.catch(() => events.push('closed')); |
| return Promise.all([ |
| promise_rejects(t, error1, closePromise, |
| 'closePromise must reject with the error returned from the sink\'s close method'), |
| promise_rejects(t, error1, abortPromise, |
| 'abortPromise must reject with the error returned from the sink\'s close method'), |
| promise_rejects(t, error2, writer.closed, |
| 'writer.closed must reject with error2') |
| ]).then(() => { |
| assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'], |
| 'promises must fulfill/reject in the expected order'); |
| }); |
| }); |
| }, 'promises must fulfill/reject in the expected order on aborted and errored closure'); |
| |
| promise_test(t => { |
| let resolveWrite; |
| let controller; |
| const ws = new WritableStream({ |
| write(chunk, c) { |
| controller = c; |
| return new Promise(resolve => { |
| resolveWrite = resolve; |
| }); |
| } |
| }); |
| const writer = ws.getWriter(); |
| return writer.ready.then(() => { |
| const writePromise = writer.write('c'); |
| controller.error(error1); |
| const closePromise = writer.close(); |
| let closeRejected = false; |
| closePromise.catch(() => { |
| closeRejected = true; |
| }); |
| return flushAsyncEvents().then(() => { |
| assert_false(closeRejected); |
| resolveWrite(); |
| return Promise.all([ |
| writePromise, |
| promise_rejects(t, error1, closePromise, 'close() should reject') |
| ]).then(() => { |
| assert_true(closeRejected); |
| }); |
| }); |
| }); |
| }, 'close() should not reject until no sink methods are in flight'); |
| |
| promise_test(() => { |
| const ws = new WritableStream(); |
| const writer1 = ws.getWriter(); |
| return writer1.close().then(() => { |
| writer1.releaseLock(); |
| const writer2 = ws.getWriter(); |
| const ready = writer2.ready; |
| assert_equals(ready.constructor, Promise); |
| return ready; |
| }); |
| }, 'ready promise should be initialised as fulfilled for a writer on a closed stream'); |