1// META: global=window,worker 2// META: script=../resources/test-utils.js 3'use strict'; 4 5const thrownError = new Error('bad things are happening!'); 6thrownError.name = 'error1'; 7 8promise_test(t => { 9 const ts = new TransformStream({ 10 transform() { 11 throw thrownError; 12 } 13 }); 14 15 const reader = ts.readable.getReader(); 16 17 const writer = ts.writable.getWriter(); 18 19 return Promise.all([ 20 promise_rejects_exactly(t, thrownError, writer.write('a'), 21 'writable\'s write should reject with the thrown error'), 22 promise_rejects_exactly(t, thrownError, reader.read(), 23 'readable\'s read should reject with the thrown error'), 24 promise_rejects_exactly(t, thrownError, reader.closed, 25 'readable\'s closed should be rejected with the thrown error'), 26 promise_rejects_exactly(t, thrownError, writer.closed, 27 'writable\'s closed should be rejected with the thrown error') 28 ]); 29}, 'TransformStream errors thrown in transform put the writable and readable in an errored state'); 30 31promise_test(t => { 32 const ts = new TransformStream({ 33 transform() { 34 }, 35 flush() { 36 throw thrownError; 37 } 38 }); 39 40 const reader = ts.readable.getReader(); 41 42 const writer = ts.writable.getWriter(); 43 44 return Promise.all([ 45 writer.write('a'), 46 promise_rejects_exactly(t, thrownError, writer.close(), 47 'writable\'s close should reject with the thrown error'), 48 promise_rejects_exactly(t, thrownError, reader.read(), 49 'readable\'s read should reject with the thrown error'), 50 promise_rejects_exactly(t, thrownError, reader.closed, 51 'readable\'s closed should be rejected with the thrown error'), 52 promise_rejects_exactly(t, thrownError, writer.closed, 53 'writable\'s closed should be rejected with the thrown error') 54 ]); 55}, 'TransformStream errors thrown in flush put the writable and readable in an errored state'); 56 57test(() => { 58 new TransformStream({ 59 start(c) { 60 c.enqueue('a'); 61 c.error(new Error('generic error')); 62 assert_throws_js(TypeError, () => c.enqueue('b'), 'enqueue() should throw'); 63 } 64 }); 65}, 'errored TransformStream should not enqueue new chunks'); 66 67promise_test(t => { 68 const ts = new TransformStream({ 69 start() { 70 return flushAsyncEvents().then(() => { 71 throw thrownError; 72 }); 73 }, 74 transform: t.unreached_func('transform should not be called'), 75 flush: t.unreached_func('flush should not be called') 76 }); 77 78 const writer = ts.writable.getWriter(); 79 const reader = ts.readable.getReader(); 80 return Promise.all([ 81 promise_rejects_exactly(t, thrownError, writer.write('a'), 'writer should reject with thrownError'), 82 promise_rejects_exactly(t, thrownError, writer.close(), 'close() should reject with thrownError'), 83 promise_rejects_exactly(t, thrownError, reader.read(), 'reader should reject with thrownError') 84 ]); 85}, 'TransformStream transformer.start() rejected promise should error the stream'); 86 87promise_test(t => { 88 const controllerError = new Error('start failure'); 89 controllerError.name = 'controllerError'; 90 const ts = new TransformStream({ 91 start(c) { 92 return flushAsyncEvents() 93 .then(() => { 94 c.error(controllerError); 95 throw new Error('ignored error'); 96 }); 97 }, 98 transform: t.unreached_func('transform should never be called if start() fails'), 99 flush: t.unreached_func('flush should never be called if start() fails') 100 }); 101 102 const writer = ts.writable.getWriter(); 103 const reader = ts.readable.getReader(); 104 return Promise.all([ 105 promise_rejects_exactly(t, controllerError, writer.write('a'), 'writer should reject with controllerError'), 106 promise_rejects_exactly(t, controllerError, writer.close(), 'close should reject with same error'), 107 promise_rejects_exactly(t, controllerError, reader.read(), 'reader should reject with same error') 108 ]); 109}, 'when controller.error is followed by a rejection, the error reason should come from controller.error'); 110 111test(() => { 112 assert_throws_js(URIError, () => new TransformStream({ 113 start() { throw new URIError('start thrown error'); }, 114 transform() {} 115 }), 'constructor should throw'); 116}, 'TransformStream constructor should throw when start does'); 117 118test(() => { 119 const strategy = { 120 size() { throw new URIError('size thrown error'); } 121 }; 122 123 assert_throws_js(URIError, () => new TransformStream({ 124 start(c) { 125 c.enqueue('a'); 126 }, 127 transform() {} 128 }, undefined, strategy), 'constructor should throw the same error strategy.size throws'); 129}, 'when strategy.size throws inside start(), the constructor should throw the same error'); 130 131test(() => { 132 const controllerError = new URIError('controller.error'); 133 134 let controller; 135 const strategy = { 136 size() { 137 controller.error(controllerError); 138 throw new Error('redundant error'); 139 } 140 }; 141 142 assert_throws_js(URIError, () => new TransformStream({ 143 start(c) { 144 controller = c; 145 c.enqueue('a'); 146 }, 147 transform() {} 148 }, undefined, strategy), 'the first error should be thrown'); 149}, 'when strategy.size calls controller.error() then throws, the constructor should throw the first error'); 150 151promise_test(t => { 152 const ts = new TransformStream(); 153 const writer = ts.writable.getWriter(); 154 const closedPromise = writer.closed; 155 return Promise.all([ 156 ts.readable.cancel(thrownError), 157 promise_rejects_exactly(t, thrownError, closedPromise, 'closed should throw a TypeError') 158 ]); 159}, 'cancelling the readable side should error the writable'); 160 161promise_test(t => { 162 let controller; 163 const ts = new TransformStream({ 164 start(c) { 165 controller = c; 166 } 167 }); 168 const writer = ts.writable.getWriter(); 169 const reader = ts.readable.getReader(); 170 const writePromise = writer.write('a'); 171 const closePromise = writer.close(); 172 controller.error(thrownError); 173 return Promise.all([ 174 promise_rejects_exactly(t, thrownError, reader.closed, 'reader.closed should reject'), 175 promise_rejects_exactly(t, thrownError, writePromise, 'writePromise should reject'), 176 promise_rejects_exactly(t, thrownError, closePromise, 'closePromise should reject')]); 177}, 'it should be possible to error the readable between close requested and complete'); 178 179promise_test(t => { 180 const ts = new TransformStream({ 181 transform(chunk, controller) { 182 controller.enqueue(chunk); 183 controller.terminate(); 184 throw thrownError; 185 } 186 }, undefined, { highWaterMark: 1 }); 187 const writePromise = ts.writable.getWriter().write('a'); 188 const closedPromise = ts.readable.getReader().closed; 189 return Promise.all([ 190 promise_rejects_exactly(t, thrownError, writePromise, 'write() should reject'), 191 promise_rejects_exactly(t, thrownError, closedPromise, 'reader.closed should reject') 192 ]); 193}, 'an exception from transform() should error the stream if terminate has been requested but not completed'); 194 195promise_test(t => { 196 const ts = new TransformStream(); 197 const writer = ts.writable.getWriter(); 198 // The microtask following transformer.start() hasn't completed yet, so the abort is queued and not notified to the 199 // TransformStream yet. 200 const abortPromise = writer.abort(thrownError); 201 const cancelPromise = ts.readable.cancel(new Error('cancel reason')); 202 return Promise.all([ 203 abortPromise, 204 cancelPromise, 205 promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]); 206}, 'abort should set the close reason for the writable when it happens before cancel during start, but cancel should ' + 207 'still succeed'); 208 209promise_test(t => { 210 let resolveTransform; 211 const transformPromise = new Promise(resolve => { 212 resolveTransform = resolve; 213 }); 214 const ts = new TransformStream({ 215 transform() { 216 return transformPromise; 217 } 218 }, undefined, { highWaterMark: 2 }); 219 const writer = ts.writable.getWriter(); 220 return delay(0).then(() => { 221 const writePromise = writer.write(); 222 const abortPromise = writer.abort(thrownError); 223 const cancelPromise = ts.readable.cancel(new Error('cancel reason')); 224 resolveTransform(); 225 return Promise.all([ 226 writePromise, 227 abortPromise, 228 cancelPromise, 229 promise_rejects_exactly(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]); 230 }); 231}, 'abort should set the close reason for the writable when it happens before cancel during underlying sink write, ' + 232 'but cancel should still succeed'); 233 234const ignoredError = new Error('ignoredError'); 235ignoredError.name = 'ignoredError'; 236 237promise_test(t => { 238 const ts = new TransformStream({ 239 start(controller) { 240 controller.error(thrownError); 241 controller.error(ignoredError); 242 } 243 }); 244 return promise_rejects_exactly(t, thrownError, ts.writable.abort(), 'abort() should reject with thrownError'); 245}, 'controller.error() should do nothing the second time it is called'); 246 247promise_test(t => { 248 let controller; 249 const ts = new TransformStream({ 250 start(c) { 251 controller = c; 252 } 253 }); 254 const cancelPromise = ts.readable.cancel(thrownError); 255 controller.error(ignoredError); 256 return Promise.all([ 257 cancelPromise, 258 promise_rejects_exactly(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError') 259 ]); 260}, 'controller.error() should do nothing after readable.cancel()'); 261 262promise_test(t => { 263 let controller; 264 const ts = new TransformStream({ 265 start(c) { 266 controller = c; 267 } 268 }); 269 return ts.writable.abort(thrownError).then(() => { 270 controller.error(ignoredError); 271 return promise_rejects_exactly(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError'); 272 }); 273}, 'controller.error() should do nothing after writable.abort() has completed'); 274 275promise_test(t => { 276 let controller; 277 const ts = new TransformStream({ 278 start(c) { 279 controller = c; 280 }, 281 transform() { 282 throw thrownError; 283 } 284 }, undefined, { highWaterMark: Infinity }); 285 const writer = ts.writable.getWriter(); 286 return promise_rejects_exactly(t, thrownError, writer.write(), 'write() should reject').then(() => { 287 controller.error(); 288 return promise_rejects_exactly(t, thrownError, writer.closed, 'closed should reject with thrownError'); 289 }); 290}, 'controller.error() should do nothing after a transformer method has thrown an exception'); 291 292promise_test(t => { 293 let controller; 294 let calls = 0; 295 const ts = new TransformStream({ 296 start(c) { 297 controller = c; 298 }, 299 transform() { 300 ++calls; 301 } 302 }, undefined, { highWaterMark: 1 }); 303 return delay(0).then(() => { 304 // Create backpressure. 305 controller.enqueue('a'); 306 const writer = ts.writable.getWriter(); 307 // transform() will not be called until backpressure is relieved. 308 const writePromise = writer.write('b'); 309 assert_equals(calls, 0, 'transform() should not have been called'); 310 controller.error(thrownError); 311 // Now backpressure has been relieved and the write can proceed. 312 return promise_rejects_exactly(t, thrownError, writePromise, 'write() should reject').then(() => { 313 assert_equals(calls, 0, 'transform() should not be called'); 314 }); 315 }); 316}, 'erroring during write with backpressure should result in the write failing'); 317 318promise_test(t => { 319 const ts = new TransformStream({}, undefined, { highWaterMark: 0 }); 320 return delay(0).then(() => { 321 const writer = ts.writable.getWriter(); 322 // write should start synchronously 323 const writePromise = writer.write(0); 324 // The underlying sink's abort() is not called until the write() completes. 325 const abortPromise = writer.abort(thrownError); 326 // Perform a read to relieve backpressure and permit the write() to complete. 327 const readPromise = ts.readable.getReader().read(); 328 return Promise.all([ 329 promise_rejects_exactly(t, thrownError, readPromise, 'read() should reject'), 330 promise_rejects_exactly(t, thrownError, writePromise, 'write() should reject'), 331 abortPromise 332 ]); 333 }); 334}, 'a write() that was waiting for backpressure should reject if the writable is aborted'); 335 336promise_test(t => { 337 const ts = new TransformStream(); 338 ts.writable.abort(thrownError); 339 const reader = ts.readable.getReader(); 340 return promise_rejects_exactly(t, thrownError, reader.read(), 'read() should reject with thrownError'); 341}, 'the readable should be errored with the reason passed to the writable abort() method'); 342