| // META: global=window,worker,jsshell |
| // META: script=../resources/test-utils.js |
| 'use strict'; |
| |
| const thrownError = new Error('bad things are happening!'); |
| thrownError.name = 'error1'; |
| |
| promise_test(t => { |
| const ts = new TransformStream({ |
| transform() { |
| throw thrownError; |
| } |
| }); |
| |
| const reader = ts.readable.getReader(); |
| |
| const writer = ts.writable.getWriter(); |
| |
| return Promise.all([ |
| promise_rejects_exactly(t, thrownError, writer.write('a'), |
| 'writable\'s write should reject with the thrown error'), |
| promise_rejects_exactly(t, thrownError, reader.read(), |
| 'readable\'s read should reject with the thrown error'), |
| promise_rejects_exactly(t, thrownError, reader.closed, |
| 'readable\'s closed should be rejected with the thrown error'), |
| promise_rejects_exactly(t, thrownError, writer.closed, |
| 'writable\'s closed should be rejected with the thrown error') |
| ]); |
| }, 'TransformStream errors thrown in transform put the writable and readable in an errored state'); |
| |
| promise_test(t => { |
| const ts = new TransformStream({ |
| transform() { |
| }, |
| flush() { |
| throw thrownError; |
| } |
| }); |
| |
| const reader = ts.readable.getReader(); |
| |
| const writer = ts.writable.getWriter(); |
| |
| return Promise.all([ |
| writer.write('a'), |
| promise_rejects_exactly(t, thrownError, writer.close(), |
| 'writable\'s close should reject with the thrown error'), |
| promise_rejects_exactly(t, thrownError, reader.read(), |
| 'readable\'s read should reject with the thrown error'), |
| promise_rejects_exactly(t, thrownError, reader.closed, |
| 'readable\'s closed should be rejected with the thrown error'), |
| promise_rejects_exactly(t, thrownError, writer.closed, |
| 'writable\'s closed should be rejected with the thrown error') |
| ]); |
| }, 'TransformStream errors thrown in flush put the writable and readable in an errored state'); |
| |
| test(() => { |
| new TransformStream({ |
| start(c) { |
| c.enqueue('a'); |
| c.error(new Error('generic error')); |
| assert_throws_js(TypeError, () => c.enqueue('b'), 'enqueue() should throw'); |
| } |
| }); |
| }, 'errored TransformStream should not enqueue new chunks'); |
| |
| promise_test(t => { |
| const ts = new TransformStream({ |
| start() { |
| return flushAsyncEvents().then(() => { |
| throw thrownError; |
| }); |
| }, |
| transform: t.unreached_func('transform should not be called'), |
| flush: t.unreached_func('flush should not be called') |
| }); |
| |
| const writer = ts.writable.getWriter(); |
| const reader = ts.readable.getReader(); |
| return Promise.all([ |
| promise_rejects_exactly(t, thrownError, writer.write('a'), 'writer should reject with thrownError'), |
| promise_rejects_exactly(t, thrownError, writer.close(), 'close() should reject with thrownError'), |
| promise_rejects_exactly(t, thrownError, reader.read(), 'reader should reject with thrownError') |
| ]); |
| }, 'TransformStream transformer.start() rejected promise should error the stream'); |
| |
| promise_test(t => { |
| const controllerError = new Error('start failure'); |
| controllerError.name = 'controllerError'; |
| const ts = new TransformStream({ |
| start(c) { |
| return flushAsyncEvents() |
| .then(() => { |
| c.error(controllerError); |
| throw new Error('ignored error'); |
| }); |
| }, |
| transform: t.unreached_func('transform should never be called if start() fails'), |
| flush: t.unreached_func('flush should never be called if start() fails') |
| }); |
| |
| const writer = ts.writable.getWriter(); |
| const reader = ts.readable.getReader(); |
| return Promise.all([ |
| promise_rejects_exactly(t, controllerError, writer.write('a'), 'writer should reject with controllerError'), |
| promise_rejects_exactly(t, controllerError, writer.close(), 'close should reject with same error'), |
| promise_rejects_exactly(t, controllerError, reader.read(), 'reader should reject with same error') |
| ]); |
| }, 'when controller.error is followed by a rejection, the error reason should come from controller.error'); |
| |
| test(() => { |
| assert_throws_js(URIError, () => new TransformStream({ |
| start() { throw new URIError('start thrown error'); }, |
| transform() {} |
| }), 'constructor should throw'); |
| }, 'TransformStream constructor should throw when start does'); |
| |
| test(() => { |
| const strategy = { |
| size() { throw new URIError('size thrown error'); } |
| }; |
| |
| assert_throws_js(URIError, () => new TransformStream({ |
| start(c) { |
| c.enqueue('a'); |
| }, |
| transform() {} |
| }, undefined, strategy), 'constructor should throw the same error strategy.size throws'); |
| }, 'when strategy.size throws inside start(), the constructor should throw the same error'); |
| |
| test(() => { |
| const controllerError = new URIError('controller.error'); |
| |
| let controller; |
| const strategy = { |
| size() { |
| controller.error(controllerError); |
| throw new Error('redundant error'); |
| } |
| }; |
| |
| assert_throws_js(URIError, () => new TransformStream({ |
| start(c) { |
| controller = c; |
| c.enqueue('a'); |
| }, |
| transform() {} |
| }, undefined, strategy), 'the first error should be thrown'); |
| }, 'when strategy.size calls controller.error() then throws, the constructor should throw the first error'); |
| |
| promise_test(t => { |
| const ts = new TransformStream(); |
| const writer = ts.writable.getWriter(); |
| const closedPromise = writer.closed; |
| return Promise.all([ |
| ts.readable.cancel(thrownError), |
| promise_rejects_exactly(t, thrownError, closedPromise, 'closed should throw a TypeError') |
| ]); |
| }, 'cancelling the readable side should error the writable'); |
| |
| promise_test(t => { |
| let controller; |
| const ts = new TransformStream({ |
| start(c) { |
| controller = c; |
| } |
| }); |
| const writer = ts.writable.getWriter(); |
| const reader = ts.readable.getReader(); |
| const writePromise = writer.write('a'); |
| const closePromise = writer.close(); |
| controller.error(thrownError); |
| return Promise.all([ |
| promise_rejects_exactly(t, thrownError, reader.closed, 'reader.closed should reject'), |
| promise_rejects_exactly(t, thrownError, writePromise, 'writePromise should reject'), |
| promise_rejects_exactly(t, thrownError, closePromise, 'closePromise should reject')]); |
| }, 'it should be possible to error the readable between close requested and complete'); |
| |
| promise_test(t => { |
| const ts = new TransformStream({ |
| transform(chunk, controller) { |
| controller.enqueue(chunk); |
| controller.terminate(); |
| throw thrownError; |
| } |
| }, undefined, { highWaterMark: 1 }); |
| const writePromise = ts.writable.getWriter().write('a'); |
| const closedPromise = ts.readable.getReader().closed; |
| return Promise.all([ |
| promise_rejects_exactly(t, thrownError, writePromise, 'write() should reject'), |
| promise_rejects_exactly(t, thrownError, closedPromise, 'reader.closed should reject') |
| ]); |
| }, 'an exception from transform() should error the stream if terminate has been requested but not completed'); |
| |
| promise_test(t => { |
| const ts = new TransformStream(); |
| const writer = ts.writable.getWriter(); |
| // The microtask following transformer.start() hasn't completed yet, so the abort is queued and not notified to the |
| // TransformStream yet. |
| const abortPromise = writer.abort(thrownError); |
| const cancelPromise = ts.readable.cancel(new Error('cancel reason')); |
| return Promise.all([ |
| abortPromise, |
| cancelPromise, |
| promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]); |
| }, 'abort should set the close reason for the writable when it happens before cancel during start, but cancel should ' + |
| 'still succeed'); |
| |
| promise_test(t => { |
| let resolveTransform; |
| const transformPromise = new Promise(resolve => { |
| resolveTransform = resolve; |
| }); |
| const ts = new TransformStream({ |
| transform() { |
| return transformPromise; |
| } |
| }, undefined, { highWaterMark: 2 }); |
| const writer = ts.writable.getWriter(); |
| return delay(0).then(() => { |
| const writePromise = writer.write(); |
| const abortPromise = writer.abort(thrownError); |
| const cancelPromise = ts.readable.cancel(new Error('cancel reason')); |
| resolveTransform(); |
| return Promise.all([ |
| writePromise, |
| abortPromise, |
| cancelPromise, |
| promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]); |
| }); |
| }, 'abort should set the close reason for the writable when it happens before cancel during underlying sink write, ' + |
| 'but cancel should still succeed'); |
| |
| const ignoredError = new Error('ignoredError'); |
| ignoredError.name = 'ignoredError'; |
| |
| promise_test(t => { |
| const ts = new TransformStream({ |
| start(controller) { |
| controller.error(thrownError); |
| controller.error(ignoredError); |
| } |
| }); |
| return promise_rejects_exactly(t, thrownError, ts.writable.abort(), 'abort() should reject with thrownError'); |
| }, 'controller.error() should do nothing the second time it is called'); |
| |
| promise_test(t => { |
| let controller; |
| const ts = new TransformStream({ |
| start(c) { |
| controller = c; |
| } |
| }); |
| const cancelPromise = ts.readable.cancel(thrownError); |
| controller.error(ignoredError); |
| return Promise.all([ |
| cancelPromise, |
| promise_rejects_exactly(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError') |
| ]); |
| }, 'controller.error() should do nothing after readable.cancel()'); |
| |
| promise_test(t => { |
| let controller; |
| const ts = new TransformStream({ |
| start(c) { |
| controller = c; |
| } |
| }); |
| return ts.writable.abort(thrownError).then(() => { |
| controller.error(ignoredError); |
| return promise_rejects_exactly(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError'); |
| }); |
| }, 'controller.error() should do nothing after writable.abort() has completed'); |
| |
| promise_test(t => { |
| let controller; |
| const ts = new TransformStream({ |
| start(c) { |
| controller = c; |
| }, |
| transform() { |
| throw thrownError; |
| } |
| }, undefined, { highWaterMark: Infinity }); |
| const writer = ts.writable.getWriter(); |
| return promise_rejects_exactly(t, thrownError, writer.write(), 'write() should reject').then(() => { |
| controller.error(); |
| return promise_rejects_exactly(t, thrownError, writer.closed, 'closed should reject with thrownError'); |
| }); |
| }, 'controller.error() should do nothing after a transformer method has thrown an exception'); |
| |
| promise_test(t => { |
| let controller; |
| let calls = 0; |
| const ts = new TransformStream({ |
| start(c) { |
| controller = c; |
| }, |
| transform() { |
| ++calls; |
| } |
| }, undefined, { highWaterMark: 1 }); |
| return delay(0).then(() => { |
| // Create backpressure. |
| controller.enqueue('a'); |
| const writer = ts.writable.getWriter(); |
| // transform() will not be called until backpressure is relieved. |
| const writePromise = writer.write('b'); |
| assert_equals(calls, 0, 'transform() should not have been called'); |
| controller.error(thrownError); |
| // Now backpressure has been relieved and the write can proceed. |
| return promise_rejects_exactly(t, thrownError, writePromise, 'write() should reject').then(() => { |
| assert_equals(calls, 0, 'transform() should not be called'); |
| }); |
| }); |
| }, 'erroring during write with backpressure should result in the write failing'); |
| |
| promise_test(t => { |
| const ts = new TransformStream({}, undefined, { highWaterMark: 0 }); |
| return delay(0).then(() => { |
| const writer = ts.writable.getWriter(); |
| // write should start synchronously |
| const writePromise = writer.write(0); |
| // The underlying sink's abort() is not called until the write() completes. |
| const abortPromise = writer.abort(thrownError); |
| // Perform a read to relieve backpressure and permit the write() to complete. |
| const readPromise = ts.readable.getReader().read(); |
| return Promise.all([ |
| promise_rejects_exactly(t, thrownError, readPromise, 'read() should reject'), |
| promise_rejects_exactly(t, thrownError, writePromise, 'write() should reject'), |
| abortPromise |
| ]); |
| }); |
| }, 'a write() that was waiting for backpressure should reject if the writable is aborted'); |
| |
| promise_test(t => { |
| const ts = new TransformStream(); |
| ts.writable.abort(thrownError); |
| const reader = ts.readable.getReader(); |
| return promise_rejects_exactly(t, thrownError, reader.read(), 'read() should reject with thrownError'); |
| }, 'the readable should be errored with the reason passed to the writable abort() method'); |