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