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